部署超簡單的Golong分布式WebSocket微服務(wù)

使用場景

在實(shí)現(xiàn)業(yè)務(wù)的時(shí)候,我們常常有些需求需要系統(tǒng)主動發(fā)送消息給客戶端彰檬,方案有輪詢和長連接,但輪詢需要不斷的創(chuàng)建銷毀http連接独榴,對客戶端僧叉、對服務(wù)器來說都挺消耗資源的,消息推送也不夠?qū)崟r(shí)棺榔。這里我們選擇了WebSocket長連接的方案瓶堕。

有大量的項(xiàng)目需要服務(wù)端主動向客戶端推送消息,為了減少重復(fù)開發(fā)症歇,我們做成了微服務(wù)郎笆。

使用于服務(wù)器需要主動向客戶端推送消息谭梗、客戶端需要實(shí)時(shí)獲取消息的請求。例如聊天宛蚓、廣播消息激捏、多人游戲消息推送、任務(wù)執(zhí)行結(jié)果推送等方面凄吏。

使用流程

用Websocket客戶端連接本服務(wù)远舅,服務(wù)端會返回客戶端一個(gè)唯一的client id铃辖,通過這個(gè)client id可以知道是哪個(gè)連接柏副,客戶端拿到這個(gè)id之后上報(bào)到服務(wù)端,服務(wù)端根據(jù)業(yè)務(wù)需求可以給這個(gè)長連接發(fā)送指定信息报破,或者綁定到分組任连。

分布式方案

維持大量的長連接對單臺服務(wù)器的壓力也挺大的蚤吹,這里也就要求該服務(wù)需要可以擴(kuò)容,也就是分布式地?cái)U(kuò)展随抠。分布式對于可存儲的公共資源有一套完整的解決方案裁着,但對于WebSocket來說,操作對象就是每一個(gè)連接拱她,它是維持在每一個(gè)程序中的二驰。每一個(gè)連接不能存儲起來共享、不能在不同的程序之間共享秉沼。所以我能想到的方案是不同程序之間進(jìn)行通訊诸蚕。

那么,怎樣知道某個(gè)連接在哪個(gè)應(yīng)用呢氧猬?答案是通過client id去判斷。那么通過client id又是如何知道的呢坏瘩?有以下幾種方案:

  1. 一致性hash算法

    一致性hash算法是將整個(gè)哈希值空間組織成一個(gè)虛擬的圓環(huán)盅抚,在redis集群中哈希函數(shù)的值空間為0-2^32-1(32位無符號整型)。把服務(wù)器的IP或主機(jī)名作為關(guān)鍵字倔矾,通過哈希函數(shù)計(jì)算出相應(yīng)的值妄均,對應(yīng)到這個(gè)虛擬的圓環(huán)空間。我們再通過哈希函數(shù)計(jì)算key的值哪自,得到一個(gè)在圓環(huán)空間的位置丰包,按順時(shí)針方向找到的第一個(gè)節(jié)點(diǎn)就是存放該key數(shù)據(jù)的服務(wù)器節(jié)點(diǎn)。

    在沒有節(jié)點(diǎn)的增減的時(shí)候壤巷,可以滿足我們的需求邑彪,但如果此時(shí)一個(gè)節(jié)點(diǎn)掛掉了或者新增一個(gè)機(jī)器怎么辦?節(jié)點(diǎn)掛點(diǎn)之后胧华,會在圓環(huán)上刪除節(jié)點(diǎn)寄症,增加節(jié)點(diǎn)則反之宙彪。這時(shí)候按順時(shí)針方向找的數(shù)據(jù)就不準(zhǔn)確,在某些業(yè)務(wù)上來說可以接受有巧,但在WebSocket微服務(wù)上來說释漆,影響范圍內(nèi)的連接會斷掉,如果要求沒那么高篮迎,客戶端再進(jìn)行重連也可以男图。

  2. hash slot(哈希槽)

    服務(wù)器的IP或者主機(jī)名作為key,對每個(gè)key進(jìn)行計(jì)算CRC16值甜橱,然后對16384進(jìn)行取模逊笆,得出一個(gè)對應(yīng)key的hash slot。

    HASH_SLOT = CRC16(key) mod 16384
    

    我們根據(jù)節(jié)點(diǎn)的數(shù)量渗鬼,給每個(gè)節(jié)點(diǎn)劃分范圍览露,這個(gè)范圍是0-16384。hash slot的重點(diǎn)就在這個(gè)虛擬表譬胎,key對應(yīng)的hash slot是永不變的差牛,增減節(jié)點(diǎn)就是維護(hù)這張?zhí)摂M表。

以上兩種方案都可以實(shí)現(xiàn)需求堰乔,但一致性hash算法的方案會使部分key找到的節(jié)點(diǎn)不準(zhǔn)確偏化;hash slot的方案需要維護(hù)一張?zhí)摂M表,在實(shí)現(xiàn)起來需要有一個(gè)功能去判斷服務(wù)器是否掛了镐侯。修改這張?zhí)摂M表侦讨,新增節(jié)點(diǎn)也一樣,在實(shí)現(xiàn)起來會遇到很多問題苟翻。

然后我采取的方案是韵卤,每個(gè)連接都保存在本應(yīng)用,然后用對稱加密加密服務(wù)器IP和端口崇猫,得到的值作為client id沈条。對指定client id進(jìn)行操作時(shí),只需要解密這個(gè)key诅炉,就能得到相應(yīng)的IP和端口蜡歹。判斷是否為本機(jī),不是本機(jī)的話進(jìn)行RPC通訊告訴相應(yīng)的程序涕烧。長連接的連接數(shù)據(jù)不可遷移月而,程序掛掉了相應(yīng)的連接也就掛了,在該程序上的連接也就斷開了议纯,這時(shí)重連的話會找到另一個(gè)可用的程序父款。

Golang實(shí)現(xiàn)的分布式WebSocket微服務(wù)

簡介

本系統(tǒng)基于Golang、Redis、RPC實(shí)現(xiàn)分布式WebSocket微服務(wù)铛漓,也可以單機(jī)部署溯香,單機(jī)部署不需要Redis、RPC浓恶。分布式部署可以支持nginx負(fù)責(zé)均衡玫坛、水平擴(kuò)容部署,程序之間使用RPC通信包晰。

目前實(shí)現(xiàn)的功能有湿镀,給指定客戶端發(fā)送消息、綁定客戶端到分組伐憾、給分組里的客戶端批量發(fā)送消息勉痴、獲取在線的客戶端、上下線自動通知树肃。適用于長連接的大部分場景蒸矛,分組可以理解為聊天室,綁定客戶端到分組相當(dāng)于把客戶端添加到聊天室胸嘴,給分組發(fā)送信息相當(dāng)于給聊天室的每個(gè)人發(fā)送消息雏掠。

架構(gòu)圖

單機(jī)服務(wù)

WebSocket單機(jī)服務(wù)架構(gòu)圖

<center>單機(jī)服務(wù)</certer>

分布式

WebSocket分布式服務(wù)架構(gòu)圖

<center>分布式</certer>

時(shí)序圖

單發(fā)消息

  1. 客戶端發(fā)送連接請求,連接請求通過nginx負(fù)載均衡找到一臺ws服務(wù)器劣像;
  2. ws服務(wù)器響應(yīng)連接請求乡话,通過對稱加密服務(wù)器IP和端口號,得到的值作為client id耳奕,并返回绑青。
  3. 客戶端拿到client id之后,交給業(yè)務(wù)系統(tǒng)屋群;
  4. 業(yè)務(wù)系統(tǒng)拿到client id之后闸婴,通過http發(fā)送相關(guān)消息,經(jīng)過nginx負(fù)載分配到一臺ws服務(wù)器芍躏;
  5. 這臺ws服務(wù)器拿到clinet id和消息掠拳,解密出對應(yīng)的服務(wù)器IP和端口;
  6. 拿到IP地址和端口纸肉,通過PRC協(xié)議給指定ws程序發(fā)送信息;
  7. 該ws程序接收到client id和信息喊熟,給指定的連接發(fā)送信息柏肪;
  8. 客戶端收到信息。
WebSocket微服務(wù)單發(fā)時(shí)序圖

<center>WebSocket微服務(wù)單發(fā)時(shí)序圖</certer>

群發(fā)消息

  1. 前3個(gè)步驟跟單發(fā)的一樣芥牌;
  2. 業(yè)務(wù)系統(tǒng)拿到client id之后烦味,通過http給指定分組發(fā)送消息,經(jīng)過nginx負(fù)載分配到一臺ws服務(wù)器;
  3. 這臺ws服務(wù)器拿到分組ID和消息谬俄,去Redis查詢服務(wù)器列表柏靶,然后發(fā)送RPC廣播;
  4. 所有收到廣播的服務(wù)溃论,找到本機(jī)所有該分組的連接屎蜓;
  5. 給所有這些連接發(fā)送消息;
  6. 客戶端收到信息钥勋。
WebSocket微服務(wù)群發(fā)消息時(shí)序圖

<center>WebSocket微服務(wù)群發(fā)消息時(shí)序圖</certer>

使用

下載本項(xiàng)目:

這里已經(jīng)打包好了炬转,下載相應(yīng)的環(huán)境,支持Linux算灸、Windows扼劈、MacOS環(huán)境。

https://github.com/woodylan/go-websocket/releases

你也可以選擇自己編譯:

git clone https://github.com/woodylan/go-websocket.git

編譯:

// 編譯適用于本機(jī)的版本
go build

// 編譯Linux版本
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build

// 編譯Windows 64位版本
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build

// 編譯MacOS版本
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build

執(zhí)行:

編譯成功之后會得到一個(gè)二進(jìn)制文件go-websocket菲驴,執(zhí)行該二進(jìn)制文件荐吵,文件名后面跟著的是端口號,下面的命令666則表示端口號赊瞬,你可以可以改成其他的先煎。

./go-websocket 666

連接測試:

打開支持Websocket的客戶端,輸入 ws://127.0.0.1:666/ws 進(jìn)行連接森逮,連接成功會返回clientId榨婆。

單機(jī)部署

單機(jī)部署很簡單,不需要配置Redis褒侧、RabbitMQ良风,只需要編譯然后運(yùn)行該二進(jìn)制文件就可以了,步驟如上闷供。

分布式部署

安裝Redis: 參考網(wǎng)上教程

配置文件:

配置文件位于項(xiàng)目根目錄的configs/config.ini烟央,cluster為true表示分布式部署。

[common]
# 是否分布式部署
cluster = true
# 對稱加密key 16位
crypto_key = xxxxxxxxxxxxxxxx

[redis]
host = 127.0.0.1
port = 6379
password =

運(yùn)行項(xiàng)目:

在不同的機(jī)器運(yùn)行本項(xiàng)目歪脏,注意配置號端口號疑俭,項(xiàng)目如果在同一機(jī)器,則必須用不同的端口婿失。你可以用supervisor做進(jìn)程管理钞艇。

配置Nginx負(fù)載均衡:

upstream ws_cluster {
    server 127.0.0.1:666;
    server 127.0.0.1:667;
}

server {
    listen  660;
    server_name ws.example.com;

    access_log /logs/access.log;
    error_log /logs/error.log;
    
    location /ws {
        proxy_pass http://ws_cluster; # 代理轉(zhuǎn)發(fā)地址
        proxy_http_version 1.1;

        proxy_read_timeout 60s; # 超時(shí)設(shè)置

        # 啟用支持websocket連接
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location /api {
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header Host            $http_host;

        proxy_pass http://ws_cluster; # 代理轉(zhuǎn)發(fā)地址
    }
}

至此,項(xiàng)目部署完成豪硅。

源碼

github:https://github.com/woodylan/go-websocket

交流

QQ群:1028314856

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末哩照,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子懒浮,更是在濱河造成了極大的恐慌飘弧,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異次伶,居然都是意外死亡痴昧,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門冠王,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赶撰,“玉大人,你說我怎么就攤上這事版确】勰遥” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵绒疗,是天一觀的道長侵歇。 經(jīng)常有香客問我,道長吓蘑,這世上最難降的妖魔是什么惕虑? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮磨镶,結(jié)果婚禮上溃蔫,老公的妹妹穿的比我還像新娘。我一直安慰自己琳猫,他們只是感情好伟叛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著脐嫂,像睡著了一般统刮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上账千,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天侥蒙,我揣著相機(jī)與錄音,去河邊找鬼匀奏。 笑死鞭衩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的娃善。 我是一名探鬼主播论衍,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼聚磺!你這毒婦竟也來了饲齐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤咧最,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體矢沿,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡滥搭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捣鲸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瑟匆。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖栽惶,靈堂內(nèi)的尸體忽然破棺而出愁溜,到底是詐尸還是另有隱情,我是刑警寧澤外厂,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布冕象,位于F島的核電站,受9級特大地震影響汁蝶,放射性物質(zhì)發(fā)生泄漏渐扮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一掖棉、第九天 我趴在偏房一處隱蔽的房頂上張望墓律。 院中可真熱鬧,春花似錦幔亥、人聲如沸耻讽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽针肥。三九已至,卻和暖如春笤昨,著一層夾襖步出監(jiān)牢的瞬間祖驱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工瞒窒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留捺僻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓崇裁,卻偏偏與公主長得像匕坯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子拔稳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

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