記一次Apache的代碼致使生產問題

引言

二狗:二胖快醒醒,趕忙看看剛纔報警郵件,你上次寫的保存用戶接口耗時(《二胖的參數校驗坎坷之路》)大大上升,趕忙排查下緣由。
二胖:好的,立刻看,心裏戲可十足(內心卻在抱怨,大中午的攪我發財好夢,剛剛夢見我買的股票又漲停了就被叫醒了)。牢騷歸牢騷,本身的問題仍是得看啊,畢竟是本身寫的bug,含着淚也要把它修復掉。二胖對分析這種問題仍是駕輕就熟的,畢竟已是久經職場的老油條了。html

測試環境復現問題

二胖首先經過內部的監控工具看了下這段時間的網絡是否正常,以及cpu的使用狀況、數據庫的耗時等,這些指標看起來都是正常的,惟一稍微有點區別的是這段時間流量上漲了一些,確定又是公司花錢搞營銷砸廣告了。接着二胖又經過cat(大衆點評開源監控工具)分析了幾個請求,每一個階段的耗時看下來都ok。臥槽這可咋辦列竟然難倒二胖了,若是生產環境問題能夠在測試環境復現就行了,這樣解覺問題就簡單多了。生產不是流量上漲了一些嗎?那測試環境來壓測一把吧,二胖果斷的下載了一個jmeter(壓測工具)在測試環境進行了一把瘋狂的壓測,果真出現了和生產同樣的問題。可以復現問題就好,這樣離解決問題就近了一大步。java

arthas定位問題

問題是復現了,接下來就是找出接口比較耗時的地方了。通常咱們找接口耗時較長的地方,都是經過記錄日誌打印每一步的耗時。這是比較常見作法,不過二胖記得上次部門技術大拿「二狗」分享過一個神器arthas能夠輸出方法路徑上的每一個節點上耗時。苦於一直沒有機會拿它來用於實際操做,今天終於能夠拿它來好好練手了。安裝什麼的就不介紹了,這個官網都寫的比較詳細,而且文檔也是中文的,很是容易上手。下面咱們就來使用下arthas吧。
啓動成功的界面
在這裏插入圖片描述
下面咱們根據arthas提供的trace命令來看看接口的耗時都是在哪裏。
在這裏插入圖片描述咱們從上面能夠看出主要耗時是集中在 org.apache.commons.beanutils.BeanUtils#copyProperties這個方法上面的,不就一個實體之間的屬性賦值轉換嗎,須要這麼耗時這麼久嗎?不科學啊,apache提供的方法還能這麼low嗎?帶着這些問題咱們看看其餘提供的屬性拷貝的工具類效率如何。git

使用JMH對常見屬性賦值操做性能比較

  • 使用getset方法複製。
  • cglibBeanCopier
  • SpringBeanUtils
  • apacheBeanUtils
  • 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

在這裏插入圖片描述

  • 從上述結論中咱們能夠發現性能最好的是排名 用getset方法複製,其次是mapStructcglib的BeanCopier,再接着是Spring的beanUtils,最後的是apache的BeanUtils
  • 若是對上述測試性能感興趣的話,代碼都已上傳到github上可自行下載運行對比下結果。代碼地址
  • 關於對JMH的使用就不介紹了,感興趣的可自行谷歌。不過若是要進行性能比較的話,真心推薦使用下,結果能夠經過導出json文件而後生成圖表。

爲何apacheBeanUtils性能最差

apacheBeanUtilsspringbeanUtils都是底層都是使用反射來進行賦值的,爲何apacheBeanUtils的性能要差一大截列。源碼之下無祕密,下面咱們來看看這個方法的源碼。
在這裏插入圖片描述
Apache BeanUtils 打印了大量的日誌、以及各類轉換、類型的判斷等等致使性能變差。數據庫

  • springbeanUtil直接使用反射省,乾淨利索,核心代碼見下圖。

在這裏插入圖片描述

  • 其實在《阿里巴巴開發手冊》(可在公衆號【java金融】回覆「泰山」獲取)裏面也有說明屬性的copy避免使用apcheBeanUtils

在這裏插入圖片描述

  • 若是生產環境已經大量使用Apache BeanUtils的話須要替換spring BeanUtils的話須要注意下他們兩個雖然提供的方法都是copyProperties可是他們的參數是反的,這點須要注意下,不要直接換個引入的包名完事。

總結

  • 實際使用中的話通常是不會使用getset方法複製,容易漏掉屬性而且也是一個體力活。推薦使用mapStruct,在編譯過程當中,MapStruct將生成該接口的實現,而且它還能夠實現不一樣名字的映射,好比能夠把name映射到username,靈活性比較高。
  • 二胖感受今天收穫滿滿啊,一下學到了jmeterarthasJMH三個軟件的使用。

結束

  • 因爲本身才疏學淺,不免會有紕漏,假如你發現了錯誤的地方,還望留言給我指出來,我會對其加以修正。
  • 若是你以爲文章還不錯,你的轉發、分享、讚揚、點贊、留言就是對我最大的鼓勵。
  • 感謝您的閱讀,十分歡迎並感謝您的關注。

在這裏插入圖片描述

相關文章
相關標籤/搜索