日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

基于nchan打造百万用户的聊天室

發(fā)布時間:2023/12/10 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 基于nchan打造百万用户的聊天室 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

大家好,我是烤鴨:

???這次介紹下nchan,nginx的一個module。

nchan

源碼: https://github.com/slact/nchan
官網(wǎng): https://nchan.io/
nginx 配置說明文檔: https://nchan.io/documents/nginxconf2016-slides.pdf

測試環(huán)境搭建

4 臺linux centos 7,都安裝了nginx和nchan。

安裝可以參考下這篇文章。

https://www.cnblogs.com/rongfengliang/p/7866122.html

目前使用的是4臺nginx做測試,1臺模擬上游的轉(zhuǎn)發(fā)服務(wù)器(類似 keepalived),后3臺是安裝了nchan的nginx,用來 pub/sub
看下配置。

nginx_master.conf

#master upstream ws {server 192.168.1.1:8080 weight=1 max_fails=2;server 192.168.1.2:8080 weight=1 max_fails=2;server 192.168.1.3:8080 weight=1 max_fails=2; }server {listen 80;server_name test.xxx.xxx.com;root /usr/local/nginx/chat;#error_page 404 /404.html;error_page 500 502 503 504 /50x.html;location = 50x.html {root /usr/local/nginx/html;}#masterlocation / {proxy_pass http://ws;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade";}access_log logs/barrage.access.log;error_log logs/nchan_error.log; }

nginx_salve.conf

nchan_shared_memory_size 256M;upstream redis_cluster {nchan_redis_server redis://:password@10.168.1.2:3001;nchan_redis_server redis://:password@10.168.1.2:3002;nchan_redis_server redis://:password@10.168.1.3:3001;nchan_redis_server redis://:password@10.168.1.3:3002;nchan_redis_server redis://:password@10.168.1.1:3001;nchan_redis_server redis://:password@10.168.1.1:3002;# you don't need to specify all the nodes, they will be autodiscovered# however, it's recommended that you do specify at least a few master nodes.}server {listen 8080;server_name localhost;root /usr/local/nginx/chat;location = /sub {add_header x-hit-from 127 always;nchan_subscriber;nchan_channel_id $arg_vid;nchan_use_redis on;nchan_redis_pass redis_cluster;}location = /pub{nchan_publisher;nchan_channel_id $arg_id;nchan_redis_pass redis_cluster;nchan_message_timeout 5m;}#..location = /private/status {nchan_stub_status;} }

websocket測試網(wǎng)站:
http://www.websocket-test.com/

ws 建聯(lián) sub接口用來接收消息,vid是nginx配置里的channel_id

postman模擬消息發(fā)送到 nchan, /pub 接口

redis 存消息數(shù)據(jù)

如果自己寫前端頁面的話:可以是創(chuàng)建原生的websocket,也可以使用官方提供的NchanSubscriber.js。

正常 websocket:

var ws =new WebScoket("ws://xxx.yyy.com/sub?id=demo") ws.onMessage=funciton(data){console.log(data)}

引入 NchanSubscriber.js:

var sub = new NchanSubscriber('http://xxx.yyy.com/sub?id=demo', 'websocket'); sub.on("message", function(message, message_metadata) {alert(message); });

架構(gòu)

# 共享內(nèi)存,單機(jī)的時候取決于單機(jī)的內(nèi)存,redis集群下取決于 單個節(jié)點的內(nèi)存 nchan_shared_memory_size 32000M;

單機(jī)nginx的worker通過channel的hash把數(shù)據(jù)同步到當(dāng)前worker的內(nèi)存,再同步到共享內(nèi)存,同時刷到第二個worker。(關(guān)于內(nèi)存操作其實調(diào)用的是nginx的api)

右邊的redis模擬的場景是不同的nchan節(jié)點,共享層用redis來實現(xiàn)。

如果量小的話,簡單的做個im或者聊天室,單機(jī)就足夠了,使用的是nginx的機(jī)器內(nèi)存。(和 springboot 直接加個 @WebSocket 注解差不多)

支持水平擴(kuò)展的集群架構(gòu):(理論上支持不限數(shù)量的橫向擴(kuò)展,瓶頸在redis集群,得做好容災(zāi)方案。)

一般單臺機(jī)器的連接數(shù)在 65535(可以改大),所以即便存儲使用了redis,單機(jī)還是有瓶頸的,當(dāng)然一般看消息體的大小,可能redis會先崩。所以需要多臺nginx機(jī)器和一個超大的redis集群,來扛得住百萬用戶。nginx集群至少20臺才能維持這么多人同時在線,如果要考慮消息的話,得看消息內(nèi)容,預(yù)估redis集群大小。

nchan 和 netty

今天有同事問我,關(guān)于分發(fā)消息的。nchan和netty有什么區(qū)別。

簡單說下netty的實現(xiàn)。

用 ConcurrentMap 維護(hù) key(聊天室id)和channelGroup(每一個用戶連接成功,就會增加一個channel)。

當(dāng)有消息需要通知的時候需要調(diào)用方法即可。

channelGroup.writeAndFlush(new TextWebSocketFrame(message));

再看下 nchan的源碼:

slact/nchan/blob/master/src/subscribers/common.c

ngx_int_t nchan_subscriber_receive_notice(subscriber_t *self, ngx_int_t code, void *data) {if(code == NCHAN_NOTICE_SUBSCRIBER_INFO_REQUEST) {// ... 構(gòu)建需要通知的內(nèi)容// 獲取需要通知的 channel_id,從這個函數(shù) nchan_get_subscriber_info_response_channel_idngx_str_t *response_channel_id = nchan_get_subscriber_info_response_channel_id(self->request, response_id);// ... 構(gòu)建msg對象cf->storage_engine->publish(response_channel_id, &msg, cf, NULL, NULL);if(result_allocd) {ngx_http_complex_value_free(&result);}}return NGX_OK; }

/src/util/nchan_channel_id.c

ngx_str_t *nchan_get_subscriber_info_response_channel_id(ngx_http_request_t *r, uintptr_t request_id) {// 全局的 request_ctx 對象,調(diào)用nginx api獲得,https://www.nginx.com/resources/wiki/extending/api/http/#ngx-http-get-module-ctxnchan_request_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_nchan_module);ngx_str_t *chid = ctx->subscriber_info_response_channel_id;// child 是空的,就新分配內(nèi)存,并給ctx的subscriber_info_response_channel_id賦值if(!chid) {// nginx 分配內(nèi)存的函數(shù),https://www.nginx.com/resources/wiki/extending/api/alloc/chid = ngx_palloc(r->pool, sizeof(ngx_str_t));if(chid == NULL) {return NULL;}ctx->subscriber_info_response_channel_id = chid;chid->data = ngx_palloc(r->pool, NCHAN_SUBSCRIBER_INFO_CHANNEL_ID_BUFFER_SIZE);if(chid->data == NULL) {ctx->subscriber_info_response_channel_id = NULL;return NULL;}}u_char *end = ngx_snprintf(chid->data, NCHAN_SUBSCRIBER_INFO_CHANNEL_ID_BUFFER_SIZE, "meta/sr%d", (ngx_int_t )request_id);chid->len = end - chid->data;return chid;}

src/nchan_types.h

看一下 nchan_request_ctx_t 的結(jié)構(gòu),訂閱者的id和channel_id 都存了。

#define NCHAN_MULTITAG_REQUEST_CTX_MAX 4 typedef struct {subscriber_t *sub;nchan_reuse_queue_t *output_str_queue;nchan_reuse_queue_t *reserved_msg_queue;nchan_bufchain_pool_t *bcp; //bufchainpool maybe?ngx_str_t *subscriber_type;nchan_msg_id_t msg_id;nchan_msg_id_t prev_msg_id;ngx_str_t *publisher_type;ngx_str_t *multipart_boundary;ngx_str_t *channel_event_name;ngx_str_t channel_id[NCHAN_MULTITAG_REQUEST_CTX_MAX];int channel_id_count;time_t channel_subscriber_last_seen;int channel_subscriber_count;int channel_message_count;ngx_str_t *channel_group_name;ngx_str_t *request_origin_header;ngx_str_t *allow_origin;ngx_int_t subscriber_info_response_id;ngx_str_t *subscriber_info_response_channel_id;unsigned sent_unsubscribe_request:1;unsigned request_ran_content_handler:1;} nchan_request_ctx_t;

其實是從nginx的全局對象獲取channel_id的。

總結(jié)

優(yōu)點:

  • 服務(wù)不需要考慮和維護(hù)鏈接等,只需要專注處理業(yè)務(wù)相關(guān)邏輯。

  • nchan由于直接在nginx層,性能更好(比起自己搭建pub/sub服務(wù)器)。

缺點:

  • 非應(yīng)用層的,出現(xiàn)問題不好排查。
  • 默認(rèn)的消息存儲時間過長(nchan_message_timeout:1h),消息大量情況下容易拖垮集群。
  • 以channelid為key的話,可能導(dǎo)致redis分配不均勻,單個節(jié)點壓力過大,影響不止當(dāng)前的channelid。
  • 集群模式依賴redis,需要考慮容災(zāi)方案。
  • 擴(kuò)展性差,由于不支持多個redis集群,沒法根據(jù)特定條件分片。(比如某個聊天室人特別多,單獨一套redis集群,用netty的話可能比較好實現(xiàn))(特意給作者留言確認(rèn)了一下,https://github.com/slact/nchan/issues/619)

總結(jié)

以上是生活随笔為你收集整理的基于nchan打造百万用户的聊天室的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。