二狗:二胖快醒醒,趕忙看看剛纔報警郵件,你上次寫的保存用戶接口耗時(《二胖的參數校驗坎坷之路》)大大上升,趕忙排查下緣由。
二胖:好的,立刻看,心裏戲可十足(內心卻在抱怨,大中午的攪我發財好夢,剛剛夢見我買的股票又漲停了就被叫醒了)。牢騷歸牢騷,本身的問題仍是得看啊,畢竟是本身寫的bug
,含着淚也要把它修復掉。二胖對分析這種問題仍是駕輕就熟的,畢竟已是久經職場的老油條了。html
二胖首先經過內部的監控工具看了下這段時間的網絡是否正常,以及cpu
的使用狀況、數據庫
的耗時等,這些指標看起來都是正常的,惟一稍微有點區別的是這段時間流量上漲了一些,確定又是公司花錢搞營銷砸廣告了。接着二胖又經過cat(大衆點評開源監控工具)分析了幾個請求,每一個階段的耗時看下來都ok
。臥槽這可咋辦列竟然難倒二胖了,若是生產環境問題能夠在測試環境復現就行了,這樣解覺問題就簡單多了。生產不是流量上漲了一些嗎?那測試環境來壓測一把吧,二胖果斷的下載了一個jmeter(壓測工具)在測試環境進行了一把瘋狂的壓測,果真出現了和生產同樣的問題。可以復現問題就好,這樣離解決問題就近了一大步。java
問題是復現了,接下來就是找出接口比較耗時的地方了。通常咱們找接口耗時較長的地方,都是經過記錄日誌打印每一步的耗時。這是比較常見作法,不過二胖記得上次部門技術大拿「二狗」分享過一個神器arthas能夠輸出方法路徑上的每一個節點上耗時。苦於一直沒有機會拿它來用於實際操做,今天終於能夠拿它來好好練手了。安裝什麼的就不介紹了,這個官網都寫的比較詳細,而且文檔也是中文的,很是容易上手。下面咱們就來使用下arthas
吧。
啓動成功的界面
下面咱們根據arthas提供的trace
命令來看看接口的耗時都是在哪裏。
咱們從上面能夠看出主要耗時是集中在 org.apache.commons.beanutils.BeanUtils#copyProperties
這個方法上面的,不就一個實體之間的屬性賦值轉換嗎,須要這麼耗時這麼久嗎?不科學啊,apache
提供的方法還能這麼low
嗎?帶着這些問題咱們看看其餘提供的屬性拷貝的工具類效率如何。git
get
、set
方法複製。cglib
的BeanCopier
。Spring
的BeanUtils
apache
的BeanUtils
MapStruct
下面咱們就來對上面這些操做來進行一波性能比較。
編寫下面的測試類。github
/** * @author: * @Date: 2020/7/11 * @Description: */ @BenchmarkMode(Mode.AverageTime) @Warmup(iterations = 3, time = 1) @Measurement(iterations = 5, time = 5) @Threads(6) @Fork(1) @State(value = Scope.Benchmark) @OutputTimeUnit(TimeUnit.NANOSECONDS) public class BeanCopyTest { @Param(value = {"1","10","100"}) private int count; public UserBO bo; public BeanCopier copier; @Setup(Level.Trial) // 初始化方法,在所有Benchmark運行以前進行 public void init() { copier = BeanCopier.create(UserBO.class, UserVO.class, false); bo = new UserBO(); bo.setUserName("java金融"); bo.setAge(1); bo.setIdCard("88888888"); bo.setEmail("java金融@qq.com"); } public static void main(String[] args) throws RunnerException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { Options opt = new OptionsBuilder().include(BeanCopyTest.class.getSimpleName()).result("result.json").resultFormat(ResultFormatType.JSON).build(); new Runner(opt).run(); } /** * 使用mapStruct來操做 */ @Benchmark public void mapStruct() { for (int i = 1; i <= count; i++) { UserVO vo = UserMapping.INSTANCE.converter(bo); } } /** * 手動set和Get */ @Benchmark public void setAndGet() { for (int i = 1; i <= count; i++) { UserVO userVO = new UserVO(); userVO.setUserName(bo.getUserName()); userVO.setEmail(bo.getEmail()); userVO.setSex(bo.getSex()); userVO.setIdCard(bo.getIdCard()); userVO.setAge(bo.getAge()); } } /** * 使用cglib的copy方法 */ @Benchmark public void cglibBeanCopier() { for (int i = 1; i <= count; i++) { UserVO vo = new UserVO(); copier.copy(bo, vo, null); } } /** * 使用spring提供的copyProperties方法 */ @Benchmark public void springBeanUtils() { for (int i = 1; i <= count; i++) { UserVO vo = new UserVO(); BeanUtils.copyProperties(bo, vo); } } /** * 使用apache的copyProperties方法 * @throws InvocationTargetException * @throws IllegalAccessException */ @Benchmark public void apacheBeanUtils() throws InvocationTargetException, IllegalAccessException { for (int i = 1; i <= count; i++) { UserVO vo = new UserVO(); org.apache.commons.beanutils.BeanUtils.copyProperties(vo, bo); } }
最後的測試結果以下所示:spring
Benchmark (count) Mode Cnt Score Error Units BeanCopyTest.apacheBeanUtils 1 avgt 5 2462103.419 ± 2292830.495 ns/op BeanCopyTest.apacheBeanUtils 10 avgt 5 21025926.689 ± 11254755.603 ns/op BeanCopyTest.apacheBeanUtils 100 avgt 5 193235312.113 ± 37929707.246 ns/op BeanCopyTest.cglibBeanCopier 1 avgt 5 4.936 ± 1.187 ns/op BeanCopyTest.cglibBeanCopier 10 avgt 5 4.820 ± 1.963 ns/op BeanCopyTest.cglibBeanCopier 100 avgt 5 4.269 ± 0.890 ns/op BeanCopyTest.mapStruct 1 avgt 5 4.809 ± 1.720 ns/op BeanCopyTest.mapStruct 10 avgt 5 4.947 ± 1.320 ns/op BeanCopyTest.mapStruct 100 avgt 5 4.440 ± 1.191 ns/op BeanCopyTest.setAndGet 1 avgt 5 3.780 ± 1.785 ns/op BeanCopyTest.setAndGet 10 avgt 5 3.930 ± 1.788 ns/op BeanCopyTest.setAndGet 100 avgt 5 4.069 ± 2.181 ns/op BeanCopyTest.springBeanUtils 1 avgt 5 1190.563 ± 165.574 ns/op BeanCopyTest.springBeanUtils 10 avgt 5 10887.244 ± 1228.026 ns/op BeanCopyTest.springBeanUtils 100 avgt 5 109686.562 ± 7485.261 ns/op
get
、set
方法複製,其次是mapStruct
和cglib的BeanCopier
,再接着是Spring的beanUtils
,最後的是apache的BeanUtils
。github
上可自行下載運行對比下結果。代碼地址 JMH
的使用就不介紹了,感興趣的可自行谷歌。不過若是要進行性能比較的話,真心推薦使用下,結果能夠經過導出json
文件而後生成圖表。apacheBeanUtils
和spring
的beanUtils
都是底層都是使用反射來進行賦值的,爲何apacheBeanUtils
的性能要差一大截列。源碼之下無祕密,下面咱們來看看這個方法的源碼。Apache BeanUtils
打印了大量的日誌、以及各類轉換、類型的判斷等等致使性能變差。數據庫
spring
的beanUtil
直接使用反射省,乾淨利索,核心代碼見下圖。
copy
避免使用apcheBeanUtils
Apache BeanUtils
的話須要替換spring BeanUtils
的話須要注意下他們兩個雖然提供的方法都是copyProperties
可是他們的參數是反的,這點須要注意下,不要直接換個引入的包名完事。get
和set
方法複製,容易漏掉屬性而且也是一個體力活。推薦使用mapStruct
,在編譯過程當中,MapStruct
將生成該接口的實現,而且它還能夠實現不一樣名字的映射,好比能夠把name
映射到username
,靈活性比較高。jmeter
、arthas
、JMH
三個軟件的使用。