秋招项目ByteTalk —— 分布式集群的聊天系统

作者:shenmingik
邮箱:2107810343@qq.com
时间:2021/5/5 12:04
开发环境:Ubuntu VS Code
编译器:g++
编译工具:CMake
数据库:MySQL Redis
注册中心:ZooKeeper
序列化格式:Protobuf
编程语言:C++
源码链接:GitHub

写在前面

针对春招各个大厂的面试官对我春招项目:基于muduo网络库的集群聊天系统 提出的问题以及建议,对原来的集群项目进行了以下改进:

  • 由简单nginx集群改为分布式集群
  • 序列化格式由 json 改为protobuf
  • 引入自己写的RPC框架
  • 引入配置注册中心 zookeeper
  • 引入数据库连接池
  • 增加新业务

整个项目从构思到架构设计再到服务端研发总共花费了一个月左右的事件,期间遇到过大大小小的问题,也都总结了下来:杂记——在开发ByteTalk中遇到的困难以及解决

系统前置知识

有些还没整理完,先放上整理过的知识:

zookeeper 系列:

protobuf 系列:

RPC 系列:

数据库系列:

Nginx系列:

ByteTalk架构设计图

在这里插入图片描述

图可能有点模糊,接下来我会把每个图放大讲解。

ByteTalk架构 —— nginx反向代理负载均衡

整个系统在客户端和实际服务的服务器中添加了一个反向代理服务器 —— nginx,同时利用nginx的负载均衡来达到降低网络和服务器的负载的目的。但是,这个也有缺陷,在本文的末尾会讲到。

这里是摘自百度百科反向代理的图,这里的反向代理服务器就是我们的nginx,后面的原始服务器就是我们实际提供服务的服务器群,也就是整个架构图大圆圈圈起来的地方。
在这里插入图片描述

nginx 反向代理的配置

具体nginx的安装其配置在之前的博客有介绍:Ubuntu 安装Nginx及简单配置

这里就来看一下,针对ByteTalk,我们应该怎样去配置?
在这里插入图片描述
这里我们配置了两个服务器群的入口地址:172.17.0.2:6051172.17.0.2:7051,nginx监听的则是172.17.0.2:8000端口。当客户端发消息过来,代理服务器来接受客户端的网络访问连接请求,然后服务器将请求有策略的转发给网络中实际工作的业务服务器,并将从业务服务器处理的结果,返回给网络上发起连接请求的客户端。但是这对代理服务器的网络I/O是一个巨大的考验,这个我们需要考虑。

ByteTalk架构 —— 服务器群(服务单元)

这里的服务器群指的是整个架构图中大圆圈的部分:
在这里插入图片描述

根据张云鹏老师在《架构师修炼之道——思维、方法、实践》一书中第29页介绍的set模型

set 模型
整个ByteTalk整体设计抽象为由多台服务器组合而成的一台超级性能的服务器,这些服务器形成一个小集合,部署一整套对外的服务。set模型弥补了单机能力的不足,对业务组合搭配成一个单元。本质上是对服务的一个高内聚的封装。

代理服务器Nginx收到的消息就会像这么一个个服务单元去分派,服务单元执行完,Nginx再将结果返回给客户端,这个过程对客户端来说,基本就是透明的,好像就是客户端和服务器是直连的一样。

另外,就像神经信号在神经纤维中传递一样,每个服务单元中的服务节点就是神经元,而RPC就是服务单元内的脉络,是神经纤维。这个过程必须十分频繁,且需求快速。所以,整个服务器最好是部署在一个局域网内,减少网络中信息传递所带来的耗时。

ByteTalk 服务单元 —— 抽象服务节点:ProxyService

在这里插入图片描述

ProxyService 作为整个服务单元的入口,整个服务单元对外暴露的也是它的 Host 信息,对于客户端的请求信息,它会首先去判断这个信息是哪个业务,如果是以下业务,它就会去Zookeeper注册中心,找到提供这个服务的服务节点,并把这个请求信息分配给它。

登录、注册节点:

  • 登录
  • 注册
  • 注销(下线)

群组节点:

  • 加入群
  • 退出群
  • 创建群
  • 获得群用户信息

用户、好友节点:

  • 得到用户信息
  • 加好友
  • 删除好友
  • 得到好友列表

离线消息节点:

  • 读取离线消息

另外对于 一对一聊天 及 群聊两个业务的设计如下:

首先,ProxyService和其下属的ChatServer 构成主从模式:
在这里插入图片描述
对于一个聊天消息我们要做的唯一的工作肯定就是要转发了。如果这个聊天信息的接受者和发送者都在同一个服务器登录,那么很简单,直接就可以获取接受者的Host信息,然后直接发送就可以了。

对于接受者和发送者不在同一服务节点的情况下,我们就需要去zookeeper注册中心,去获得我们的一个从节点:chatserver x,然后将这条消息交给它去处理就好。

ByteTalk 抽象服务节点 —— 聊天处理服务器:ChatServer

在这里插入图片描述
对于一个聊天信息,ChatServer 会先去 Redis 服务器上查询这个用户是否在线;如果在线,取得它的 Host 信息(也就是用户所在的服务单元),然后去已建立的连接map 中看是否建立过,如果没有建立,那么就建立,然后将这个连接放入map。接下来将这条信息转发过去;如果不在线,就将这个消息存储到 mysql 的OfflineMsg表中,供客户端下次上线时读取。

注:
Redis 服务器主要存储了用户的Host信息如下: id号 ip:host
例:10086 “127.0.0.1:3001”

ByteTalk 业务服务单元 —— 以登录、注册节点:UserService为例

对于登录功能来说,ProxyService节点会先去 mysql 表中查询,是否和数据库中的数据匹配;如果不匹配,就给客户端返回错误信息。如果正确,就由ProxyService将 此用户的 Host 信息写入Redis服务器中。

在这里插入图片描述

对于UserService服务节点来说,采用的也是主从架构,收到这么一个消息,就会去zookeeper注册中心中获取一个可用的服务节点UserServer x,然后将这个消息派发给它,让它去执行。

UserServer x会和持久层的 mysql 进行交互,具体的读取这个用户信息。

具体服务单元是怎么实现的,可用查看博客:完成一个分布式的登录、注册、注销服务器并构建zookeeper集群

其他的业务服务单元,也就是下图这些,都是和UserService才有相同的设计。
在这里插入图片描述


日志服务器设计不同,具体参考这篇博文:做一个分布式的日志服务器

具体可以看一下源码,有异议的地方可以发博主邮箱讨论。

ByteTalk 架构缺陷

  1. 由于博主技术栈原因(就是菜),对于数据库,包括mysql Redis部分,其并没有考虑高并发和高可用,也没有进行分库分表等设计。
  2. 对于Nginx来说,这里的设计不是很好,其会承担很高的网络I/O压力。其实更应该去自己实现以下一个服务单元的注册中心,里面存储了服务单元的Host信息及负载压力等信息,然后客户端去主动拉取服务单元的信息,用哈希或者最小负载等信息去自己连接一个服务单元
  3. 对于某个Service去选择具体的服务节点Server,这里博主采用的是最简单的轮询方法。但是我的Server都是动态上限的,如果Server 1上线工作了很久,处理的请求达到了3w,这个时候Server 2上线了,那么我的Service去分派请求的时候,就应该去侧重Server 2,而不是这个时候还要轮询。这个选择应该是最小负载,而不是轮询
  4. 系统的容灾设计考虑还是不够全面,对于Server掉线,这方面可以用集群保证。但是对于Service节点掉线,其意味着整个具体业务服务不可用。另外,对于ProxyServiec掉线,将会导致整个服务单元不可用

如果还有,还会继续去补充

参考文献

  • 7
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
zkfire = zookeeper openfire(3.8.1)     Openfire 采用Java开发,开源的实时协作(RTC)服务器基于XMPP(Jabber)协议,您可以使用它轻易的构建高效率的即时通信服务器.    根据对xmpp与openfire的理解,我在openfire中相应的地方植入少量的代码,并把zookeeper包也一并打包到zkfire中。使用zookeeper(http://zookeeper.apache.org/)管理集群中的节点。   客户登陆集群中的不同服务器进行通信就如登陆同一台服务器一样。   openfire自身也有一套集群的实现,使用了oracle 的coherence的中间件,使用时要自己加入相应的jar包与集群插件。   之所以又自己开发了一套集群实现,一个是给集群提供多一些选择,一个是兴趣^_^,让openfire天然就支持集群      zkfire使用的场景:   zkfire中有zookeeper的服务器监听与客户端连接程序,但可以不依赖自身的zookeeper服务,可以在openfire之外另外开启其他zookeeper服务,此时只需指定   cluster.xml配置文件中zClient节点的连接地址即可。   如果只是zookeeper单机服务,那么所有openfire服务器只需要连到同一个zookeeper服务器就可以完成openfire的集群   如果是zookeeper集群,根据zookeeper的集群特点,集群中节点不应该少于3台。如果超过一半的zk节点宕机,那么整个集群境将不能正常的工作。      使用方法:   将zkfire.jar包替换lib下的openfire.jar,之所以命名zkfire.jar只是为了易于区分,名字可以随意取。并将cluster.xml放到bin目录下。   zkfire基于单openfire的实现,所以如果使用的话建议不要开启openfire自身的集群功能。   在安装的openfire目前bin下,放入cluster.xml文件。   示例内容如下:    <?xml version="1.0" encoding="UTF-8"?>    <jive>         <!-- 该节点用于openfire服务器之间通讯。IP为本机IP地址,需其他服务器能访问到 --> <notice>10.10.152.180:3004</notice>          <!-- zoo节点用于配置zkfire的zookeeper服务。如果用其他zk服务器,那么这个节点可以去掉。-->    <zoo>               <tickTime>2000</tickTime>              <initLimit>10</initLimit>              <syncLimit>5</syncLimit>              <dataDir>E:/zoo/data</dataDir>              <clientPort>3181</clientPort>               <server name="server.1">10.10.152.180:2888:3888</server>               <server name="server.2">10.10.152.185:2888:3888</server>               <server name="server.3">10.10.152.189:2888:3888</server>               <myid>1</myid>    </zoo>                <!-- 该节点用于连接zk服务器,如果连接zkfire自身的zk服务器,那么该节点可以去掉 -->      <zClient>127.0.0.1:3181</zClient>    </jive>  zoo中的节点server用于配置zookeeper的集群,myid指定本身zookeeper服务器的myid值,server.X 这个数字就是对应myid中的数字,集群中不同zk服务器的myid值不同。    zoo中其他节点的内容皆对应zk配制文件的键值内容。这里不再详述,可以参考 http://rdc.taobao.com/team/jm/archives/665,但dataDir与clientPort是必须配置,用于指定zookeeper数据文件地址与监听端口。    有任何问题请随时email给我donnie4w@gmail.com 标签:zkfire

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shenmingik

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值