日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

ConcurrentModificationException日志关键字报警引发的思考

發布時間:2023/12/24 windows 28 coder
生活随笔 收集整理的這篇文章主要介紹了 ConcurrentModificationException日志关键字报警引发的思考 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文將記錄和分析日志中的ConcurrentModificationException關鍵字報警,還有一些我的思考,希望對大家有幫助。

一、背景

近期,在日常的日志關鍵字報警分析時,發現我負責的一個電商核心系統在某時段存在較多ConcurrentModificationException異常日志,遂進行分析和改進,下面是我的一些思考。

1.1?系統架構

一直以來,無狀態的服務都被當作分布式服務設計的最佳實踐。因為無狀態的服務對于擴展性和運維方面有著得天獨厚的優勢,可以隨意地增加和減少節點。本系統的整體架構可以認為是由一個MQ應用、一個RPC應用底層存儲組成。

RPC應用是無狀態服務,對外提供常用的查詢和操作接口;采用雙機房部署,每個機房10*8C16G;

MQ應用是無狀態服務,負責消費MQ消息,在消費過程中會調用該RPC應用提供方法;采用雙機房部署,每個機房5*8C16G;

底層存儲用的是數據庫集群和緩存集群,大概如圖所示:

1.2?關鍵代碼

MyRpcService 對外提供RPC服務,getList 方法可以根據入參中的狀態進行查詢,由于業務需要,需要對入參的狀態進行排序,實現部分關鍵代碼如下:

public class?MyRpcServiceImpl?implements?MyRpcService{

    @Override
    public?BaseResult?getList(ListParam?listParam) {

????????BaseResult?baseResult?= new BaseResult();

????????List<Integer>?states?=?listParam.getStateList();

        //?省略大段代碼
????????KeyUtil.getKeyString(states);
        //?省略大段代碼

????????baseResult.setSuccess(true);

        return?baseResult;
    }

}

KeyUtil 是一個工具類,getKeyString 方法對入參的itemList進行排序使用的是Java集合框架內置的sort?方法,代碼如下:

public class?KeyUtil?{

    public static?String?getKeyString(List<Integer>?itemList) {
????????String?result?= "";
        //省略代碼
????????Collections.sort(itemList);
        //省略代碼
        return?result;
    }

}

MyMqConsumer是MQ消費者,負責監聽消息進行消費。在消費邏輯中,會調用MyRpcServicegetList()?方法進行狀態查詢,因為查詢的狀態是固定的,所以在Consumer類中定義了static?final?類型的stateList?,關鍵代碼如下:

public class?MyMqConsumer?implements?MessageListener{

    public static?final?List<Integer>?stateList?=?Stream.of(1).collect(Collectors.toList());

    @Resource
    private?MyRpcService?myRpcService;

    @Override
    public?void?onMessage(List<Message>?messageList) {

        //?省略代碼

        for (Message?message?:?messageList) {

            //?省略其他代碼
????????????ListParam?listParam?= new ListParam();
????????????listParam.setStateList(stateList);
????????????BaseResult?result?=?myRpcService.getList(listParam);
            //?省略其他代碼

        }

    }

}

二、? 原因分析

看了上面的系統架構和關鍵代碼,不知道你有沒有發現問題?可以先拋開設計和代碼實現方面的問題不談,只看這樣的代碼能不能正常執行,得到正確的業務結果。

既然這么問了,當然會有問題:在高并發環境下,MQ應用在消費消息時,調用RPC服務查詢時可能會拋出異常,從而觸發MQ異常重試,至于對業務有沒有影響,得具體問題具體分析了。

ERROR?執行流程時出錯
java.util.ConcurrentModificationException:null
at?java.util.ArrayList.forEach(ArrayList.java:1260)~[:?1.8.0_192]
at?com.shangguan.test.util.KeyUtil.getKeyString(KeyUtil.java:10)
...

2.1?分析1-ArrayList源碼

從日志中可以看到,ConcurrentModificationExceptionjava.util.ArrayList類里面的forEach方法拋出來的,源碼如下:

    @Override
    public?void?forEach(Consumer<??super?E>?action) {
????????Objects.requireNonNull(action);
????????final?int?expectedModCount?=?modCount;
        @SuppressWarnings("unchecked")
????????final?E[]?elementData?= (E[]) this.elementData;
????????final?int?size?= this.size;
        for (int?i=0;?modCount?==?expectedModCount?&&?i?<?size;?i++) {
????????????action.accept(elementData[i]);
        }
        if (modCount?!=?expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

在該方法中,內部會維護一個expectedModCount變量,賦值為modCount,在每次迭代過程中,迭代器會檢查expectedModCount是否等于當前的modCount。如果不等,說明在迭代過程中ArrayList的結構發生了修改,迭代器會拋出ConcurrentModificationException異常。這種設計可以確保在多線程環境下,當一個線程修改ArrayList時,其他線程在迭代過程中可以立即發現這種修改,從而避免潛在的數據不一致問題。

再可以看下源碼中modCount的注釋,大意是:

modCount表示ArrayList自從創建以來結構上發生的修改次數。結構修改是指改變列表大小的修改,或者以其他方式擾亂列表,使正在進行的迭代可能產生不正確的結果。

modCount字段用于iteratorlistIterator方法返回的迭代器(或列表迭代器)。如果這個字段的值在迭代過程中發生意外的變化,迭代器(或列表迭代器)將在next、remove、previous、set或add操作時拋出ConcurrentModificationException異常。這提供了fail-fast(快速失敗)行為,而不是在迭代過程中遇到并發修改時具有不確定性。

子類可以選擇使用這個字段。如果子類希望提供fail-fast迭代器(和列表迭代器),那么它只需在其add(int,?E)remove(int)方法(以及覆蓋的任何其他導致列表結構修改的方法)中遞增此字段。單次調用add(int,?E)remove(int)應該在此字段上增加不超過1次,否則迭代器(和列表迭代器)將拋出虛假的ConcurrentModificationException。如果實現不希望提供fail-fast迭代器,可以忽略此字段。

2.2?分析2-線程安全問題

有個有趣的現象是,這個異常日志僅存在MQ應用中,這是為什么呢?

這其實是一個多線程問題。我們知道,static對象是在類加載時創建的全局對象,它們的生命周期與類的生命周期相同。static對象在程序啟動時創建,在程序結束時銷毀。這意味著static對象在多個線程之間共享的,可能存在線程安全問題。

翻回去仔細看下代碼,可以看到MyMqConsumer定義的stateList是static類型的,是否是否存在線程安全問題呢?

在流量較低的情況下,多個消息不在同一時刻到達,每個線程處理消息將不會爭奪static對象,所以不會有問題;

當流量較大情況下,有多個消息可能在同一時刻到達,每個線程處理過程中都會對stateList進行賦值,調用遠程RPC接口,它們之間將會爭奪static對象,可能存在問題。例如上圖中右半部分,線程1還沒有處理完消息1時,線程2就開始爭搶,那么就可能使ArrayList中modCount?!=?expectedModCount條件滿足,從而拋出異常。

三、改進思考

3.1?本問題的優化

經過上述分析,已經清楚問題的產生原因了。對于本問題的優化,其實也比較簡單。有如下兩種方式可供選擇:

1.? 在MyMqConsumer調用RPC查詢的入參,使用new?List來替代原來的類中定義好的static對象;

2.? 修改KeyUtil代碼,淺拷貝傳入的itemList,再進行排序

3.2?類似問題的發現和改進

本問題已經修復,那類似的問題是否可以避免或者減少,將是接下來值得思考的一個問題。為了減少這類問題發生,我結合平時工作過程中的幾個階段,認為可以從以下幾個方面進行改進:

  • 開發

開發過程中,開發人員需要提升認知和水平,注意代碼中可能存在的線程問題;注意編寫單元測試,可以通過模擬多線程環境來檢測潛在的問題。

  • 代碼評審

開發完成的代碼一定需要進行代碼評審,評審過程中架構師需要發揮自己豐富的開發經驗和較強的代碼直覺,“火眼金睛”,發現代碼中的漏洞;當然這對評審人員的要求很高,因為僅通過改動的幾行代碼發現問題確實是一件很有挑戰的事情。如果要有一些自動化工具或者插件,則可以起到事半功倍的效果。這里其實我還沒有調研相關的工具,如果有大佬有相關經驗歡迎評論交流。

  • 測試

測試階段除了驗證正常的業務功能,還需要進行集成測試和性能測試。在集成測試中,將多個模塊組合在一起,測試整個系統在多線程環境中的行為,有助于發現模塊之間的交互問題。除了繼承測試,有時還需要性能測試,性能測試可以發現潛在的競爭條件、死鎖、資源爭用等多線程問題。

四、小結

最后,我簡單總結一下本文內容。本文主要記錄和分析日志中的ConcurrentModificationException關鍵字報警,首先介紹了系統整體架構和關鍵代碼;然后從ArrayList源碼和線程安全兩個方面分析問題產生原因,最后我提出了修復該問題的方案和類似問題的思考,希望對大家有幫助。

總結

以上是生活随笔為你收集整理的ConcurrentModificationException日志关键字报警引发的思考的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。