概述
“每一個成功的男人背後都有一個女人”。Mycat 也逃脫不了這個法則。Mycat 背後是阿里曾經開源的知名產品——Cobar。Cobar 的核心功能和優勢是 MySQL 資料庫分片,此產品曾經廣為流傳,據說最早的發起者對 Mysql 很精通,後來從阿里跳槽了,阿里隨後開源的 Cobar,並維持到 2013 年年初,然後,就沒有然後了。
今天介紹的Cobar是阿里巴巴開源( 官方github )的一個對應用保持透明的MySQL資料庫分散式處理中介軟體。 下面介紹一下關於Cobar的11個祕密...
Cobar 最重要的特性是分庫分表。Cobar 可以讓你把一個 MySQL 的 Table 放到 10 個甚至 100 個位於不同物理機上的 MySQL 伺服器上去儲存,而在使用者看來是一張表(邏輯表)。這樣功能很有價值。
Cobar 第一個祕密:Cobra 會假死?是的,很多人遇到這個問題。如何來驗證這點呢?可以做個簡單的小實驗,假如你的分片表中配置有表
company,則開啟 mysql 終端,執行下面的 SQL:
select sleep(500) from company;
此 SQL 會執行等待 500 秒,你再努力以最快的速度開啟 N 個 mysql 終端,都執行相同的 SQL,確保 N>當前 Cobra 的執行執行緒數:
show @@threadpool
的所有 Processor1-E 的執行緒池的執行緒數量總和,然後你再執行任何簡單的 SQL,或者試圖新建立連線,都會無法響應,此時
show @@threadpool
裡面看到 TASK_QUEUE_SIZE 已經在積壓中。
不可能吧,據說 Cobra 是 NIO 的非阻塞的,怎麼可能阻塞!別激動,去看看程式碼,Cobra 前端是 NIO 的, 而後端跟 Mysql 的互動,是阻塞模式,其 NIO 程式碼只給出了框架,還未來得及實現。真相永遠在程式碼裡,所以, 為了發現真相,還是轉行去做碼農吧!貌似碼農也像之前的技術工人,越來越稀罕了。
Cobar 第二個祕密:高可用的陷阱?每一個祕密的背後,總是隱藏著更大的祕密。Cobra 假死的的祕密背後,還隱藏著一個更為“強大”的祕密, 那就是假死以後,Cobra 的頻繁主從切換問題。我們看看 Cobra 的一個很好的優點——“高可用性”的實現機制:
分片節點 dn2_M1 配置了兩個 dataSource,並且配置了心跳檢測(heartbeat)語句,在這種配置下,每個dataNode 會定期對當前正在使用的 dataSource 執行心跳檢測,預設是第一個,頻率是 10 秒鐘一次,當心跳檢測失敗以後,會自動切換到第二個 dataSource 上進行讀寫,假如 Cobra 發生了假死,則在假死的 1 分鐘內,Cobra 會自動切換到第二個節點上,因為假死的緣故,第二個節點的心跳檢測也超時。於是,1 分鐘內 Cobra 頻繁來回切換,懂得 MySQL 主從複製機制的人都知道,在兩個節點上都執行寫操作意味著什麼?——可能資料一致性被破壞,誰也不知道那個機器上的資料是最新的。
還有什麼情況下,會導致心跳檢測失敗呢?這是一個不得不說的祕密:當後端資料庫達到最大連線後,會對新建連線全部拒絕,此時,Cobar 的心跳檢測所建立的新連線也會被拒絕,於是,心跳檢測失敗。
Cobar 第三個祕密:看上去很美的自動切換Cobar 很誘人的一個特性是高可用性,高可用性的原理是資料節點 DataNode 配置引用兩個 DataSource,並做心跳檢測,當第一個 DataSource 心跳檢測失敗後,Cobar 自動切換到第二個節點,當第二個節點失敗以後, 又自動切換回第一個節點,一切看起來很美,無人值守,幾乎沒有宕機時間。
在真實的生產環境中,我們通常會用至少兩個 Cobar 例項組成負載均衡,前端用硬體或者 HAProxy 這樣的負載均衡元件,防止單點故障,這樣一來,即使某個 Cobar 例項死了,還有另外一臺接手,某個 Mysql 節點死了, 切換到備節點繼續,至此,一切看起來依然很美,喝著咖啡,聽著音樂,領導視察,你微笑著點頭——No problem,Everything is OK!直到有一天,某個 Cobar 例項果然如你所願的死了,不管是假死還是真死,你按照早已做好的應急方案,優雅的做了一個不是很艱難的決定——重啟那個故障節點,然後繼續喝著咖啡,聽著音樂, 輕鬆寫好故障處理報告發給領導,然後又度過了美好的一天。
你忽然被深夜一個電話給驚醒,你來不及發火,因為你的直覺告訴你,這個問題很嚴重,大量的訂單資料發生錯誤很可能是昨天重啟 cobar 導致的資料庫發生奇怪的問題。你努力排查了幾個小時,終於發現,主備兩個庫都在同時寫資料,主備同步失敗,你根本不知道那個庫是最新資料,緊急情況下,你做了一個很英明的決定,停 止昨天故障的那個 cobar 例項,然後你花了 3 個通宵,解決了資料問題。
那麼,怎麼避免這個陷阱?目前只有一個辦法,節點切換以後,儘快找個合適的時間,全部叢集都同時重啟, 避免隱患。為何是重啟而不是用節點切換的命令去切換?想象一下 32 個分片的資料庫,要多少次切換?
MyCAT 怎麼解決這個問題的?很簡單,節點切換以後,記錄一個 properties 檔案( conf 目錄下),重啟的時候,讀取裡面的節點 index,真正實現了無故障無隱患的高可用性。
Cobar 第四個祕密:只實現了一半的 NIONIO 技術用作 JAVA 伺服器程式設計的技術標準,已經是不容置疑的業界常規做法,若一個 Java 程式設計師,沒聽說過 NIO,都不好意思說自己是 Java 人。所以 Cobar 採用 NIO 技術並不意外,但意外的是,只用了一半。
Cobar 本質上是一個“資料庫路由器”,客戶端連線到 Cobar,發生 SQL 語句,Cobar 再將 SQL 語句通過後端與 MySQL 的通訊介面 Socket 發出去,然後將結果返回給客戶端的 Socket 中。下面給出了 SQL 執行過程簡要邏輯:
SQL->FrontConnection->Cobar->MySQLChanel->MySQL\t
FrontConnection 實現了 NIO 通訊,但 MySQLChanel 則是同步的 IO 通訊,原因很簡單,指令比較複雜,NIO 實現有難度,容易有 BUG。後來最新版本 Cobar 嘗試了將後端也 NIO 化,大概實現了 80%的樣子,但沒有完成,也存在缺陷。
由於前端 NIO,後端 BIO,於是另一個有趣的設計產生了——兩個執行緒池,前端 NIO 部分一個執行緒池,後端 BIO 部分一個執行緒池。各自相互不干擾,但這個設計的結果,導致了執行緒的浪費,也對效能調優帶來很大的困難。
由於後端是 BIO,所以,也是 Cobar 吞吐量無法太高、另外也是其假死的根源。
MyCAT 在 Cobar 的基礎上,完成了徹底的 NIO 通訊,並且合併了兩個執行緒池,這是很大一個提升。從 1.1版本開始,MyCAT 則徹底用了 JDK7 的 AIO,有一個重要提升。
Cobar 第五個祕密:阻塞、又見阻塞Cobar 本質上類似一個交換機,將後端 Mysql 的返回結果資料經過加工後再寫入前端連線並返回,於是前後端連線都存在一個“寫佇列”用作緩衝,後端返回的資料發到前端連線 FrontConnection 的寫佇列中排隊等待被髮送,而通常情況下,後端寫入的的速度要大於前端消費的速度,在跨分片查詢的情況下,這個現象更為明顯, 於是寫執行緒就在這裡又一次被阻塞。
解決辦法有兩個,增大每個前端連線的“寫佇列”長度,減少阻塞出現的情況,但此辦法只是將問題拋給了 使用者,要是使用者能夠知道這個寫佇列的預設值小了,然後根據情況進行手動嘗試調整也行,但 Cobar 的程式碼中並沒有把這個問題暴露出來,比如寫一個告警日誌,佇列滿了,建議增大佇列數。於是絕大多數情況下,大家就默默的排隊阻塞,無人知曉。
MyCAT 解決此問題的方式則更加人性化,首先將原先陣列模式的固定長度的佇列改為連結串列模式,無限制,並且併發性更好,此外,為了讓使用者知道是否佇列過長了(一般是因為 SQL 結果集返回太多,比如 1 萬條記錄), 當超過指定閥值(可配)後,會產生一個告警日誌。
Cobar 第六個祕密:又愛又恨的 SQL 批處理模式正如一枚硬幣的正反面無法分離,一塊磁石怎樣切割都有南北極,愛情中也一樣,愛與恨總是糾纏著,無法理順,而 Cobar 的 SQL 批處理模式,也恰好是這樣一個令人又愛又恨的個性。
通常的 SQL 批處理,是將一批 SQL 作為一個處理單元,一次性提交給資料庫,資料庫順序處理完以後,再返回處理結果,這個特性對於資料批量插入來說,效能提升很大,因此也被普遍應用。JDBC 的程式碼通常如下:
String sql = "insert into travelrecord (id,user_id,traveldate,fee,days) values(?,?,?,?,?)"; ps = con.prepareStatement(sql);for (Map<String, String> map : list){ ps.setLong(1, Long.parseLong(map.get("id")));ps.setString(2, (String) map.get("user_id"));ps.setString(3, (String) map.get("traveldate"));ps.setString(4, (String) map.get("fee"));ps.setString(5, (String) map.get("days"));ps.addBatch();}ps.executeBatch();con.commit();ps.clearBatch();
但 Cobar 的批處理模式的實現,則有幾個地方是與傳統不同的:
·提交到 cobar 的批處理中的每一條 SQL 都是單獨的資料庫連線來執行的;·批處理中的 SQL 併發執行。併發多連線同時執行,則意味著 Batch 執行速度的提升,這是讓人驚喜的一個特性,但單獨的資料庫連線併發執行,則又帶來一個意外的副作用,即事務跨連線了,若一部分事務提交成功,而另一部分失敗,則導致髒資料問題。看到這裡,你是該“愛”呢還是該“恨”?
先不用急著下結論,我們繼續看看 Cobar 的邏輯,SQL 併發執行,其實也是依次獲取獨立連線並執行,因此還是有稍微的時間差,若某一條失敗了,則 cobar 會在會話中標記”事務失敗,需要回滾“,下一個沒執行的SQL 就丟擲異常並跳過執行,客戶端就捕獲到異常,並執行 rollback,回滾事務。絕大多數情況下,資料庫正常執行,此刻沒有宕機,因此事務還是完整保證了,但萬一恰好在某個 SQL commit 指令的時候宕機,於是杯具了,部分事務沒有完成,資料沒寫入。但這個概率有多大呢?一條 insert insert 語句執行 commit 指令的時間假如是50 毫秒,100 條同時提交,最長跨越時間是 5000 毫秒,即 5 秒中,而這個 C 指令的時間佔據程式整個插入邏輯的時間的最多 20%,假如程式批量插入的執行時間佔整個時間的 20%(已經很大比例了),那就是 20%×20%=4%的概率,假如機器的可靠性是 99.9%,則遇到失敗的概率是 0.1%×4%=十萬分之四。十萬分之四,意味著 99.996%的可靠性,親,可以放心了麼?
另外一個問題,即批量執行的 SQL,通常都是 insert 的,插入成功就 OK,失敗的怎麼辦?通常會記錄日誌, 重新找機會再插入,因此建議主鍵是能日誌記錄的,用於判斷資料是否已經插入。
最後,假如真要多個 SQL 使用同一個後端 MYSQL 連線並保持事務怎麼辦?就採用通常的事務模式,單條執行 SQL,這個過程中,Cobar 會採用 Session 中上次用過的物理連線執行下一個 SQL 語句,因此,整個過程是與通常的事務模式完全一致。
Cobar 第七個祕密:庭院深深鎖清秋說起死鎖,貌似我們大家都只停留在很久遠的回憶中,只在教科書裡看到過,也看到過關於死鎖產生的原因以及破解方法,只有 DBA 可能會偶爾碰到資料庫死鎖的問題。但很多用了 Cobar 的同學後來經常發現一個奇怪 的問題,SQL 很久沒有應答,百思不得其解,無奈之下找 DBA 排查後發現竟然有資料庫死鎖現象,而且比較頻繁發生。要搞明白為什麼 Cobar 增加了資料庫死鎖的概率,只能從原始碼分析,當一個 SQL 需要拆分為多條 SQL 去到多個分片上執行的時候,這個執行過程是併發執行的,即 N 個 SQL 同時在 N 個分片上執行,這個過程抽象為教科書裡的事務模型,就變成一個執行緒需要鎖定 N 個資源並執行操作以後,才結束事務。當這 N 個資源的鎖定順序是隨機的情況下,那麼就很容易產生死鎖現象,而恰好 Cobar 並沒有保證 N 個資源的鎖定順序,於是我們再次榮幸“中獎”。
Cobar 第八個祕密:出乎意料的連線池資料庫連線池,可能是僅次於執行緒池的我們所最依賴的“資源池”,其重要性不言而喻,業界也因此而誕生了多個知名的開源資料庫連線池。我們知道,對於一個 MySQL Server 來說,最大連線通常是 1000-3000 之間, 這些連線對於通常的應用足夠了,通常每個應用一個 Database 獨佔連線,因此足夠用了,而到了 Cobar 的分表分庫這裡,就出現了問題,因為 Cobar 對後端 MySQL 的連線池管理是基於分片——Database 來實現的,而不是整個 MySQL 的連線池共享,以一個分片數為 100 的表為例,假如 50 個分片在 Server1 上,就意味著 Server1上的資料庫連線被切分為 50 個連線池,每個池是 20 個左右的連線,這些連線池並不能互通,於是,在分片表的情況下,我們的併發能力被嚴重削弱。明明其他水池的水都是滿的,你卻只能守著空池子等待。。。
Cobar 第九個祕密:無奈的熱裝載Cobar 有一個優點,配置檔案熱裝載,不用重啟系統而熱裝載配置檔案,但這裡存在幾個問題,其中一個問題是很多人不滿的,即每次過載都把後端資料庫重新斷連一次,導致業務中斷,而很多時候,大家改配置僅僅是為了修改分片表的定義,規則,增加分片表或者分片定義,而不會改變資料庫的配置資訊,這個問題由來已久, 但卻不太好修復。
Cobar 第十個祕密:不支援讀寫分離不支援讀寫分離,可能熟悉相關中介軟體的同學第一反應就是驚訝,因為一個 MySQL Proxy 最基本的功能就是提供讀寫分離能力,以提升系統的查詢吞吐量和查詢效能。但的確 Cobar 不支援讀寫分離,而且根據 Cobar 的配置檔案,要實現讀寫分離,還很麻煩。可能有些人認為,因為無法保證讀寫分離的時延,因此無法確定是否能查到之前寫入的資料,因此讀寫分離並不重要,但實際上,Mycat 的使用者裡,幾乎沒有不使用讀寫分離功能的, 後來還有志願者增加了強制查詢語句走主庫(寫庫)的功能,以解決剛才那個問題。
Cobar 第十一個祕密:不可控的主從切換Cobar 提供了 MySQL 主從切換能力,這個功能很實用也很方便,但你無法控制它的切換開啟或關閉,有時候我們不想它自動切換,因為到目前為止,還沒有什麼好的方法來確認 MySQL 寫節點宕機的時候,備節點是否已經 100%完成資料同步,因此存在資料不一致的風險,如何更可靠的確定是否能安全切換,這個問題比較複雜,Mycat 也一直在努力完善這個特性。