1、IM概述
IM 是“instant messaging”的简称,翻译成即时通讯。说到即时通讯,我们可能最先想到的是一款叫 ICQ的聊天软件 ,后来者还有微信/skype/msn/momo等 。IM包含即时和通讯是两个关键词。
- 即时
- 通讯
所以“即时通讯”,从字面上看,就是快速,按照一定协议交换信息。 下图是一个ICQ的截图:
2、IM系统特性
IM系统相对其他系统而言有自己的几个特点:
- 实时性
- 推送性
以股市变化曲线为例,客户端不主动发送请求,曲线会随时间自动变化 :
- 消息可达性
消息可达性即消息的可靠投递 ,有一个著名的定理:SMC定理,Single-Message Communication,Published in : Communications, IEEE Transactions on (Volume:24 , Issue: 2 ) ,很短的一个论文,文章的结论是,任何端到端的消息传递协议 ,消息既不丢失,同时也不重复是不可能的。后续会分享,系统层面的重复,可以换取业务层面的不丢失和不重复。
- 状态一致性
大部分人可能了解XMPP(Extensible Messaging and Presence Protocol )协议 ,即可扩展消息和Presence协议 ,这个Presence直接翻译就是“出席“,何为出席? 大家都到群内签到了,叫出席 ,没来听讲座叫缺席 ,在IM中出席缺席表示状态 。登录在线叫出席,没登录叫缺席 。
举个例子:
假设用户A有100个好友,用户A加入了10个群,每个群有100个人。用户A在登录的一瞬间,状态由“不在线”变为了“在线”,他的状态的变更需要通知要通知1100个人,包括100个好友和1000个群友,这个扩散系数是非常庞大,
如何做这1100个人的状态推送? 常见的做法和Feed,微博类似 ,你的100个好友(实时性要求很高),采用推的模式 ,你的1000个群友采用拉的模式(实时性要求不高) 。当然,这里还只是说状态很很复杂,还没涉及到状态一致性问题。
二、协议设计
网络协议是由三个要素组成,语义、语法、时序。语义表示要做什么,语法表示要怎么做,时序表示做的顺序。 本文主要讲解重点语法的设计。
IM的协议分为3层,如下图:
1、应用层
常用的IM应用层协议有3种 :
1.1、文本协议
文本协议是“贴近人类书面语言”的协议,典型例子是MSN ,另外HTTP协议也是文本协议,如下图所示:
文本协议有几个特点:
- 可读性好/便于调试
- 扩展性也好(key:value)
- 解析效率一般(一行一行读入,按照冒号分割,解析key和value),
- 对二进制的支持不好 ,比如语音/视频
1.2、 二进制协议
二进制协议最典型的是IP协议,如下图所示:
二进制协议一般定长包头和可扩展变长包体 ,每个字段固定了含义 ,例如IP协议的前4个bit表示协议版本号 (Version)。IM中,QQ使用的时二进制协议。
二进制协议有几个特点:
- 可读性差/难于调试
- 扩展性不好 ,如果要扩展字段,旧版协议就不兼容了,所以一般设计时会有一个Version字段
- 解析效率超高(几乎没有解析代价)
- 对二进制的支持不好 ,比如语音/视频
1.3、流式XML
Xmpp协议就是使用流式XML,像Gtalk,校内通都是基于Xmpp的。
举一个罗密欧给朱丽叶发消息的例子:
< message to='romeo@example.net' from='juliet@example.com' type='chat' xml:lang='en' >
Xmpp用 romeo@example.net 来标示一个账号,称为JID 。JID由两个部分组成 ,前半部分是该域中的账号体系 ,后半部分是域标识(编者注:JID还包含一个Resource部分)。Xmpp协议可以实现跨域的互通。例如Gtalk和校内通用户聊天。只要服务端实现了s2s服务(server to server) ,不过现在的IM基本没有互通需求 ,所以 这个服务基本没有人实现。
Xmpp协议有几个特点:
- 它是准标准协议,可以跨域互通
- XML的优点,可读性好,扩展性好
- 解析代价超高 (dom解析)
- 有效数据传输率超低(大量的标签 )
我个人强烈不建议使用Xmpp,特别是无线端IM,这个是google上个世纪的产物了,如果要用,一定要自己做压缩 ,减少网络流量。
实际例子
下面来看一个IM协议的实际例子 ,一般常见的做法是:定长二进制包头,可扩展变长包体可以使用用文本、XML等。 百度Hi 的IM包体用的是XML ,58同城的IM包体用的是protobuffer,包头负责传输和解析效率,包体与业务无关负责保证扩展性。
这是一个简单例子,我们来看一下包头和包体 。
定长包头(16个字节)
- 前4个字节是Version
- 接下来的4个字节是个“魔法数字(magic_num)“,用来保证数据错位或丢包问题,常见的做法是,包头放几个约定好的特殊字符,包尾放几个约定好的特殊字符 约定好,发给你的协议,某几个字节位置,是0x 01020304 。才是正常报文
- 接下来是command(命令号),用来区分是keepalive报文、业务报文、密钥交换报文等
- len(包体长度),告知服务端要接收多长的包体
变长包体
可选择 Xml、protobuffer、mcpack等 ,某些公司使用protobuffer,作者也强烈推荐,主要有几个原因:
- 现成的解析库种类多,可以生成C++、Java、php等代码
- 自带压缩功能
- 在工业界已广泛应用
- Google制造
下面贴一个protobuffer写的用户登录协议的示例:
2、安全层
IM协议,消息的保密性非常重要 ,谁都不希望自己聊天内容被看到
1、HTTPS
稍微复杂,代价有点高 。
2、自行加解密
密钥管理方式有多种
1、固定密钥
2、一人一密钥
据传,QQ是这么使用这种方式(未核实)
3、动态密钥
大家了解ssl的过程么,动态密钥 一Session一密钥的安全性更高,每次传输交互前协商密钥, 客户端第一个报文:服务端,请告诉我这次通话的密钥 ,服务器就随机生成一个,返回给客户端 然后这次会话用这个密钥来通信 ,这样可以简单的做到动态密钥,但是一旦攻击者截取报文,就能知道你的动态密钥。
SSL的用法,是2次生成非对称加密密钥,1次生成对称加密密钥 ,具体详情这里不展开,有兴趣的可以深入研究一下。
3、传输层
传输层:TCP/UDP
现在的IM传输层基本都是使用TCP。有了epoll/kqueue等技术后 ,单机多连接就不是瓶颈了,单机几十万链接没什么问题。58同城现在线上单机连接好像是10w?(可能单机性能测试可以到百万,线上一般跑到几十万)
关于QQ是使用UDP问题(作者观点)
10多年前,一台服务器支撑不了1W个TCP连接 ,腾讯的同时在线量高,没办法,只有用UDP了 ,但UDP又不可靠,腾讯使用UDP实现了TCP的超时/重传/确认等机制
三、WEB聊天室
1、需求
下面是一个Web聊天室的截图
Web聊天室需求主要有一下几点:
1)用户可以设置自己的名字
2)进入聊天室后,可以看到所有其他人的名字
3)可以看到所有人的聊天
4)可以发言给所有人
上面是Web聊天室的一些简单业务(像群吧?) ,设置名字/拉取其他人的信息 都是简单的请求响应式的需求这里不展开,主要重点讲怎么看历史聊天消息,怎么发消息给别人 。
2、系统架构
Web站点三层架构大家都很熟悉,大致是这个样子
LAMP是个典型解决方案 ,更典型的是这个样子
- 数据层
对于聊天室的简单需求,一个存用户信息,一个存消息两个表就可以了,下面是一个简化了的示例:
user( name vchar(16) unique );
message( time timestamp, name vchar(16), msg vchar(140) );
有人进入,就往user里插入 ,有人退出,就从user里删除 ,有人发消息,就往message里插入 ,有人进入,往message里拉取,就能查询历史信息。
3、技术核心点
Web聊天室的核心店在于如何把发送的消息通知User表里的所有人?消息实时性,主要有三种方式,websocket、flashsocket、http轮询,本文只讲http轮询。
1、轮询
什么是轮询?
程序代码:while(1){sleep 500ms; get msg;}
大部分人最容易想到的解决方案就是轮询(poll) 。十几年前,四通利方/碧海银沙就是用的轮询,轮询拉取消息,每隔几秒往message表里,拉取最新的聊天室消息 ,这样做能简单实现一个聊天室。
但轮询问题也显而易见 ,每隔N分钟,轮询调用 “获取消息”接口,有可能出现消息的延时,某一时刻刚刚拉取完消息,突然又产生了一条新消息,这条消息就必须等到N分钟之后,再次发起“获取消息”轮询时,才有机会获取到。 可以降低时间间隔来降低延时,但绝大部分的轮询调用,都没有消息返回,造成服务端极大的资源浪费
2、消息连接
Web聊天室通过“http消息连接”来保证消息的绝对实时性,何谓消息连接?
用户和服务器建立一条http连接,专门用来传递Notify 。例如,手机上,web聊天室里,有一个B用户 专门有一条http消息连接,用来投递Notify(消息),如下图 。
消息连接如何保证 web聊天室消息的实时性呢?它具有以下几个特性:
1、没有消息到达的时候,这个http消息连接将被夯住,不返回,由于http是短连接,这个http消息连接最多被夯住90秒,就会被断开(这是浏览器或者webserver的行为)。
2、如果http消息连接被断开,立马再发起一个http消息连接
如上图,http消息连接90s超时了,服务器返回空了,web浏览器会立马再次发起一个新的消息连接 。目的是,保证一个用户一直有一条消息连接连着,可以接受消息
3、每次收到消息时,这个消息连接就能及时将消息带回浏览器页面,并且在返回后,会立马再发起一个http消息连接
如上图,某人发送了一条聊天室消息 ,这个消息要投递给user里的所有人,B是其中之一,此时会有一条消息连接在,消息连接就直接将消息带回 ,带会消息后,立马再发起消息连接 。
4、消息池
上面三大特性保证了:
a)任何时间都有消息连接在
b)消息能在第一时间通过消息连接返回
但这里有个小概率事件,正在返回消息的时候(可以认为此时没有消息连接),瞬间又到达了一条消息怎么办,此时服务端要有一个类似于“消息池”的东西,将这个消息暂存起来 。消息连接到达后,从消息池中将消息取回,再通过消息连接返回。
1)消息要投递给聊天室中的所有人,B是其中之一
2)此时没有消息连接,msg放入消息池
3)消息连接来晚了
4)从消息池中获取消息
5)获取到消息了
6)返回消息给web浏览器里的B
7)新的消息连接发起
结论:
Web聊天室通过http长轮询可以保证消息的实时性。这种实时性的保证不是通过增加轮询频率来保证的,而是通过夯住http消息连接来保证的,在大部分时间没有实时消息的情况下,这个http消息连接对于webserver的请求压力是90秒1次,能够大大节省了web服务器资源。
这个消息连接的思想,是一个观察者模式 ,所有的聊天室用户是observer,聊天室是subject,observer订阅subject ,subject保存有所有observer的集合,当有消息发出,即subject发生改变时,通过http消息连接反向通知subject
这里重点讨论了 web聊天室 消息的实时性,聊天消息的可靠投递,暂时也先不展开了
即时,是相对时间而不是绝对时间 ,m的即时,消息投递在几百毫秒,1秒内投递完成,一般都可以接受(站点应用这个时间就接受不了解),这个时间对未来我讲im服务的网络模型,影响很大 。 im的所有业务逻辑,都是在这几百毫秒内完成的 。
4、IM典型业务场景
IM业务逻辑有一定的复杂度,举例IM中一个典型业务场景,用户A将用户B加入到分组G中,如下图:
这里包含多少业务逻辑呢
第一步,判断A是否是正常im用户
都9步了,真正的加好友步骤却还没开始,这里的每一个步骤,都不是一个简单的本地cpu计算能完成的,都需要访问数据库或者后端服务 ,所以im的业务逻辑是很复杂的,做过的人懂。
上面有一个步骤,是检查加好友的验证短语的合法性 。IM系统中,所有能被别人看到的话,都要经过antispam验证 ,antispam一般有敏感词(政治,黄色)过滤,消息频率过滤,广告过滤 ,每一个步骤,都有很复杂的词库过滤,或者分析过滤(分析一句话是不是广告,非常复杂)
四、Q&A
Q1: xmpp中,在跨域通讯网络没有保证的情况下,xmpp如何保证跨域消息的可靠送达的。
A1:im消息的可达性,是通过超时重传确认保证的(未来会再次重点讲细节),跨域只是提供了一种不同域的通信机制
Q2:应用层协议设计中,为啥 version 会放在 magic_num 之前呢? 为啥magic_num 不是第一个呢? 是考虑不同版本的 magic_num 不一样?
A2:是的
Q3:我没懂magic_num的作用,这个是常量吗?另外,为啥不直接采用http,而要自定义协议?
A3:web上可以选择http作为应用层协议,直接tcp长连接搞的话协议得自己来
Q4:聊天室怎么保证消息的可靠性?
A4: 可靠性以后讲,或者可以看下刚才群内有同学发的那篇文章 webim如何保证消息的可靠投递
Q5:安全层加密, 是对应用层的:定长二进制包头 + 可扩展变长包体 都做加密?
A5:包体和业务相关,要加密
Q6: 现在通信行业的IM(RCS等业务)基本都用SIP(单独或结合MSRP)来做,协议安全用TCP/TLS,WebRTC也用SIP,请问@58沈剑 不用这些公共协议而用私有协议的主要出发点除了性能,还有什么?
A6:自己做更可控,结合自己业务做优化。另,im行业准标准协议是xmpp。
Q7:消息推送的话,用长连接保持的话,现在也比较通用了吧?
A7:能用tcp就用tcp,有些场景,例如web,选择http消息连接是被逼的
Q8:实时推送,http轮询和websocket如何选择呢?
A8:websocket有兼容性问题,很多浏览器不支持,据我了解,大规模高在线的im,好像web端还木有用websocket搞的
Q9:手机端的聊天室如何处理用户频繁断线的问题?如何在断线后获取服务器端聊天历史并与本地聊天历史合并?
A9:web聊天室,消息连接不上,消息就放在消息池里,上来再给他,一定时间上不来,就丢掉。消息id可以去重。
Q10:能不能介绍一下注册/IM/Subscribe/Presence等服务器结合起来的部署架构?
A10:架构后续课程分享
Q11:websocket有什么弊端?目前单机最高业务承载多少?
A11:同A8回答。
Q12:移动端im的注意事项
A12:后续专门讲移动优化
Q13:聊天室达到10万级别的时候,在效率推送上是怎么考虑的?
A13:聊天室和群一样,消息扩散系数很影响性能,一般群不能到这个级别,这个级别的群对服务器影响很大(微信群上限是多少?qq群上限是多少?)
Q14:我想问下,刚说的消息池是全局的还是针对每个用户单独的?
A14:消息池本质是个map,key是uid
Q15:为什么不用BOSH(Bidirectional-streams Over Synchronous HTTP)来描述刚才web HTTP机制?有什么细微区别吗?
A15:我猜bosh的本质也是消息连接,bosh/comet还是什么,叫法不一样,实现方式应该是类似的
Q16:另外你说的移动IM不建议XMPP协议,是单指XML格式本身的缺陷,有效数据太少,数据解析慢,仅仅是这个原因吗?解析这方面有做过具体的性能测试吗?另外如果是有效数据太少,是否可以稍微扩展就可以大幅度减少无效数据问题。还是有其它方面原因,比如XMPP协议本身交互协商次数太多的问题?
A16:xmpp解析慢,有效传输率低(无线端的表现就是耗流量),你们猜用xmpp发一个登录包多大?
Q17:websocket性能会比long-polling好多少呢?
A17:tcp长连接 和 http短连接 的差异
Q18:聊天消息中含html、css、js相关代码是如何处理的?
A18:这是一个富文本消息的好问题,服务器不管消息的内容,只进行投递,富文本消息的内容,是客户端需要理解的
Q19:im怎么保证时序性?当服务器接收msgpack时处理不过来,客户端就会阻塞发送消息。但没阻塞接收的消息。这样发送与接着就不同步了
A19:一言难尽,简单说,单对单的消息,用uid+msgid进行时序保证;群消息,用gid+msgid进行时序保