最近基于Hystrix源碼添加一些花邊功能技俐,比如數(shù)據(jù)埋點(diǎn)乘陪、參數(shù)動態(tài)配置等,交付給業(yè)務(wù)之后雕擂,經(jīng)過一系列的壓測之后啡邑,發(fā)現(xiàn)了各種問題。
1井赌、埋點(diǎn)數(shù)據(jù)有問題
2谤逼、熔斷一直不恢復(fù)
先來看下埋點(diǎn)數(shù)據(jù)的問題贵扰,經(jīng)過封裝的Hystrix會把正常請求、試探請求流部、異常請求和熔斷狀態(tài)都記錄下來戚绕,通過壓測后發(fā)現(xiàn)發(fā)生熔斷的時(shí)候,出現(xiàn)了好幾個(gè)數(shù)據(jù)打點(diǎn)枝冀,正常情況下應(yīng)該只有一個(gè)舞丛。通過debug,加日志之后果漾,才發(fā)現(xiàn)這個(gè)隱秘的問題球切。
每次請求都會獲取一個(gè)對應(yīng)的熔斷器,假設(shè)服務(wù)剛啟動的時(shí)候绒障,對應(yīng)的熔斷器還沒有初始化吨凑,這時(shí)每個(gè)線程都會去嘗試初始化一個(gè),通過ConcurrentHashMap
的putIfAbsent
方法保證最后拿到的是同一個(gè)熔斷器端盆,但是作者忽略了一個(gè)問題怀骤,在熔斷器的初始化中费封,有這么一段邏輯:
上述邏輯中焕妙,熔斷器在初始化時(shí),會去注冊Metrics的數(shù)據(jù)流的回調(diào)(本質(zhì)是500ms執(zhí)行一次弓摘,判斷是不是達(dá)到熔斷閾值了)焚鹊。所以,如果有10個(gè)線程同時(shí)初始化熔斷韧献,雖然最終只會使用一個(gè)末患,但是其它9個(gè)熔斷器也會注冊回調(diào)。這就導(dǎo)致了當(dāng)發(fā)生熔斷時(shí)锤窑,熔斷標(biāo)識的埋點(diǎn)數(shù)據(jù)就有問題璧针。
最好的辦法就是初始化熔斷器的時(shí)候,加個(gè)鎖渊啰。
能避免的無效計(jì)算探橱,都盡量的避免。
再來看看第二個(gè)問題绘证,我覺得這算是一個(gè)大bug了隧膏,熔斷一直不恢復(fù),這意味著什么嚷那,意味著錢啊胞枕。
先看看什么情況下,熔斷之后不會恢復(fù)吧魏宽。
熔斷器內(nèi)部有三個(gè)狀態(tài):CLOSED
腐泻,HALF_OPEN
决乎,OPEN
。默認(rèn)情況下派桩,都是處于CLOSED
狀態(tài)瑞驱,當(dāng)請求的失敗率過高,達(dá)到閾值時(shí)窄坦,就自動從CLOSED
切成OPEN
唤反,這是所有的請求會執(zhí)行降級邏輯,這些都沒問題鸭津。熔斷開啟之后彤侍,如果過了一個(gè)試探窗口(5000ms),其中一個(gè)請求線程會把熔斷狀態(tài)從OPEN
切成HALF_OPEN
逆趋,表明要開始試探下游服務(wù)是不是已經(jīng)恢復(fù)了盏阶。如果下游已經(jīng)恢復(fù),那么這個(gè)請求正常返回之后闻书,會執(zhí)行markSuccess
方法名斟,該方法實(shí)現(xiàn)如下:
在這個(gè)方法中,會把熔斷轉(zhuǎn)態(tài)從HALF_OPEN
切成CLOSED
魄眉,熔斷恢復(fù)砰盐,分析下來,好像這一切是那么的理所當(dāng)然坑律,順理成章岩梳。
但是,但是;卧瘛<街怠!
在熔斷開啟期間宫屠,執(zhí)行降級的請求最后會執(zhí)行一個(gè)叫unsubscribeCommandCleanup
的Action列疗,代碼如下:
在該Action中,執(zhí)行circuitBreaker.markNonSuccess()
浪蹂,這個(gè)會導(dǎo)致什么問題抵栈?設(shè)想一下,如果試探請求剛把熔斷從OPEN
切成HALF_OPEN
乌逐,正在等待下游返回時(shí)竭讳,這時(shí)一個(gè)降級請求,理所當(dāng)然執(zhí)行了markNonSuccess
浙踢,順帶把熔斷又從HALF_OPEN
切成OPEN
绢慢,一切都是默默的發(fā)生,不留下一絲痕跡,不加個(gè)日志胰舆,你都不知道發(fā)生了什么骚露。
熔斷狀態(tài)被降級請求切回了OPEN
,這時(shí)試探請求結(jié)果成功返回缚窿,執(zhí)行markSuccess
方法棘幸,準(zhǔn)備把熔斷從從HALF_OPEN
切成CLOSED
,殊不知已經(jīng)被內(nèi)部間諜提早偷偷換了轉(zhuǎn)態(tài)倦零,就導(dǎo)致了應(yīng)該恢復(fù)的服務(wù)误续,繼續(xù)降級著,只能祈禱下次沒有降級請求提早偷換狀態(tài)扫茅。
去github上翻了一下蹋嵌,在1.5.12版本中,Action unsubscribeCommandCleanup
并不會執(zhí)行 circuitBreaker.markNonSuccess()
葫隙,而是在1.5.13中栽烂,為了修復(fù)一個(gè)bug而加入的。
加這段代碼的本意是:Fixed bug where an unsubscription of a command in half-open state leaves circuit permanently open恋脚,就是下面這種寫法腺办。
Observable<Boolean> o = cmd5.observe();
Subscription s = o.subscribe();
s.unsubscribe();
結(jié)果,種下了另一個(gè)隱患糟描,而這個(gè)問題17年7月就被種下了怀喉,遲遲未得到修復(fù),Hystrix難道沒人維護(hù)了蚓挤?
如果想用原生的Hystrix磺送,建議使用1.5.12版本;
如果想用最新的版本灿意,建議對源碼進(jìn)行適當(dāng)?shù)男薷模龠M(jìn)行使用崇呵。