构建一个你自己的类微信系统 — 可扩展通信系统实践

##前言

正如你们所知的那样,微信是一个非常成功的在线服务系统,由几万台服务器组成的系统为几亿人提供着稳定的业务服务。可惜作为一个普通的工程师基本上不可能有整体设计这样一个系统的机会,即使加入xx 也基本是个螺丝钉,只见树木不见树林。看着inforQ 上面高大上的架构设计,高来高去,个人资质驽钝,看过以后,所得有限,仍然大脑空空,没有太多收获。

2016年春节前后,我个人做了一个可扩展的系统实践,试图构建一个可扩展的类微信系统,本文记录了构建一个设计,实践可扩展通信系统的过程。

本文只是从外部反推后的一个设计,实践,模拟,实现的过程,和真实环境也不存在任何可比性,本人也和tx公司没有任何交集。文中必然充满了臆想,猜测以及错误,不能对本文的任何正确性做任何保证,也不对可能造成的损失负责。

##1. 目标及局限性 ###1.1 核心目标:

这个系统可扩展,可抗压,稳定并且正确服务。

可扩展,scale out。就是加一些服务器 就可以服务更多的用户

可抗压,系统在负载压力大的情况下,应该能自我保护,并且保证服务质量,不能因此崩溃掉。

稳定并且正确服务,这是一个通信系统,在任何时候都应该保证2个用户之间的通信是稳定并且正确的。举例来说 我和一个好友通信,无论对象处于什么样的环境,网络,在任何情况下,我们之间的通信消息都将按照原始顺序发送和接受,而不会出现重复,乱序,掉消息。

注意 因为服务器负载和网络的原因,消息延迟到达是允许的。

###1.2 本文及实验的局限性

局限性… 太多了,初始数据是随机的,数据集合大小是随机的,系统的架构是理想状况的,所以里面所有的结论都可以认为是不可信。

##2. 技术要求

Linux 基本操作

熟悉高性能程序开发更佳

TCP/IP 以及编程 至少得熟悉

系统设计 对计算机体系有一定了解

Linux常用工具熟练使用

C/CPP 或者java

一门脚本语言 python

常用的Linux server

大型分布式系统管理 监控 调优能力

数据库使用和简单调优

海量服务器管理维护经验

##3. 环境准备 

###3.1 开发语言选择

我选择的语言是Go, 版本1.4/1.5 而不是CPP。主要的原因 在前文c1000k practice guide 一文中,我认为go 是很好的后台开发语言。在此次测试中,我也期待使用go 进行一次实践。go 开发速度快,编译更是便捷,事实也证明的确很好用。

但是GO可能存在以下的问题

内存占用高,GO 使用的内存可控性不如CPP。不可控

CPU占用高。我的GO语言代码水平偏低,学习时间大概也就1-2个月

GC的不确定性. 这是我最担心的,Go作为使用GC的一种语言,是否能在高负载的情况下,依然很好的工作,是否会出现FullGC这样的灾难,还不得而知。虽然已经有小米利用go做了很多高性能的负载工作,但我认为最好的办法是做更多定量的测试,设定系统的阀值来避免。

###3.2 系统优化设置 我觉得以下几篇文章很好 可以参照进行设置,过程略

淘宝千石 高性能服务器架构设计和调优

锋寒 Linux TCP队列相关参数的总结

前文 c1000k practice guide

###3.3 脚本语言 python

###3.4 数据库比较及选型 数据库可以分为关系型的mysql/PG和非关系型的 Redis /memcached

关系型的数据库 Mysql 和 pg 都能满足业务要求,mysql 我更熟悉,所以选Mysql 非关系型数据库 Redis/ memcached

设计需要支持原子操作,也就是CAS操作

Redis 天生单线程,不存在这个问题,而memcached 也满足这个要求。

内存情况

Redis 存在fork问题,对内存使用有要求 memcached 的缺点数据不能持久化,数据存在被淘汰的问题。所以选Redis

Mysql和 Redis 之间进行对比

以最后1000万用户在线考虑 以每人有50条相互好友关系,每个用户id为4字节 1000*10*1000*50*4/1024/1024/1024 = 1.8G,数据量并不大。同样情况下,Redis 操作更简单,可控性很强

CAS mysql 支持 Redis 支持

吞吐量

mysql 强 Redis 很强

数据管理

mysql 可以有完整的数据类型,可以严格保证数据安全,分表机制比较复杂,对硬件要求高 Redis 没有完整的数据类型约束,完全靠客户程序自己保证,风险很大,对硬件要求低

数据估算

所以 最终选Redis

###3.5 缓存系统 暂无,

##4. 系统分析以及设计 

###4.1. 账号

首先考虑 一个用户的信息 简单的看 微信的界面,头像 昵称 微信号。没有类似QQ 这样的数字ID,而且微信号可以不设置。我个人觉得从系统设计的角度看,微信自己也需要一个唯一的可以标识用户的标识。这个唯一的标记应该是什么有资料描述,我将它设计为一个整数了,我在系统里会用一个唯一的整数表述一个用户。

###4.2 账户间的好友关系

微信不存在单向好友,所以好友链就是2个数字 在用户100 的关系链表里存在100,200 这样一个记录,表明 用户100 和用户200互为好友。则在用户200 的关系链里也应该存在200,100 这样一个记录。

###4.3. 多IDC以及账号区域问题 

####4.3.1 单IDC

这个可以从腾讯大讲堂中看到一些端倪,QQ也有这样的发展历程。最早所有的客户端是连到深圳一个IDC区域的,由单个点来完成。这样的好处是业务简单

但是单IDC也带来了很多问题

1.单点问题,如果过于集中接入,一旦出现节点故障,可能导致大量的不可服务。这就是所谓的鸡蛋在一个篮子里。2.服务距离太远影响服务质量,设想一下,服务器在深圳,有一个用户,身在在东北,那么他每个消息都是要穿过整个中国大陆。要为他提供很好的服务的质量,代价会比为一位广州的用户大很多。距离的增加必然带来延迟上的增加。3.单节点是中心节点 压力太大 继续做扩展很难,有技术和物理的上限。你见过10吨的卡车, 100吨的卡车,但是你见过1000吨的卡车吗展有难度

###4.3.2 多IDC

虽然多年以前,我认为互联网是传统电信是不一样的东西,但是多年以后,我发现世界上很多东西都是类似的。电信的网络和Internet本身就是解决这个问题最好的例子。

你的电话是有区域的,也就是本地电话和国内长途和国际长途。你拨打的电话,绝大多数的电话是在本地的,有一些是国内的,更少一些是国际的。对绝大多数人来说本地的朋友要比远方的朋友多一些额。如果你和你的朋友都在一个IDC,那么肯定能更好的服务。(8/2原则,至少我如此认为)

所以在用户的属性里应该有一项很重要的属性 Region, Region代表了你这个用户属于哪个IDC,类似于你电话区号,唯一的不同是,你的区号是可以变化的,只是这种变化不是很经常。平均每人每年的变化都不大的。并且这种改变不是由客户端发起,而是由服务器决定

多IDC的优势:

数据规模减小,每个IDC里的大小都更容易控制

系统更稳定,鸡蛋都放到了各个篮子。杭州被挖断光缆,不会让全中国都无法购物了

即使某个IDC故障,其他IDC不受影响

IDC相互支撑,当某些IDC出现故障,可以暂时把用户导流到其他IDC,比如滨海IDC 被大爆炸影响后,迁徙所有用户,就是这样的例子。

多IDC好,但是IDC带来一系列问题,需要仔细考虑。本次实践没有做这些部分,这是个大工程。实践过程中,将所有的用户按照不同的号段,分配到不同的Region, 模拟出多IDC的情况。而更普遍的情况,则是每个Region内用户的号段是不连续,并且离散的。这需要一个专门的服务来解决,我会慢慢实现。

###4.3.3 IDC内部的用户如何分布到均匀地服务器上

假设有IDC内部有1000台服务器可以服务1000万用户,如何将用户均衡的分布到所有的用户上始的办法有按照号段来区分,或者用户id 取余来做,但是这样都存在数目不均衡的问题,一旦存在服务器宕机无法快速迁徙的问题。

可以利用一个一致性hash算法,将用户id映射成另一个id值,解决分布不集中的问题。然后做sharding.如果用户数目足够多的话,应该非常均衡。(需要慢慢实践这一想法)

##4.4. 多设备消息的支持

这个主要依靠后面的设计

##5 核心服务器设计 以及通信保证

这部分是系统的核心,我是这样理解和设计它们的。

###5.1 背景和需求

首先看前提, 这是一个大型的系统,里面任何一个服务器都是不稳定的,可以崩溃的。原因可以是软件故障,bug,操作系统,断电,操作失误。任何一个服务器的崩溃都是在预期之中的。(这又是云计算的一个典型特征)

然后看消息的传输, 这里的消息可以暂时看作用户之间的聊天消息

首先看丢失,一个消息在这样的系统里传输,是随时可能被丢弃的。虽然我们使用了TCP 协议,但是需要途径多个服务器,而每个服务器的崩溃都是可以无法预测的,所以任何一个消息都可能丢弃。也就是传输不可靠

再看重传,因为整个系统的服务器是不稳定的,一条消息发出去以后,是否到达了目的服务器,这不可知,所以一定是存在重传机制的,那么对方在收到2个重复的消息,一定要能区别出来。一条消息重复收到100次 和处理1次没有区别。这也就是所谓的幂等性

最后是乱序,同样因为服务器是不稳定的,那么先传的消息,可能因为途经一个缓慢的服务器,会后到目的地,而后传的消息,也许会因为网络优化而先到目的地,这一点接受消息的服务器也需要能区分出来。

###5.2 预设条件

系统里所有的服务器都是不稳定,可以崩溃的。唯一能保证安全的是被持久化的数据。简单的说,就是数据库Redis里的数据,我认为即使重启,在重启前后不会发生丢失和错乱,而其他所有的服务器都可以崩溃,崩溃以后数据就丢失了。(真实的业务情况需要考虑持久设备的崩溃,持久化的服务器也是不可信的,因为硬盘会损坏。即使使用了多备份的机制,CAP 又会成为一个绕不过去的坑)

###5.3 详细设计

这样背景情况好像在哪里见过多年前,第一次看TCP/IP 卷一,我们不就是带着这样的疑惑吗理解这个的背景其实就是Internet本身,问题则是TCP解决的问题。所有的路由器都是不稳定的,消息可能丢失,可能丢失,重传,乱序。所以回顾我开始的观点,世界上很多东西都是类似的。

下面是真正消息系统设计

####5.3.1简单图解

发起客户端(client) ====> 网关(gw)==> 源头服务器(localposter) =====> [中转服务器组(poster)] ======> 目标服务器 (localposter) ==>网关(gw) =====> 接受客户端(client)

角色:

发起客户端,接受客户端(client): 是同样的软件,也就是一个网页,或者app 源头服务器,目标服务器(localposter) : 是同样的软件,它们负责消息的处理和存储到Redis工作 网关(gw): 接入客户端的请求,并做入口控制,防止压力过大,压垮后面的服务器 中转服务器组(poster):负责不同RG间消息传递。

##5.4 用户数据部分

完全参照了TCP协议的设计 用户包含了几个计数器和 2个队列

用户的全局计数器

用户的好友计数器

队列

用户的全局计数器包括

|._名称|._类型|._作用| |sendid |整数| 记录用户所有发过的消息ID| |sendackid| 整数| 记录用户所有发出的消息,被对方服务器接受的响应的ID| |Recvid| 整数 |记录用户接受的消息ID| |clientrecvid| 整数 |记录客户端已经获取的消息ID|

用户的好友计数器

2个好友之间还维护一对 1:1 的sendid, sendackid 来表明相互之间消息的顺序。

队列

sendqueue 发送队列,用户所有发送的消息都按照发送顺序排列在里面

recvqueue 接受队列,用户所有接受的消息都按照接受顺序排列在里面

##5.5 客户端发送消息的简单流程:

工作流程:发起客户端查询自己的计数器sendid, 假设服务器返回100, 此时发起客户端发送一条消息,在消息上带上sendid 101。此后重新查询自己的计数器sendid, 如果数值变为101了,这说明,这条消息已经被持久化了,好友一定会得到它。如果没有就不断重试,直到数值变为101,多次发送101号消息,对系统是没有负作用的。

具体的消息处理会有很多细节问题,可以学习一下TCP的工作方式。

#6 架构图 ##6.1 架构图

构建一个你自己的类微信系统 -- 可扩展通信系统实践

操作7: 根据结果,计算下一个消息的id, 对比本地的消息id,计算有多少新消息 操作10: 读取消息发送者的用户信息,同时对消息发送者的用户的信息展开watch, 保证操作是原子的 操作11: 对消息进行处理,如果是id 是新的则处理,消息发送者的发送消息id增加1 否则抛弃 操作12: 新消息则存入持久化数据库,同时更新用户的信息,如果操作失败意味非原子,需要重回第10步重做 操作16: 读取消息接受者用户的信息,同时对消息接受者用户的信息展开watch, 保证操作是原子的 操作17: 对消息进行处理,如果是id 是新的则处理,同时接收者的接受消息id 增加1, 其他情况抛弃 操作18:新消息则存入持久化数据库,同时更新消息接受用户的信息,如果操作失败意味非原子,需要重回第15步重做 操作19:发回一个同样id号的ack消息,接收者是 原消息的发送者 操作22: 读取发送用户的信息,同时对用户的信息展开watch, 保证操作是原子的 操作23: 确认ack是新的id, 发送用户的ackid 增加1 如果已经过时就丢弃 操作24: 更新发送用户信息,如果失败就重回22步

接受消息流程

构建一个你自己的类微信系统 -- 可扩展通信系统实践

这样就将所有需要的可执行文件生成了

进入 python 目录,创建用户之间的关系链

./gen.py -a 1,1000 -r 1000 -c 30 这表示我创建了1 到1000 用户的好友关系,分成了1个Region,平均每个人有大概30个好友

输出大概是这样的

构建一个你自己的类微信系统 -- 可扩展通信系统实践

启动服务, 启动服务的操作,可以通过listcmd.py 这个脚本自动生成,当然你需要根据你的实际情况进行一些修改。

运行的脚本大概是这样的。

构建一个你自己的类微信系统 -- 可扩展通信系统实践

开始测试

在一台主机上运行以下脚本即可

构建一个你自己的类微信系统 -- 可扩展通信系统实践

整个操作大概花了182 秒,大约处理了15条消息的发送,处理和接受。总数约为45次

但是这样并不能获得我们想要的东西,可扩展性,如果加一台服务器结果会如何果不能以线性的扩展处理能力,那么这个系统就是失败的。所以我们简单的再加一台服务器。用这样的命令 同时在2台服务器上

构建一个你自己的类微信系统 -- 可扩展通信系统实践 构建一个你自己的类微信系统 -- 可扩展通信系统实践

这里可以看到 2台服务器的峰值能到4万,而单机只有1.8万。同时也可以看出在削峰这方面,系统还有很大的欠缺

redis服务器的负载情况

构建一个你自己的类微信系统 -- 可扩展通信系统实践

网关的处理能力从2.5万 升到4.5万,有提高

poster服务器的处理情况

构建一个你自己的类微信系统 -- 可扩展通信系统实践

具体的细节挖取。

name:”GetRequest” timestamp:1458402690 servername:”local4″ servertype:”localposter” absolutevalue:253321

name:”GetRequest_time” timestamp:1458402690 servername:”local4″ servertype:”localposter” absolutevalue:71715733476

将一条消息从系统里获取出来需要0.28毫秒。这0.28毫秒包含redis 的读取和反序列化的时间

name:”StoreRequest” timestamp:1458402690 servername:”local4″ servertype:”localposter” absolutevalue:329376

name:”StoreRequest_time” timestamp:1458402690 servername:”local4″ servertype:”localposter” absolutevalue:105441499384

将一条消息存储进系统的时间为0.32 毫秒,包含序列对象和redis 存储的时间

name:”rpc_send_time” timestamp:1458402690 servername:”local4″ servertype:”localposter” absolutevalue:102540007263

name:”rpc_send” timestamp:1458402690 servername:”local4″ servertype:”localposter” absolutevalue:200038

一次golang 的rpc 需要0.52 毫秒,god的序列化对象的速度并不快

name:”CLientSendRequest_time” timestamp:1458402690 servername:”local4″ servertype:”localposter” absolutevalue:366212620457

name:”CLientSendRequest” timestamp:1458402690 servername:”local4″ servertype:”localposter” absolutevalue:67787

一个普通的消息请求 需要0.5 毫秒才能完全处理完成,非常的慢。解决的办法是加大线程并发能力,改进流程,因为流程中有多次数据的读取和写入非常影响了速度,同时也要避免数据冲突。当然最简单的办法 就是换一颗更强力的CPU!这2台服务器都是7年以前的产品了性能可能比较低下。

#8. 回顾以及目标

本文主要是实践了构建一个可扩展,通信系统的各个最基本的层次。用代码实现了自己的一些构想。作为一个玩具级别的项目,我觉得它达到了预想的目标,但是还存在很多问题

还有下面的问题要解决:

1) 性能不高,一个消息从发生到最终处理大概需要做6次序列号和反序列化

用户数据需要做3-4 次序列化和反序列化,甚至更多

2) 代码没有考虑大多数异常和安全问题, 缺乏足够的单元测试。

3) 缺乏真正的多副本 高可用的机制

4) 缺乏sharding 机制

5) 缺乏负载迁移的机制

6) 缺乏大规模分布式系统的跟踪系统. 类似阿里的分布式调用监控系统 鹰眼,如果要真正的上线运作,这也是必须的。可以监控整个业务链的

7) 灰度上线,回滚 监控

8) 代码优化

9) 系统动态调度

  …..

文章知识点与官方知识档案匹配,可进一步学习相关知识云原生入门技能树首页概览8683 人正在系统学习中

来源:普通网友

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2021年4月21日
下一篇 2021年4月21日

相关推荐