大家好,我是烤鸭:

这次介绍下nchan,nginx的一个module。

nchan

源码: https://github.com/slact/nchan
官网: https://nchan.io/
nginx 配置说明文档: https://nchan.io/documents/nginxconf2016-slides.pdf

测试环境搭建

4 台linux centos 7,都安装了nginx和nchan。

安装可以参考下这篇文章。

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

目前使用的是4台nginx做测试,1台模拟上游的转发服务器(类似 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测试网站:
http://www.websocket-test.com/

ws 建联 sub接口用来接收消息,vid是nginx配置里的channel_id

postman模拟消息发送到 nchan, /pub 接口

redis 存消息数据

如果自己写前端页面的话:可以是创建原生的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);
});

架构

# 共享内存,单机的时候取决于单机的内存,redis集群下取决于 单个节点的内存
nchan_shared_memory_size 32000M;

单机nginx的worker通过channel的hash把数据同步到当前worker的内存,再同步到共享内存,同时刷到第二个worker。(关于内存操作其实调用的是nginx的api)

右边的redis模拟的场景是不同的nchan节点,共享层用redis来实现。

如果量小的话,简单的做个im或者聊天室,单机就足够了,使用的是nginx的机器内存。(和 springboot 直接加个 @WebSocket 注解差不多)

支持水平扩展的集群架构:(理论上支持不限数量的横向扩展,瓶颈在redis集群,得做好容灾方案。)

一般单台机器的连接数在 65535(可以改大),所以即便存储使用了redis,单机还是有瓶颈的,当然一般看消息体的大小,可能redis会先崩。所以需要多台nginx机器和一个超大的redis集群,来扛得住百万用户。nginx集群至少20台才能维持这么多人同时在线,如果要考虑消息的话,得看消息内容,预估redis集群大小。

nchan 和 netty

今天有同事问我,关于分发消息的。nchan和netty有什么区别。

简单说下netty的实现。

用 ConcurrentMap 维护 key(聊天室id)和channelGroup(每一个用户连接成功,就会增加一个channel)。

当有消息需要通知的时候需要调用方法即可。

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) {// ... 构建需要通知的内容// 获取需要通知的 channel_id,从这个函数 nchan_get_subscriber_info_response_channel_idngx_str_t *response_channel_id = nchan_get_subscriber_info_response_channel_id(self->request, response_id);// ... 构建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 对象,调用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 是空的,就新分配内存,并给ctx的subscriber_info_response_channel_id赋值if(!chid) {// nginx 分配内存的函数,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 的结构,订阅者的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的。

总结

优点:

  • 服务不需要考虑和维护链接等,只需要专注处理业务相关逻辑。

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

缺点:

  • 非应用层的,出现问题不好排查。
  • 默认的消息存储时间过长(nchan_message_timeout:1h),消息大量情况下容易拖垮集群。
  • 以channelid为key的话,可能导致redis分配不均匀,单个节点压力过大,影响不止当前的channelid。
  • 集群模式依赖redis,需要考虑容灾方案。
  • 扩展性差,由于不支持多个redis集群,没法根据特定条件分片。(比如某个聊天室人特别多,单独一套redis集群,用netty的话可能比较好实现)(特意给作者留言确认了一下,https://github.com/slact/nchan/issues/619)

基于nchan打造百万用户的聊天室相关推荐

  1. 基于Node.js + WebSocket 的简易聊天室

    代码地址如下: http://www.demodashi.com/demo/13282.html Node.js聊天室运行说明 Node.js的本质就是运行在服务端的JavaScript.Node.j ...

  2. Android基于XMPP Smack openfire 开发的聊天室

    公司刚好让做即时通讯模块,服务器使用openfire,偶然看到有位仁兄的帖子,拷贝过来细细研究,感谢此仁兄的无私,期待此仁兄的下次更新 转自http://blog.csdn.net/lnb333666 ...

  3. 【java毕业设计】基于java+原生Sevlet+socket的聊天室系统设计与实现(毕业论文+程序源码)——聊天室系统

    基于java+原生Sevlet+socket的聊天室系统设计与实现(毕业论文+程序源码) 大家好,今天给大家介绍基于java+原生Sevlet+socket的聊天室系统设计与实现,文章末尾附有本毕业设 ...

  4. 基于C/S模式的简单聊天室

    基于C/S模式的简单聊天室 要求: 使用Socket实现网上聊天室,要求基于TCP或UDP协议,用户可以通过客户端连接到服务器端并进行聊天,聊天时可以启动多个客户端:服务器启动后,接收客户端发来的用户 ...

  5. java毕业设计——基于java+TCP+UDP的局域网聊天室系统设计与实现(毕业论文+程序源码)——局域网聊天室系统

    基于java+TCP+UDP的局域网聊天室系统设计与实现(毕业论文+程序源码) 大家好,今天给大家介绍基于java+TCP+UDP的局域网聊天室系统设计与实现,文章末尾附有本毕业设计的论文和源码下载地 ...

  6. 基于Socket通信的在线网络聊天室

    文章目录 项目结构 client constans entity util IO server ui other 大致思路 演示 时间:2021/4/20 开发环境: jdk1.8 编译器:idea2 ...

  7. python实现简易聊天需要登录博客园zip下载_Python基于Socket实现简易多人聊天室的示例代码...

    前言 套接字(Sockets)是双向通信信道的端点. 套接字可以在一个进程内,在同一机器上的进程之间,或者在不同主机的进程之间进行通信,主机可以是任何一台有连接互联网的机器. 套接字可以通过多种不同的 ...

  8. 基于Linux下的即时通讯聊天室项目(全代码 有注释 可直接运行)

    基于Linux下的即时通讯聊天室项目 一.序言 二.具体功能 三.系统客户要求 四.具体代码 1.服务器代码 2.客户端代码 一.序言 最近在写一个基于Linux下的聊天工具 它适合于局域网内所有人进 ...

  9. [源码和文档分享]基于Netty和WebSocket的Web聊天室

    一.背景 伴随着Internet的发展与宽带技术的普及,人们可以通过Internet交换动态数据,展示新产品,与人进行沟通并进行电子商务贸易.作为构成网站的重要组成部分,留言管理系统为人们的交流提供了 ...

最新文章

  1. Mininet的介绍安装
  2. 使用Hyperledger Ursa简化区块链安全性
  3. 如何确定固定资产入账价值
  4. Android Kotlin Exception处理
  5. Android RxJava(一) create操作符的用法和源码分析
  6. linux屏幕分辨率文件,Ubuntu 16.04 LTS设置屏幕分辨率显示Unknown display 解决
  7. 有很帅气的微信头像推荐吗?
  8. 内网渗透扫描神器 Perun
  9. Eloquent JavaScript 阅读笔记一
  10. 一直都说字节跳动有点难,这次体会了,而且被怼了~
  11. eclipse创建maven web工程,以及maven工程转化为web工程的简单介绍。
  12. Linux系统镜像源替换
  13. 如何禁止树莓派屏幕休眠
  14. Array Shrinking(区间DP)
  15. 观察 | 家长焦虑,教培着急,暑期“培训热”今年还会持续吗?
  16. Termux搭建图形化环境及tk开发
  17. 苹果手机还原网络设置会怎样_苹果手机老是信号不好,只要掌握这4个小技巧,信号便能立马增强...
  18. c#圆的周长和面积面向对象_初遇C#:一个简单的小程序(圆形周长,面积计算器)...
  19. 阳光教练隐私政策URL
  20. 1年时间从12K到30K,一位外卖小哥的真实逆袭历程!

热门文章

  1. [css] 使用css实现悬浮提示文本
  2. 工作212:不能改变父组件值
  3. 前端学习(2320):typeScript的概述和安装
  4. 前端学习(2080):计算属性和methods得对比
  5. 前端学习(1902)vue之电商管理系统电商系统之渲实现添加用户前的预先校验
  6. 前端学习(1403):多人管理23错误unexpected identifier
  7. 实例18:python
  8. RMSE、MAPE、准确率、召回率、F1、ROC、AUC总结
  9. 禁止微信公众号页面上下滑动
  10. CSS深入理解vertical-align和line-height的基友关系