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

大家好,我是烤鸭:

   这次介绍下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)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/412521.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

rocketmq 初探(二)

大家好,我是烤鸭: 上一篇简单介绍和rocketmq,这一篇看下源码之注册中心。 namesrv 先看两个初始化方法 NamesrvController.initialize() 和 NettyRemotingServer.start(); public boolean initialize() {// 加载配置文件this.kvConfigMana…

2 JVM 运行机制

转载于:https://www.cnblogs.com/likevin/p/10186591.html

WIN10远程连接时提示内部错误

微软官方的解决方案是重置远程连接设置,步骤如下: 1、以管理员身份运行命令提示符 2、输入以下命令: netsh winsoc reset 随后会提示重启电脑,遂解决。 3、重启后还不行的话,再试试删除掉远程连接保存的凭据&#xff0…

rocketmq 初探(四)

大家好,我是烤鸭: 上一篇简单介绍broker的初始化,这一篇介绍 NettyRequestProcessor 的实现(主要是broker里用到的)。 AdminBrokerProcessor、ClientManageProcessor、ConsumerManageProcessor、EndTransactionProcessor NettyRequestProce…

《自律100天,穿越人生盲点》读书笔记

大家好,我是烤鸭: 《自律100天,穿越人生盲点》,读书笔记。 第一章 “自律100天”的华丽开启 第一节 “自律100天”的底层逻辑 习惯没办法用金钱换,只能用时间。 训练延迟满足(增强自控、培养耐心、减少短期诱惑…

2021 年终总结

2021 年终总结 大家好,我是烤鸭,这是一篇无关技术的记录,总结一下这一年干了什么。 年初的目标 减肥 炒股回本 PMP拿证 多赚钱 多学习新技术 坚持写博客(一周一篇) 多看书 没达成的目标 减肥这个事,拖了好久&#xff0c…

记一次线上cpu飙升100%的排查过程

大家好&#xff0c;我是烤鸭&#xff1a; 最近没怎么写技术文章&#xff0c;还是得回归下初心&#xff0c;正好前几天出现个线上问题&#xff0c;记录下排查过程。 问题描述 某个时间点&#xff0c;接收到接口响应慢报警。 过一会收到服务器cpu可用率低(<10%)报警。 去c…

Node.js(爱前端) 一

一 Node.js 简介 1.1 官网 https://nodejs.org/en/ 官网介绍&#xff1a; Node.js是一个构建在 Chrome 浏览器V8引擎上的 JavaScript 运行环境。 Node.js 使用了事件驱动、非阻塞I/O模型&#xff0c;这些都使它轻量、好用。 Node.js 的包生态&#xff08;npm&#xff09;&#…

记一次线上服务假死排查过程

大家好&#xff0c;我是烤鸭&#xff1a; 最近线上问题有点多啊&#xff0c;分享一个服务假死的排查过程。 问题描述 9点10分&#xff0c;收到进程无响应报警(一共6台机器&#xff0c;有1台出现)&#xff0c;后来又有1台出现。 排查思路 首先确认是否误报或者网络抖动&…

vue小记

1.vue绑定属性&#xff0c;点击事件 1.<!-- 完整语法 --> <a v-bind:href"url">...</a><!-- 缩写 --> <a :href"url">...</a>2.<!-- 完整语法 --> <a v-on:click"doSomething">...</a>&l…

nacos注册中心自动上下负载

大家好&#xff0c;我是烤鸭&#xff1a; 还有2天就过年了&#xff0c;祝大家新年快乐。最近好久没写技术文章了&#xff0c;还是得回归下主业&#xff0c;今天分享下nacos注册中心自动上下负载的方式和组件。 组件版本 <properties><java.version>1.8</java.v…

windows10 C盘清理

大家好&#xff0c;我是烤鸭&#xff1a; 身为一个号称修电脑的&#xff0c;磁盘清理是必备技能了。前几天刚出的新闻 男子帮女友清理电脑C盘&#xff0c;扫出17万个文件。 想必大家都经历过清理C盘的痛苦&#xff0c;这两天正好又清了&#xff0c;分享下。 先给个结论&#…

《实现领域驱动设计》读书笔记

大家好&#xff0c;我是烤鸭&#xff1a; 《实现领域驱动设计》&#xff0c;读书笔记&#xff0c;贴个封面&#xff0c;要不不知道是哪本。 了解概念 刚开始接触DDD&#xff0c;肯定懵逼&#xff0c;很多名词&#xff0c;一点点看下。 领域&#xff1a;带有业务属性的范…

spring junit单元测试

项目是有很多个功能块组成的&#xff0c;我们开发的时候&#xff0c;当我们开发出来一个功能&#xff0c;想要测试这个功能是否正确&#xff0c;不可能等到前端和后端全部写好了再进行测试&#xff0c;这样太浪费时间&#xff0c;有没有什么方法能直接测试后台的功能写的是否正…

windows docker redis

大家好&#xff0c;我是烤鸭&#xff1a; docker真的太方便了&#xff0c;尤其是对windows系统&#xff0c;友好的不得了。以前还只能是正版的专业版才能用&#xff0c;现在已经没有限制了&#xff0c;虽然加了收费&#xff0c;个人用免费就够了。redis 新版也不支持windows系统…

[css] CSS3新增伪类有哪些并简要描述

[css] CSS3新增伪类有哪些并简要描述 个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢迎大家一起讨论 主目录 与歌谣一起通关前端面试题

模拟微信自动化发送(微信公众号文章自动点击)

大家好&#xff0c;我是烤鸭&#xff1a; 分享个微信自动化发送的新方式&#xff0c;仅技术分享。 本来是公众号文章抓取相关的&#xff0c;审核一直不过&#xff0c;将就看吧。 需要的工具 Java&#xff08;jdk1.8&#xff09; Fiddler Python&#xff08;3.8&#xff09;…

Entity FrameWork 操作使用详情

Entity FrameWork 是以ADO.net为基础发展的ORM解决方案。 一、安装Entity FrameWork框架 二、添加ADO.Net实体数据模型 三、EF插入数据 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace EFDem…

网络通信中TCP出现的黏包以及解决方法 socket 模拟黏包

粘包问题概述 1.1 描述背景 采用TCP协议进行网络数据传送的软件设计中&#xff0c;普遍存在粘包问题。这主要是由于现代操作系统的网络传输机制所产生的。我们知道&#xff0c;网络通信采用的套接字(socket)技术&#xff0c;其实现实际是由系统内核提供一片连续缓存(流缓冲)来…

windows docker redis 集群部署

大家好&#xff0c;我是烤鸭&#xff1a; 上次分享了windows docker redis&#xff0c;这么快就不够用了&#xff0c;单机的不行&#xff0c;整个集群的&#xff0c;看了网上的教程都好麻烦&#xff0c;简单点。 单机的&#xff1a;https://blog.csdn.net/Angry_Mills/article…