Orleans之EventSourcing
引入:
????如果沒(méi)有意外,我再這篇文章中用ES代替EventSourcing,如果碰到"事件回溯","事件溯源","事溯"等詞語(yǔ),都一般代表Eventsourcing.
????如果引入Orleans而不用es的話,那就只用了Orleans一半的優(yōu)點(diǎn),多線程編程的邏輯\排錯(cuò)的簡(jiǎn)化以及可分布式.下面我聊聊重頭戲ES,這些都是我個(gè)人理解,如果有錯(cuò)誤歡迎指正.
????有時(shí)候產(chǎn)生一個(gè)理論是為了解決目前的困難,但是隨著事物的發(fā)展,原先的困難不再是主要困難,新的夜王必然會(huì)出現(xiàn),聽(tīng)聞三眼烏鴉要成為新的夜王我很是不解.馬丁這么寫(xiě)會(huì)收到憤怒的讀者寄來(lái)的刀片,我估計(jì)馬丁把收到全世界的刀片都賣(mài)了,都比他的小說(shuō)值錢(qián).扯遠(yuǎn)了。總而言之,我們關(guān)注的矛盾點(diǎn)會(huì)隨著時(shí)間變化而變化。以前為了節(jié)省存儲(chǔ)空間(當(dāng)然并不僅僅是為了空間考慮),對(duì)數(shù)據(jù)庫(kù)的設(shè)計(jì)提出了三個(gè)范式,來(lái)指導(dǎo)數(shù)據(jù)庫(kù)的設(shè)計(jì).可是現(xiàn)在時(shí)代變了狗蛋,存儲(chǔ)空間不值錢(qián)了.現(xiàn)在數(shù)據(jù)庫(kù)讀寫(xiě)成為了新的瓶頸,所以我們?cè)O(shè)計(jì)數(shù)據(jù)庫(kù)的理念就應(yīng)該變了.
????ES遵循這一個(gè)簡(jiǎn)單的思想,就是存儲(chǔ)的時(shí)候只存儲(chǔ)變化量,而不存儲(chǔ)最終結(jié)果.需要最終結(jié)果的地方,就必須提取所有的變化量以及初始狀態(tài),讓它們相加得到最終結(jié)果.這樣的看起來(lái)是個(gè)麻煩的迂回(我不就是想得到最終結(jié)果嘛),但是卻隱藏著一個(gè)事實(shí):由于只存儲(chǔ)變化量,意味著數(shù)據(jù)只增不減,意味著數(shù)據(jù)存儲(chǔ)后就不會(huì)被更改,意味著高并發(fā)和高吞吐量.但是有個(gè)缺點(diǎn)就是數(shù)據(jù)量增加很多,現(xiàn)在硬盤(pán)是最不值錢(qián)的,如果隨著時(shí)間的推移,變化量變的很多,要得到最終結(jié)果需要大量的運(yùn)算,但是.這些缺點(diǎn)不算什么大缺點(diǎn),有方法可以避免.雖然一直說(shuō)空間不值錢(qián),并不是說(shuō)數(shù)據(jù)大小無(wú)關(guān)緊要,在合理的設(shè)計(jì)中,數(shù)據(jù)應(yīng)當(dāng)能小則小.
????簡(jiǎn)單的一句總結(jié)就是滿足只增不減,存后不改的數(shù)據(jù),都能夠設(shè)計(jì)成ES.以便于提高吞吐量.
????為啥只增不減會(huì)提供吞吐量?因?yàn)閿?shù)據(jù)庫(kù)的數(shù)據(jù)永遠(yuǎn)不變,所以就放心大膽的讀取吧。哪怕是多線程都不需要架鎖??墒沁@里隱藏著另一個(gè)風(fēng)險(xiǎn),你讀取的數(shù)據(jù)不一定是最新的。這個(gè)"非最新"的確是個(gè)難題,不過(guò)好消息是,如果你一直讀,最終能夠讀到最新的。這就是"最終一致性"。
????關(guān)于es網(wǎng)絡(luò)上有很多的文章,可以拿來(lái)讀讀,這里就不做過(guò)多的敘述了。
????Orleans內(nèi)置了三種ES的實(shí)現(xiàn)方式,但并不是我提到的ES.它內(nèi)置的三種ES實(shí)現(xiàn)方式,分別是:StateStorage.LogConsistencyProvider,LogStorage.LogConsistencyProvider和CustomStorage.LogConsistencyProvider。這里我只使用介紹官方例子,它使用的是CustomStorage這個(gè)類(lèi)。
????使用Orleans達(dá)成ES,要明白幾個(gè)基本的概念,所謂的Event是個(gè)啥東西?在Orleans中Event一般是指的自定義的grain事件,這些事件的發(fā)生更改了grain的狀態(tài)。籠統(tǒng)的講就是更改了grain類(lèi)相關(guān)的各種變量的值。那么你要把這些值存起來(lái),就要問(wèn)兩個(gè)問(wèn)題,存到哪里去,用哪個(gè)類(lèi)來(lái)控制存。又要要求溯源,那就必須讀,那就要解決用什么序列化的問(wèn)題。這本質(zhì)上是一個(gè)問(wèn)題:用什么類(lèi)控制存儲(chǔ)。
????我打算拿官方的例子來(lái)介紹一下ES在orleans中基本實(shí)現(xiàn)方式??赐杲榻B,如果讀者要實(shí)現(xiàn)自己的ES,可以仿照更改。Orleans源碼里有一個(gè)例子是ReplicatedEventSample(以下簡(jiǎn)稱(chēng)RES),這個(gè)是我下面要說(shuō)的重點(diǎn)。
????RES例子是一個(gè)網(wǎng)頁(yè)例子,它的網(wǎng)頁(yè)如何體現(xiàn)的,以及如何運(yùn)行RES,我將不做介紹,這里我只是重點(diǎn)說(shuō)明網(wǎng)頁(yè)后邊的支撐程序:就是以下三個(gè)項(xiàng)目
????
????其中主要設(shè)計(jì)的類(lèi)總體圖像如下:
????EventGrain是這個(gè)項(xiàng)目的主角,它接受到外界發(fā)送來(lái)的一些消息,把這些消息使用ES的方式存儲(chǔ)下來(lái)。在使用RES的時(shí)候,要注意EventGrain的基類(lèi)以及需要實(shí)現(xiàn)的接口。
????EventState是Grain的狀態(tài)值。
????ReplicatedEventTable控制著實(shí)際的讀取與存儲(chǔ)。
????GeneratorGrain一個(gè)消息制造器,它發(fā)送OutCome消息給EventGrain
????TickerGrain,可有可無(wú)的東西。
????它們工作流程如下
????剛開(kāi)始啟動(dòng)的時(shí)候,silo會(huì)調(diào)用GeneratorGrain,這個(gè)GeneratorGrain的n個(gè)實(shí)例會(huì)激活EventGrain并每2.5秒發(fā)送一次OutCome消息。如下圖
????
????EventGrain接受到激活指令后初始化一個(gè)ReplicatedEventTable實(shí)例用來(lái)控制自己的存儲(chǔ),并調(diào)用RefreshNow()獲取最新的狀態(tài),這時(shí)候orleans會(huì)調(diào)用ReadStateFromStorage,EventGrain在這里使用ReplicatedEventTable完成真正的讀取動(dòng)作。
GeneratorGrain每隔2.5秒制造一個(gè)OutCome消息發(fā)送給EventGrain,EventGrain接受到消息并立即確認(rèn)此消息,這時(shí)候可以在OnStateChanged函數(shù)中針對(duì)Grain狀態(tài)值變化做一些必要的處理。這些必要的處理不應(yīng)該包括ES存儲(chǔ)動(dòng)作,ES存儲(chǔ)動(dòng)作會(huì)在ApplyUpdatesToStorage中進(jìn)行處理,它里面使用了ReplicatedEventTable來(lái)進(jìn)行真正的存儲(chǔ)。
????這樣一個(gè)簡(jiǎn)單的ES就實(shí)現(xiàn)了。這個(gè)ES里面的每一條數(shù)據(jù)都是OutCome,我們只需要改寫(xiě)ReplicatedEventTable實(shí)現(xiàn)自己的存儲(chǔ)控制類(lèi)。在本例子中,使用的是AzureTable作為存儲(chǔ)媒介的。
????這里有幾個(gè)問(wèn)題,如果我改寫(xiě)后的新存儲(chǔ)控制類(lèi),假設(shè)名字是EventSql是一個(gè)普通類(lèi),在EventSql中我精確的控制了sql連接,sql讀取等等。因?yàn)镋ventGrain是一個(gè)key一個(gè)實(shí)例的。在一個(gè)soli中有可能存在非常多個(gè)Grain實(shí)例,這時(shí)候我再使用EventSql就有可能會(huì)產(chǎn)生很多的鏈接,導(dǎo)致出錯(cuò)。要改進(jìn)這里,只需要讓EventSql本身也是擴(kuò)展自Grain(而不僅僅是一個(gè)普通的類(lèi))。同時(shí)使用一些機(jī)制來(lái)控制鏈接數(shù)??梢猿鼗@些EventSqlGrain,使用的時(shí)候從池子中取出一個(gè)。
????這樣官方的例子就算介紹完畢了,剩下的來(lái)聊聊一些其他細(xì)節(jié)的問(wèn)題。
????Orleans運(yùn)行時(shí)會(huì)時(shí)不時(shí)的自動(dòng)更新Grain的狀態(tài)值。但是有時(shí)候也是需要針對(duì)某些事件,我們自定義的去更新?tīng)顟B(tài)值,要實(shí)現(xiàn)這樣的動(dòng)作,可以重寫(xiě)TransitionState?函數(shù)。
????ReadStateFromStorage返回的值中需要有version?和對(duì)應(yīng)的State。剛開(kāi)始的時(shí)候,壓根什么都沒(méi)有存,那么就應(yīng)該返回0和一個(gè)State的默認(rèn)值。ApplyUpdatesToStorage有時(shí)候會(huì)存儲(chǔ)失敗,這時(shí)候運(yùn)行時(shí)會(huì)進(jìn)行重試。有時(shí)候雖然狀態(tài)值已經(jīng)實(shí)際存儲(chǔ)了,但是卻返回一個(gè)錯(cuò)誤,這樣會(huì)造成同一狀態(tài)值的重復(fù)存儲(chǔ),這里就需要程序有一個(gè)過(guò)濾機(jī)制或者確保一個(gè)重復(fù)的狀態(tài)值不會(huì)對(duì)程序邏輯造成損害。
????使用Orleans.EventSourcing.CustomStorage.LogConsistencyProvider(本例就是),它并不支持JournaledGrain類(lèi)中的RetrieveConfirmedEvents方法,如果需要使用它,要另行實(shí)現(xiàn)。
這里只是我個(gè)人寫(xiě)一些基礎(chǔ)的東西,具體改寫(xiě)還是需要自己去實(shí)現(xiàn),剛開(kāi)始可以仿照這個(gè)RES進(jìn)行改寫(xiě),把OutCome存儲(chǔ)到自己想要的地方。等改寫(xiě)完畢就會(huì)一個(gè)大致的概念。
至此我寫(xiě)完了orleans所有的主要方面,剩余的orleans底層的實(shí)現(xiàn)細(xì)節(jié),還是需要自己對(duì)照文檔啃源碼了。
????ES是實(shí)現(xiàn)業(yè)務(wù)需求的方法,一個(gè)項(xiàng)目本身是一個(gè)客觀物體,我們選擇了使用ES的辦法去實(shí)現(xiàn)它,會(huì)將這個(gè)項(xiàng)目的復(fù)雜度進(jìn)行轉(zhuǎn)移,降低了業(yè)務(wù)在程序?qū)崿F(xiàn)上的復(fù)雜度而同時(shí)增加了數(shù)據(jù)設(shè)計(jì)的復(fù)雜度。ES這個(gè)新的方法會(huì)讓某些復(fù)雜的業(yè)務(wù)邏輯變的簡(jiǎn)單,特別適合于那些需要高并發(fā)與高吞吐量的場(chǎng)景。
開(kāi)篇曾經(jīng)說(shuō)過(guò)的,"如果隨著時(shí)間的推移,變化量變的很多,要得到最終結(jié)果需要大量的運(yùn)算"是個(gè)缺點(diǎn),要克服這個(gè)缺點(diǎn),可以這樣搞:每隔一段時(shí)間(比如一周)就計(jì)算所有的最終結(jié)果,并把這個(gè)最終結(jié)果當(dāng)作新的初始值,同時(shí)把變化量轉(zhuǎn)移備份。簡(jiǎn)單的如下圖所示:
原文地址:?http://www.cnblogs.com/gaopang/p/8457990.html
.NET社區(qū)新聞,深度好文,歡迎訪問(wèn)公眾號(hào)文章匯總 http://www.csharpkit.com
總結(jié)
以上是默认站点為你收集整理的Orleans之EventSourcing的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 在.NetCore中使用Myrmec检测
- 下一篇: 如何停止Internet Explore