【IM】即时消息技术剖析与实战

即时消息技术剖析与实战:10周精通IM后端架构技术点

https://time.geekbang.org/column/intro/225


目前有没有什么不错的开源IM项目可以学习和研究?

可以看看mqtt的github项目,另外国内应该蘑菇街开源了一款叫teamtalk的im软件,也可以看看。

有用到哪些技术以及编程语言可以先说明下?

即时消息技术实际上是众多前后端技术的组合,包括但不限于:网络,分布式应用,DB,缓存,安全,服务高可用等等,专栏会用java完成一个简单im系统的搭建。


IM技术在不同的应用场景,基本协议是一致的吗?

您是说通信协议吗?这个不太一定,像微博这边web端走websocket用的是json,移动端是二进制私有协议。

请问一般会使用哪些进行数据存储?

您是指消息的存储用到的DB吗?这个因项目而异吧,最常见的mysql,hbase,像我们这般还用到了redis和pika。

作者觉得IM是一种客户端还是服务端逻辑更重、实现更复杂的服务?

重客户端和重服务端各有优劣吧,对于不需要支持多终端消息漫游的场景,可以让客户端承担更多逻辑

TCP 长连接的方式是怎么实现“当有消息需要发送给某个用户时,能够准确找到这个用户对应的网络连接”?

 首先用户有一个登陆的过程: 

(1)tcp客户端与服务端通过三次握手建立tcp连接;

(2)基于该连接客户端发送登陆请求;

(3)服务端对登陆请求进行解析和判断,如果合法,就将当前用户的uid和标识当前tcp连接的socket描述符(也就是fd)建立映射关系;

 (4)这个映射关系一般是保存在本地缓存或分布式缓存中,然后当服务端收到要发送给这个用户的消息时,先从缓存中根据uid查找fd,如果找到,就基于fd将消息推送出去。


用户使用客户端与服务器建立连接时携带了userid与clientid,连接建立成功后,服务器端记录用户、客户端及连接之间的关系。

一个可以用户使用多个客户端建立连接,一般是不同类型的客户端(网页、APP),后续有消息可根据此关系进行多端推送。


不管是websocket全双工还是tcp有状态链接,都是应用了ack机制,用户访问服务器,ack会保存用户的信息,服务器收到ack后会开辟一块专有内存来保存所有的客户的ack信息;同理,服务器发送信息至客户,客户端也会保存服务器的ack信息。

总的来说,客户端和服务器应该都是采用异步方式来提升效率和客户体验,并且降低服务器连接压力。


TCP长连接本质上是在客户端和服务器端各自维护一个成对的传输层Socket,Socket由dip, sip, dport, sport, protocol五元组唯一标识,在服务器端把用户uid和socket相关联,就可以把消息精准发给对应用户。



服务器端如果突然进程被kill掉,客户端如何及时得到通知并下线?

进程kill是会对socket执行close操作的,所以客户端是能感知到的。

真要是通过拔网线的方式把服务端网络断开,这种情况客户端在发送数据时就会失败,然后短连后重连其他服务器就可以了。



一个Linux服务器可以接受多少tcp连接,如何量化这个数字?

当业务层对持久化层操作响应慢时,为何客户端会频繁掉线?

聊天数据的持久化与下发操作查库对数据库的读写压力如何缓解?

实时聊天的数据库如果宕机,能否做自动切换数据库服务器的操作?

连接数一般不是问题,服务端单机几百万上千万都可以的,受限于分配给每个连接的buffer占用的内存。

业务层持久化会慢会导致客户端掉线这个没看懂呀,能具体一点吗?

第三个问题可以做  DB的读写分离

第四个问题应该是说db的自动切主,这个是可以的,各类数据库都有很成熟的方案。



为什么说一台服务器可以最多可以支持1000万的连接请求,这个数值是怎么推算出来的?

不具备读写操作仅仅只是维护静态连接的话基本上就是个拼内存的活,调整tcp单连接的读写缓冲区大小到4k,优化下其他系统参数比如句柄限制啥的,建连速度稍微慢点,有个200g内存是没问题的。这里其实想说的是,不要光看单机能维护多少连接,每秒收发包数这些才是关键。



微信聊天记录保存规则说明

https://kf.qq.com/faq/161224feeE3I161224MrMvqY.html


客户端发送心跳包,一般多久一次比较合适?

国内的话最好不要超过5分钟,微信应该是4分半。

以前有大厂测试过国内各家运营商的NAT超时情况,有的运营商在2/3G网络下NAT超时是5分钟。



如果遇到ddos攻击,有啥处理方案吗?

硬件层面可以上硬件防火墙来清洗异常流量,软件层面可以通过缩短syn半连接的超时来缓降。


请问长链接请求是怎么从lvs转到网关的, 是接入服务先返回给用户一个网关地址 然后用户直接长链接到那个网关的吗?

用户拿到的是vip地址,实际请求会路由到lvs所在的机器,再由lvs转发给真正后面的网关服务器。当然,也可以不用vip来直连网关机,存在的问题是服务上线时需要暂时下线这台网关机可能比较麻烦。



用分片来做水平扩展的话灵活性是低的吧,是不是不适合随着每天波峰、波谷来调整?感觉只能扩很难收。

如果某个时段有大量写入,用什么方式应对比较合适呢?

跟随流量波动来进行资源层的扩缩容对于数据读取可能比较有效,但是对于数据依赖master的写入扩缩容实现会比较麻烦。对于写入的优化可以采用类似操作系统buffer的机制,来对写入数据进行合并后批量写入,甚至根据业务来进行更高级的定制化合并逻辑。



物联网场景下,如果设备处理能力比较弱,怎么确保数据安全性?

你好,你说的设备处理能力比较弱 是说加解密比较吃力吗?

如果设备对于TLS和端到端加密这些都处理不了的话,可以考虑设计一套简单的二进制私有协议,即使不做加解密,安全性方面也有一定的保障。


IM即时通讯,感觉是一种多种技术整合的架构,提升了双方的通信效率,只要是需要即时的场景都需要用到,如一些注重社交的游戏或QQ或钉钉等OA软件的好友上线通知、网络通话、视频聊天等。网络电话这种实时语音的也是IM范畴?

对的,QQ和钉钉这些都是典型的IM软件不用说,游戏里面的聊天、互动也深度使用到IM技术,虽然类似网络通话、视频聊天这些场景主要是音视频的推流和拉流,但一般也都会配合使用到IM。


我们也落地了一套im,服务端和本地都有存储,和用websoket。

现在存在一个问题。群聊离线推送是将离线后产生的所有消息再用户上线的时候推送,还是客户端分页拉取?

可以考虑推拉结合,离线消息太多如果全部都推的话性能和带宽上都不太友好,可以离线推送一页,后续的再由客户端来拉取。


请问websocket为什么能实现实时通信?

如果是websocket可以使用stomp协议解决?

websocket支持双向全双工的传输,所以可以做到服务端推送,让消息接收更加实时。

有这么玩的,STOMP Over Websocket,可以基于STOMP的消息队列模式来实现消息推送。


长连接是不是 "header: keep-alive" 的连接? 

英文对应 persistent HTTP connection,但大家用得不多。

 webSocket 是HTML5才出来的,类似的机制,但各种浏览器支持得比较多。

因为机制和实现上,我好像看不出它们有太大的区别? 还是说 persistent HTTP 仍然是3次握手,websocket是一次?

最大的区别在于websocket是支持全双工的,也就是说是可以由服务端主动进行推送的,http 1.1 的keep alive只是能够多次http请求复用同一个tcp连接,但只能由客户端发起请求。


请问如果维护一个全局在线状态的情况下,精确定位通知的方案怎么做呢?

这个实现也比较简单,用户上线的时候把用户和连接的网关ip作为映射存在中央存储,然后消息推送时读取这个映射,查询待推送消息的接收人所在的网关机,再通过rpc方式把这条消息发给这台网关机就可以了。


海量用户场景如QQ,微信,除了加机器以外,还有些什么方式可以处理海量的tcp连接吗?

tcp连接的维护并不是一个耗资源的事情,只是占用一个句柄而已,没有消息传输时基本不怎么费资源,真正有压力的是连接上消息的下推。除了加机器,还可以通过优化系统参数等来提升单机的承载能力。


有新的消息是全部推送给客户端吗?那么瞬间服务器压力飙高如何解决?

每个服务器能维护多少长链接?如果数量有限那么微博抖音需要多大的集群支撑?

普通一对一场景消息扇出小,一般网关服务器没啥压力。

大型直播间可能会有你刚才说的情况,这种情况可以通过扩容,限流,熔断来解决。

单台服务器如果只是挂连接的话几百万上千万都没问题,但对于线上实际业务一般都不会真挂这么多,一般单机实际会控制在100w以内。


为什么web端不能像客户端一样,用tcp呢?

也是可以的哈,考虑到web端很多浏览器都原生支持websocket协议,实现上更简单,而且web端对于流量相对没有那么敏感,所以web端更多的可以考虑websocket。


如果产品只有Web端,TCP 长连接和WebSocket相比,还有别的优势么?

那还是websocket更好一些,很多浏览器默认就支持了呀。


静默推送支持的“唤醒 App 在后台运行”功能,你觉得都有哪些应用场景呢?

可以唤起app,重新建立长连接。


TCP 长连接的方式是怎么实现“当有消息需要发送给某个用户时,能够准确找到这个用户对应的网络连接”?

会在连接服务器维护一个用户id到连接id映射关系。

在分布式场景下,需要维护用户id到 连接服务唯一标识 + connid,连接服务唯一标识和connid可以整合一下。


抛开使用通讯协议本身,现在面临的一个挑战是Android程序保活。现在国内Android手机的系统都是各大厂商自己定制修改的。为了保证使用电量,杀程序,杀后台服务情况严重,包括一些第三方的推送也会被杀死。这样消息的及时推送就是一个很大的问题。请问有什么好的应对策略么?

 这种目前也没有太好的办法:一个是加入“保活联盟”类似的组织通过app间的互相拉起来提升;

另外就是通过多个第三方系统推送sdk来提升系统push的到达率。


1.及时率的统计方案是什么样的呢?业界各个的及时率大概是什么样的标准?

2.从移动客户端的角度如果想优化及时率的话,有什么切入点可以分享么?

一般只需要衡量服务端内部消息流转的延时就可以,标准这一块貌似没听到过有公开的。

服务端内部耗时优化就不说了,到公网传输的耗时可以考虑:

1. 多线接入,选择最优路径。

2. 优化传输的包大小,比如协议精简,压缩等。


对于连接安全如何搞,如只允许合法的websocket连接到服务器,不允许非法的连接。

目前我们是在连接的时候传递一个加密串,在服务器进行解密,不知道是否可行?

防止客户端的连接伪造是比较困难的哈,比如你的app源代码泄露了,稍微改一改就能仿冒APP连接上来。这种情况可以考虑采用人机验证(比如极验,比如手机验证码)来排除“机器行为”,更极端的可以采取网银的方式,通过客户端插入的U盾来提供真实访问的验证。


课程会加些推送相关的内容吗,目前部分厂商还存在限流问题,想知道怎么保证推送时效性和到达率等?

 有一篇会专门讲到APNs和android的厂商通道,不过厂商限流这个除非是超级APP才能被厂商独立保护。其他的只能尽量保活走自己的长连,或者通过保活联盟这种的来互相拉起。



每天几千万条消息 ,用到的这些DB的存取流程是什么样的 比如暂存 转存 转取 等等?

需要实时推送的消息一般来说需要在服务端按接收人维度进行暂存,只是存多久的问题。

对于需要支持消息多终端漫游的场景,不管在哪一端登录都需要能查看很久前的消息,这个时候只是暂存可能就不够了,需要能支持接收人、发送人等多个维度来获取消息,所以这种情况还不如直接先落DB,利用DB来帮助索引查询。


1、客户端和服务端只需要完成一次握手,就可以创建持久的长连接,并进行随时的双向数据传输;如果是这样的话,那是不是意味着需要服务器资源去存储这个长连接,那当客户端很多的情况下,服务器资源会不会被大量消耗。

2、在一些不是html的场景下,如微服务中消息推送,是不是还是只能通过消息队列等方式发送消息?

1. 映射关系需要服务端存储,有一定的资源开销。

2. 和html没关系哈,微服务里面的rpc调用也是可以通过tcp长连实现,不一定需要队列。


在《网页端收消息,究竟是推还是拉?》文章中讲,网页端的消息发送是用长轮询机制,而不是长连接。貌似长轮询并不会造成后端资源轮询的压力?

https://mp.weixin.qq.com/s/Z_xItxDUmUJO-W3bgYwb5Q

长轮询实际上也并不是一直hang在服务端,而是会有一个timeout超时,因此当没有消息时浏览器还是会每隔一段时间重新发起一次轮询请求,实际上这个时候是会去查询资源是否有新消息的,当没有新消息的时候这些轮询请求都是无效的。

长连接的方式避免了这种无效请求,真正实现了“有消息时服务端主动推送”。

在websocket基本普及的今天,应该是web端更多应该采取的策略。



这种客户端发送心跳需要服务端响应吗,毕竟连接数较多?

一般是需要的,如果客户端发送的心跳包没有收到服务端的回应,可能是中间网络出现了问题或者连接的服务器出现故障,这种情况客户端可以断线重连。


维护用户tcp链接到tcp-gateway的IP的映射,是在tcp-gateway上实现还是单独做一个服务来维护呢?

如果是用户和接入的ip的映射那肯定是单独的中央存储或者服务来维护了。

当然,可以不去记录这个映射,让每台网关机自己维护自己的,所有消息发给所有网关机去认领也是可以的。


为什么不选用UDP呢?

1. 用户基于UDP登录注册到服务侧以后,服务侧也可以根据用户IP+Port找到用户。

2. 即使基于TCP,也需要有应用层可靠性机制,来保证可靠投递;那基于UDP也可以通过应用层保证可靠性。

3. TCP还容易拥塞,产生较大时延。


udp也是可以的呀,早期QQ的通信协议就是采用的udp。

只不过针对udp协议,由于不能保证连接层的可靠性,所以需要应用层来保证。

对于早期互联网场景,网络状况大都不好,网络经常容易断开,tcp的三次握手和四次挥手的开销会比较大,另外TCP有拥塞控制,会导致弱网情况下传输消息不高,这应该也是QQ早期采用udp的原因。但是随着网络状况的改善,而且udp在网络穿透能力上相比tcp还是会差一些,现在大部分IM(包括QQ)实际上都是采用tcp为主的传输协议了。


QQ、微信这种同时在线人数好几亿,这么多连接,应该有服务集群来支撑。是怎么做到连接的负载均衡?

怎么做到某个用户端连接哪个IM的接入服务端的,如何定位要链接的服务器的?


在线连接数不是问题呀,很多用户只是连接在,没有消息收发的。集群化部署需要让用户能随机连接到任意的接入服务器,也就是接入服务器需要无状态。一般不要让某一个用户定点连某一台服务器,这种的扩展性和可靠性都比较低。


长连续缺点说虽然减少了Qps,但是并没有减小服务器读盘动作,想问一个问题,我觉得webSocket在替代客户端和服器轮询请求是有优势的,但好像他也没缓解服务器读盘。这一块服务端内怎么处理的呢?

轮询的话服务端需要不停去查询存储有没有新消息,不管实际有没有新消息。长连接的话可以做到服务端推送,真正有新消息时只需要推送一次就可以了,不需要一直轮询。


断网后,我们可以在浏览器断网后,触发offline之前,在做一些什么行动去链接么?

断网重连一般需要先建连并提供本地存储的最新的消息信息,然后服务端根据这些信息去取离线消息,然后再推送给客户端。


 客户端接收到服务器的数据后 如何确定消息的类型?

消息类型和消息内容是一起在数据包里推送到客户端的,解析出来就好了。


如果APP不在线,需要通过第三方系统推送,但是现在Android系统厂商很多,是不是需要APP服务商封装一个统一的API去自适配多个厂商的系统推送?

 这种的有很多第三方推送服务了,比如:腾讯的信鸽,极光等等。

Linux 服务器的socket文件描述符,一般都是有数量限制的,当一个用户保持一个 tcp 连接,就会占用一个文件描述符,这样当socket文件描述符耗尽的时候,在有新的连接进来,就会堵塞等待了? 这个地方逻辑是怎么处理的呢?


文件描述符一般情况下不是问题,单台机器句柄数开到1000w都没问题。真要是耗尽了就连不上了来了,可以通过客户端的重连机制来尝试其他机器。


我想问一下mqtt的遗嘱消息,不是可以实现离线消息吗?

will message是用来当某一个设备离线时留下一条遗嘱好用来告知其他设备这个设备下线了。


一般链接量大了,不用内存缓存连接映射信息,而是放在redis集群中,key是mid,valie是server:channel,就可以在集群中,找到对应server的连接?

嗯,是一种方式,不过还需要考虑维护这个全局在线状态时的开销和一致性问题。


websocket长连接,服务端推消息给客户端,怎么样确保消息到达了?就是有回调函数吗?

消息的可靠性可以通过应用层实现的类似tcp的ack机制来让消息接收方收到后”确认“给IM服务器。


用java做了一个与物联网设备的连接应用,是将连接存储到map中,然后通过设备的唯一标识为key从中取出连接。

将每一个指令状态存到了db中,但这就会有种情况发生,当连接数量过大时,服务器端cpu资源消耗过大,整个系统出现明显卡顿。这个该怎么解决?

一个是分析一下cpu资源消耗在哪了?另外,指令存储DB可以放到异步的进程或者服务中处理,连接层只处理连接相关的轻量操作。


HTTP 可以是有状态的吧?

更准确的说是指:无法从服务端主动发起操作。


客户端与服务端通过三次握手建立连接后,服务端会保存唯一的socket连接,socket包含了客户端的ip和port,服务端需要保存连接用户的ID和socket的映射关系()。这样当服务端需要给某个用户发送消息时,通过映射关系就可以获取到socket,通过socket发送数据给客户端。

连接层的信息映射保存时不需要关心客户端的ip和port,一般可以保存为socket对象或者channel对象就可以。


tcp链接和用户id以及设备id 绑定,消息体有发送目标,进行匹配即可.

http基础上实现能控制timeout的长轮询,这个怎么做的啊?

简单实现的话:ajax发起一个请求,超时比如设置为60s,后端servlet接收到之后,while循环判断当前时刻和请求时刻是否到达超时时间,没有的话调用后端service获取数据,如果没有获取到就sleep 1s,继续while循环。



1.连接层和业务层之间增加一个业务代理,这样连接层可以更加专注于处理连接问题,让业务代理来完成服务发现,管理业务集群,这样会不会更好?

2.为什么在连接层直接rpc调用业务端。那业务端需要通过这条连接推送消息不是会很不方便吗?

服务发现本身是rpc框架自带的通用特性,对系统消耗很小,再增加一层会使消息收发链路变长,整体稳定性可能会有影响。

业务层推送消息是通过消息队列来实现的,不需要依赖rpc。


 用websocket做长链接通信在网络较好情况下没什么问题 但在弱网下如2g 3g下就会频繁掉线,但是微信却做的这么好,地铁里仍然很稳定,它走的绝对不是websocket,它怎么做到的这么稳定通畅呢?有什么资料可以查看学习吗?

之前有提到微信手机端应该走的是tcp私有协议,不是websocket。

另外你提到的websocket弱网下容易掉线这个得具体case分析一下,不能把这个归结到websocket协议本身,底层一样都是基于tcp来传输的。

连接不稳定和很多因素有关,连接入口的是否够快,有没有故障转移通道,能否快速识别到连接断开然后快速进行重连,网络传输的数据是否够小够精简,这些都有关系。

微信有一些公开对外演讲的资料,可以查一下。


突然有个问题想问:像微博、微信等这些APP一般的实时在线人数大概是什么量级?一台网关机大概能承载多少在线用户呀?

像微博是千万级在线,微信具体数据不太清楚。单台网关机虽然理论上能到百万级别,但线上一般控制在几十万比较正常,如果是类似直播的业务会控制在更低水平。


万人群聊,系统是维护了万个TCP链接吗?

这里的意思是一个群有一万人的这种超大规模群。只有在线的用户才会建立长连


有些疑惑想跟您确认一下,消息多终端漫游,为解决用户离线期间收不到消息的问题,我们会在服务端按照接收用户维度,暂存用户离线期间的消息,等该用户下次上线时再进行拉取同步。

1、这里的同步,实际上是把服务端的消息同步到客户端,客户端也保存这些消息吧?

2、假如用户重新安装了APP,把客户端保存的数据也清空了,像这种情况下,一般处理是只同步离线消息,不同步历史数据吗?

3、如果客户端不保存消息记录,但又要显示所有历史数据,是不是不需要设计这个离线存储了,直接读取会话维度的所有消息就好?

1. 是的。

2. 一般这种情况可以只下推离线消息,历史消息通过用户来触发拉取。

3. 不保存消息记录是指客户端不在本地存储所有消息?这种方式不太好呀,要不没网就看不了所有聊天记录了。

你可能感兴趣的:(【IM】即时消息技术剖析与实战)