rainzhaojy / blogs

200 stars 28 forks source link

深度解析XMPP协议的缺点和优点 #2

Open rainzhaojy opened 7 years ago

rainzhaojy commented 7 years ago

XMPP是一个开源的基于XML的IM协议, 协议的3个关键特点是TCP长连接, 类Email, XML报文. XMPP诞生在PC时代,并没有生来就为mobile而设计,XMPP协议也很繁杂,全面了解XMPP协议集并不那么容易

XMPP的问题

XMPP诞生于99年, 那时还没有智能手机, 因此协议在制定之初是针对桌面的分布式通讯的, 并没有考虑到mobile的一些特殊情况:

下面将详细讲述XMPP的一些问题, 尤其是常被诟病的不适应mobile的一些问题, 并探讨对应的解决方案.

问题1 - 登陆步骤太多

XMPP的标准登录流程有如下交互:

  1. TCP连接 1.1 SRV lookup 1.2 DNS to IP address 1.3 建立TCP连接 - 3步握手建立连接
  2. Open Stream 2.1 Client要求open stream 2.2 Server返回stream响应 2.3 Server返回stream:features
  3. TLS协商 3.1 Client开始starttls 3.2 Server返回proceed 3.3 建立TLS连接 - 4步握手(client hello, server hello, client key exchange, finish) 3.4 重走步骤2里的3步
  4. SASL 4.1 Client发送认证信息 4.2 Server返回认证成功 4.3 重走步骤2里的3步
  5. Resource binding 5.1 Client发送要求resource binding 5.2 Server返回绑定成功
  6. Create session 6.1 Client发送要求创建session 6.2 Server返回成功

登录步骤之所以这么多, 一个原因是XMPP先构建于TCP之上, 然后再建立TLS连接, 而不是直接构建于TLS-over-TCP之上, 另一个原因是为了保证XML streaming的逻辑完整性.

如果使用自有协议, 一个最精简的登录过程可以只需要1.1, 1.2, 1.3, 3.3, 4.1, 4.2, 可以大大减少登录步骤, 差不多可以减少15次左右的TCP交互, 假设一次TCP的RTT为200ms, 那么就可以差不多节省3秒, 实际应用中手机网络更差的话, 需要的时间可能会更多. 尤其是手机经常断网或前后台切换的话, 快速登录或快速resume就非常有必要了.

问题2 - 低效的XML

XMPP里所有的报文都是XML, 在XMPP里称为Stanza, XML可读性可扩展性都很好, 但数据冗余较大, 相同大小的报文能承载的有效数据量较少.

那么不用XML用什么呢? 其实有很多选择, 基于文本的有JSON, 基于二进制的有protobuf, MQTT等. 如果希望保密, 可以定义私有的PDU. 如果你不仅需要报文格式还需要RPC框架, 可以使用Thrift.

JSON在web系统中应用非常广泛. Cisco Jabber基于XMPP, 而Cisco Spark就是基于JSON.

protobuf是google提供的一个开源序列化框架, 类似于XML/JSON这样的数据表示语言, 但是基于二进制, 因此protobuf比XML/JSON都高效短小得多. 虽然是二进制数据格式, 但protobuf仍然具有非常好的扩展性和兼容性.

MQTT是IBM开发的一种轻量级的基于二进制的消息格式, 用于无线或低带宽网络, 目前常用于物联网中. 据说Facebook Messenger用了MQTT.

如果你希望保密, 你也可以自定义数据格式. WebEx Meeting就是定义了自己的PDU格式, 基于二进制.

当然, XMPP协议本身也提供了一些优化方案, 譬如可以zlib压缩. 参考XEP-0286

问题3 - 碎片化的Stanza

XMPP协议为了灵活性和扩展性,定义了很多琐碎的交互,一个例子是login,另一个例子是XEP-0030: Service Discovery,XMPP Client为了知道Server端提供了哪些服务,每种服务支持哪些feature,需要多次反复的使用disco#items和disco#info,如果不考虑兼容异构的XMPP client/server之间的交互,这些灵活性和扩展性经常是不必要的。

每一个数据交互对应一个Stanza, 一个Stanza一般会产生一次网络交互, 这样就会出现很多碎片化的stanza在网络上传输, 网络交互效率不高. 虽然TCP是流协议, 理论上可能会合并Stanza或拆分Stanza, 但从实际来看, 一般一个Stanza就是一个报文, 只有大的Stanza可能会被拆成多个TCP报文.

如果可以合并报文以减少网络交互, 那么可以更有效的利用网络. 合并报文直接的好处就是减少RTT, 假设有100个Stanza要传输, 没合并报文的话这100个Stanza会产生100次TCP传输的RTT, 而合并后可以减少RTT.

合并报文减少网络交互对于mobile就更有必要了, 移动网络容易出现丢包和乱序, 碎片化的报文容易引起TCP的队首阻塞(HOL, Head of Line)问题. 另外移动网络存在DCH (高耗能模式) 和FACH (低耗能模式) 切换的情况, 一次耗能模式切换大概需要花费10s, 而且不管你传输的数据大小, 它都会触发一次耗电周期, 消耗10s的电量, 频繁的小报文会严重浪费手机电量.

问题4 - 每次登录后请求roster list

如果你的roster list超过几百人, 对应的XML就会比较大, 每次登录后都要获取完整roster list的话会非常影响性能.

XEP-0237提供了roster versioning来实现增量更新.

Client可以缓存roster list, 每次resume时不显示登录过程, 马上显示缓存的roster list, 然后后台登录并获取roster list增量更新, 这样可以实现IM的秒开, 可以很好的改善用户体验.

问题5 - 复杂的Presence

XMPP是有Presence的, TCP长连接的状态决定了Client是否在线, 为了避免假连接, 频繁掉线重连干扰用户体验等, 需要维护好TCP的长连接, 定时heartbeat, 要有断网重连机制(用户无感知)等等. 相关的协议有 XEP-0198: Stream Management, whitespace ping, XEP-0199: XMPP Ping

另外, XMPP里的Presence业务逻辑也比较复杂, 包括RFC 6121里的Basic Presence, 还有MUC presence, Temp Presence等, 为了实现Presence, Client和Server都增加了非常多的复杂度.

对于mobile-first的IM产品, 都是假设用户时刻available的, 对于用户来说最重要的是聊天列表, 传统IM的Presence并不重要, 譬如微信就没有Presence.

如果你的IM产品无需Presence, 那么就可以简化很多问题. 结合问题4的改进, Client缓存roster list和chat history, 就可以实现app的秒开了.

问题6 - 后台数据交互消耗电量

手机app进入后台后, 如果有机会保持TCP长连接(Android, iOS10之前), 这时与Server之间的数据交互仍然保持不变的话, 大量的数据交互会很快消耗手机电量.

其实Presence Stanza对于后台app是无用的, 而且Presence Stanza所占的报文数量是非常高的. XEP-0273: SIFT 定义了一种机制可以在手机进入后台后过滤某类或某些Stanza, 这样可以有效节省手机电量.

问题7 - TCP连接断则表示Client离线

传统的XMPP Server在管理Client状态时完全依赖于TCP长连接, 连接断则表示Client离线, 当Client离线后:

  1. 其他人会看到一个离线状态
  2. 可能会有丢消息问题, 譬如消息正在deliver过程中Client断线
  3. Mobile用户不会即时收到消息 3.1 如果不支持Offline Message, 发送方无法发送消息 3.2 如果支持Offline Message, 消息会存在offline storage, 接收方下次上线时才会看到消息

XMPP定义了一些扩展协议来改善这个问题, XEP-0198定义了TCP连接管理, 消息ACK机制等, XEP-0357定义了如何实现Push Notification.

对于mobile用户而言, 将状态和TCP长连接捆绑是不准确的, ejabberd的改进是增加一个standby状态(detached session).

问题8 - Federation使得Server实现更复杂

XMPP协议设计的目标是变成类似Email这样的协议, 所有分布式的XMPP Server遵守federation, 最后组成一个完整的互联互通的XMPP网络. 但是对于绝大多数公司而言, 在设计IM产品时互联互通并不是一个需求, 甚至出于商业原因, 很多公司并不希望连通, 但是一些开源的XMPP Servere为了遵循协议, 都会实现server to server federation, 这无谓的增加了Server复杂度.

问题9 - 发送图片、语音、视频等富媒体

XMPP定义了in-band和out-of-band两类传文件的方式,in-band(XEP-0047)的方式就是将要发送的富媒体做base64编码后使用IM的TCP连接发送,比较适合发送较小的富媒体。XEP-0234: Jingle File TransferXEP-0260定义了out-of-band方式。

目前的IM应用发送富媒体基本都是使用对象存储了,发送方将要发送的图片、语音、视频等上传到对象存储,然后将URL送给接收方即可。

XMPP的亮点

XMPP诞生在PC时代,并没有生来就为mobile而设计,XMPP协议也很繁杂,全面了解XMPP协议集并不那么容易,但是XMPP完整解决了IM通讯产品领域的绝大多数问题,很多问题解决的也还比较巧妙,这些设计思路和解决方案并不仅仅适用于XMPP,也适用于任何非XMPP的分布式通信产品中,因此即使不使用XMPP,了解这些巧妙的设计也是有用的。

1) Resource ID

在mobile时代,多终端登录很常见,XMPP里bare jid(格式为"user@example.com")用于表示某个用户, full jid用于表示某个具体的endpoint(格式为"user@example.com/resourceID"), full jid里的resourceID很好的支持了多终端登录,你的产品里也许叫device id, location id, 但本质上和resource id是一样的思路。

2) Service Discovery

XEP-0030定义了一种发现服务的机制。

3) Capabilities

XEP-0115定义了发现buddy能力的机制,方便的实现向前向后兼容。

4) DNS SRV Lookup

一种比较简单的服务发现机制。用户"alice@cisco.com", "_xmpp-client._tcp.cisco.com", isj3cmx.webexconnect.com

5) Extensibility

使用标记可以非常方便的扩展各个xmpp stanza.

6) Decentralized

整个XMPP Cloud可以是完全分布式的。

总结

XMPP有各种问题和不足,但是XMPP仍然是一个优秀的协议集, 仍然是被很多公司使用的IM协议, 对于桌面端, 强网络的情况, XMPP还是很好的选择.

XMPP开放、标准、易扩展, 并且客户端和服务端都有很多开源的实现, 所以初期出产品会快一些. XMPP也在让协议更modern, 定义了一些扩展协议来改善mobile上的体验, 参考 XEP-0286, 比较知名的XMPP Server为ejabberd, ejabberd针对mobile也做了很多优化.

但在移动为先的时代, 如果你不需要federation, 你也不需要Presence, 你需要最高效的数据传输, 最优化的移动体验, 你的团队也不具有比较好的XMPP知识和定制/裁剪能力, 那么XMPP就不是很好的选择.

有很多公司并不照搬使用XMPP协议, 定制裁剪后使用XMPP, 譬如WhatsApp, APNS等.

或者你完全不用XMPP, XMPP协议也还是值得学习的, 换一个角度来看, XMPP其实就是一组非常丰富的用于解决分布式网络应用(尤其是IM类应用)的设计文档, 里面有大量的具体问题的解决方案, 不管是用JSON/protobuf/MQTT/PDU等, 都可以从中借鉴设计思路.

另外, XMPP是定义完整的IM协议, 因此如果你需要允许第三方Client登录, 或者需要和其他IM Server互联互通, 一般都是提供XMPP gateway. 据说Facebook就是这么做的, 核心IM并不基于XMPP, 但提供了XMPP gateway.

最后, 有兴趣的同学也可以看看Peter Saint-Andre(XMPP协议主要作者, XSF执行董事, jabber.org owner)2009年时针对XMPP问题的回答: XMPP Is Not Bloated

综上,随着移动互联网和各种新技术的发展,现在从头构建一个IM应用,不建议使用XMPP,XMPP比较适合作为异构IM系统之间互联互通时的协议。

sueLan commented 5 years ago

问题1: XMPP的扩展协议支持WebSocket后登录步骤就减少了。 https://tools.ietf.org/html/rfc7395

defp commented 3 years ago

💯

mengfei86 commented 3 years ago

💯

RaymanMartin commented 3 years ago

mark~

FangJZSP commented 4 months ago

mark