從事件總線和消息隊(duì)列說起

從事件總線和消息隊(duì)列說起

Jusfr 原創(chuàng),轉(zhuǎn)載請(qǐng)注明來源

系列目錄


事件總線(EventBus)及其演進(jìn)過程必須提到內(nèi)存模型、傳統(tǒng)的隊(duì)列模型、發(fā)布-訂閱模型。

  • 內(nèi)存模型:進(jìn)程內(nèi)模型,事件總線(EventBus)在內(nèi)部遍歷消費(fèi)者(Consumer)列表傳遞數(shù)據(jù);
  • 隊(duì)列模型:消息或事件持久化到傳統(tǒng)消息隊(duì)列(Queue)即返回,以實(shí)時(shí)性降低換取吞吐能力提升;
  • 發(fā)布-訂閱模型:事件源(EventSource)得到強(qiáng)化,出現(xiàn)如分布式、持久化、消費(fèi)復(fù)制/分區(qū)等特性;

文中使用了“術(shù)語(單詞)”的形式引入概念,用詞可能有差異,只是力求表義清楚,下文描述將直接使用單詞。

內(nèi)存模型

內(nèi)存模型可以很好地解耦,舉例來說,版本初期我們有 IUserService 負(fù)責(zé)用戶創(chuàng)建,邏輯如下:

interface IUserService {
    void CreateNewUser(String name);
}

class UserService1 : IUserService {
    public void CreateNewUser(String name) {
        Console.WriteLine("User \"{0}\" created", name);
    }
}

現(xiàn)在希望在用戶創(chuàng)建后,進(jìn)行一次消息服務(wù)調(diào)用,發(fā)送歡迎辭。為了解決這個(gè)需求,需要添加和實(shí)現(xiàn)新的 MessageService , 并添加依賴,在 CreateNewUser() 方法某入插入調(diào)用邏輯,于是代碼變這樣:

interface IMessageService {
    void NotifyWelcome(User user);
}

class UserService2 : IUserService {
    private readonly IMessageService _messageService;
    
    public UserService2(IMessageService messageService) {
        _messageService = messageService;
    }
    
    public void CreateNewUser(String name) {
        var user = new User { Name = name };
        Console.WriteLine("User \"{0}\" created", user.Name);        
        _messageService.NotifyWelcome(user); //添加消息服務(wù)調(diào)用
    }
}

目前看起來好像沒啥問題,因?yàn)榇a簡單,但是當(dāng)邏輯越來越復(fù)雜時(shí)情況就變得不一樣了,比如我們希望用戶創(chuàng)建后將數(shù)據(jù)寫入索引,需要依賴 ISearchService;比如希望調(diào)用報(bào)表服務(wù) IReportService 添加每日新增用戶數(shù);

    public void CreateNewUser(String name) {
        var user = new User { Name = name };
        Console.WriteLine("User \"{0}\" created", user.Name);        
        _messageService.NotifyWelcome(user); //添加消息服務(wù)調(diào)用
        _searchService.SaveIndex(user)       //搜索服務(wù)調(diào)用
        _reportService.CounterNewUser(user); //報(bào)表服務(wù)調(diào)用;
    }

class dependency before

<center>更多的依賴</center>

如此多的依賴實(shí)在時(shí)重負(fù)難堪,當(dāng)然你可以說這些應(yīng)該異步處理、應(yīng)該放到后端隊(duì)列,沒錯(cuò)。現(xiàn)實(shí)中需要同步處理的邏輯并不少見,而規(guī)模尚小時(shí)引入隊(duì)列將帶來額外的開發(fā)測試、部署監(jiān)控成本。使用 EventBus 的內(nèi)存模型可以比較優(yōu)雅地處理此問題,以下是實(shí)現(xiàn)思路。

場景和實(shí)現(xiàn)思路

引入 EventBus 作為共同依賴,IUserService 視為生產(chǎn)者,IMessageService 視為對(duì)用戶創(chuàng)建事件感興趣的 Consumer ,其消費(fèi)邏輯調(diào)用 NotifyWelcome() 方法。EventBus 內(nèi)部維護(hù)了一份 EventType-Consumer 列表,遍歷列表分發(fā) Event 實(shí)例;ISearchService 、IReportService 等類似,同樣注冊(cè)到 EventBus 內(nèi)即可。

abstract class Event {
}

interface IConsumer {
    void Proceed(Event @event);
}

class EventBus {
    private readonly HashSet<IConsumer> _consumers = new HashSet<IConsumer>();
    //... 更多細(xì)節(jié)

    public void Publish(Event @event) {
        foreach (var consumer in _consumers) {
            consumer.Proceed(@event);
        }
    }
}

class UserService3 : IUserService {
    private readonly EventBus _eventBus;
    
    public UserService3(EventBus eventBus) {
        _eventBus = eventBus;
    }
    
    public void CreateNewUser(String name) {
        var user = new User { Name = name };
        Console.WriteLine("User \"{0}\" created", user.Name);
        var @event = ...           //創(chuàng)建消息
        _eventBus.Publish(@event); //交由 EventBus 發(fā)布
    }
}

class dependency using eventBus

<center>依賴關(guān)系的轉(zhuǎn)變</center>

在此過程中,Consumer 并不知道誰創(chuàng)建了 Event,不同的 Producer 對(duì)各 Consumer 的依賴統(tǒng)一變更為對(duì) EventBus 的依賴,內(nèi)存模型達(dá)到了解耦目的。


隊(duì)列模型

在內(nèi)存模型的場景中,我們確認(rèn)這些業(yè)務(wù)需要由異步進(jìn)程處理。從 MSMQ 到各種第3方實(shí)現(xiàn)方案眾多,但真實(shí)業(yè)務(wù)中 while(true) 循環(huán)有太多問題,比較棘手的像

  • 異常處理:消息處理中發(fā)生異常,但短時(shí)間內(nèi)重試可能解決不了問題;
  • 多消費(fèi)者:大家都有消費(fèi)程序,可能監(jiān)聽相同隊(duì)列;

對(duì)于異常,常規(guī)做法是使用監(jiān)聽時(shí)間依次延長的多個(gè)異常隊(duì)列,定時(shí)檢查并出隊(duì)處理;

多消費(fèi)者麻煩一點(diǎn),由于傳統(tǒng)隊(duì)列出隊(duì)即消息的特性,這意味著要么數(shù)據(jù)寫多份大家各自消費(fèi),要么消費(fèi)者集中管理遍歷調(diào)用。

Queue and EventBus

<center>Queue 與 EventBus 協(xié)同工作</center>

  • 異常隊(duì)列誰來監(jiān)聽和分發(fā)?
  • 如果數(shù)據(jù)寫多份,生產(chǎn)者如何得知消費(fèi)者數(shù)量?寫入性能損失怎樣?動(dòng)態(tài)添加消費(fèi)者時(shí)怎么辦?消費(fèi)者又如何路由到自己的隊(duì)列上?
  • 果數(shù)據(jù)寫一份,消費(fèi)者同步調(diào)用還是異步調(diào)用?等待所有的消費(fèi)邏輯完成既可能存在短板,某消費(fèi)者出現(xiàn)異常時(shí)又如何進(jìn)行進(jìn)度區(qū)分?

發(fā)布-訂閱模型及各 EventSource 的諸多特性提供了解決思路。


發(fā)布-訂閱模型

本文是 Kafka 系列文章之一,故使用 Kafka 作為 EventSource 描述和參考,其他隊(duì)列并未過多涉及請(qǐng)有限參考。

隊(duì)列模型雖然存在許多問題,但應(yīng)用與業(yè)務(wù)規(guī)模并不龐大時(shí)仍可一用。我們可以使用宿主代為監(jiān)聽列隊(duì)和消息分發(fā)、插件式寄宿消費(fèi)程序,使消費(fèi)者可以專注于業(yè)務(wù);由于消費(fèi)者短板效應(yīng)無法避免,可以在業(yè)務(wù)層面妥協(xié),盡量聚合高效、有限的消費(fèi)者等等。

在應(yīng)用與業(yè)務(wù)繼續(xù)擴(kuò)展時(shí),發(fā)布訂閱模型的事件總線變得不可或缺,甚至流式處理框架也不可避免地提上日程,使用 Kafka 對(duì)前文問題作出解答。

  • Kafka 基于文件系統(tǒng),消息移除是基于時(shí)間和磁盤的策略,并不會(huì)輕易丟失數(shù)據(jù),消費(fèi)者出現(xiàn)異常也不用擔(dān)心;
  • Kafka 將 Consumer 的當(dāng)前位置的管理職責(zé)交由消費(fèi)者負(fù)責(zé),只是提供了可選的 OffsetCommit 和 OffsetFetch API,這帶來了極大的便利性和一定的復(fù)雜度;你可以從任何位置開始消費(fèi),也沒有重復(fù)消費(fèi)限制,附加的是需要合適的 Offset 策略;
  • Kafka 提供了 Topic Partition + Consumer Group 并定義了發(fā)布-訂閱語義,可以配合堵塞式 API 保障消息處理的低延遲。

關(guān)于推與拉

Kafka 遵循傳統(tǒng)的 Pull 模式,由消費(fèi)者決定數(shù)據(jù)流速,畢竟寫入速率遠(yuǎn)高于消費(fèi)的情況下,消費(fèi)者實(shí)際是處于過載狀態(tài)。個(gè)人的理解的推拉(Push/Pull 或 Publish/Subscribe)并不是主要差異而只是受制于事件源(EventSource)的實(shí)現(xiàn)細(xì)節(jié)。

關(guān)于 Chuye.Kafka

Chuye.Kafka 是 Kafka 0.9版本 API 的 .NET 實(shí)現(xiàn),其 Consumer、Producer 是 low levl API 的輕度封裝,使用它實(shí)現(xiàn) EventBus 并沒有過多障礙,消費(fèi)者分組管理、狀態(tài)監(jiān)控和異常策略才是重點(diǎn)。

Jusfr 原創(chuàng),轉(zhuǎn)載請(qǐng)注明來源

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評(píng)論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,520評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,541評(píng)論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,896評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評(píng)論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,062評(píng)論 0 290
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,608評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,356評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,555評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,769評(píng)論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評(píng)論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,289評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,516評(píng)論 2 379

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,826評(píng)論 18 139
  • 姓名:周小蓬 16019110037 轉(zhuǎn)載自:http://blog.csdn.net/YChenFeng/art...
    aeytifiw閱讀 34,741評(píng)論 13 425
  • 消息隊(duì)列已經(jīng)逐漸成為企業(yè)IT系統(tǒng)內(nèi)部通信的核心手段。它具有低耦合、可靠投遞、廣播、流量控制、最終一致性等一系列功能...
    Sophie12138閱讀 725評(píng)論 0 7
  • 背景介紹 Kafka簡介 Kafka是一種分布式的,基于發(fā)布/訂閱的消息系統(tǒng)。主要設(shè)計(jì)目標(biāo)如下: 以時(shí)間復(fù)雜度為O...
    高廣超閱讀 12,865評(píng)論 8 167
  • kafka的定義:是一個(gè)分布式消息系統(tǒng),由LinkedIn使用Scala編寫,用作LinkedIn的活動(dòng)流(Act...
    時(shí)待吾閱讀 5,353評(píng)論 1 15