ShardingSphere 對接京東白條實戰
—
“ 作者
張永倫,京東數科高階軟體工程師,Apache ShardingSphere(Incubating) PPMC。長期從事分散式系統的高可用、高併發相關工作。熱衷於網路IO、效能優化方面的技術挑戰。目前專注於Sharding-Proxy的持續優化和PostgreSQL協議的開發工作。
2018年11月10日,ShardingSphere進入Apache孵化器,團隊在經歷了短暫的慶祝和喜悅後,立即投入到了3.1.0的開發工作中。3.1.0版本首次支援了DISTINCT子句,並且在全新解析引擎的支援下,能夠100%相容路由至單一資料節點的全部SQL(目前僅MySQL)。正是在這兩個重要特性的支援下,Apache ShardingSphere迎來了2018年度的最後一個挑戰:對接京東金融的殺手級應用—— 京東白條!
啟用京東白條,請掃描二維碼↑↑↑
京東白條由於業務體量巨大,資料庫不可避免地進行了水平拆分。拆分之後的資料節點規模達到了十萬級別,是極度少見的金融級、高併發、海量資料並存的應用系統。為了追求效能極致以及程式碼的可控性,京東白條之前是在業務框架中,根據分片鍵替換資料庫和表名稱進行分片的。
隨著業務的發展,通過業務框架進行分片的方式,使得程式碼的維護成本不斷攀升。ShardingSphere在經過了大量系統的驗證之後,理所當然的成為了京東白條的資料分片中介軟體的首選方案,ShardingSphere團隊也非常願意幫助京東白條團隊解耦業務和底層技術程式碼,緩解開發工程師肩上的重擔。
雖然開源三年有餘,ShardingSphere經歷了大量系統的檢驗,專案已經相對成熟,但面對國內乃至世界上屈指可數的京東金融王牌級產品,仍將是一次嚴峻的考驗。而事實上,對接工作也確實不是一帆風順,遇到了很多在以往系統上遇不到的深層次問題。本文將這些問題的解決過程記錄下來,供大家參考。
分散式鏈路追蹤系統
值得一提的是,本次對接大量運用了分散式鏈路追蹤技術,對問題定位起到了事半功倍的作用。京東數科平臺開發部的SGM(Service Governance And Monitoring),是一套致力於分散式服務監控、跟蹤、預警的綜合服務治理解決方案,是實現全鏈路監控的基礎。它的特點:
-
亞秒級延遲(被監控方法從呼叫產生到圖形展示、預警的總延時在10秒左右)
-
程式碼零侵入(啟動指令碼指定探針即可實現全鏈路的監控與預警)
-
全鏈路分析,快速故障定位(呼叫鏈整體分析,每個節點資料庫耗時、快取耗時、日誌耗時、網路耗時一目瞭然,快速根源問題定位)
-
高吞吐,彈性伸縮(資料結構及演算法極度優化,單核心支援5000QPS的計算量,支援橫向擴容)
-
效能影響小(服務平均響應時間在5ms時,效能影響小於3%)
-
多維度業務監控(靈活可配置的多維度業務監控,如分類監控、比值監控、累加監控、狀態轉移監控、業務流程監控等)
更多詳情請點選官網: http://imsgm.io
連線池配置
很多人根據經驗或公式設定資料庫連線池的大小,從幾十到幾百都有。但對於大規模分散式系統,每個例項可能只允許分配2~3個連線。比如,500個例項的系統,每個例項分配3個連線,那麼對於一個MySQL例項來說就產生了1500個連線。這種配置下,資料庫連線的問題會被無限放大,任何風吹草動都會導致getConnection()超時等問題。這次對接暴露出的問題,多少都與這種配置有關,所以有必要在前面單獨說明一下。京東白條為了更高的效能以及無中心化架構,採用了Sharding-JDBC作為資料分片的接入端,但同時也不得不面對應用例項過多會佔用資料庫大量連線的問題,因此需要將每個應用的連線數降至最低。
問題1 請求響應時間變長
現象
整合ShardingSphere後,請求響應較白條原系統慢,且GC比原系統多。
分析
經過一段時間的觀察,這個問題在幾分鐘後逐步恢復。檢視執行緒使用情況,發現系統啟動後,執行緒數量飆升。
而原系統的執行緒數量穩定。同時,還發現新系統的負載也比較高。
看到這個現象後,問題就很容易定位了。熟悉ShardingSphere的小夥伴都知道,程式啟動後會對所有分表的Metadata進行校驗。對於幾十,幾百張表的校驗,使用者幾乎感知不到,但面向十萬級別的資料庫表,再加上業務的壓力,導致校驗需要幾分鐘才能完成,嚴重降低系統性能。
處理
Metadata的校驗是非常有必要的,之前是強制必須校驗。經過討論決定,將校驗調整為可配置,使用者可以通過 check.table.metadata.enabled 屬性設定是否校驗,預設不校驗。
問題2 獲取資料庫連線超時
現象
隨著業務量的增加,系統頻繁丟擲異常: java.sql.SQLTransientConnectionException: HikariPool-25 - Connection is not available, request timed out after 3000ms.
同時,大量的請求超時,監控平臺不停報警。
分析
通過SGM的秒級監控,可以看到平均響應時間達到3秒,TP99高於15秒。
看到這個異常後,首先懷疑的是有慢SQL,會導致資料庫連線被長時間佔用,連線池佇列阻塞,大量超時,比較容易解釋這個現象,但可能性較小。
事實上,確實不是慢SQL的問題,隨便找一次請求,顯示Hikari耗時3372ms,MySQL耗時0.65ms,一定還存在更深層次的問題。是效能問題嗎?可是其他系統從來沒有測出過如此差的效能,Sharding-JDBC相對於原始JDBC的損耗是很小的。
經過一番波折,又有了新的發現。在檢視某個介面完整呼叫鏈的時候,發現了getConnection()被呼叫了6次,但只執行了3次SQL。多出來一倍的getConnection()幹什麼?這又與獲取連線超時有什麼關係呢?
還是在呼叫鏈中,發現了業務框架會在每次執行SQL前呼叫getMetaData()方法進行元資料檢查,這個方法返回一個DatabaseMetaData物件,並且需要額外佔用一個連線。這就能解釋,為什麼getConnection()被呼叫了6次,但只執行了3次SQL了。連線消耗大了一倍,連線池又配置的那麼小,所以超時很合理吧?
瞭解Apache ShardingSphere原理的小夥伴會知道,Sharding-JDBC的Connection物件是先於真正的資料庫Connection物件建立的,原因是如果不知道當前要執行SQL,是無法預知最終分片結果的,因此也無法預先建立與資料庫的真實連線。業務框架在每次執行SQL前呼叫getMetaData()方法,ShardingSphere只能在將第一個真實資料來源的MetaData取出。這就造成了無論最終執行的SQL路由在那個資料分片,所有的getMetaData()操作都將路由至第一個真實資料來源。顯然,單一的資料來源難以承受來自白條系統的全部壓力,因此導致了該資料來源的連接獲取超時。
處理
解決方法比較簡單,快取DatabaseMetaData物件,每次呼叫getMetaData()直接返回快取結果。當時並沒有意識到,DatabaseMetaData被快取的同時,也保持了與每一個物理庫的連線。但是,這次更新為另一個大型翻車埋下了伏筆。
問題3 SQL解析耗時高
現象
SQL解析偶爾出現幾十毫秒的耗時。這是在SGM中對parse()方法的耗時進行排序時發現的。
分析
ShardingSphere在3.1.0版為了更好的SQL相容性,採用基於ANTLR的解析引擎,其效率比原有自研解析引擎會低一些。但是由於解析結果的快取,真實應用時,影響可以忽略不計。上圖中的系統已經運行了幾個小時,不存在新出現並未加入快取的SQL情況,因此基本可以斷定是快取出了問題。
處理
直接看快取的程式碼,解析結果是通過弱引用儲存的。弱引用遇到GC就會被回收,應該改成軟引用,軟引用只會在OOM之前的那次GC被回收。
問題4 獲取資料庫連線超時
現象
標題怎麼跟問題2重複了?是的,前面一頓操作猛於虎,本以為可以高枕無憂了,然而卻被現實啪啪打臉。依然拋異常,獲取連線依然失敗,而且響應時間居高不下,也就比原系統慢個六七倍吧。
經過一段時間的觀察,又發現了響應時間很不穩定,存在較大的抖動。
同時,Young GC頻率明顯比原系統高。
分析
是時候認真分析一下這個異常了: java.sql.SQLTransientConnectionException: HikariPool-25 - Connection is not available, request timed out after 3000ms.
之所以會丟擲這個異常,與HikariCP的兩個引數有關:
如果你的應用等待連線池中連線的時間超過connectionTimeout,就會丟擲上面那個異常,所以Connection is not available, request timed out after 3000ms,說明這個引數白條配置的是3000。
這個引數控制了連線池的大小,如果maximumPoolSize個連線都不空閒,那麼新的getConnection()會在一個佇列裡阻塞住,然後要麼排隊拿到連線,要麼等待時間到達connectionTimeout後丟擲異常。
對於當前系統,要滿足什麼條件才會出現這個異常呢?我們按一個請求1ms計算,3個連線處理同步請求,每個連線每秒處理1000個,那麼吞吐就是3000。就是說,即使你從圖上即使看到QPS是3000,連線池的佇列裡都不用排隊的,不會存在積壓,不積壓就不會超時。如果積壓了,就一定會超時,即使1秒只積壓1個請求,按照當前的配置,排隊3秒,需要佇列裡有9000個請求,其中的第9000個請求會超時。所以,9000秒之後,就會持續不斷的拋異常。
從圖上可以看到,這個例項的QPS才100左右,所以即使峰值比現在高10倍,也是抗的住的(不考慮其他資源瓶頸的情況)。心裡有了這把尺子,就知道,當前連線池承受的壓力,至少是正常情況的幾十倍。第一時間就會懷疑存在SQL全路由的情況。這裡補充一下,系統正常執行情況下,SQL只會路由到一個真實表。全路由指的是SQL被路由到所有的真實庫或真實表。如果是庫全路由,會增加幾十倍的查詢,如果是表全路由,會增加幾千倍的查詢。
一條邏輯SQL期望路由到一個物理表,對應一次getSQLExecuteGroup呼叫。在SGM中過濾出慢SQL,檢視其完整呼叫鏈,發現getSQLExecuteGroup被呼叫了n次,基本可以確定是全路由的情況。下圖就是真實翻車現場:
全路由使系統的壓力數十倍的增加,不僅會造成getConnection()超時,還會導致系統響應時間抖動和GC頻繁等問題。
處理
根據呼叫鏈中的介面,找到邏輯SQL,類似:
這個包含子查詢的SQL沒有解析正確,導致被路由至全庫。
另一個SQL是CASE WHEN語法沒有解析正確,同樣被路由至全庫:
修復這兩個Bug後,系統恢復正常,再也沒出現類似的異常。
PS:以上兩個舉例的兩個SQL並非京東白條的業務系統真實SQL。
問題5 啟動8小時後服務不可用
現象
服務在毫無徵兆的的情況下,瞬間變為不可用。
檢視日誌,發現異常資訊:
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException:
No operations allowed after connection closed.
分析
為什麼是8小時呢?根據異常資訊,自然聯想到某個定時器超時導致連線被釋放,然後這個被釋放的連線又被服務拿到了去進行資料庫操作。首先想到的就是MySQL的wait_timeout和interactive_timeout兩個引數。
interactive_timeout針對互動式連線,wait_timeout針對非互動式連線。所謂的互動式連線,即在mysql_real_connect()函式中使用了CLIENT_INTERACTIVE選項。說得直白一點,通過MySQL客戶端連線資料庫是互動式連線,通過JDBC連線資料庫是非互動式連線。他們的預設值都是28800秒,也就是8小時。再一查日誌,從啟動到不可用這段時間也差不多8小時,貌似找到了問題的原因。
可是,在MySQL文件裡寫的很清楚,連線只有在8小時什麼也不幹的情況才會被關閉。而我們的情況是,掛掉的前一秒還在幹活。
在這裡糾結了很久,直到檢查白條應用配置的時候看到了HikariCP的maxLifetime引數:
這個引數的原理不同於MySQL的timeout,它並不探測連線是否一直在活躍中,它的原理更加類似於快取中的TTL概念,只要時間一到,做完當前工作後即會優雅關閉。京東白條中這個值是28770,也和日誌吻合。好了,終於證明了8小時後連線會關閉,然而並沒有什麼用,因為連線關閉後會被連線池清理掉,下一次getConnection()不會拿到已經關閉的連線。
還記得之前我們快取的DatabaseMetaData嗎?它在系統啟動的時取出DatabaseMetaData並快取,以供應用在獲取到真實的資料庫連線之前呼叫getMetaData()時使用,快取了DatabaseMetaData的引用之後,就將該連線關閉了。由於應用一直可以正常執行,因此讓我們產生了一個錯覺,認為快取的DatabaseMetaData引用實際是無需連線的,因為連線關了仍然可用。但我們忽略了連線池的特性,採用了連線池的連線關閉,其實並不是真正的斷開與資料庫的連線,而是將其歸還給了連線池,以便於其他請求複用。因此,快取一份引用的DatabaseMetaData仍然在隱式的使用當初建立它的那個連線。直到maxLifetime時間閾值,這個連線才真正被關閉,導致所有對DatabaseMetaData的操作,仍然使用那個被關閉的連線而產生異常。
處理
把DatabaseMetaData常用方法的返回結果也快取,完成之後關閉連線,也就是把連線還給連線池。最後既解決了問題,也節省了一個連線。順便吐個槽,DatabaseMetaData這個介面好幾百個方法,完全實現了一遍,工作量還是很大的。
當前進展
現在,ShardingSphere已經平穩的在白條生產環境運行了幾周,效能與原生JDBC幾乎一致,GC次數,資源消耗也未見異常。
與白條的對接還在繼續,不知道以後還會遇到什麼問題。在經歷了這輪血腥洗禮後,我們相信,以後不管再遇到什麼問題,都會有底氣的說一句:這是常規操作。
經歷了京東白條的挑戰,使ShardingSphere的穩定性得到了進一步的提升。關注ShardingSphere的小夥伴們,你們有計劃使用ShardingSphere了麼?
福利
想了解問題定位神器SGM?
https://mp.weixin.qq.com/s/EHC1M_TIR5tyyp1oCSJTww
Apache ShardingSphere(Incubating)自2016開源以來,不斷精進、不斷髮展,被越來越多的企業和個人認可:Github上收穫6000+的stars,70+公司企業的成功案例。此外,越來越多的企業和個人也加入到Apache ShardingSphere(Incubating)的開源專案中,為它的成長和發展貢獻了巨大力量。
我們從未停息過腳步,聆聽社群夥伴的需求和建議,不斷開發新的、強大的功能,不斷使其健壯可靠!
開源不易, 我們卻願向著最終的目標,步履不停!
那麼,正在閱讀的你,是否可以助我們一臂之力呢?分享、轉發、使用、交流,以及加入我們,都是對我們最大的鼓勵!
專案地址:
https://github.com/apache/incubator-shardingsphere
更多資訊請瀏覽官網:
http://shardingsphere.apache.org/
掃碼進群