溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

微服務(wù)設(shè)計(jì)關(guān)鍵的難點(diǎn):微服務(wù)架構(gòu)的數(shù)據(jù)庫(kù)是如何設(shè)計(jì)的?

發(fā)布時(shí)間:2020-06-19 20:35:18 來(lái)源:網(wǎng)絡(luò) 閱讀:504 作者:Java_老男孩 欄目:編程語(yǔ)言

單獨(dú)的數(shù)據(jù)庫(kù):

微服務(wù)設(shè)計(jì)的一個(gè)關(guān)鍵是數(shù)據(jù)庫(kù)設(shè)計(jì),基本原則是每個(gè)服務(wù)都有自己?jiǎn)为?dú)的數(shù)據(jù)庫(kù),而且只有微服務(wù)本身可以訪問(wèn)這個(gè)數(shù)據(jù)庫(kù)。它是基于下面三個(gè)原因。

  • 優(yōu)化服務(wù)接口:微服務(wù)之間的接口越小越好,最好只有服務(wù)調(diào)用接口(RPC或消息),沒有其他接口。如果微服務(wù)不能獨(dú)享自己的數(shù)據(jù)庫(kù),那么數(shù)據(jù)庫(kù)也變成了接口的一部分,這大大拓展了接口范圍。
  • 錯(cuò)誤診斷:生產(chǎn)環(huán)境中的錯(cuò)誤大部分都是和數(shù)據(jù)庫(kù)有關(guān)的,要么是數(shù)據(jù)出了問(wèn)題,要么是數(shù)據(jù)庫(kù)的使用方式出了問(wèn)題。當(dāng)你不能完全控制數(shù)據(jù)庫(kù)的訪問(wèn)時(shí),會(huì)有各種各樣的錯(cuò)誤發(fā)生。它可能是別的程序直接連到你的數(shù)據(jù)庫(kù)或者是其他部門直接用客戶端訪問(wèn)數(shù)據(jù)庫(kù)的數(shù)據(jù),而這些都是在程序中查不到的,增加了錯(cuò)誤排查難度。如果是程序中的問(wèn)題,只要修改了代碼,那么這個(gè)錯(cuò)誤就不會(huì)再有。而上面提到的錯(cuò)誤,你永遠(yuǎn)都沒法預(yù)測(cè)它們什么時(shí)候還會(huì)再次發(fā)生。
  • 性能調(diào)優(yōu):性能調(diào)優(yōu)也是一樣,你需要對(duì)數(shù)據(jù)庫(kù)有全權(quán)控制才能保證它的性能。如果其他部門一定要訪問(wèn)數(shù)據(jù)庫(kù),而且只是查詢的話,那么可以另外創(chuàng)建一份只讀數(shù)據(jù)庫(kù),讓他們?cè)诹硪粋€(gè)庫(kù)中查詢,這樣才不會(huì)影響到你的庫(kù)。

理想的設(shè)計(jì)是你的數(shù)據(jù)庫(kù)只有你的服務(wù)能訪問(wèn),你也只調(diào)用自己數(shù)據(jù)庫(kù)中的數(shù)據(jù),所有對(duì)別的微服務(wù)的訪問(wèn)都通過(guò)服務(wù)調(diào)用來(lái)實(shí)現(xiàn)。當(dāng)然,在實(shí)際應(yīng)用中,單純的服務(wù)調(diào)用可能不能滿足性能或其他要求,不同的微服務(wù)都多少需要共享一些數(shù)據(jù)。

共享數(shù)據(jù):

微服務(wù)之間的數(shù)據(jù)共享可以有下四種方式。

靜態(tài)表:

有一些靜態(tài)的數(shù)據(jù)庫(kù)表,例如國(guó)家,可能會(huì)被很多程序用到,而且程序內(nèi)部需要對(duì)國(guó)家這個(gè)表做連接(join)生成最終用戶展示數(shù)據(jù),這樣用微服務(wù)調(diào)用的方式就效率不高,影響性能。一個(gè)辦法是在每個(gè)微服務(wù)中配置一個(gè)這樣的表,它是只讀的,這樣就可以做數(shù)據(jù)庫(kù)連接了。當(dāng)然你需要保證數(shù)據(jù)同步。這個(gè)方案在多數(shù)情況下都是可以接受的,因?yàn)橐韵聝牲c(diǎn):

  1. 靜態(tài)的數(shù)據(jù)庫(kù)表結(jié)構(gòu)基本不變:因?yàn)橐坏┍斫Y(jié)構(gòu)變了,你不但要更改所有微服務(wù)的數(shù)據(jù)庫(kù)表,還要修改所有微服務(wù)的程序。
  2. 數(shù)據(jù)庫(kù)表中的數(shù)據(jù)變化不頻繁:這樣數(shù)據(jù)同步的工作量不大。另外當(dāng)你同步數(shù)據(jù)庫(kù)時(shí)總會(huì)有延遲,如果數(shù)據(jù)變化不頻繁那么你有很多同步方式可供選擇。
只讀業(yè)務(wù)數(shù)據(jù)訪問(wèn):

如果你需要讀取別的數(shù)據(jù)庫(kù)里的動(dòng)態(tài)業(yè)務(wù)數(shù)據(jù), 理想的方式是服務(wù)調(diào)用。如果你只是調(diào)用其他微服務(wù)做一些計(jì)算,一般情況下性能都是可以接受的。如果你需要做數(shù)據(jù)的連接,那么你可以用程序代碼來(lái)做,而不是用SQL語(yǔ)句。如果測(cè)試之后性能不能滿足要求,那你可以考慮在自己的數(shù)據(jù)庫(kù)里建一套只讀數(shù)據(jù)表。數(shù)據(jù)同步方式大致有兩種。如果是事件驅(qū)動(dòng)方式,就用發(fā)消息的方式進(jìn)行同步,如果是RPC方式,就用數(shù)據(jù)庫(kù)本身提供的同步方式或者第三方同步軟件。

通常情況下,你可能只需要其他數(shù)據(jù)庫(kù)的幾張表,每張表只需要幾個(gè)字段。這時(shí),其他數(shù)據(jù)庫(kù)是數(shù)據(jù)的最終來(lái)源,控制所有寫操作以及相應(yīng)的業(yè)務(wù)驗(yàn)證邏輯,我們叫它主表。你的只讀庫(kù)可以叫從表。 當(dāng)一條數(shù)據(jù)寫入主表后,會(huì)發(fā)一條廣播消息,所有擁有從表的微服務(wù)監(jiān)聽消息并更新只讀表中的數(shù)據(jù)。但這時(shí)你要特別小心,因?yàn)樗奈kU(xiǎn)性要比靜態(tài)表大得多。第一它的表結(jié)構(gòu)變更會(huì)更頻繁,而且它的變更完全不受你控制。第二業(yè)務(wù)數(shù)據(jù)不像靜態(tài)表,它是經(jīng)常更新的,這樣對(duì)數(shù)據(jù)同步的要求就比較高。要根據(jù)具體的業(yè)務(wù)需求來(lái)決定多大的延遲是可以接受的。

另外它還有兩個(gè)問(wèn)題:

  1. 數(shù)據(jù)的容量:數(shù)據(jù)庫(kù)中的數(shù)據(jù)量是影響性能的主要因素。因?yàn)檫@個(gè)數(shù)據(jù)是外來(lái)的,不利于掌握它的流量規(guī)律,很難進(jìn)行容量規(guī)劃,也不能更好地進(jìn)行性能調(diào)優(yōu)。
  2. 接口外泄:微服務(wù)之間的接口本來(lái)只有服務(wù)調(diào)用接口,這時(shí)你可以對(duì)內(nèi)部程序和數(shù)據(jù)庫(kù)做任何更改,而不影響其他服務(wù)?,F(xiàn)在數(shù)據(jù)庫(kù)表結(jié)構(gòu)也變成了接口的一部分。接口一旦發(fā)布之后,基本是不能更改的,這大大限制了你的靈活性。幸運(yùn)的是因?yàn)榱硗饨艘惶妆?,有了一個(gè)緩沖,當(dāng)主表修改時(shí),從表也許不需要同步更新。

除非你能用服務(wù)調(diào)用(沒有本地只讀數(shù)據(jù)庫(kù))的方式完成所有功能,不然不管你是用RPC方式還是事件驅(qū)動(dòng)方式進(jìn)行微服務(wù)集成,上面提到的問(wèn)題都是不可避免的。但是你可以通過(guò)合理規(guī)劃數(shù)據(jù)庫(kù)更改,來(lái)減少上面問(wèn)題帶來(lái)的影響,下面將會(huì)詳細(xì)講解。

讀寫業(yè)務(wù)數(shù)據(jù)訪問(wèn):

這是最復(fù)雜的一種情況。一般情況下,你有一個(gè)表是主表,而其他表是從表。主表包含主要信息,而且這些主要信息被復(fù)制到從表,但微服務(wù)會(huì)有額外字段需要寫入從表。這樣本地微服務(wù)對(duì)從表就既有讀也有寫的操作。而且主表和從表有一個(gè)先后次序的關(guān)系。從表的主鍵來(lái)源于主表,因此一定先有主表,再有從表。

微服務(wù)設(shè)計(jì)關(guān)鍵的難點(diǎn):微服務(wù)架構(gòu)的數(shù)據(jù)庫(kù)是如何設(shè)計(jì)的?

上圖是例子。假設(shè)我們有兩個(gè)與電影有關(guān)的微服務(wù),一個(gè)是電影論壇,用戶可以發(fā)表對(duì)電影的評(píng)論。另一個(gè)是電影商店?!癿ovie”是共享表,左邊的一個(gè)是電影論壇庫(kù),它的“movie”表是主表。右邊的是電影商店庫(kù),它的“movie”表是從表。它們共享“id”字段(主鍵)。主表是數(shù)據(jù)的主要來(lái)源,但從表里的“quantity”和“price”字段主表里面沒有。主表插入數(shù)據(jù)后,發(fā)消息,從表接到消息,插入一條數(shù)據(jù)到本地“movie”表。并且從表還會(huì)修改表里的“quantity”和“price”字段。在這種情況下,要給每一個(gè)字段分配一個(gè)唯一源頭(微服務(wù)),只有源頭才有權(quán)利主動(dòng)更改字段,其他微服務(wù)只能被動(dòng)更改(接收源頭發(fā)出的更改消息之后再改)。在本例子中, “quantity”和“price”字段的源頭是右邊的表,其他的字段的源頭都是左邊的表。本例子中“quantity”和“price”只在從表中存在,因此數(shù)據(jù)寫入是單向的,方向是主表到從表。如果主表也需要這些字段,那么它們還要被回寫,那數(shù)據(jù)寫入就變成雙向的。

直接訪問(wèn)其它數(shù)據(jù)庫(kù):

這種方式是要絕對(duì)禁止的。生產(chǎn)環(huán)境中的許多程序錯(cuò)誤和性能問(wèn)題都是由這種方式產(chǎn)生的。上面的三種方式由于是另外新建了本地只讀數(shù)據(jù)庫(kù)表,產(chǎn)生了數(shù)據(jù)庫(kù)的物理隔離,這樣一個(gè)數(shù)據(jù)庫(kù)的性能問(wèn)題不會(huì)影響到另一個(gè)。另外,當(dāng)主庫(kù)中的表結(jié)構(gòu)更改時(shí),你可以暫時(shí)保持從庫(kù)中的表不變,這樣程序還可以運(yùn)行。如果直接訪問(wèn)別人的庫(kù),主庫(kù)一修改,別的微服務(wù)程序馬上就會(huì)報(bào)錯(cuò)。

向后兼容的數(shù)據(jù)庫(kù)更新:

從上面的論述可以看出,數(shù)據(jù)庫(kù)表結(jié)構(gòu)的修改是一個(gè)影響范圍很廣的事情。在微服務(wù)架構(gòu)中,共享的表在別的服務(wù)中也會(huì)有一個(gè)只讀的拷貝?,F(xiàn)在當(dāng)你要更改表結(jié)構(gòu)時(shí),還需要考慮到對(duì)別的微服務(wù)的影響。當(dāng)在單體(Monolithic)架構(gòu)中,為了保證程序部署能夠回滾,數(shù)據(jù)庫(kù)的更新是向后兼容的。需要兼容性的另一個(gè)原因是支持藍(lán)綠發(fā)布(Blue-Green Deployment)。在這種部署方式中,你同時(shí)擁有新舊版本的代碼,由負(fù)載均衡來(lái)決定每一個(gè)請(qǐng)求指向那個(gè)版本。它們可以共享一個(gè)數(shù)據(jù)庫(kù)(這就要求數(shù)據(jù)庫(kù)是向后兼容的),也可以使用不同的數(shù)據(jù)。數(shù)據(jù)庫(kù)的更新簡(jiǎn)單來(lái)講有以下幾種類型:

  • 增加表或字段:如果字段可取空值,這個(gè)操作是向后兼容的。如果是非空值就要插入一個(gè)缺省值。
  • 刪除表或字段:可先暫時(shí)保留被刪除表或字段,經(jīng)過(guò)幾個(gè)版本之后再刪除。
  • 修改字段名:新增加一個(gè)字段,把數(shù)據(jù)從舊字段拷貝到新字段,用數(shù)據(jù)庫(kù)觸發(fā)器(或程序)同步舊字段和新字段(供過(guò)渡時(shí)期使用)。 然后再在幾個(gè)版本之后把原來(lái)的字段刪除。
  • 修改表名:如果數(shù)據(jù)庫(kù)支持可更新視圖,最簡(jiǎn)單的辦法是先修改表的名字,然后創(chuàng)建一個(gè)可更新視圖指向原來(lái)的表。如果數(shù)據(jù)庫(kù)不支持可更新視圖,使用的方法與修改字段名相似,需要?jiǎng)?chuàng)建新的表并做數(shù)據(jù)同步。
  • 修改字段類型:與修改字段名幾乎相同,只是在拷貝數(shù)據(jù)時(shí),需要做數(shù)據(jù)類型轉(zhuǎn)換。

向后兼容的數(shù)據(jù)庫(kù)更新的好處是,當(dāng)程序部署出現(xiàn)問(wèn)題時(shí),如需進(jìn)行回滾。只要回滾程序就行了,而不必回滾數(shù)據(jù)庫(kù)。回滾時(shí)一般只回滾一個(gè)版本。凡是需要?jiǎng)h除的表或字段在本次部署時(shí)都不做修改,等到一個(gè)或幾個(gè)版本之后,確認(rèn)沒有問(wèn)題了再刪除。它的另一個(gè)好處就是不會(huì)對(duì)其他微服務(wù)中的共享表產(chǎn)生立刻的直接影響。當(dāng)本微服務(wù)升級(jí)后,其他微服務(wù)可以評(píng)估這些數(shù)據(jù)庫(kù)更新帶來(lái)的影響再?zèng)Q定是否需要做相應(yīng)的程序或數(shù)據(jù)庫(kù)修改。

跨服務(wù)事物:

微服務(wù)的一個(gè)難點(diǎn)是如何實(shí)現(xiàn)跨服務(wù)的事物支持。兩階段提交(Two-Phase Commit)已被證明性能上不能滿足需求,現(xiàn)在基本上沒有人用。被一致認(rèn)可的方法叫Saga。它的原理是為事物中的每個(gè)操作寫一個(gè)補(bǔ)償操作(Compensating Transaction),然后在回滾階段挨個(gè)執(zhí)行每一個(gè)補(bǔ)償操作。示例如下圖,在一個(gè)事物中共有3個(gè)操作T1,T2,T3。每一個(gè)操作要定義一個(gè)補(bǔ)償操作,C1,C2,C3。事物執(zhí)行時(shí)是按照正向順序先執(zhí)行T1,當(dāng)回滾時(shí)是按照反向順序先執(zhí)行C3。 事物中的每一個(gè)操作(正向操作和補(bǔ)償操作)都被包裝成一個(gè)命令(Command),Saga執(zhí)行協(xié)調(diào)器(Saga Execution Coordinator (SEC))負(fù)責(zé)執(zhí)行所有命令。在執(zhí)行之前,所有的命令都會(huì)按順序被存入日志中,然后Saga執(zhí)行協(xié)調(diào)器從日志中取出命令,依次執(zhí)行。當(dāng)某個(gè)執(zhí)行出現(xiàn)錯(cuò)誤時(shí),這個(gè)錯(cuò)誤也被寫入日志,并且所有正在執(zhí)行的命令被停止,開始回滾操作。

微服務(wù)設(shè)計(jì)關(guān)鍵的難點(diǎn):微服務(wù)架構(gòu)的數(shù)據(jù)庫(kù)是如何設(shè)計(jì)的?

Saga放松了對(duì)一致性(Consistency)的要求,它能保證的是最終一致性(Eventual Consistency),因此在事物執(zhí)行過(guò)程中數(shù)據(jù)是不一致的,并且這種不一致會(huì)被別的進(jìn)程看到。在生活中,大多數(shù)情況下,我們對(duì)一致性的要求并沒有那么高,短暫的不一致性是可以接收的。例如銀行的轉(zhuǎn)賬操作,它們?cè)趫?zhí)行過(guò)程中都不是在一個(gè)數(shù)據(jù)庫(kù)事物里執(zhí)行的,而是用記賬的方式分成兩個(gè)動(dòng)作來(lái)執(zhí)行,保證的也是最終一致性。

Saga的原理看起來(lái)很簡(jiǎn)單,但要想正確的實(shí)施還是有一定難度的。它的核心問(wèn)題在于對(duì)錯(cuò)誤的處理,要把它完全講明白需要另寫一遍文章,我現(xiàn)在只講一下要點(diǎn)。網(wǎng)絡(luò)環(huán)境是不可靠的,正在執(zhí)行的命令可能很長(zhǎng)時(shí)間都沒有返回結(jié)果,這時(shí),第一,你要設(shè)定一個(gè)超時(shí)。第二,因?yàn)槟悴恢罌]有返回值的原因是,已經(jīng)完成了命令但網(wǎng)絡(luò)出了問(wèn)題,還是沒完成就犧牲了,因此不知道是否要執(zhí)行補(bǔ)償操作。這時(shí)正確的做法是重試原命令,直到得到完成確認(rèn),然后再執(zhí)行補(bǔ)償操作。但這對(duì)命令有一個(gè)要求,那就是這個(gè)操作必須是冪等的(Idempotent),也就是說(shuō)它可以執(zhí)行多次,但最終結(jié)果還是一樣的。

另外,有些操作的補(bǔ)償操作比較容易生成,例如付款操作,你只要把錢款退回就可以了。但有些操作,像發(fā)郵件,完成之后就沒有辦法回到之前的狀態(tài)了,這時(shí)就只能再發(fā)一個(gè)郵件更正以前的信息。因此補(bǔ)償操作不一定非要返回到原來(lái)的狀態(tài),而是抵消掉原來(lái)操作產(chǎn)生的效果。

微服務(wù)的拆分:

我們?cè)瓉?lái)的程序大多數(shù)都是單體程序,但現(xiàn)在要把它拆分成微服務(wù),應(yīng)該怎樣做才能降低對(duì)現(xiàn)有應(yīng)用的影響呢?

微服務(wù)設(shè)計(jì)關(guān)鍵的難點(diǎn):微服務(wù)架構(gòu)的數(shù)據(jù)庫(kù)是如何設(shè)計(jì)的?

我們用上面的圖來(lái)做例子。它共有兩個(gè)程序,一個(gè)是“Styling app”,另一個(gè)是“Warehouse app”,它們共享圖中下面的數(shù)據(jù)庫(kù),庫(kù)里有三張表,“core client”,“core sku”,“core item”。

微服務(wù)設(shè)計(jì)關(guān)鍵的難點(diǎn):微服務(wù)架構(gòu)的數(shù)據(jù)庫(kù)是如何設(shè)計(jì)的?

假設(shè)我們要拆分出來(lái)一個(gè)微服務(wù)叫“client-service”,它需要訪問(wèn)“core client”表。第一步,我們先把程序從原來(lái)的代碼里拆分出來(lái),變成一個(gè)服務(wù). 數(shù)據(jù)庫(kù)不動(dòng),這個(gè)服務(wù)仍然指向原來(lái)的數(shù)據(jù)庫(kù)。其他程序不再直接訪問(wèn)這個(gè)服務(wù)管理的表,而是通過(guò)服務(wù)調(diào)用或另建共享表來(lái)獲取數(shù)據(jù)。

微服務(wù)設(shè)計(jì)關(guān)鍵的難點(diǎn):微服務(wù)架構(gòu)的數(shù)據(jù)庫(kù)是如何設(shè)計(jì)的?

第二步,再把服務(wù)的數(shù)據(jù)庫(kù)表拆分出來(lái),這時(shí)微服務(wù)就擁有它自己的數(shù)據(jù)庫(kù)了,而不再需要原來(lái)的共享數(shù)據(jù)庫(kù)了。這時(shí)就成了一個(gè)真正意義上的的微服務(wù)。

上面只講了拆分一個(gè)微服務(wù),如果有多個(gè)需要拆分,則需一個(gè)一個(gè)按照上面講的方法依次進(jìn)行。

另外,Martin Fowler有一個(gè)很好的建議。那就是,當(dāng)你把服務(wù)從單體程序里拆分時(shí),不要只想著把代碼拆分出來(lái)。因?yàn)楝F(xiàn)在的需求可能已經(jīng)跟原來(lái)有所不同,原先的設(shè)計(jì)可能也不太適用了。而且,技術(shù)也已更新,代碼也要作相應(yīng)的改造。更好的辦法是重寫原來(lái)的功能(而不是重寫原來(lái)的代碼),把重點(diǎn)放在拆分業(yè)務(wù)功能上,而不是拆分代碼上,用新的設(shè)計(jì)和技術(shù)來(lái)實(shí)現(xiàn)這個(gè)業(yè)務(wù)功能。

結(jié)論:

數(shù)據(jù)庫(kù)設(shè)計(jì)是微服務(wù)設(shè)計(jì)的一個(gè)關(guān)鍵點(diǎn),基本原則是每個(gè)微服務(wù)都有自己?jiǎn)为?dú)的數(shù)據(jù)庫(kù),而且只有微服務(wù)本身可以訪問(wèn)這個(gè)數(shù)據(jù)庫(kù)。微服務(wù)之間的數(shù)據(jù)共享可以通過(guò)服務(wù)調(diào)用,或者主、從表的方式實(shí)現(xiàn)。在共享數(shù)據(jù)時(shí),要找到合適的同步方式。在微服務(wù)架構(gòu)中,數(shù)據(jù)庫(kù)的修改影響廣泛,需要保證這種修改是向后兼容的。實(shí)現(xiàn)跨服務(wù)事物的標(biāo)準(zhǔn)方法是Saga。當(dāng)把單體程序拆分成微服務(wù)時(shí),可以分步進(jìn)行,以減少對(duì)現(xiàn)有程序的影響。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI