作為開(kāi)發(fā)人員跪解,我們有很大概率會(huì)遇到需要將當(dāng)前正在使用的數(shù)據(jù)庫(kù)遷移到另一個(gè)數(shù)據(jù)庫(kù)的場(chǎng)景。比如你在項(xiàng)目早期選擇了 MySQL 作為數(shù)據(jù)庫(kù) 遇伞,雖然它已經(jīng)能滿足大多數(shù)業(yè)務(wù)場(chǎng)景和性能要求字旭,但隨著你的數(shù)據(jù)量越來(lái)越大業(yè)務(wù)日趨復(fù)雜,繼續(xù)使用 MySQL 則可能成為瓶頸莺奔,這時(shí)候你可能要開(kāi)始考慮將 MySQL 替換為更適合的數(shù)據(jù)庫(kù)比如 HBase(或 Cassandra...)欣范。
對(duì)于一個(gè)在線業(yè)務(wù)系統(tǒng)來(lái)說(shuō),遷移數(shù)據(jù)庫(kù)的挑戰(zhàn)在于不僅要做到不停機(jī)無(wú)縫遷移還要保證遷移過(guò)程風(fēng)險(xiǎn)可控令哟,這也正是本文要分享的內(nèi)容恼琼,介紹如何使用功能開(kāi)關(guān)無(wú)縫、安全地實(shí)現(xiàn)數(shù)據(jù)庫(kù)遷移励饵。
遷移案例:將 MySQL 遷移到 HBase
接下來(lái)將通過(guò)一個(gè)案例介紹整個(gè)遷移的實(shí)現(xiàn)過(guò)程驳癌。某個(gè)在線消息業(yè)務(wù)由于受限于 MySQL 性能和存儲(chǔ)瓶頸,所以需要將數(shù)據(jù)遷移到 HBase 以便于進(jìn)一步擴(kuò)大業(yè)務(wù)規(guī)模役听。
1颓鲜、使用功能開(kāi)關(guān)實(shí)現(xiàn)遷移控制
我們這里假設(shè)應(yīng)用程序邏輯都是通過(guò) DAO (數(shù)據(jù)存取對(duì)象)來(lái)查詢/讀取 MySQL 持久化數(shù)據(jù)。為了能將數(shù)據(jù)存取切換到 HBase 中典予,所以第一步是編寫(xiě)一個(gè)針對(duì) HBase 的 DAO甜滨,并且和 MySQL DAO 實(shí)現(xiàn)相同的接口以提供相同數(shù)據(jù)讀寫(xiě)能力。
現(xiàn)在已經(jīng)有兩種 DAO 接口實(shí)現(xiàn)瘤袖,一種有支持 MySQL衣摩,另一種是支持 HBase。此時(shí)開(kāi)始引入我們的功能開(kāi)關(guān)管理服務(wù) FeatureProbe捂敌。
先在eatureProbe 上創(chuàng)建四個(gè) Boolean 類型功能開(kāi)關(guān)來(lái)獨(dú)立控制對(duì) MySQL 和 HBase 的讀寫(xiě)艾扮,以其中一個(gè)開(kāi)關(guān)(messages-mysql-write)為例配置如下所示:
接下來(lái)我們對(duì)外統(tǒng)一提供 saveMessage 方法保存一個(gè)消息,代碼如下所示:
public void storeMessage(Message message, FPUser fpUser) {
if(fpClient.booleanValue('messages-mysql-write', fpUser, true)) {
mysqlMesseageDao.save(message)
}
if(fpclient.BooleanValue('messages-hbase-write', fpUser, true)) {
hbaseMesseageDao.save(message)
}
}
需要注意的是占婉,我們?cè)试S同一消息可能會(huì)保存在兩個(gè)地方泡嘴,其目的是保證了舊存儲(chǔ)(MySQL)的完整性的同時(shí)開(kāi)始將消息寫(xiě)入新數(shù)據(jù)存儲(chǔ)。對(duì)于讀取來(lái)說(shuō)其實(shí)現(xiàn)思路大致和存儲(chǔ)類似逆济,例如實(shí)現(xiàn)一個(gè)能根據(jù) ID 查詢消息的方法酌予,代碼如下所示:
public Message findMessageById(Long id, FPUser fpUser) {
// 同時(shí)獲取查詢 mysql 和 hbase 的開(kāi)關(guān)結(jié)果
boolean shouldReadMySQL = fpClient.booleanValue('events-mysql-read', fpUser, true)
boolean shouldReadHbase = fpClient.booleanValue('events-hbase-read', fpUser, true)
if (shouldReadMySQL && shouldReadHbase) { // 當(dāng)開(kāi)關(guān)被同時(shí)開(kāi)啟時(shí),將對(duì)比兩者結(jié)果奖慌,但仍然返回舊存儲(chǔ)數(shù)據(jù)
mysqlMessage = mysqlMessageDao.findMessageById(id)
hbaseMessage = hbaseMessageDao.findMessageById(id)
if(deepEqualsEntity(mysqlMessage, hbaseMessage)) {
logger.error(
"MySQL and Hbase message differ: mysql: {}, hbase: {}",
mysqlMessage,
hbaseMessage)
}
return mysqlMessage
} else if (shouldReadMySQL) { // 只從 MySQL 查詢
return mysqlMessageDao.findMessageById(id)
} else { // 只從 HBase 中查詢
return hbaseMessageDao.findMessageById(id)
}
}
這里的點(diǎn)在于抛虫,我們檢查兩個(gè)“讀取”的開(kāi)關(guān)結(jié)果,當(dāng)發(fā)現(xiàn)兩者都啟用時(shí)简僧,我們會(huì)分別兩個(gè)數(shù)據(jù)庫(kù)讀取數(shù)據(jù)建椰,并比較結(jié)果是否一致,當(dāng)發(fā)現(xiàn)不一致時(shí)涎劈,我們會(huì)記錄錯(cuò)誤广凸,并最終返回舊存儲(chǔ)(MySQL)中的數(shù)據(jù)阅茶。
2、漸進(jìn)式放量遷移
現(xiàn)在我們已經(jīng)將從新老存儲(chǔ)讀寫(xiě)的代碼封裝在功能開(kāi)關(guān)中谅海,接下來(lái)需要部署代碼并進(jìn)行測(cè)試脸哀。此時(shí)只需要關(guān)閉 HBase 讀寫(xiě)開(kāi)關(guān),并打開(kāi) mysql 讀寫(xiě)開(kāi)關(guān)扭吁,便可以安全地將代碼部署上線撞蜂。
我們測(cè)試遷移時(shí),只需要在 FeatureProbe 上修改 HBase “寫(xiě)開(kāi)關(guān)”的人群規(guī)則侥袜,僅為特定用戶 QA 開(kāi)啟 蝌诡,此時(shí)只有 QA 的消息會(huì)存儲(chǔ)到 HBase,過(guò)程中觀察性能指標(biāo)和錯(cuò)誤日志枫吧,如果一切符合預(yù)期浦旱,進(jìn)一步修改人群規(guī)則將寫(xiě) HBase 在更多的用戶上生效,直到所有用戶的 “寫(xiě)操作” 均同時(shí)寫(xiě)入了兩個(gè)數(shù)據(jù)庫(kù)九杂。
當(dāng)我們寫(xiě)入性能感到滿意時(shí)颁湖,此時(shí)可以開(kāi)始為一小部分用戶打開(kāi) HBase 的讀取開(kāi)關(guān),然后再次觀察性能指標(biāo)和錯(cuò)誤日志例隆,其中特別需要關(guān)注是否有** “MySQL and Hbase message differ...”的日志來(lái)確保兩個(gè)存儲(chǔ)數(shù)據(jù)的一致性甥捺。當(dāng)然,這里即便我們看到數(shù)據(jù)不一致的錯(cuò)誤日志镀层,對(duì)用戶也不會(huì)產(chǎn)生任何影響镰禾,因?yàn)樗麄內(nèi)栽谑褂门f數(shù)據(jù)存儲(chǔ)中的數(shù)據(jù)。如果數(shù)據(jù)和指標(biāo)均符合預(yù)期唱逢,您可以為部分用戶關(guān)閉 MySQL 的讀關(guān)開(kāi)吴侦,將使用 HBase 的數(shù)據(jù),并最終直到所有用戶均從 HBase 中讀取坞古。
最后妈倔,當(dāng)我們向所有用戶開(kāi)啟 HBase 讀取和寫(xiě)入操作后,應(yīng)該將所有舊數(shù)據(jù)從舊數(shù)據(jù)存儲(chǔ)遷移到新數(shù)據(jù)存儲(chǔ)(確保以冪等方式執(zhí)行此操作)來(lái)保證數(shù)據(jù)的整體完整性绸贡。
可見(jiàn),通過(guò)上述功能開(kāi)關(guān)漸進(jìn)式放量遷移的方式毅哗,不僅讓使得遷移可以無(wú)縫進(jìn)行(對(duì)用戶無(wú)感知)听怕,還有效保障的遷移的安全性。
3虑绵、收尾工作
最后一步尿瞭,我們需要確保關(guān)閉了舊數(shù)據(jù)庫(kù)(MySQL)的讀寫(xiě)開(kāi)關(guān),同時(shí)刪除代碼中對(duì)所有四個(gè)開(kāi)關(guān)的所有引用翅睛,使代碼中最終只剩下對(duì) HBase (新數(shù)據(jù)庫(kù))的讀/寫(xiě)声搁。再次部署上線后黑竞,便完成了整個(gè)數(shù)據(jù)庫(kù)的遷移工作。
在 FeatureProbe 開(kāi)關(guān)管理也很簡(jiǎn)單疏旨,由于以上四個(gè)開(kāi)關(guān)已經(jīng)完成了數(shù)據(jù)庫(kù)遷移的使命很魂,對(duì)于已經(jīng)過(guò)期、已完成工作的開(kāi)關(guān)都可以使用下線操作進(jìn)行管理檐涝。
FeatureProbe 就是一個(gè)高效的功能管理 (Feature management) 開(kāi)源服務(wù)遏匆,它提供了灰度放量、AB實(shí)驗(yàn)谁榜、實(shí)時(shí)配置變更等針對(duì)功能粒度的一系列管理能力幅聘。目前 FeatureProbe 使用 Apache 2.0 License 協(xié)議已經(jīng)完全開(kāi)源, 開(kāi)源地址:https://github.com/FeatureProbe/FeatureProbe。