1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        【124期】面試官:談?wù)勎⒎?wù)的數(shù)據(jù)庫設(shè)計(jì)思路吧

        共 5560字,需瀏覽 12分鐘

         ·

        2021-01-25 02:56

        程序員的成長之路
        互聯(lián)網(wǎng)/程序員/技術(shù)/資料共享?
        關(guān)注


        閱讀本文大概需要 10 分鐘。

        作者:倚天碼農(nóng)

        cnblogs.com/code-craftsman/p/11702814.html

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

        微服務(wù)設(shè)計(jì)的一個(gè)關(guān)鍵是數(shù)據(jù)庫設(shè)計(jì),基本原則是每個(gè)服務(wù)都有自己單獨(dú)的數(shù)據(jù)庫,而且只有微服務(wù)本身可以訪問這個(gè)數(shù)據(jù)庫。它是基于下面三個(gè)原因。
        • 優(yōu)化服務(wù)接口:微服務(wù)之間的接口越小越好,最好只有服務(wù)調(diào)用接口(RPC或消息),沒有其他接口。如果微服務(wù)不能獨(dú)享自己的數(shù)據(jù)庫,那么數(shù)據(jù)庫也變成了接口的一部分,這大大拓展了接口范圍。
        • 錯(cuò)誤診斷:生產(chǎn)環(huán)境中的錯(cuò)誤大部分都是和數(shù)據(jù)庫有關(guān)的,要么是數(shù)據(jù)出了問題,要么是數(shù)據(jù)庫的使用方式出了問題。當(dāng)你不能完全控制數(shù)據(jù)庫的訪問時(shí),會有各種各樣的錯(cuò)誤發(fā)生。它可能是別的程序直接連到你的數(shù)據(jù)庫或者是其他部門直接用客戶端訪問數(shù)據(jù)庫的數(shù)據(jù),而這些都是在程序中查不到的,增加了錯(cuò)誤排查難度。如果是程序中的問題,只要修改了代碼,那么這個(gè)錯(cuò)誤就不會再有。而上面提到的錯(cuò)誤,你永遠(yuǎn)都沒法預(yù)測它們什么時(shí)候還會再次發(fā)生。
        • 性能調(diào)優(yōu):性能調(diào)優(yōu)也是一樣,你需要對數(shù)據(jù)庫有全權(quán)控制才能保證它的性能。如果其他部門一定要訪問數(shù)據(jù)庫,而且只是查詢的話,那么可以另外創(chuàng)建一份只讀數(shù)據(jù)庫,讓他們在另一個(gè)庫中查詢,這樣才不會影響到你的庫。
        理想的設(shè)計(jì)是你的數(shù)據(jù)庫只有你的服務(wù)能訪問,你也只調(diào)用自己數(shù)據(jù)庫中的數(shù)據(jù),所有對別的微服務(wù)的訪問都通過服務(wù)調(diào)用來實(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ù)庫表,例如國家,可能會被很多程序用到,而且程序內(nèi)部需要對國家這個(gè)表做連接(join)生成最終用戶展示數(shù)據(jù),這樣用微服務(wù)調(diào)用的方式就效率不高,影響性能。一個(gè)辦法是在每個(gè)微服務(wù)中配置一個(gè)這樣的表,它是只讀的,這樣就可以做數(shù)據(jù)庫連接了。當(dāng)然你需要保證數(shù)據(jù)同步。這個(gè)方案在多數(shù)情況下都是可以接受的,因?yàn)橐韵聝牲c(diǎn):
        • 靜態(tài)的數(shù)據(jù)庫表結(jié)構(gòu)基本不變:因?yàn)橐坏┍斫Y(jié)構(gòu)變了,你不但要更改所有微服務(wù)的數(shù)據(jù)庫表,還要修改所有微服務(wù)的程序。
        • 數(shù)據(jù)庫表中的數(shù)據(jù)變化不頻繁:這樣數(shù)據(jù)同步的工作量不大。另外當(dāng)你同步數(shù)據(jù)庫時(shí)總會有延遲,如果數(shù)據(jù)變化不頻繁那么你有很多同步方式可供選擇。

        只讀業(yè)務(wù)數(shù)據(jù)訪問

        如果你需要讀取別的數(shù)據(jù)庫里的動態(tài)業(yè)務(wù)數(shù)據(jù), 理想的方式是服務(wù)調(diào)用。如果你只是調(diào)用其他微服務(wù)做一些計(jì)算,一般情況下性能都是可以接受的。如果你需要做數(shù)據(jù)的連接,那么你可以用程序代碼來做,而不是用SQL語句。如果測試之后性能不能滿足要求,那你可以考慮在自己的數(shù)據(jù)庫里建一套只讀數(shù)據(jù)表。數(shù)據(jù)同步方式大致有兩種。如果是事件驅(qū)動方式,就用發(fā)消息的方式進(jìn)行同步,如果是RPC方式,就用數(shù)據(jù)庫本身提供的同步方式或者第三方同步軟件。
        通常情況下,你可能只需要其他數(shù)據(jù)庫的幾張表,每張表只需要幾個(gè)字段。這時(shí),其他數(shù)據(jù)庫是數(shù)據(jù)的最終來源,控制所有寫操作以及相應(yīng)的業(yè)務(wù)驗(yàn)證邏輯,我們叫它主表。你的只讀庫可以叫從表。當(dāng)一條數(shù)據(jù)寫入主表后,會發(fā)一條廣播消息,所有擁有從表的微服務(wù)監(jiān)聽消息并更新只讀表中的數(shù)據(jù)。但這時(shí)你要特別小心,因?yàn)樗奈kU(xiǎn)性要比靜態(tài)表大得多。第一它的表結(jié)構(gòu)變更會更頻繁,而且它的變更完全不受你控制。第二業(yè)務(wù)數(shù)據(jù)不像靜態(tài)表,它是經(jīng)常更新的,這樣對數(shù)據(jù)同步的要求就比較高。要根據(jù)具體的業(yè)務(wù)需求來決定多大的延遲是可以接受的。
        另外它還有兩個(gè)問題:
        • 數(shù)據(jù)的容量:數(shù)據(jù)庫中的數(shù)據(jù)量是影響性能的主要因素。因?yàn)檫@個(gè)數(shù)據(jù)是外來的,不利于掌握它的流量規(guī)律,很難進(jìn)行容量規(guī)劃,也不能更好地進(jìn)行性能調(diào)優(yōu)。
        • 接口外泄:微服務(wù)之間的接口本來只有服務(wù)調(diào)用接口,這時(shí)你可以對內(nèi)部程序和數(shù)據(jù)庫做任何更改,而不影響其他服務(wù)。現(xiàn)在數(shù)據(jù)庫表結(jié)構(gòu)也變成了接口的一部分。接口一旦發(fā)布之后,基本是不能更改的,這大大限制了你的靈活性。幸運(yùn)的是因?yàn)榱硗饨艘惶妆?,有了一個(gè)緩沖,當(dāng)主表修改時(shí),從表也許不需要同步更新。
        除非你能用服務(wù)調(diào)用(沒有本地只讀數(shù)據(jù)庫)的方式完成所有功能,不然不管你是用RPC方式還是事件驅(qū)動方式進(jìn)行微服務(wù)集成,上面提到的問題都是不可避免的。但是你可以通過合理規(guī)劃數(shù)據(jù)庫更改,來減少上面問題帶來的影響,下面將會詳細(xì)講解。

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

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

        直接訪問其它數(shù)據(jù)庫

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

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

        從上面的論述可以看出,數(shù)據(jù)庫表結(jié)構(gòu)的修改是一個(gè)影響范圍很廣的事情。在微服務(wù)架構(gòu)中,共享的表在別的服務(wù)中也會有一個(gè)只讀的拷貝?,F(xiàn)在當(dāng)你要更改表結(jié)構(gòu)時(shí),還需要考慮到對別的微服務(wù)的影響。當(dāng)在單體(Monolithic)架構(gòu)中,為了保證程序部署能夠回滾,數(shù)據(jù)庫的更新是向后兼容的。
        需要兼容性的另一個(gè)原因是支持藍(lán)綠發(fā)布(Blue-Green Deployment)。在這種部署方式中,你同時(shí)擁有新舊版本的代碼,由負(fù)載均衡來決定每一個(gè)請求指向那個(gè)版本。它們可以共享一個(gè)數(shù)據(jù)庫(這就要求數(shù)據(jù)庫是向后兼容的),也可以使用不同的數(shù)據(jù)。數(shù)據(jù)庫的更新簡單來講有以下幾種類型:
        • 增加表或字段:如果字段可取空值,這個(gè)操作是向后兼容的。如果是非空值就要插入一個(gè)缺省值。
        • 刪除表或字段:可先暫時(shí)保留被刪除表或字段,經(jīng)過幾個(gè)版本之后再刪除。
        • 修改字段名:新增加一個(gè)字段,把數(shù)據(jù)從舊字段拷貝到新字段,用數(shù)據(jù)庫觸發(fā)器(或程序)同步舊字段和新字段(供過渡時(shí)期使用)。然后再在幾個(gè)版本之后把原來的字段刪除(請參閱Update your Database Schema Without Downtime)。
        • 修改表名:如果數(shù)據(jù)庫支持可更新視圖,最簡單的辦法是先修改表的名字,然后創(chuàng)建一個(gè)可更新視圖指向原來的表(請參閱Evolutionary Database Design )。如果數(shù)據(jù)庫不支持可更新視圖,使用的方法與修改字段名相似,需要創(chuàng)建新的表并做數(shù)據(jù)同步。
        • 修改字段類型:與修改字段名幾乎相同,只是在拷貝數(shù)據(jù)時(shí),需要做數(shù)據(jù)類型轉(zhuǎn)換。
        向后兼容的數(shù)據(jù)庫更新的好處是,當(dāng)程序部署出現(xiàn)問題時(shí),如需進(jìn)行回滾。只要回滾程序就行了,而不必回滾數(shù)據(jù)庫?;貪L時(shí)一般只回滾一個(gè)版本。凡是需要刪除的表或字段在本次部署時(shí)都不做修改,等到一個(gè)或幾個(gè)版本之后,確認(rèn)沒有問題了再刪除。它的另一個(gè)好處就是不會對其他微服務(wù)中的共享表產(chǎn)生立刻的直接影響。當(dāng)本微服務(wù)升級后,其他微服務(wù)可以評估這些數(shù)據(jù)庫更新帶來的影響再決定是否需要做相應(yīng)的程序或數(shù)據(jù)庫修改。

        跨服務(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í)行之前,所有的命令都會按順序被存入日志中,然后Saga執(zhí)行協(xié)調(diào)器從日志中取出命令,依次執(zhí)行。當(dāng)某個(gè)執(zhí)行出現(xiàn)錯(cuò)誤時(shí),這個(gè)錯(cuò)誤也被寫入日志,并且所有正在執(zhí)行的命令被停止,開始回滾操作。
        Saga放松了對一致性(Consistency)的要求,它能保證的是最終一致性(Eventual Consistency),因此在事物執(zhí)行過程中數(shù)據(jù)是不一致的,并且這種不一致會被別的進(jìn)程看到。在生活中,大多數(shù)情況下,我們對一致性的要求并沒有那么高,短暫的不一致性是可以接收的。例如銀行的轉(zhuǎn)賬操作,它們在執(zhí)行過程中都不是在一個(gè)數(shù)據(jù)庫事物里執(zhí)行的,而是用記賬的方式分成兩個(gè)動作來執(zhí)行,保證的也是最終一致性。
        Saga的原理看起來很簡單,但要想正確的實(shí)施還是有一定難度的。它的核心問題在于對錯(cuò)誤的處理,要把它完全講明白需要另寫一遍文章,我現(xiàn)在只講一下要點(diǎn)。
        網(wǎng)絡(luò)環(huán)境是不可靠的,正在執(zhí)行的命令可能很長時(shí)間都沒有返回結(jié)果,這時(shí),第一,你要設(shè)定一個(gè)超時(shí)。第二,因?yàn)槟悴恢罌]有返回值的原因是,已經(jīng)完成了命令但網(wǎng)絡(luò)出了問題,還是沒完成就犧牲了,因此不知道是否要執(zhí)行補(bǔ)償操作。
        這時(shí)正確的做法是重試原命令,直到得到完成確認(rèn),然后再執(zhí)行補(bǔ)償操作。但這對命令有一個(gè)要求,那就是這個(gè)操作必須是冪等的(Idempotent),也就是說它可以執(zhí)行多次,但最終結(jié)果還是一樣的。
        另外,有些操作的補(bǔ)償操作比較容易生成,例如付款操作,你只要把錢款退回就可以了。但有些操作,像發(fā)郵件,完成之后就沒有辦法回到之前的狀態(tài)了,這時(shí)就只能再發(fā)一個(gè)郵件更正以前的信息。因此補(bǔ)償操作不一定非要返回到原來的狀態(tài),而是抵消掉原來操作產(chǎn)生的效果。

        微服務(wù)的拆分

        我們原來的程序大多數(shù)都是單體程序,但現(xiàn)在要把它拆分成微服務(wù),應(yīng)該怎樣做才能降低對現(xiàn)有應(yīng)用的影響呢?
        我們用上面的圖來做例子。它共有兩個(gè)程序,一個(gè)是“Styling app”,另一個(gè)是“Warehouse app”,它們共享圖中下面的數(shù)據(jù)庫,庫里有三張表,“core client”,“core sku”,“core item”。
        假設(shè)我們要拆分出來一個(gè)微服務(wù)叫“client-service”,它需要訪問“core client”表。第一步,我們先把程序從原來的代碼里拆分出來,變成一個(gè)服務(wù). 數(shù)據(jù)庫不動,這個(gè)服務(wù)仍然指向原來的數(shù)據(jù)庫。其他程序不再直接訪問這個(gè)服務(wù)管理的表,而是通過服務(wù)調(diào)用或另建共享表來獲取數(shù)據(jù)。
        第二步,再把服務(wù)的數(shù)據(jù)庫表拆分出來,這時(shí)微服務(wù)就擁有它自己的數(shù)據(jù)庫了,而不再需要原來的共享數(shù)據(jù)庫了。這時(shí)就成了一個(gè)真正意義上的的微服務(wù)。
        上面只講了拆分一個(gè)微服務(wù),如果有多個(gè)需要拆分,則需一個(gè)一個(gè)按照上面講的方法依次進(jìn)行。
        另外,Martin Fowler在他的文章"Break Monolith into Microservices"里有一個(gè)很好的建議。那就是,當(dāng)你把服務(wù)從單體程序里拆分時(shí),不要只想著把代碼拆分出來。因?yàn)楝F(xiàn)在的需求可能已經(jīng)跟原來有所不同,原先的設(shè)計(jì)可能也不太適用了。而且,技術(shù)也已更新,代碼也要作相應(yīng)的改造。更好的辦法是重寫原來的功能(而不是重寫原來的代碼),把重點(diǎn)放在拆分業(yè)務(wù)功能上,而不是拆分代碼上,用新的設(shè)計(jì)和技術(shù)來實(shí)現(xiàn)這個(gè)業(yè)務(wù)功能。

        結(jié)論

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

        推薦閱讀:

        【123期】一文搞定那些難纏的并發(fā)面試題

        【122期】談?wù)剮讉€(gè)SpringCloud常見面試題及答案

        【121期】面試官:什么是熔斷?什么是服務(wù)降級?

        5T技術(shù)資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,單片機(jī),樹莓派,等等。在公眾號內(nèi)回復(fù)「2048」,即可免費(fèi)獲取?。?/span>

        微信掃描二維碼,關(guān)注我的公眾號

        朕已閱?

        瀏覽 55
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            在线不卡视频 | 日韩三级大片 | 偷拍网址大全 | 精品欧美乱码久久久久久 | 插阴道网站 | 黄色成人视频在线免费看 | 艹少妇小说 | 中文字幕日韩在线视频 | 日本成人中文字幕在线 | 波多野结衣av电影 |