億級核心表如何優(yōu)雅擴(kuò)展字段?中臺團(tuán)隊實戰(zhàn)經(jīng)驗揭秘
1.導(dǎo)語
億級數(shù)據(jù)的核心表新增一個字段,遠(yuǎn)不止一句簡單的“ALTER TABLE”,鎖表風(fēng)險、頁分裂、索引性能衰減……每一個問題都可能引發(fā)線上事故。如何在不影響業(yè)務(wù)的前提下,只需簡單的配置,即可實現(xiàn)字段的動態(tài)擴(kuò)展?本文將帶你揭秘中臺團(tuán)隊的實戰(zhàn)解決方案。
2.背景
軟件行業(yè)中,唯一不變的因素就是“變化”。一個新項目上線后,業(yè)務(wù)需要對現(xiàn)有功能做一些改動或升級,而實現(xiàn)這個功能必須要新增字段。新增字段乍一看無所謂,但如果加上一些前提條件,這張表是一張百億級數(shù)據(jù)的表,而且是公司的核心熱點表,你還能隨心所欲地加么。拋開加字段過程中的鎖表問題不說,后續(xù)隨著字段和數(shù)據(jù)的不斷增多,還會引發(fā)MySQL的頁分裂、碎片化、索引性能衰減等一系列問題。
如何處理這個問題呢?我們最直接能想到的就是兩種方式,擴(kuò)展字段和擴(kuò)展表。當(dāng)然也有人可能會想到用非關(guān)系型數(shù)據(jù)庫解決。而數(shù)據(jù)庫選型往往是在項目初期時考慮,如果是老項目,實現(xiàn)起來就有不小的難度和風(fēng)險,并且非關(guān)系型數(shù)據(jù)庫也有自己無能為力的方面。所以其他方式我們暫且先按下不表,下面我們主要討論怎么基于現(xiàn)有條件更優(yōu)雅地解決這個問題。
3.擴(kuò)展字段
擴(kuò)展字段是最容易想到的解決方案,在表中增加一個擴(kuò)展字段,以JSON格式存儲數(shù)據(jù)。這也是我們之前一直采用的方式,我們的使用方式是這樣的:
表結(jié)構(gòu):
order_id | extend |
111 | {"uid":1,"name":"張三"} |
222 | {"uid":2,"name":"李四"} |
... | ... |
偽代碼:
public class Order {
private Long orderId;
private String extend;
/**
* 為擴(kuò)展字段extend建一個內(nèi)部類
*/
@Data
public static class ExtendObj implements Serializable {
private Long uId;
private String name;
...
}
/**
* 擴(kuò)展字段的get、set方法
*/
public void setExtendObj(ExtendObj extendObj) {
// 為展示方便,省略判空等邏輯
this.extend = JSON.toJSONString(extendObj);
}
public ExtendObj getExtendObj() {
// 為展示方便,省略判空等邏輯
return JSON.parseObject(extend, ExtendObj.class);
}
}
如代碼所示,為了便于管理字段,我們?yōu)閿U(kuò)展字段創(chuàng)建了內(nèi)部類,并實現(xiàn)get、set方法,以便于使用。如果你是獨(dú)立部門,你的數(shù)據(jù)存儲只服務(wù)于自己的系統(tǒng),在表設(shè)計之初可以根據(jù)經(jīng)驗預(yù)留一些備用字段,再配合擴(kuò)展字段,基本上可以做到很少添加字段了。但這個方案也存在一些問題:
- 不可索引。extend里的字段無法建立索引進(jìn)行檢索。這里有經(jīng)驗的讀者可能會提出,MySQL 5.7.8版本支持JSON數(shù)據(jù)類型,可以為擴(kuò)展字段中的某一個或一部分字段建立索引。是的沒錯,但如果你之前用的是更老的版本,就需要升級,MySQL版本升級也不是說升就能升的,懂的都懂,這里就不展開說了。
- 并發(fā)覆蓋。當(dāng)對extend并發(fā)更新的時候,會出現(xiàn)覆蓋問題。這里我們采用了CAS更新的方式去避免,但是隨著extend中的字段不斷增多,沖突問題越來越頻繁,CAS策略就影響到了接口成功率。
- 重復(fù)工作。每次新增字段都需要為內(nèi)部類增加屬性,拉分支,重新打包上線。
如果你是中臺部門,除了上面的硬傷,還有一些更傷腦筋的問題在等著你:
- 數(shù)據(jù)膨脹。你不可能基于當(dāng)前的擴(kuò)展字段,來者不拒地存,字段總有一天會超長,就像一柄達(dá)摩克利斯之劍。當(dāng)然,你擁有的劍還不止一柄,你同樣需要擔(dān)心字段不斷膨脹之后的數(shù)據(jù)庫性能。然而你也不能來者皆拒,業(yè)務(wù)部門因為你的拒絕,需要為一兩個字段去自己新建一張表存儲維護(hù),成也很高,于是你陷入了兩難。
- 維護(hù)黑洞。隨著業(yè)務(wù)迭代,你的維護(hù)成本會越來越高,面對幾百個擴(kuò)展字段,你無法快速知道這些字段具體是哪個業(yè)務(wù)、什么場景在用,當(dāng)前還有沒有在使用、可不可以被下掉。
4.擴(kuò)展表
另一種方案是擴(kuò)展表。擴(kuò)展表將擴(kuò)展字段中的每個字段轉(zhuǎn)成一行,存儲到另外一張表中:
order_id | key | value |
111 | uId | 1 |
111 | name | 張三 |
222 | uId | 2 |
222 | name | 李四 |
如果后續(xù)新增了age屬性,數(shù)據(jù)就變?yōu)椋?/span>
order_id | key | value |
111 | uId | 1 |
111 | name | 張三 |
222 | uId | 2 |
222 | name | 李四 |
111 | age | 26 |
222 | age | 38 |
擴(kuò)展表解決了擴(kuò)展字段無法索引的問題,由于把字段拆開了存,也很大程度上緩解了并發(fā)問題。同時,由于擴(kuò)展數(shù)據(jù)不在主表存儲了,也釋放了主表的壓力,讓加字段更從容一些。但也引進(jìn)來一個新問題:本來一條記錄的許多屬性,變成了多條記錄,行數(shù)成倍增加了。為解決這個問題,我們基于主表現(xiàn)有的分庫分表邏輯,對擴(kuò)展表也進(jìn)行了分庫和分表。
擴(kuò)展表方案貌似解決了擴(kuò)展字段方案的大部分問題,可索引、沒有并發(fā)覆蓋問題、不影響主表性能。但還是沒有解決字段維護(hù)問題,你還是不知道哪些字段場景在用什么字段,哪些字段可以下線。并且在實踐中,我們還歸納出其他一些使用場景:
場景1:有些業(yè)務(wù)期望將數(shù)據(jù)存儲在訂單中臺后,在訂單的后續(xù)某個節(jié)點傳遞給下游服務(wù)。
場景2:有些業(yè)務(wù)期望擴(kuò)展字段中的某些字段與主表上的某些字段一起進(jìn)行檢索。
這兩個場景實現(xiàn)起來比較機(jī)械,我們也不希望每次都去開發(fā)。
5.現(xiàn)在的方案
為了滿足上面兩種使用場景,并且實現(xiàn)只需簡單的配置,即可實現(xiàn)字段的動態(tài)擴(kuò)展的愿景,最終,我們將整個系統(tǒng)拆分為三部分:數(shù)據(jù)管理、數(shù)據(jù)存儲、數(shù)據(jù)檢索。數(shù)據(jù)管理部分用于管理動態(tài)字段準(zhǔn)入、接口透傳信息、檢索要求、歸屬以及其他基本信息,數(shù)據(jù)存儲部分核心還是采用擴(kuò)展表的方案,數(shù)據(jù)檢索采用ES集群及自研ES管理系統(tǒng)ECP。
descript
現(xiàn)在,我們來看看當(dāng)前的系統(tǒng)能做什么:
- 首先,有了管理系統(tǒng)之后,字段的歸屬、作用域、場景都很清晰,不再是一個黑盒,生效狀態(tài)也可以進(jìn)行標(biāo)記,被標(biāo)記失效的字段后續(xù)就可以逐步下線。
- 對于場景1,業(yè)務(wù)可以將數(shù)據(jù)通過下單接口(或其他節(jié)點接口)傳入訂單系統(tǒng),并保存到數(shù)據(jù)庫。當(dāng)訂單流轉(zhuǎn)到對應(yīng)節(jié)點,需要調(diào)用相關(guān)接口時,會檢查參數(shù)透傳信息,搜集需要透傳到當(dāng)前接口的所有參數(shù)數(shù)據(jù),然后根據(jù)參數(shù)路徑,將之前保存的值設(shè)置到對應(yīng)請求參數(shù)中透傳下去。
- 對于場景2,為了規(guī)避連表查詢,我們還是借助了ES,通過ES合并主表和擴(kuò)展表數(shù)據(jù)進(jìn)行檢索。
至此,業(yè)務(wù)方再提出新增字段訴求,只需要在數(shù)據(jù)管理后臺進(jìn)行配置上線,使用者即可通過指定接口,或接口的指定參數(shù)將數(shù)據(jù)傳入,實現(xiàn)數(shù)據(jù)的存儲、傳遞、檢索能力,全程無需開發(fā)介入。
6.結(jié)語
在大數(shù)據(jù)量表上的動態(tài)擴(kuò)展字段,本質(zhì)上是靈活性與穩(wěn)定性的博弈,既要支撐業(yè)務(wù)快速迭代,又要規(guī)避“野蠻生長”的技術(shù)風(fēng)險?;诜种嗡枷耄覀儗⒑诵臄?shù)據(jù)與擴(kuò)展數(shù)據(jù)分離;在系統(tǒng)設(shè)計上,對數(shù)據(jù)管理、數(shù)據(jù)存儲、數(shù)據(jù)檢索三部分進(jìn)行解耦,把問題拆解,降低每一部分的設(shè)計難度;而在具體實踐上,我們也將整個系統(tǒng)功能進(jìn)行了封裝,以便其他有同樣困境的系統(tǒng)能夠快速擴(kuò)展該項能力。
關(guān)于作者王帥 轉(zhuǎn)轉(zhuǎn)交易中臺研發(fā)工程師