這篇文章主要講解了“如何理解資源庫Repository的性能優(yōu)化”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“如何理解資源庫Repository的性能優(yōu)化”吧!
站在用戶的角度思考問題,與客戶深入溝通,找到德保網(wǎng)站設(shè)計與德保網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗,讓設(shè)計與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個性化、用戶體驗好的作品,建站類型包括:成都網(wǎng)站設(shè)計、成都做網(wǎng)站、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣、國際域名空間、雅安服務(wù)器托管、企業(yè)郵箱。業(yè)務(wù)覆蓋德保地區(qū)。
聚合根需通過資源庫(Repository)持久化,資源庫將聚合根的存儲與存儲中間件(MySQL、ElasticSearch、MonogoDB等)解耦,我們可以根據(jù)聚合的業(yè)務(wù)特性決定選擇關(guān)系型數(shù)據(jù)庫還是非關(guān)系型數(shù)據(jù)庫存儲聚合根。
很多讀者可能還存在疑問,為什么資源庫只提供一個save方法持久化聚合根。原因是在DDD中,資源庫是聚合根的容器,但并不限制容器是什么做的,也就是前面說的與底層解耦。如果容器是Key-value數(shù)據(jù)庫做的,是不支持update某個字段的,并且inset和update是不區(qū)分的。資源庫與DAO不同,資源庫只是向領(lǐng)域模型提供聚合根以及持久化聚合根。
如果我們選擇關(guān)系型數(shù)據(jù)庫作為聚合根的容器,那么在存儲聚合根時可能就需要將聚合根以及聚合根下的實體拆分到多個表存儲,這就可能導(dǎo)致每次save聚合根都需要執(zhí)行多條update語句,即便聚合根下的實體并沒有發(fā)生任何的改變,即便只是聚合根修改了一個字段(值對象),因此會嚴重影響到應(yīng)用的性能。
為解決選擇關(guān)系數(shù)據(jù)庫作為聚合根容器導(dǎo)致的性能問題,我們需要付出額外的努力,如用內(nèi)存快照去判斷每次save聚合根只需要更新哪些表。
基于每個業(yè)務(wù)用例都需要通過資源庫獲取聚合根最后也通過資源庫持久化聚合根的特性,我們可以在獲取聚合根時創(chuàng)建快照,并且在持久化聚合根時對比(diff)快照,獲取差異信息,只執(zhí)行需要更新的差異信息。
本篇分享的是筆者實現(xiàn)的一種方案,雖然每個團隊定義的DDD代碼規(guī)范不同,但資源庫的實現(xiàn)上差異也并不大,因此也具有參考價值。
首先,抽象聚合根快照存儲器AggregateRootSnapshot,提供緩存聚合根快照、根據(jù)聚合根ID獲取快照、移除快照的方法。
提示:我們約定聚合根必須繼承一個抽象類BaseAggregate,該抽象類定義獲取聚合根ID的方法,在緩存快照時可以用聚合根id作為key緩存,這樣在拿的時候才能根據(jù)聚合根id拿到。
我們可以使用redis實現(xiàn)聚合根的緩存,但不建議使用性能低的存儲中間件存儲,因為那樣不僅資源庫的性能沒能得到優(yōu)化,反正還更影響性能。當(dāng)然,最好的方式是存儲在內(nèi)存中,雖然犧牲點內(nèi)存,這便是以空間換時間。
我們使用ThreadLocal存儲聚合根快照,因此編寫的AggregateRootSnapshot實現(xiàn)類如下。
如果聚合根id不是依賴數(shù)據(jù)庫生成的(我們也不推薦聚合根id依賴數(shù)據(jù)庫生成,原因在之前的文章已經(jīng)介紹過了)。為了避免在聚合根為新創(chuàng)建的情況下獲取到錯誤的快照,如線程在執(zhí)行上一次業(yè)務(wù)用例(一次接口請求)時,只調(diào)用獲取聚合根的方法,之后沒有調(diào)用聚合根的存儲方法移除快照(如獲取聚合根詳情),而這次是創(chuàng)建新的聚合根,當(dāng)然也沒有調(diào)用一次資源庫獲取聚合根的方法更新快照,那么這次獲取的就將是前一次的快照,因此我們還需要對比聚合根id是否相同。
只對比聚合根id當(dāng)然不能確保獲取的就是新的聚合根,能確保聚合根唯一還有這個條件:“基于每個業(yè)務(wù)用例都需要先通過資源庫獲取到聚合根,最后也需要通過資源庫持久化聚合根的特性”,這句話才是最重要的。
提示:ThreadLocal類型字段非靜態(tài),不會導(dǎo)致內(nèi)存泄露嗎?答案是不會,后面會講到。
接著,我們?yōu)槭褂藐P(guān)系型數(shù)據(jù)庫存儲聚合根的資源庫寫一個抽象類,需要使用快照優(yōu)化性能的資源庫可繼承此抽象類。
RepositorySnapshotSupper實現(xiàn)Repositor接口的findById、save、deleteById方法,另外提供抽象方法由子類實現(xiàn)。因為我們需要在findById獲取到聚合根時創(chuàng)建一份聚合根快照并緩存,在真正save聚合根之前獲取快照完成diff判斷,然后將diff結(jié)果交給子類,這樣子類在實現(xiàn)save時就可以根據(jù)diff結(jié)果減少不必要的sql。
提示:RepositorySnapshotSupper的快照存儲器并非靜態(tài)的,而快照存儲器的ThreadLocal類型字段也非靜態(tài),因此我們需要確保一個資源庫只存在一個實例(單例),才不會導(dǎo)致ThreadLocal內(nèi)存泄露,只是每個聚合根強引用一個ThreadLocal。
以上幾步都不是難點,難點在于如何實現(xiàn)快照的創(chuàng)建,以及diff實現(xiàn)。
快照工具類(SnnapshotUtils)實現(xiàn)思路:
提前條件:要求實體與聚合根提供一個私有的無參構(gòu)造函數(shù),用于通過反射創(chuàng)建實例。
1.通過反射實現(xiàn)字段值拷貝,當(dāng)聚合根的字段類型為非實體類型,那么就是值對象類型,對于值對象類型我們只需要拷貝引用即可;
2.如果是實體類型集合,則創(chuàng)建一個新的集合,并將原集合中每個實體元素都拷貝一份添加到新集合,將新集合賦給快照,實體的拷貝規(guī)則同聚合根,可使用遞歸實現(xiàn)。
Diff工具類實現(xiàn)思路:
先定義diff結(jié)果類型:未修改、新增、更新、刪除。 圖片
1.對于聚合根,如果不存在快照即認為Insert類型,聚合根下的實體也全部為Insert類型;
2.對于聚合根,如果存在快照,那么除實體類型或?qū)嶓w類型集合字段外,只要其它的任意一個值對象不同,就認為聚合根diff結(jié)果為Update類型,否則為Non類型;
3.只要聚合根不是新增,不管聚合根有沒有更新,都不會影響聚合根下的實體的diff;
4.如果實體與聚合根一對一,即不是集合類型字段,那么:如果對應(yīng)實體快照不存在,則認diff結(jié)果為Insert,否則如果實體快照存在但新的為null則認為是Delete,否則對比實體的各個值對象,未修改則為Non,修改則為Update;
5.如果實體與聚合根是多對一,即實體集合,如訂單有多個訂單item,那么需要一個個對比:新的item在快照中找不到,則為Insert,快照中的item已經(jīng)不存在新的實體集合,則為Delete,否則對比item,未修改則為Non,修改則為Update。
定義存儲diff結(jié)果的類:
由于BaseAggregate聚合根實現(xiàn)了實體接口(聚合根也是實體),因此我們在EntityDiff中使用Entity引用聚合根/實體,方便后續(xù)直接從diff中獲取entity執(zhí)行插入、更新,或是獲取entitySnapshot執(zhí)行刪除。(對于實體集合,也可存實體在集合中的索引。)
如果聚合根下的實體字段是集合類型,那么diff結(jié)果也使用集合存儲:
diff工具類的實現(xiàn):
由于項目代碼不便貼出來,在此我簡單寫了一個測試用例,分享下成果。
訂單聚合根:
提示:使用lombok有個坑,如果使用@Builder注解,需要提供一個無參構(gòu)建方法(建議是私有的構(gòu)建方法),然后在構(gòu)建方法上添加@Tolerate注解。
訂單item實體:
訂單資源庫實現(xiàn):
當(dāng)聚合根的diff結(jié)果類型為Insert時,全量存儲聚合根、聚合根下的實體;
當(dāng)聚合根的diff結(jié)果類型為Non時,不需要更新聚合根,但聚合根下的實體是否需要更新還需要根據(jù)聚合根實體的diff結(jié)果確定;
當(dāng)聚合根的diff結(jié)果類型為Update時,需要更新聚合根;
獲取實體的diff結(jié)果,根據(jù)diff結(jié)果決定是插入、更新、刪除、還是什么也不做。
單元測試:
單元測試結(jié)果如下:
本篇介紹如何通過快照+diff的方式優(yōu)化資源庫的性能,之所有能這樣做是因為每個業(yè)務(wù)用例都需要先通過資源庫獲取到聚合根,最后也需要通過資源庫持久化聚合根。出于性能考慮,我們決定以空間換時間,使用ThrealLocal+反射實現(xiàn)創(chuàng)建和緩存聚合根快照,最后也使用反射完成diff邏輯。當(dāng)然diff類還存在優(yōu)化空間。
本篇介紹的快照是基于聚合根(DO)的,當(dāng)然我們還可以基于(PO)去實現(xiàn),也會更簡單。
感謝各位的閱讀,以上就是“如何理解資源庫Repository的性能優(yōu)化”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對如何理解資源庫Repository的性能優(yōu)化這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!