微服務(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è)原因。
創(chuàng)新互聯(lián)建站-專(zhuān)業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性?xún)r(jià)比徐州網(wǎng)站開(kāi)發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫(kù),直接使用。一站式徐州網(wǎng)站制作公司更省心,省錢(qián),快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋徐州地區(qū)。費(fèi)用合理售后完善,十年實(shí)體公司更值得信賴(lài)。
理想的設(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ù)。
微服務(wù)之間的數(shù)據(jù)共享可以有下四種方式。
有一些靜態(tài)的數(shù)據(jù)庫(kù)表,例如國(guó)家,可能會(huì)被很多程序用到,而且程序內(nèi)部需要對(duì)國(guó)家這個(gè)表做連接(join)生成最終用戶(hù)展示數(shù)據(jù),這樣用微服務(wù)調(diào)用的方式就效率不高,影響性能。一個(gè)辦法是在每個(gè)微服務(wù)中配置一個(gè)這樣的表,它是只讀的,這樣就可以做數(shù)據(jù)庫(kù)連接了。當(dāng)然你需要保證數(shù)據(jù)同步。這個(gè)方案在多數(shù)情況下都是可以接受的,因?yàn)橐韵聝牲c(diǎ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)源,控制所有寫(xiě)操作以及相應(yīng)的業(yè)務(wù)驗(yàn)證邏輯,我們叫它主表。你的只讀庫(kù)可以叫從表。 當(dāng)一條數(shù)據(jù)寫(xiě)入主表后,會(huì)發(fā)一條廣播消息,所有擁有從表的微服務(wù)監(jiān)聽(tīng)消息并更新只讀表中的數(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)題:
除非你能用服務(wù)調(diào)用(沒(méi)有本地只讀數(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ì)講解。
這是最復(fù)雜的一種情況。一般情況下,你有一個(gè)表是主表,而其他表是從表。主表包含主要信息,而且這些主要信息被復(fù)制到從表,但微服務(wù)會(huì)有額外字段需要寫(xiě)入從表。這樣本地微服務(wù)對(duì)從表就既有讀也有寫(xiě)的操作。而且主表和從表有一個(gè)先后次序的關(guān)系。從表的主鍵來(lái)源于主表,因此一定先有主表,再有從表。
上圖是例子。假設(shè)我們有兩個(gè)與電影有關(guān)的微服務(wù),一個(gè)是電影論壇,用戶(hù)可以發(fā)表對(duì)電影的評(píng)論。另一個(gè)是電影商店?!癿ovie”是共享表,左邊的一個(gè)是電影論壇庫(kù),它的“movie”表是主表。右邊的是電影商店庫(kù),它的“movie”表是從表。它們共享“id”字段(主鍵)。主表是數(shù)據(jù)的主要來(lái)源,但從表里的“quantity”和“price”字段主表里面沒(méi)有。主表插入數(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ù)寫(xiě)入是單向的,方向是主表到從表。如果主表也需要這些字段,那么它們還要被回寫(xiě),那數(shù)據(jù)寫(xiě)入就變成雙向的。
這種方式是要絕對(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ù)表結(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)講有以下幾種類(lèi)型:
向后兼容的數(shù)據(jù)庫(kù)更新的好處是,當(dāng)程序部署出現(xiàn)問(wèn)題時(shí),如需進(jìn)行回滾。只要回滾程序就行了,而不必回滾數(shù)據(jù)庫(kù)?;貪L時(shí)一般只回滾一個(gè)版本。凡是需要?jiǎng)h除的表或字段在本次部署時(shí)都不做修改,等到一個(gè)或幾個(gè)版本之后,確認(rèn)沒(méi)有問(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ù)的一個(gè)難點(diǎn)是如何實(shí)現(xiàn)跨服務(wù)的事物支持。兩階段提交(Two-Phase Commit)已被證明性能上不能滿足需求,現(xiàn)在基本上沒(méi)有人用。被一致認(rèn)可的方法叫Saga。它的原理是為事物中的每個(gè)操作寫(xiě)一個(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ò)誤也被寫(xiě)入日志,并且所有正在執(zhí)行的命令被停止,開(kāi)始回滾操作。
Saga放松了對(duì)一致性(Consistency)的要求,它能保證的是最終一致性(Eventual Consistency),因此在事物執(zhí)行過(guò)程中數(shù)據(jù)是不一致的,并且這種不一致會(huì)被別的進(jìn)程看到。在生活中,大多數(shù)情況下,我們對(duì)一致性的要求并沒(méi)有那么高,短暫的不一致性是可以接收的。例如銀行的轉(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ě)一遍文章,我現(xiàn)在只講一下要點(diǎn)。網(wǎng)絡(luò)環(huán)境是不可靠的,正在執(zhí)行的命令可能很長(zhǎng)時(shí)間都沒(méi)有返回結(jié)果,這時(shí),第一,你要設(shè)定一個(gè)超時(shí)。第二,因?yàn)槟悴恢罌](méi)有返回值的原因是,已經(jīng)完成了命令但網(wǎng)絡(luò)出了問(wèn)題,還是沒(méi)完成就犧牲了,因此不知道是否要執(zhí)行補(bǔ)償操作。這時(shí)正確的做法是重試原命令,直到得到完成確認(rèn),然后再執(zhí)行補(bǔ)償操作。但這對(duì)命令有一個(gè)要求,那就是這個(gè)操作必須是冪等的(Idempotent),也就是說(shuō)它可以執(zhí)行多次,但最終結(jié)果還是一樣的。
另外,有些操作的補(bǔ)償操作比較容易生成,例如付款操作,你只要把錢(qián)款退回就可以了。但有些操作,像發(fā)郵件,完成之后就沒(méi)有辦法回到之前的狀態(tài)了,這時(shí)就只能再發(fā)一個(gè)郵件更正以前的信息。因此補(bǔ)償操作不一定非要返回到原來(lái)的狀態(tài),而是抵消掉原來(lái)操作產(chǎn)生的效果。
我們?cè)瓉?lái)的程序大多數(shù)都是單體程序,但現(xiàn)在要把它拆分成微服務(wù),應(yīng)該怎樣做才能降低對(duì)現(xiàn)有應(yīng)用的影響呢?
我們用上面的圖來(lái)做例子。它共有兩個(gè)程序,一個(gè)是“Styling app”,另一個(gè)是“Warehouse app”,它們共享圖中下面的數(shù)據(jù)庫(kù),庫(kù)里有三張表,“core client”,“core sku”,“core item”。
假設(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ù)庫(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)的改造。更好的辦法是重寫(xiě)原來(lái)的功能(而不是重寫(xiě)原來(lái)的代碼),把重點(diǎn)放在拆分業(yè)務(wù)功能上,而不是拆分代碼上,用新的設(shè)計(jì)和技術(shù)來(lái)實(shí)現(xiàn)這個(gè)業(yè)務(wù)功能。
數(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)有程序的影響。