es按日分表造成的數(shù)據(jù)不一致性

之前業(yè)務(wù)線上出現(xiàn)了es日表數(shù)據(jù)不一致的情況,我一開始一臉蒙蔽,后來請教同事也好,自己查閱資料也好,最后的問題其實(shí)是小到自己看不見的代碼問題。最近是個空檔期,記錄一下血案。

業(yè)務(wù)場景描述:

業(yè)務(wù)項目中用戶,股票,文章之間有著關(guān)聯(lián)關(guān)系,線上數(shù)據(jù)越來越多時,采取的方案是數(shù)據(jù)首先落庫,然后同步到es(elasticsearch)中,即是做緩存數(shù)據(jù)庫,也方便了搜索業(yè)務(wù)需要。

后來隨著用戶和文章之間的關(guān)聯(lián)關(guān)系數(shù)據(jù)量越來越大,決定對es按天分表,即每天會自動的產(chǎn)生一個天索引(這里的索引和mysql索引不一樣),es中索引的概念可以類比與mysql中數(shù)據(jù)庫db的概念,類似于庫,索引是具有某些相似特征的文檔的集合。我們用的es版本較高,所以一個索引下只有一個type(type好比關(guān)系型數(shù)據(jù)庫中的table),type下有著大量的document。那出現(xiàn)的數(shù)據(jù)問題是什么呢?我們的業(yè)務(wù)邏輯代碼是根據(jù)這個業(yè)務(wù)線中一條document中的publishTime字段來決定這條數(shù)據(jù)落到哪個索引中。比如這條數(shù)據(jù)中的publishTime為20191023,那它就會落到index_20191023這個索引中。但是現(xiàn)在居然在index_20191023這個索引中發(fā)現(xiàn)了其他天的數(shù)據(jù),而且并沒有規(guī)律可言,不同索引中有著很多不同日期的數(shù)據(jù),造成了es數(shù)據(jù)的混亂。

主要從下面幾個方面來排查:

1.es集群本身的問題,或許由于多節(jié)點(diǎn),多分片造成的。

? ? ? 一個完整的索引分成多個分片,這樣的好處是可以把一個大的索引拆分成多個,分布到不同的節(jié)點(diǎn)上。構(gòu)成分布式搜索。分片的數(shù)量只能在索引創(chuàng)建前指定,并且索引創(chuàng)建后不能更改。

2.線上環(huán)境數(shù)據(jù)量太大,當(dāng)代碼中有線程不安全的地方,會造成數(shù)據(jù)錯亂

3.關(guān)系型數(shù)據(jù)庫同步到es的方案機(jī)制是否有問題

不管是哪個方面問題,核心就是根據(jù)字段publishTime找到相對應(yīng)的es索引,然后存儲這個過程除了問題,我一開始一度懷疑是es研發(fā)部門的問題,,,,我在document中多加了一個字段index,index就是publishTime的一種拼接形式,如果所有數(shù)據(jù)中index和es索引名一致,那就不是人家es部門的問題,如果不一致,說明落到es庫時出了問題。但是我加了一條日志后,在線上看到的index和es索引是一樣的,于是第一種原因就排除了。

之后幾天反復(fù)的看預(yù)發(fā)環(huán)境和線上環(huán)境的數(shù)據(jù),發(fā)現(xiàn)新測試的數(shù)據(jù)在預(yù)發(fā)環(huán)境上是保持一致的,沒有錯誤。在線上就會出問題,看來就是數(shù)據(jù)量一上來,就會造成數(shù)據(jù)混亂,這顯然就是代碼中有危險的地方,有線程不安全的地方。

于是就回去仔細(xì)看代碼,當(dāng)然核心還是publishTime前前后后各種格式的轉(zhuǎn)換,終于發(fā)現(xiàn)了simpledateFormat這個類,它是線程不全的,如果非要用它,那就不要讓所有線程共享,應(yīng)把它設(shè)置成局部變量。當(dāng)然還有其他的解決辦法,比如設(shè)置成threadLocal類型,或者利用同步鎖。當(dāng)然這里如果要用threadLocal的話,也是有點(diǎn)不穩(wěn)妥的,如果線程數(shù)過多,threadlocal要是常駐內(nèi)存會有風(fēng)險,我當(dāng)時也不確定threadlocal會不會釋放回收,可能會造成內(nèi)存泄漏的問題。

經(jīng)過查閱資料,發(fā)現(xiàn)threadlocal內(nèi)部果然會有無法釋放的部分,如下圖,實(shí)現(xiàn)是強(qiáng)引用,虛線是弱引用

每個thread中都存在一個map, map的類型是ThreadLocal.ThreadLocalMap. Map中的key為一個threadlocal實(shí)例. 這個Map的確使用了弱引用,不過弱引用只是針對key. 每個key都弱引用指向threadlocal. 當(dāng)把threadlocal實(shí)例置為null以后,沒有任何強(qiáng)引用指向threadlocal實(shí)例,所以threadlocal將會被gc回收. 但是,我們的value卻不能回收,因為存在一條從current thread連接過來的強(qiáng)引用. 只有當(dāng)前thread結(jié)束以后, current thread就不會存在棧中,強(qiáng)引用斷開, Current Thread, Map, value將全部被GC回收.


threadLocal生命相關(guān)引用

所以最后修改bug就顯而易見了,即保證線程初始化的時候單獨(dú)調(diào)用simpledateFormat,各自享用空間,或者在jdk1.8之后有代替它的線程安全的類。

這次修復(fù)es線上數(shù)據(jù)優(yōu)化問題其實(shí)很多時候都是代碼中我們很難發(fā)現(xiàn)的一些不安全問題,但更重要的是排查問題的定位與分析。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 不足的地方請大家多多指正,如有其它沒有想到的常問面試題請大家多多評論,一起成長,感謝!~ String可以被繼承嗎...
    啟示錄是真的閱讀 3,073評論 3 3
  • 包含的重點(diǎn)內(nèi)容:JAVA基礎(chǔ)JVM 知識開源框架知識操作系統(tǒng)多線程TCP 與 HTTP架構(gòu)設(shè)計與分布式算法數(shù)據(jù)庫知...
    消失er閱讀 4,552評論 1 10
  • 一、基礎(chǔ)知識:1、JVM、JRE和JDK的區(qū)別:JVM(Java Virtual Machine):java虛擬機(jī)...
    殺小賊閱讀 2,565評論 0 4
  • 九種基本數(shù)據(jù)類型的大小,以及他們的封裝類。(1)九種基本數(shù)據(jù)類型和封裝類 (2)自動裝箱和自動拆箱 什么是自動裝箱...
    關(guān)瑋琳linSir閱讀 2,064評論 0 47
  • 一 基礎(chǔ)篇 1.1 Java基礎(chǔ) 面向?qū)ο蟮奶卣鞒橄?將一類對象的共同特征總結(jié)出來構(gòu)建類的過程。繼承:對已有類的一...
    essential_note閱讀 769評論 0 0

友情鏈接更多精彩內(nèi)容