首頁>科技>

概述

“每一個成功的男人背後都有一個女人”。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 第四個祕密:只實現了一半的 NIO

NIO 技術用作 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 也一直在努力完善這個特性。

  • 整治雙十一購物亂象,國家再次出手!該跟這些套路說再見了
  • 華為蘋果升級快充技術,OPPO 65W超級閃充更加領先