作者 | 菜v菜
責(zé)編 | maozz
YY妹:菜菜哥,你換形象啦?
菜菜:這么巧,你也換啦!聽說是不會(huì)畫畫的菜嫂經(jīng)過九牛二虎之力的功勞哦!結(jié)果.....
YY妹:前幾天我出去面試了,面試官問我微服務(wù)的知識(shí),我回答的可好了
菜菜:看來微服務(wù)你真的下工夫研究了呀
YY妹:是呀是呀,但是碰到一個(gè)問題,有狀態(tài)的服務(wù)是什么意思呢?
菜菜:看來你又掛在這個(gè)問題上了,且聽這次分解
YY妹:菜菜哥,你換形象啦?
菜菜:這么巧,你也換啦!聽說是不會(huì)畫畫的菜嫂經(jīng)過九牛二虎之力的功勞哦!結(jié)果.....
YY妹:前幾天我出去面試了,面試官問我微服務(wù)的知識(shí),我回答的可好了
菜菜:看來微服務(wù)你真的下工夫研究了呀
YY妹:是呀是呀,但是碰到一個(gè)問題,有狀態(tài)的服務(wù)是什么意思呢?
菜菜:看來你又掛在這個(gè)問題上了,且聽這次分解
簡(jiǎn)介
對(duì)于初學(xué)者,心里對(duì)“有狀態(tài)服務(wù)”的理解可能比較模糊,但是從面向?qū)ο缶幊趟枷氲慕嵌热ダ斫庖苍S會(huì)明朗很多。面向?qū)ο缶幊趟枷胩岢氖怯镁幊陶Z言去描述世間萬物,所以面向?qū)ο缶幊痰恼Z言都會(huì)提供描述對(duì)象的容器以及對(duì)象行為的表達(dá)方式。
舉一個(gè)很簡(jiǎn)單的栗子,在c#或者java中,表達(dá)對(duì)象的容器就是class,對(duì)象的行為通過一系列的接口或者函數(shù)來表達(dá)。更進(jìn)一步,對(duì)象抽象出來之后,大多數(shù)對(duì)象都有自己的內(nèi)部狀態(tài),體現(xiàn)到代碼上也就是常見的類的屬性。
面向?qū)ο缶幊痰幕舅枷氡举|(zhì)上是對(duì)現(xiàn)實(shí)世界的一種抽象,萬物皆可抽象。
面向?qū)ο缶幊痰幕舅枷氡举|(zhì)上是對(duì)現(xiàn)實(shí)世界的一種抽象,萬物皆可抽象。
根據(jù)業(yè)務(wù)把對(duì)象抽象出來之后,每一個(gè)實(shí)例化的對(duì)象其實(shí)都可以有自己的狀態(tài),比如:在最常見的游戲場(chǎng)景中,每一個(gè)玩家都是“玩家"這類對(duì)象的一個(gè)實(shí)例,每一個(gè)玩家都有自己的名字,性別,等級(jí),HP等屬性,這些屬性本質(zhì)上就是玩家的狀態(tài),隨著時(shí)間的推移,每個(gè)玩家的HP,等級(jí)等屬性會(huì)隨之變化,這些變化其實(shí)就是這個(gè)玩家狀態(tài)的變化。
對(duì)應(yīng)到有狀態(tài)的服務(wù)也是如此,之所以稱之為有狀態(tài),是因?yàn)榉?wù)內(nèi)部的對(duì)象狀態(tài)會(huì)隨著業(yè)務(wù)有著對(duì)應(yīng)的變動(dòng),而這些變動(dòng)只發(fā)生在這個(gè)服務(wù)內(nèi)部,在外界看來,這個(gè)服務(wù)好像是有狀態(tài)的。
有狀態(tài)的服務(wù)本質(zhì)上是一些有狀態(tài)對(duì)象的集合,這些對(duì)象狀態(tài)的變化只發(fā)生在當(dāng)前服務(wù)進(jìn)程中。
有狀態(tài)的服務(wù)本質(zhì)上是一些有狀態(tài)對(duì)象的集合,這些對(duì)象狀態(tài)的變化只發(fā)生在當(dāng)前服務(wù)進(jìn)程中。
優(yōu)勢(shì)和劣勢(shì)
有狀態(tài)服務(wù)之所以被稱為有狀態(tài),一個(gè)很大的原因是它可以追溯狀態(tài)的變化過程,也就是說一個(gè)有狀態(tài)的服務(wù)保存著狀態(tài)變化的記錄,并可以根據(jù)這些歷史記錄恢復(fù)到指定的狀態(tài),這在很多場(chǎng)景下非常有用。
舉一個(gè)很簡(jiǎn)單的栗子:我們平時(shí)玩的斗地主游戲,三個(gè)玩家,當(dāng)有一個(gè)玩家因?yàn)榫W(wǎng)絡(luò)原因掉線,經(jīng)過一段時(shí)間,這個(gè)玩家又重新上線,需要根據(jù)某些記錄來恢復(fù)玩家掉線期間系統(tǒng)自動(dòng)出牌的記錄,這些出牌記錄在這個(gè)業(yè)務(wù)中其實(shí)就是這個(gè)玩家的狀態(tài)變化記錄。在有狀態(tài)的服務(wù)中,很容易做到這一點(diǎn)。
其實(shí)實(shí)際開發(fā)中很多場(chǎng)景不需要記錄每個(gè)狀態(tài)的變化,只保留最新狀態(tài)即可,不單單是因?yàn)楸4婷總€(gè)狀態(tài)的變化需要大量的存儲(chǔ)和架構(gòu)設(shè)計(jì),更因?yàn)槭呛芏鄻I(yè)務(wù)根本不需要這些狀態(tài)變化記錄,業(yè)務(wù)需要的只是最新的狀態(tài),所以大部分有狀態(tài)的服務(wù)只保存著最新的狀態(tài)。
有狀態(tài)的服務(wù)在設(shè)計(jì)難度上比無狀態(tài)的服務(wù)要大很多,不僅僅是因?yàn)殚_發(fā)設(shè)計(jì)人員需要更好的抽象能力,更多的是一致性的設(shè)計(jì)問題。
現(xiàn)代的分布式系統(tǒng),都是由多個(gè)服務(wù)器組成一個(gè)集群來對(duì)外提供服務(wù),當(dāng)一個(gè)對(duì)象在服務(wù)器A產(chǎn)生之后,如果請(qǐng)求被分配到了服務(wù)器B上,這種情況下有狀態(tài)的服務(wù)毫無意義,為什么呢?
當(dāng)一個(gè)相同的業(yè)務(wù)對(duì)象存在于不同的服務(wù)器上的時(shí)候,本質(zhì)上就違背了現(xiàn)實(shí)世界的規(guī)則,你能說一個(gè)人,即出生在中國,又出生在美國嗎?
所以有狀態(tài)的服務(wù)對(duì)于一致性問題有著天然的要求,這種思想和微服務(wù)設(shè)計(jì)理想不謀而合,舉個(gè)栗子:一個(gè)用戶信息的服務(wù),對(duì)外提供查詢修改能力,凡是用戶信息的業(yè)務(wù)必須通過這個(gè)服務(wù)來實(shí)現(xiàn)。同理,一個(gè)對(duì)象狀態(tài)的查詢修改以及這個(gè)對(duì)象的行為,必須由這個(gè)對(duì)象的服務(wù)來完成。
有狀態(tài)的服務(wù)要求相同業(yè)務(wù)對(duì)象的請(qǐng)求必須被路由到同一個(gè)服務(wù)進(jìn)程。
有狀態(tài)的服務(wù)要求相同業(yè)務(wù)對(duì)象的請(qǐng)求必須被路由到同一個(gè)服務(wù)進(jìn)程。
因此,有狀態(tài)的服務(wù)對(duì)于同一個(gè)對(duì)象的橫向擴(kuò)容是做不到的,就算是做的到,多個(gè)相同對(duì)象之間的狀態(tài)同步工作也必然會(huì)花費(fèi)更多的資源。
在很多場(chǎng)景下,有狀態(tài)的服務(wù)要注意熱點(diǎn)問題,例如最常見的秒殺,這里并非是說有狀態(tài)服務(wù)不適合大并發(fā)的場(chǎng)景,反而在高并發(fā)的場(chǎng)景下,有狀態(tài)的服務(wù)往往表現(xiàn)的比無狀態(tài)服務(wù)更加出色。
Actor模型
在眾多的并發(fā)模型中,最適合有狀態(tài)服務(wù)設(shè)計(jì)的莫過于Actor模型了,actor模型天生就具備了一致性這種特點(diǎn),讓我們?cè)趯?duì)業(yè)務(wù)進(jìn)行抽象的時(shí)候,不必考慮一致性的問題,而且每一個(gè)請(qǐng)求都是異步模式,在對(duì)象內(nèi)部修改對(duì)象的狀態(tài)不必加鎖,這在傳統(tǒng)的架構(gòu)中是做不到的。
基于actor模型,系統(tǒng)設(shè)計(jì)的難點(diǎn)在于抽象業(yè)務(wù)模型,一旦業(yè)務(wù)模型穩(wěn)定,我們完全可以用內(nèi)存方式來保存對(duì)象狀態(tài)(也可以定時(shí)去持久化),內(nèi)存方式比用其他網(wǎng)絡(luò)存儲(chǔ)(例如redis)要快上幾個(gè)量級(jí)。
既滿足了一致性,又可以利用進(jìn)程內(nèi)對(duì)象狀態(tài)來應(yīng)對(duì)高并發(fā)業(yè)務(wù)場(chǎng)景,何樂而不為呢?
有不少同學(xué)問過,actor模型要避免出現(xiàn)熱點(diǎn)問題,就算有內(nèi)存狀態(tài)為其加速,那并發(fā)數(shù)還是超過actor的處理能力怎么辦呢?
其實(shí)和傳統(tǒng)做法類似,所有的高并發(fā)系統(tǒng)設(shè)計(jì)無非就是“分”一個(gè)字,無論是簡(jiǎn)單的負(fù)載均衡,還是復(fù)雜的分庫分表策略,都是分治的一種體現(xiàn)。一臺(tái)服務(wù)器不夠,我就上十臺(tái),百臺(tái).....
所有的高并發(fā)系統(tǒng)設(shè)計(jì)都是基于分治思想,把每一臺(tái)服務(wù)器的能力發(fā)揮到極致,難度最大的還是其中的調(diào)度算法。
用actor模型來應(yīng)對(duì)高并發(fā),我們可以采用讀寫分離的思想,主actor負(fù)責(zé)寫請(qǐng)求,并利用某種通信機(jī)制把狀態(tài)的變化通知到多個(gè)從actor,從actor負(fù)責(zé)對(duì)外的讀請(qǐng)求,這個(gè)DB的讀寫分離思想一致,其中最難的當(dāng)屬actor的狀態(tài)同步問題了,解決問題的方式千百種,總有一種適合你,歡迎你留言寫下你認(rèn)為最好的解決方案。
案例(玩家信息服務(wù))
由于筆者是c#出身,對(duì)c#的Actor服務(wù)框架Orleans比較熟悉,這里就以O(shè)rleans為例,其他語言的coder不要見怪,Orleans是一個(gè)非常優(yōu)秀的Actor模型框架,而且支持最新的netcore 3.0版本,地址為:https://github/dotnet/orleans 有興趣的同學(xué)可以去看一下,而且分布式事物已經(jīng)出正式版,非常給力。其他語言的也非常出色java:https://github/akka/akka
golang:https://github/AsynkronIT/protoactor-go
1. 首先我們定義玩家的狀態(tài)信息
//玩家的信息,其實(shí)也就是玩家的狀態(tài)信息
publicclassPlayer{
///<summary>
///玩家id,同時(shí)也是玩家這個(gè)服務(wù)的主鍵
///</summary>
publiclongId { get; set; }
///<summary>
///玩家姓名
///</summary>
publicstringName { get; set; }
///<summary>
///玩家等級(jí)
///</summary>
publicintLevel { get; set; }
}
2. 接下來定義玩家的服務(wù)接口
/// <summary>
/// 玩家的服務(wù)接口
/// </summary>
interfaceIPlayerService: Orleans.IGrainWithIntegerKey
{
//獲取玩家名稱
Task<string> GetName;
//獲取玩家等級(jí)
Task<int> GetLevel;
//設(shè)置玩家等級(jí),這個(gè)操作會(huì)改變玩家的狀態(tài)
Task<int> SetLevel(intnewLevel);
}
3. 接下來實(shí)現(xiàn)玩家服務(wù)的接口
publicclassPlayerService: Grain, IPlayerService
{
//這里可以用玩家的信息來代表玩家的狀態(tài)信息,而且這個(gè)狀態(tài)信息又充當(dāng)了進(jìn)程內(nèi)緩存的作用
Player playerInfo;
publicasyncTask<int> GetLevel
{
return(awaitLoadPlayer).Level;
}
publicasyncTask<string> GetName
{
return(awaitLoadPlayer).Name;
}
publicasyncTask<int> SetLevel(intnewLevel)
{
varplayerInfo =awaitLoadPlayer;
if(playerInfo != null)
{
//先進(jìn)行數(shù)據(jù)庫的更新,然后在更新緩存的狀態(tài), 進(jìn)程內(nèi)緩存更新失敗的幾率幾乎為0
playerInfo.Level = newLevel;
}
return1;
}
privateasyncTask< Player> LoadPlayer
{
if(playerInfo == null)
{
varid = this.GetPrimaryKeyLong;
//這里模擬的信息,真實(shí)環(huán)境完全可以從持久化設(shè)備進(jìn)行讀取
playerInfo= newPlayer { Id = id, Name = "玩家姓名", Level = 1};
}
returnplayerInfo;
}
}
以上只是一個(gè)簡(jiǎn)單案例,有狀態(tài)的服務(wù)還有更多的設(shè)計(jì)方案,以上只供參考
本文為作者投稿,版權(quán)歸作者個(gè)人所有。
?蘋果公司 50% 員工沒大學(xué)學(xué)歷,細(xì)數(shù)不看學(xué)歷看能力的 IT 大佬!


