ElasticSearch 2.3 Master OOM

環境

ES

version:2.3.0

部署:Master Node+Data Node

配置:3臺 master爲8C8G,data node爲8C16G

備註:此問題與JDK、操作系統無關,因此就沒列詳細信息

問題描述

ES集羣每天動態創建500+索引,索引保留三天,三天後清掉。近期,Master出現兩次OOM,且內存一直不會釋放。查看dump文件如下:

 

                                                                                            (圖1)

                                                                                            (圖2)

定位一階段

從dump文件發現InternalClusterService中變量updateTaskPerExecutor持有800M內存,

updateTaskPerExecutor的定義

private final Map<ClusterStateTaskExecutor, List<UpdateTask>> updateTasksPerExecutor = new HashMap<>();

添加操作:

innerSubmitStateUpdateTask中以ClusterStateTaskExecutor爲key添加List<UpdateTask>到HashMap中

private <T> void innerSubmitStateUpdateTask(...) {
        try {
            //new 新的updateTask
            synchronized (updateTasksPerExecutor) {
                if (updateTasksForExecutor == null) {
                    //創建新的ArrayList
                    updateTasksPerExecutor.put(executor, new ArrayList<UpdateTask>());
                }
                //只有此處會添加數據到updateTasksPerExecutor
                updateTasksPerExecutor.get(executor).add(updateTask);
            }
            //timeout不爲空,延遲執行updateTask
            if (config.timeout() != null) {
                updateTasksExecutor.execute(...)
            } else {
                //在線程池(中有一個線程)中立即執行updateTask
                updateTasksExecutor.execute(updateTask);
            }
        } catch (EsRejectedExecutionException e) {
        }
    }

刪除操作:

Master啓動了單線程

//只有一個線程的線程池
private volatile PrioritizedEsThreadPoolExecutor updateTasksExecutor;

在updateTask的runTaskForExecutor中刪除List<UpdateTask>,

void runTasksForExecutor(ClusterStateTaskExecutor<T> executor) {
        synchronized (updateTasksPerExecutor) {
            //按executor刪除List<UpdatTask>
            List<UpdateTask> pending = updateTasksPerExecutor.remove(executor);
            ...
        }
     ...
}

思考:

代碼看沒有問題,有釋放內存的地方,難道是線程堵塞了,或者死鎖了?

                                                                                            (圖3)

 

很顯然線程沒有堵塞,也沒有死鎖。難道是一個線程處理不過來,updateTask任務積壓,處理不過來?但爲什麼內存會持續增加,full gc也沒用呢?從dump文件中看Task是積壓了,但是晚上沒有業務的,積壓的任務總會做完,但爲什麼內存一直不釋放呢?

定位二階段

沒把握問題,肯定是細節忽略了。繼續看dump文件。圖2中顯示updateTaskPerExecutor中很多ClusterState的transitive reference引用,佔用內存的是 RoutingTable,而且每個ClusterState和其中的RoutingTable的內存地址均不一樣。每個ClusterState對象大小差不多。爲什麼會有這麼多ClusterState對象呢?

                                                                                     (圖4)

先看InternalClusterService中對ClusterState的定義

//volatile保證線程之間的內存可見性,總是能看到新的clusterState
private volatile ClusterState clusterState;

每個線程都能看到新的clusterState,那更不應該有問題了。

再看新、老clusterState更新

void runTasksForExecutor(ClusterStateTaskExecutor<T> executor) {
    ...
    // update the current cluster state
    clusterState = newClusterState;
    ...
}

還是沒有頭緒,感覺不應該有問題

定位三階段

UpdateTask介紹

Master節點的對create索引、刪除索引、修改mapping等操作最終都包裝成一個UpdateTask添加到updateTaskPerExecutor中由updateTasksExecutor異步執行。

TransportMasterNodeAction類是所有操作類型的父類,主要完成master節點判斷請求和處理失敗重試功能。實現了doExecute方法,新啓動一個異步單線程(異步響應,不佔用接收request線程),如果本節點是master節點,則啓動執行masterOperation;如果不是,則發送給mastr節點執行,執行抽象方法masterOperation由子類實現;另外通過ClusterStateObserver.waitForNextChange完成了錯誤重試功能。ClusterStateObserver類,集羣狀態監控器。每次動作都會創建一個AsyncSingleAction和ClusterStateObserver實例。

分析

分析其中一個clusterState對象的incoming reference

                                                                                            (圖5)

注意:每個clusterStateObserver代表一次Master的操作,圖5所示,引用關係很複雜。

問題原因

條件:當對Master有頻繁的操作時,導致UpdateTask在updateTaskPerExecutor中積壓

1. 由於InternalClusterService中定義的clusterState實例對外的引用關係太複雜了,同時clusterState變更保護不夠,僅僅是加了一個volatile,很容易產生ABA問題,最終導致就算clusterState變更後,老的clustertState不會被釋放

2. ClusterStateObserver中定義的

final AtomicReference<ObservedState> lastObservedState;

還會持有老的引用。最終clusterState是哪個版本只有天知道。

3. 每次創建新的clusterState都會新建一份RoutingTable,

RoutingTable(long version, Map<String, IndexRoutingTable> indicesRouting) {
        this.version = version;
        //copy一份,這也解釋上面看到大小差不多
        this.indicesRouting = ImmutableMap.copyOf(indicesRouting);
    }

持續分析

ES的高版本是否存在這問題?查看github在5.x解決了這問題

問題:https://github.com/elastic/elasticsearch/issues/21439

問題原因官方解說:https://github.com/elastic/elasticsearch/issues/21568

問題解決:

https://github.com/elastic/elasticsearch/pull/21578

清掉和多線程只是緩解

https://github.com/elastic/elasticsearch/pull/21631,這個改造很有基本上解決了這個問題是,觀察者根本不需要觀察clusterState本身,而是觀察masterid和version即可。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章