Jack Jiang

我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
posts - 226, comments - 13, trackbacks - 0, articles - 0

2018年9月21日

     摘要: 本文作者芋艿,原题“使用 Netty 实现 IM 聊天贼简单”,本底价有修订和改动。一、本文引言上篇《跟着源码学IM(七):手把手教你用WebSocket打造Web端IM聊天》中,我们使用 WebSocket 实现了一个简单的 IM 功能,支持身份认证、私聊消息、群聊消息。然后就有人发私信,希望使用纯 Netty 实现一个类似的功能,因此就有了本文。注:源码请从同步链接附件...  阅读全文

posted @ 2021-04-12 15:43 Jack Jiang 阅读(60) | 评论 (0)编辑 收藏

     摘要: 本文作者芋艿,原题“芋道 Spring Boot WebSocket 入门”,本次有修订和改动。一、引言WebSocket如今在Web端即时通讯技术应用里使用广泛,不仅用于传统PC端的网页里,也被很多移动端开发者用于基于HTML5的混合APP里。对于想要在基于Web的应用里添加IM、推送等实时通信功能,WebSocket几乎是必须要掌握的技术。本文将基于Tomcat和Spr...  阅读全文

posted @ 2021-04-06 22:05 Jack Jiang 阅读(75) | 评论 (0)编辑 收藏

     摘要: 本文原作者Chank,原题“如何设计一个亿级消息量的 IM 系统”,为了提升内容质量,本次有修订和改动。1、写有前面本文将在亿级消息量、分布式IM系统这个技术前提下,分析和总结实现这套系统所需要掌握的知识点,内容没有高深的技术概念,尽量做到新手老手皆能读懂。本文不会给出一套通用的IM方案,也不会评判某种架构的好坏,而是讨论设计IM系统的常见难题跟业界的解决方案。因为也没有所...  阅读全文

posted @ 2021-03-29 22:36 Jack Jiang 阅读(93) | 评论 (0)编辑 收藏

本文内容和编写思路是基于邓昀泽的“大规模并发IM服务架构设计”、“IM的弱网场景优化”两文的提纲进行的,感谢邓昀泽的无私分享。

1、引言

接上篇《一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等》,本文主要聚焦这套亿级用户的IM架构的一些比较细节但很重要的热门问题上,比如:消息可靠性、消息有序性、数据安全性、移动端弱网问题等。

以上这些热门IM问题每个话题其实都可以单独成文,但限于文章篇幅,本文不会逐个问题详细深入地探讨,主要以抛砖引玉的方式引导阅读者理解问题的关键,并针对问题提供专项研究文章链接,方便有选择性的深入学习。希望本文能给你的IM开发带来一些益处。

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注。公众号上的链接是:点此进入

2、系列文章

为了更好以进行内容呈现,本文拆分两了上下两篇。

本文是2篇文章中的第2篇:

一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等

一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等》(本文)

本篇主要聚焦这套亿级用户的IM架构的一些比较细节但很重要的热门问题上。

3、消息可靠性问题

消息的可靠性是IM系统的典型技术指标,对于用户来说,消息能不能被可靠送达(不丢消息),是使用这套IM的信任前提。

换句话说,如果这套IM系统不能保证不丢消息,那相当于发送的每一条消息都有被丢失的概率,对于用户而言,一定会不会“放心”地使用它,即“不信任”这套IM。

从产品经理的角度来说,有这样的技术障碍存在,再怎么费力的推广,最终用户都会很快流失。所以一套IM如果不能保证消息的可靠性,那问题是很严重的。

PS:如果你对IM消息可靠性的问题还没有一个直观的映象的话,通过《零基础IM开发入门(三):什么是IM系统的可靠性?》这篇文章可以通俗易懂的理解它。

如上图所示,消息可靠性主要依赖2个逻辑来保障:

  • 1)上行消息可靠性;
  • 2)下行消息可靠性。

1)针对上行消息的可靠性,可以这样的思路来处理:

用户发送一个消息(假设协议叫PIMSendReq),用户要给这个消息设定一个本地ID,然后等待服务器操作完成给发送者一个PIMSendAck(本地ID一致),告诉用户发送成功了。

如果等待一段时间,没收到这个ACK,说明用户发送不成功,客户端SDK要做重试操作。

2)针对下行消息的可靠性,可以这样的思路来处理:

服务收到了用户A的消息,要把这个消息推送给B、C、D 3个人。假设B临时掉线了,那么在线推送很可能会失败。

因此确保下行可靠性的核心是:在做推送前要把这个推送请求缓存起来。

这个缓存由存储系统来保证,MsgWriter要维护一个(离线消息列表),用户的一条消息,要同时写入B、C、D的离线消息列表,B、C、D收到这个消息以后,要给存储系统一个ACK,然后存储系统把消息ID从离线消息列表里拿掉。

针对消息的可靠性问题,具体的解决思路还可以从另一个维度来考虑:即实时消息的可靠性和离线消息的可靠性。

有兴趣可以深入读一读这两篇:

IM消息送达保证机制实现(一):保证在线实时消息的可靠投递

IM消息送达保证机制实现(二):保证离线消息的可靠投递

而对于离线消息的可靠性来说,单聊和群聊又有很大区别,有关群聊的离线消息可靠投递问题,可以深入读一读《IM开发干货分享:如何优雅的实现大量离线消息的可靠投递》。

4、消息有序性问题

消息的有序性问题是分布式IM系统中的另一个技术“硬骨头”。

因为是分布式系统,客户端和服务器的时钟可能是不同步的。如果简单依赖某一方的时钟,就会出现大量的消息乱序。

比如只依赖客户端的时钟,A比B时间晚30分钟。所有A给B发消息,然后B给A回复。

发送顺序是:

客户端A:“XXX”

客户端B:“YYY”

接收方的排序就会变成:

客户端B:“YYY”

客户端A:“XXX”

因为A的时间晚30分钟,所有A的消息都会排在后面。

如果只依赖服务器的时钟,也会出现类似的问题,因为2个服务器时间可能也不一致。虽然客户端A和客户端B时钟一致,但是A的消息由服务器S1处理,B的消息由服务器S2处理,也会导致同样消息乱序。

为了解决这种问题,我的思路是通过可以做这样一系列的操作来实现。

1)服务器时间对齐:

这部分就是后端运维的锅了,由系统管理员来尽量保障,没有别的招儿。

2)客户端通过时间调校对齐服务器时间:

比如:客户端登录以后,拿客户端时间和服务器时间做差值计算,发送消息的时候考虑这部分差值。

在我的im架构里,这个能把时间对齐到100ms这个级,差值再小的话就很困难了,因为协议在客户端和服务器之间传递速度RTT也是不稳定的(网络传输存在不可控的延迟风险嘛)。

3)消息同时带上本地时间和服务器时间:

具体可以这样的处理:排序的时候,对于同一个人的消息,按照消息本地时间来排;对于不同人的消息,按照服务器时间来排,这是插值排序算法。

PS:关于消息有序性的问题,显然也不是上面这三两句话能讲的清楚,如果你想更通俗一理解它,可以读一读《零基础IM开发入门(四):什么是IM系统的消息时序一致性?》。

另外:从技术实践可行性的角度来说,《一个低成本确保IM消息时序的方法探讨》、《如何保证IM实时消息的“时序性”与“一致性”?》这两篇中的思路可以借鉴一下。

实际上,消息的排序问题,还可以从消息ID的角度去处理(也就是通过算法让消息ID产生顺序性,从而根据消息ID就能达到消息排序的目的)。

有关顺序的消息ID算法问题,这两篇非常值得借鉴:IM消息ID技术专题(一):微信的海量IM聊天消息序列号生成实践(算法原理篇)》、《IM消息ID技术专题(三):解密融云IM产品的聊天消息ID生成策略》,我就不废话了。

5、消息已读同步问题

消息的已读未读功能,如下图所示: 

上图就是钉钉中的已读未读消息。这在企业IM场景下非常有用(因为做领导的都喜欢,你懂的)。

已读未读功能,对于一对一的单聊消息来说,还比较好理解:就是多加一条对应的回执息(当用户阅读这条消息时发回)。

但对于群聊这说,这一条消息有多少人已读、多少人未读,想实现这个效果,那就真的有点麻烦了。对于群聊的已读未读功能实现逻辑,这里就不展开了,有兴趣可以读一下这篇《IM群聊消息的已读回执功能该怎么实现?》。

回归到本节的主题“已读同步”的问题,这显示难度又进一级,因为已读未读回执不只是针对“账号”,现在还要细分到“同一账号在不同端登陆”的情况,对于已读回执的同步逻辑来说,这就有点复杂化了。

在这里,根据我这边IM架构的实践经验,提供一些思路。

具体来说就是:用户可能有多个设备登录同一个账户(比如:Web PC和移动端同时登陆),这种情况下的已读未读功能,就需要来实现已读同步,否则在设备1看过的消息,设备2看到依然是未读消息,从产品的角度来说,这就影响用户体验了。

对于我的im架构来说,已读同步主要依赖2个逻辑来保证:

  • 1)同步状态维护,为用户的每一个Session,维护一个时间戳,保存最后的读消息时间;
  • 2)如果用户打开了某个Session,且用户有多个设备在线,发送一条PIMSyncRead消息,通知其它设备。

6、数据安全问题

6.1 基础

IM系统架构中的数据安全比一般系统要复杂一些,从通信的角度来说,它涉及到socket长连接通信的安全性和http短连接的两重安全性。而随着IM在移动端的流行,又要在安全性、性能、数据流量、用户体验这几个维度上做权衡,所以想要实现一套完善的IM安全架构,要面临的挑战是很多的。

IM系统架构中,所谓的数据安全,主要是通信安全和内容安全。

6.2 通信安全

所谓的通信安全,这就要理解IM通信的服务组成。

目前来说,一个典型的im系统,主要由两种通信服务组成:

  • 1)socket长连接服务:技术上也就是多数人耳熟能详的网络通信这一块,再细化一点也就是tcp、udp协议这一块;
  • 2)http短连接服务:也就是最常用的http rest接口那些。

对于提升长连接的安全性思路,可以深入阅读《通俗易懂:一篇掌握即时通讯的消息传输安全原理》。另外,微信团队分享的《微信新一代通信安全解决方案:基于TLS1.3的MMTLS详解》一文,也非常有参考意义。

如果是通信安全级别更高的场景,可以参考《即时通讯安全篇(二):探讨组合加密算法在IM中的应用》,文中关于组合加密算法的使用思路非常不错。

至于短连接安全性,大家就很熟悉了,开启https多数情况下就够用了。如果对于https不甚了解,可以从这几篇开始:《一文读懂Https的安全性原理、数字证书、单项认证、双项认证等》、《即时通讯安全篇(七):如果这样来理解HTTPS,一篇就够了》。

6.3 内容安全

这个可能不太好理解,上面既然实现了通信安全,那为什么还要纠结“内容安全”?

我们了解一下所谓的密码学三大作用:加密( Encryption)、认证(Authentication),鉴定(Identification) 。

详细来说就是:

加密:防止坏人获取你的数据。

认证:防止坏人修改了你的数据而你却并没有发现。

鉴权:防止坏人假冒你的身份。

在上节中,恶意攻击者如果在通信环节绕开或突破了“鉴权”、“认证”,那么依赖于“鉴权”、“认证”的“加密”,实际上也有可有被破解。

针对上述问题,那么我们需要对内容进行更加安全独立的加密处理,就这是所谓的“端到端加密”(E2E)。

比如,那个号称无法被破解的IM——Telegram,实际上就是使用了端到端加密技术。

关于端到端加密,这里就不深入探讨,这里有两篇文章有兴趣地可以深入阅读:

移动端安全通信的利器——端到端加密(E2EE)技术详解

简述实时音视频聊天中端到端加密(E2EE)的工作原理

7、雪崩效应问题

在分布式的IM架构中,存在雪崩效应问题。

我们知道,分布式的IM架构中,为了高可用性,用户每次登陆都是根据负载均衡算法分配到不同的服务器。那么问题就来了。

举个例子:假设有5个机房,其中A机房故障,导致这个机房先前服务的用户都跑去B机房。B机房不堪重负也崩溃了,A+B的用户跑去机房C,连锁反应会导致所有服务挂掉。

防止雪崩效应需要在服务器架构,客户端链接策略上有一些配合的解决方案。服务器需要有限流能力作为基础,主要是限制总服务用户数和短时间链接用户数。

在客户端层面,发现服务断开之后要有一个策略,防止大量用户同一时间去链接某个服务器。

通常有2种方案:

  • 1)退避:重连之间设置一个随机的间隔;
  • 2)LBS:跟服务器申请重连的新的服务器IP,然后由LBS服务去降低短时间分配到同一个服务器的用户量。

这2种方案互不冲突,可以同时做。

8、弱网问题

8.1 弱网问题的原因

鉴于如今IM在移动端的流行,弱网是很常态的问题。电梯、火车上、开车、地铁等等场景,都会遇到明显的弱网问题。

那么为什么会出现弱网问题?

要回答这个问题,那就需要从无线通信的原理上去寻找答案。

因为无线通信的质量受制于很多方面的因素,比如:无线信号强弱变化快、信号干扰、通信基站分布不均、移动速度太快等等。要说清楚这个问题,那就真是三天三夜都讲不完。

有兴趣的读者,一定要仔细阅读下面这几篇文章,类似的跨学科科谱式文章并不多见:

IM开发者的零基础通信技术入门(十一):为什么WiFi信号差?一文即懂!

IM开发者的零基础通信技术入门(十二):上网卡顿?网络掉线?一文即懂!

IM开发者的零基础通信技术入门(十三):为什么手机信号差?一文即懂!

IM开发者的零基础通信技术入门(十四):高铁上无线上网有多难?一文即懂!

弱网问题是移动端APP的必修课,下面这几篇总结也值得借鉴:

移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”

移动端IM开发者必读(二):史上最全移动弱网络优化方法总结

现代移动端网络短连接的优化手段总结:请求速度、弱网适应、安全保障

百度APP移动端网络深度优化实践分享(三):移动端弱网优化篇

8.2 IM针对弱网问题的处理

对于IM来说,弱网问题并不是很复杂,核心是做好消息的重发、排序以及接收端的重试。

为了解决好弱网引发的IM问题,通常可以通过以下手段改善:

  • 1)消息自动重发;
  • 2)离线消息接收;
  • 3)重发消息排序;
  • 4)离线指令处理。

下面将逐一展开讨论。

8.3 消息自动重发

坐地铁的时候,经常遇到列车开起来以后,网络断开,发送消息失败。

这时候产品有2种表现形式:

  • a、直接告诉用户发送失败;
  • b、保持发送状态,自动重试3-5次(3分钟)以后告诉用户发送失败。

显然:自动重试失败以后再告诉用户发送失败体验要好很多。尤其是在网络闪断情况下,重试成功率很高,很可能用户根本感知不到有发送失败。

从技术上:客户端IMSDK要把每条消息的状态监控起来。发送消息不能简单的调用一下网络发送请求,而是要有一个状态机,管理几个状态:初始状态,发送中,发送失败,发送超时。对于失败和超时的状态,要启用重试机制。

这里还有一篇关于重试机制设计的讨论帖子,有兴趣可以看看:完全自已开发的IM该如何设计“失败重试”机制?》。

IM消息送达保证机制实现(一):保证在线实时消息的可靠投递》一文中关于消息超时与重传机制的实现思路,也可以参考一下。

8.4 离线消息接收

现代IM是没有“在线”这个状态的,不需要给用户这个信息。但是从技术的层面,用户掉线了还是要正确的去感知的。

感知方法有几条:

  • a、信令长连接状态:如果长时间没收到到服务器的心跳反馈,说明掉线了;
  • b、网络请求失败次数:如果多次网络请求失败,说明”可能“掉线了;
  • c、设备网络状态检测:直接检测网卡状态就好,一般Android/iOS/Windows/Mac都有相应系统API。

正确检测到网络状态以后,发现网络从”断开到恢复“的切换,要去主动拉取离线阶段的消息,就可以做到弱网状态不丢消息(从服务器的离线消息列表拉取)。

上面文字中提到的网络状态的确定,涉及到IM里网络连接检查和保活机制问题,是IM里比较头疼的问题。

一不小心,又踩进了IM网络保活这个坑,我就不在这里展开,有兴趣一定要读读下面的文章:

为何基于TCP协议的移动端IM仍然需要心跳保活机制?

一文读懂即时通讯应用中的网络心跳包机制:作用、原理、实现思路等

微信团队原创分享:Android版微信后台保活实战分享(网络保活篇)

移动端IM实践:实现Android版微信的智能心跳机制

移动端IM实践:WhatsApp、Line、微信的心跳策略分析

8.5 重发消息排序

弱网逻辑的另一个坑是消息排序。

假如有A、B、C  3条消息,A、C发送成功,B发送的时候遇到了网络闪断,B触发自动重试。

那么接收方的接收顺序应该是 A B C还是A C B呢?我观察过不同的IM产品,处理逻辑各不相同,这个大家有兴趣可以去玩一下。

这个解决方法是要依赖上一篇服务架构里提到的差值排序,同一个人发出的消息,排序按消息附带的本地时间来排。不同人的消息,按照服务器时间排序。

具体我这边就不再得复,可以回头看看本篇中的第四节“4、消息有序性问题”。

8.6 离线指令处理

部分指令操作的时候,网络可能出现了问题,等网络恢复以后,要自动同步给服务器。

举一个例子,大家可以试试手机设置为飞行模式,然后在微信里删除一个联系人,看看能不能删除。然后重新打开网络,看看这个数据会不会同步到服务器。

类似的逻辑也适用于已读同步等场景,离线状态看过的信息,要正确的跟服务器同步。

8.7 小结一下

IM的弱网处理,其实相对还是比较简单的,基本上自动重试+消息状态就可以解决绝大部分的问题了。

一些细节处理也并不复杂,主要原因是IM的消息量比较小,网络恢复后能快速的恢复操作。

视频会议在弱网下的逻辑,就要复杂的多了。尤其是高丢包的弱网环境下,要尽力去保证音视频的流畅性。

9、本文小结

《一套亿级用户的IM架构技术干货》这期文章的上下两篇就这么侃完了,上篇涉及到的IM架构问题倒还好,下篇一不小心又带出了IM里的各种热门问题“坑”,搞IM开发直是一言难尽。。。

建议IM开发的入门朋友们,如果想要系统地学习移动端IM开发的话,应该去读一读我整理的那篇IM开发“从入门到放弃”的文章(哈哈哈),就是这篇《新手入门一篇就够:从零开发移动端IM》。具体我就不再展开了,不然这篇幅又要刹不住车了。。。

10、参考资料

[1] 大规模并发IM服务架构设计

[2] IM的弱网场景优化

[3] 零基础IM开发入门(三):什么是IM系统的可靠性?

[4] IM消息送达保证机制实现(一):保证在线实时消息的可靠投递

[5] IM开发干货分享:如何优雅的实现大量离线消息的可靠投递

[6] 即时通讯安全篇(二):探讨组合加密算法在IM中的应用

[7] 微信新一代通信安全解决方案:基于TLS1.3的MMTLS详解

附录:更多IM开发文章汇总

零基础IM开发入门(一):什么是IM系统?

零基础IM开发入门(二):什么是IM系统的实时性?

IM开发干货分享:如何优雅的实现大量离线消息的可靠投递

IM开发干货分享:有赞移动端IM的组件化SDK架构设计实践

IM开发宝典:史上最全,微信各种功能参数和逻辑规则资料汇总

IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的

从客户端的角度来谈谈移动端IM的消息可靠性和送达机制

腾讯技术分享:社交网络图片的带宽压缩技术演进之路

移动端IM中大规模群消息的推送如何保证效率、实时性?

移动端IM开发需要面对的技术问题

开发IM是自己设计协议用字节流好还是字符流好?

请问有人知道语音留言聊天的主流实现方式吗?

IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?

IM群聊消息如此复杂,如何保证不丢不重?

谈谈移动端 IM 开发中登录请求的优化

移动端IM登录时拉取数据如何作到省流量?

通俗易懂:基于集群的移动端IM接入层负载均衡方案分享

微信对网络影响的技术试验及分析(论文全文)

本文已同步发布于“即时通讯技术圈”公众号。

▲ 本文在公众号上的链接是:点此进入。同步发布链接是:http://www.52im.net/thread-3445-1-1.html

posted @ 2021-03-22 16:10 Jack Jiang 阅读(80) | 评论 (0)编辑 收藏

1、引言

经历过稍有些规模的IM系统开发的同行们都有体会,要想实现大规模并发IM(比如亿级用户和数十亿日消息量这样的规模),在架构设计上需要一些额外的考虑,尤其是要解决用户高并发、服务高可用,架构和实现细节上都需要不短时间的打磨。

我在过往的工作经历里,亲手设计和实现了一套亿级用户量的IM,平台上线并经过6年多的验证,稳定性和可用性被验证完全达到预期。

这套IM系统,从上线至今已6年有余,本人也已经离职创业近2年,但当初设计和开发这套系统时积累和收获了大量的第一手实践经验和技术心得。

因此,想借本文把当时的架构设计经历记录下来,作为同行交流和参考,希望能提供一些启发,少走弯路。

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注。公众号上的链接是:点此进入

2、系列文章

为了更好以进行内容呈现,本文拆分两了上下两篇。

本文是2篇文章中的第1篇:

一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等》(本文)

《一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等(稍后发布...)》

本篇主要总结和分享这套IM架构的总体设计和服务拆分等。

3、原作者

本文基于邓昀泽的“大规模并发IM服务架构设计”一文进行的扩展和修订,感谢原作者的分享。

邓昀泽:毕业于北京航空航天大学,现蓝猫微会创始人兼CEO,曾就职于美团、YY语音、微软和金山软件等公司,有十多年研发管理经验。

4、技术指标

在这套IM系统的架构上,技术上我们坚持高要求,经过数年的验证,也确实达到了设计预期。

这4大技术指标是:

具体解释就是:

  • 1)高可靠:确保不丢消息;
  • 2)高可用:任意机房或者服务器挂掉,不影响服务;
  • 3)实时性:不管用户在哪里,在线用户消息在1秒内达到(我们实际是75%消息可以做到120ms);
  • 4)有序性:确保用户消息的有序性,不会出现发送和接受的乱序。

5、架构拆分

从整体架构上来说,亿级用户量的IM架构整体上偏复杂。

传统开源的IM服务喜欢把所有服务做到1-2个服务里(Connector+Service模型),这样带来的问题比较严重。

传统开源的IM的问题主要体现在:

  • 1)服务代码复杂,难以持续开发和运维;
  • 2)单一业务逻辑出问题,可能会影响到其它逻辑,导致服务的全面不可用。

因此,我在做架构设计的时候尽量追求微服务化。即把整体架构进行分拆为子系统,然后子系统内按照业务逻辑分拆为微服务。

系统拆分如下图:

4个子系统的职责是:

  • 1)IM业务系统:服务IM相关的业务逻辑(比如好友关系、群关系、用户信息等);
  • 2)信令系统:负责用户登录,用户在线状态的维护,以及在线用户的下行推送;
  • 3)推送系统:负责消息的在线推送和离线推送;
  • 4)存储系统:负责消息和文件的存储和查询;

其中:信令系统和推送系统是基础设施,不只是可以为IM业务服务,也可以承载其它类似的业务逻辑(比如客服系统)。

在部署层面:采用存储3核心机房,信令和推送节点按需部署的方式(国内业务推荐8-10个点)。实际上我们只做了了北京3个机房,上海1个机房和香港一个机房的部署,就基本上满足了大陆+香港的业务需求。

下面将逐个介绍这4个子系统的细节方面。

6、IM业务系统

一说到IM,很多人脑海里跳出的第一个关键就是“即时通信”,技术上理所当然的联想到了socket,也就是大家成天嘴上说的:“长连接”。换句话说,很多对IM不了解或了解的不多的人,认为IM里的所有数据交互、业务往来都是通过“长连接”来实现的,这样话,对于本文章中拆分出的“IM业务系统”就有点不理解了。

实际上,早期的IM(比如20年前的QQ、MSN、ICQ),确实所有数据基本都是通过“长连接”(也就是程序员所说的“socket”)实现。

但如今,移动端为主端的IM时代,IM系统再也不是一个条“长连接”走天下。

现在,一个典型的IM系统数据往来通常拆分成两种服务:

  • 1)socket长连接服务(也就是本文中的“推送服务”);
  • 2)http短连接服务(就是最常用的http rest接口那些,也就是本文中的“IM业务系统”)。

通俗一点,也也就现在的IM系统,通常都是长、短连接配合一起实现的。 

比如论坛里很多热门技术方案都是这样来做的,比如最典型的这两篇:《IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?》、《IM消息送达保证机制实现(二):保证离线消息的可靠投递》,文记里提到的“推”其实就是走的“长连接”、“拉”就上指的http短连接。

对于socket长连接服务就没什么好说,就是大家最常理解的那样。

IM业务系统详细来说,就是专注处理IM相关的业务逻辑,比如:

  • 1)维护用户数据:用户基本信息等;
  • 2)维护好友关系:好友请求、好友列表、好友信息等;
  • 3)维护群组信息:群创建、解散、成员管理等;
  • 4)提供数据:离线拉取、历史记录同步;
  • 5)其它逻辑:比如通过存储和推送系统,存储消息和发送通知;

按照微服务的原则,IM业务系统也被分拆为多个服务,比如:

  • 1)GInfo服务:群组信息维护;
  • 2)IM服务:处理1V1消息;
  • 3)GIM服务:处理群组消息。

7、信令系统

7.1 基本情况

信令系统主要职责是3部分: 

 

1)维护用户在线状态:

因为用户规模庞大,必然是多个集群,每个集群多台服务器为用户提供服务。

考虑到服务器运维的复杂性,我们要假定任何一个集群,任何一个服务器都可能会挂掉,而且在这种情况下要能够继续为用户提供服务。

在这种情况下,如果用户A给用户B发消息,我们需要知道用户B在哪个服务器上,才能把消息正确推送给用户B。用户在哪个信令服务,这个信息就是在线状态数据。

2)下行消息推送:

跟上一个职责有关,用户在线的时候,如果有其它用户给他发消息,那就最好不要走离线推送,而是走在线推送。

在线推送的最后一个环节,是把用户消息推送给用户设备,因为就需要知道用户登录到哪个服务器上。

3)业务分发:

信令服务不只可以处理IM请求,也可以处理其它类型的业务请求。为了处理不同的业务,就需要有分发能力。

具体做法是通过一个SVID(service id)来实现,不同的业务携带不同的SVID,信令服务就知道如何分发了。

用户通过登录服务把数据(比如IM消息)发送到信令系统,信令系统根据SVID转发给IM系统。不管后台有多少个业务,用户只需要一条链接到信令。

7.2 服务拆分

信令系统为了实现以上这3个职责,同时要确保我们服务可平行扩展的能力和稳定性,在实际的技术实现上,我们实际上把信令服务分拆为3个服务模块。

如下图所示: 

下面将逐个介绍这3个子服务。

7.3 Login服务

Login服务主要负责维护用户长链接:

  • 1)每个用户一条链接到Login服务,并按时间发心跳包给Login服务;
  • 2)服务定时检查用户链接状态和心跳包,比如发现2个心跳周期都没收到心跳,就认为用户掉线了(有假在线问题,有兴趣同学可回贴讨论)。

Login服务收到用户登录请求以后,验证uid/cookie,如果成功就把这个用户的登录信息发送给online。

此过程主要记录的信息包含:

  • 1)uid(用户id);
  • 2)Login服务器IP/Port;
  • 3)Route服务器的IP/Port。

如果用户发送IM消息,先发送到Login,Login转发给Route,Route根据服务的类型(SVID),发现是IM协议就发送给后端的IM服务。

Login对并发要求比较高,一般要支持TCP+UDP+Websocket几种方式,单服务可以做到10-250万之间。从服务稳定性角度触发,建议是控制VM的CPU/内存,单服务器以20-50万为合适。

Login服务器本身没有状态,任何一个Login服务断掉,用户端检测到以后重连另一个Login服务器就可以了,对整体服务可靠性基本没有影响。

7.4 Online服务

Online服务主要负责维护用户的在线信息:

  • 1)如果用户掉线,Online服务里信息就是空;
  • 2)如果用户在线,Online就能找到用户登录在哪个集群,哪个Login服务器上。

Online业务相对简单:多个Login服务器会连接到Online,定期同步用户登录和离线信息。

Online主要职责是:把用户状态信息存储在Redis集群里。因此也是无状态的,任何一个Online服务挂掉,不影响整体服务能力。

如果集群规模不大,用户规模也不大,Online服务也可以收到Login服务里去。

如果规模比较大,建议分拆出来,一方面简化Login的逻辑复杂度,同时避免写Redis的慢操作放在Login服务里。因为Login要同时处理50万以上的并发链接,不适合在循环里嵌入慢操作。

7.5 Route服务

Route服务的设计核心,是作为信令系统跟其它子系统的交互层。Route下接Login服务,可以接受用户业务信息(IM),也可以往用户推送下行消息。

多个后端业务系统可以接入到Route,按照服务类型(SVID, service id)注册。比如IM服务可以接入到Route, 注册SVID_IM。这样Login接收到SVID=SVID_IM的消息,转发给Route,Route就可以根据SVID转发给IM相关的服务。

Route简单的根据SVID做转发,不处理具体的业务逻辑,因此也是无状态的。一个信令集群可以有多个Route服务,任何服务挂了不影响整体服务能力。

8、推送系统

推送系统的核心任务:是接收到给用户发送下行消息的请求以后,去信令服务查询用户是否在线,如果在线走信令推送,如果不在线走离线推送(如iOS的APNS、华为推送、小米推送等)。

因为推送服务可能出现大规模并发蜂拥,比如大群激烈讨论的时候,会触发亿级的TPS。因此推送服务用Kafka做了削峰。

我在实际的技术实现上,将推送系统进行了如下细分: 

具体就是:

  • 1)PushProxy:接受用户的推送请求,写入Kafka;
  • 2)Kafka:缓存推送服务;
  • 3)PushServer:从Kafka获取推送请求,判断用户是否在线;
  • 4)PushWorker:真正推送给信令或者APNS,华为推送等。

这里同样,除了Kafka以外每个服务都是无状态的,因为也可以实现平行扩展和容错,任何服务挂掉不影响整体服务可用性。

9、存储系统

存储服务主要是负责消息的存储和查询,因为消息量巨大,对存储服务的并发能力和存储量要求巨大。

为了平衡性能、空间和成本,存储服务按数据的热度进行了分级和区别对待。

具体是:

  • 1)短期消息(7天):存储在Redis里;
  • 2)近期消息(1-3个月):存储在Mysql里,以备用户实时查询;
  • 3)历史信息:存储在HBase里,作为历史数据慢查询。

同时,为了应对超大群的大量消息处理,存储服务在实际的技术实现上,也做了比较细的分拆。

存储服务具体拆分如下图:

具体的业务划分就是:

  • 1)MsgProxy:负责接受IM子系统的存储请求,写入Kafka;
  • 2)MsgWriter:从Kafka获取写请求,按需写入Redis和Mysql;
  • 3)MsgReader:接受用户的消息查询请求,从Redis,Mysql或者HBase读数据;
  • 4)运维工具:主要是数据库的运维需求。

消息队列(Kafka)在这里角色比较重要,因为对于高并发请求(100万人公众号),需要通过消息队列来做削峰和并行。

在具体部署上:可能是3-4个MsgProxy,后端可以对应15个左右的MsgWriter。MsgWriter是比较慢的,需要同时操作多个数据库,还要保证操作的原子性。

10、本篇小结

本篇主要总结了这套亿级用户量IM系统的总体架构设计,为了高性能和横向扩展性,基于微信的理念将整个架构在实现上分成了4个子系统,分别是:IM业务系统、信令系统、推送系统、存储系统。

针对这4个子系统,在实际的技术应用层上,又进行了进一步的服务拆分和细化,使得整个架构伸缩性大大增强。

—— 下篇《一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等》稍后发布,敬请期待 ——

附录:相关文章

浅谈IM系统的架构设计

简述移动端IM开发的那些坑:架构设计、通信协议和客户端

一套海量在线用户的移动端IM架构设计实践分享(含详细图文)》(* 力荐

一套原创分布式即时通讯(IM)系统理论架构方案》(* 力荐

从零到卓越:京东客服即时通讯系统的技术架构演进历程》(* 力荐

蘑菇街即时通讯/IM服务器开发之架构选择

腾讯QQ1.4亿在线用户的技术挑战和架构演进之路PPT

微信后台基于时间序的海量数据冷热分级架构设计实践

移动端IM中大规模群消息的推送如何保证效率、实时性?

现代IM系统中聊天消息的同步和存储方案探讨》(* 力荐

以微博类应用场景为例,总结海量社交系统的架构设计步骤

一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践》(* 力荐

社交软件红包技术解密(一):全面解密QQ红包技术方案——架构、技术实现等

社交软件红包技术解密(二):解密微信摇一摇红包从0到1的技术演进

社交软件红包技术解密(三):微信摇一摇红包雨背后的技术细节

社交软件红包技术解密(四):微信红包系统是如何应对高并发的

社交软件红包技术解密(五):微信红包系统是如何实现高可用性的

社交软件红包技术解密(六):微信红包系统的存储层架构演进实践

社交软件红包技术解密(七):支付宝红包的海量高并发技术实践

社交软件红包技术解密(八):全面解密微博红包技术方案

从游击队到正规军(一):马蜂窝旅游网的IM系统架构演进之路》(* 力荐

从游击队到正规军(二):马蜂窝旅游网的IM客户端架构演进和实践总结

从游击队到正规军(三):基于Go的马蜂窝旅游网分布式IM系统技术实践

瓜子IM智能客服系统的数据架构设计(整理自现场演讲,有配套PPT)

阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处

微信后台基于时间序的新一代海量数据存储架构的设计实践

一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等

>> 更多同类文章 ……

 

本文已同步发布于“即时通讯技术圈”公众号。

▲ 本文在公众号上的链接是:点此进入。同步发布链接是:http://www.52im.net/thread-3393-1-1.html

posted @ 2021-03-15 23:07 Jack Jiang 阅读(188) | 评论 (0)编辑 收藏

     摘要: 本文由微信开发团队工程师“ kellyliang”原创发表于“微信后台团队”公众号,收录时有修订和改动。1、引言随着直播和类直播场景在微信内的增长,这些业务对临时消息(在线状态时的实时消息)通道的需求日益增长,直播聊天室组件应运而生。直播聊天室组件是一个基于房间的临时消息信道,主要提供消息收发、在线状态统计等功能。本文将回顾微信直播聊天室单房间海量用...  阅读全文

posted @ 2021-03-06 17:08 Jack Jiang 阅读(164) | 评论 (0)编辑 收藏

     摘要: 本文引用了“一文读懂什么是进程、线程、协程”一文的主要内容,感谢原作者的无私分享。1、系列文章引言1.1 文章目的作为即时通讯技术的开发者来说,高性能、高并发相关的技术概念早就了然与胸,什么线程池、零拷贝、多路复用、事件驱动、epoll等等名词信手拈来,又或许你对具有这些技术特征的技术框架比如:Java的Netty、Php的workman、Go的gnet等熟练掌握。但真正到...  阅读全文

posted @ 2021-03-03 13:02 Jack Jiang 阅读(132) | 评论 (0)编辑 收藏

本文原题“你管这破玩意儿叫TCP?”,由闪客sun分享,转载请联系作者。

1、引言

网络编程能力对于即时通讯技术开发者来说是基本功,而计算机网络又是网络编程的理论根基,因而深刻准确地理解计算机网络知识显然能夯实你的即时通讯应用的实践品质。

本文风格类似于《网络编程懒人入门》、《脑残式网络编程入门》两个系列,但通俗又不失内涵,简洁又不简陋,非常适合对计算机网络知识有向往但又有惧怕的网络编程爱好者们阅读,希望能给你带来不一样的网络知识入门视角。

本篇将运用通俗易懂的语言,配上细致精确的图片动画,循序渐进地引导你理解TCP协议的主要特性和技术原理,让TCP协议的学习不再如此枯燥和生涩,非常适合入门者阅读。

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注。公众号上的链接是:点此进入

2、系列文章

本文是该系列文章中的第2篇:

本文主要涉及计算机网络的传输层,希望让TCP协议的学习不再枯燥和生涩。

3、初识传输层

你是一台电脑,你的名字叫 A。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_1-1.png
经过上篇《假如你来设计网络,会怎么做?》的一番折腾,只要你知道另一位伙伴 B 的 IP 地址,且你们之间的网络是通的,无论多远,你都可以将一个数据包发送给你的伙伴 B。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_1-2.png

上篇中分享的这就是物理层、数据链路层、网络层这三层所做的事情。

站在第四层的你,就可以不要脸地利用下三层所做的铺垫,随心所欲地发送数据,而不必担心找不到对方了。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_1-3.gif

虽然你此时还什么都没干,但你还是给自己这一层起了个响亮的名字,叫做传输层。

你本以为自己所在的第四层万事大吉,啥事没有,但很快问题就接踵而至。

4、问题来了

前三层协议只能把数据包从一个主机搬到另外一台主机,但是到了目的地以后,数据包具体交给哪个程序(进程)呢?

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_2-1.png

所以:你需要把通信的进程区分开来,于是就给每个进程分配一个数字编号,你给它起了一个响亮的名字:端口号。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_2-2.png

然后:你在要发送的数据包上,增加了传输层的头部:源端口号与目标端口号。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_2-3.png

OK,这样你将原本主机到主机的通信,升级为了进程和进程之间的通信。

你没有意识到,你不知不觉实现了UDP协议

当然 UDP 协议中不光有源端口和目标端口,还有数据包长度和校验值,我们暂且略过。

就这样,你用 UDP 协议无忧无虑地同 B 进行着通信,一直没发生什么问题。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_2-4.gif

但很快,你发现事情变得非常复杂 ... ...

5、丢包问题

由于网络的不可靠,数据包可能在半路丢失,而 A 和 B 却无法察觉。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_3-1.gif

对于丢包问题,只要解决两个事就好了。

第一个:A 怎么知道包丢了?
答案是:让 B 告诉 A。

第二个:丢了的包怎么办?
答案是:重传。

于是你设计了如下方案:A 每发一个包,都必须收到来自 B 的确认(ACK),再发下一个,否则在一定时间内没有收到确认,就重传这个包。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_3-2.gif

你管它叫停止等待协议。

只要按照这个协议来,虽然 A 无法保证 B 一定能收到包,但 A 能够确认 B 是否收到了包,收不到就重试,尽最大努力让这个通信过程变得可靠,于是你们现在的通信过程又有了一个新的特征,可靠交付。

6、效率问题

停止等待虽然能解决问题,但是效率太低了。

A 原本可以在发完第一个数据包之后立刻开始发第二个数据包,但由于停止等待协议,A 必须等数据包到达了 B ,且 B 的 ACK 包又回到了 A,才可以继续发第二个数据包。这效率慢得可不是一点两点。

于是:你对这个过程进行了改进,采用流水线的方式,不再傻傻地等。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_4-1.gif

7、顺序问题

但是网路是复杂的、不可靠的。

这导致的问题是:有的时候 A 发出去的数据包,分别走了不同的路由到达 B,可能无法保证和发送数据包时一样的顺序。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_5-1.gif

对应于我们的例子:在流水线中有多个数据包和ACK包在乱序流动,他们之间对应关系就乱掉了。

如果回到上面的停止等待协议,那么A 每收到一个包的确认(ACK)再发下一个包,那就根本不存在顺序问题。但,应该有更好的办法吧?

是的,更好的办法就是:A 在发送的数据包中增加一个序号(seq),同时 B 要在 ACK 包上增加一个确认号(ack)。这样不但解决了停止等待协议的效率问题,也通过这样标序号的方式解决了顺序问题。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_5-2.gif

而 B 这个确认号意味深长:比如 B 发了一个确认号为 ack = 3,它不仅仅表示 A 发送的序号为 2 的包收到了,还表示 2 之前的数据包都收到了。这种方式叫累计确认累计应答

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_5-3.gif

注意:实际上 ack 的号是收到的最后一个数据包的序号 seq + 1,也就是告诉对方下一个应该发的序号是多少。但图中为了便于理解,ack 就表示收到的那个序号,不必纠结。

8、流量问题

有的时候,A 发送数据包的速度太快,而 B 的接收能力不够,但 B 却没有告知 A 这个情况。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_6-1.gif

怎么解决呢?

很简单:B 告诉 A 自己的接收能力,A 根据 B 的接收能力,相应控制自己的发送速率就好了。

B 怎么告诉 A 呢?B 跟 A 说"我很强"这三个字么?那肯定不行,得有一个严谨的规范。

于是 B 决定:每次发送数据包给 A 时,顺带传过来一个值,叫窗口大小(win),这个值就表示 B 的接收能力

同理:每次 A 给 B 发包时也带上自己的窗口大小,表示 A 的接收能力。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_6-2.gif

B 告诉了 A 自己的窗口大小值,A 怎么利用它去做 A 这边发包的流量控制呢?

很简单:假如 B 给 A 传过来的窗口大小 win = 5,那 A 根据这个值,把自己要发送的数据分成这么几类。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_6-3.png

图片过于清晰,就不再文字解释了。

当 A 不断发送数据包时,已发送的最后一个序号就往右移动,直到碰到了窗口的上边界,此时 A 就无法继续发包,达到了流量控制。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_6-4.gif

但是:当 A 不断发包的同时,A 也会收到来自 B 的确认包,此时整个窗口会往右移动,因此上边界也往右移动,A 就能发更多的数据包了。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_6-5.gif

以上都是在窗口大小不变的情况下。而 B 在发给 A 的 ACK 包中,每一个都可以重新设置一个新的窗口大小,如果 A 收到了一个新的窗口大小值,A 会随之调整。

如果 A 收到了比原窗口值更大的窗口大小,比如 win = 6,则 A 会直接将窗口上边界向右移动 1 个单位。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_6-6.gif

如果 A 收到了比原窗口值小的窗口大小,比如 win = 4,则 A 暂时不会改变窗口大小,更不会将窗口上边界向左移动,而是等着 ACK 的到来,不断将左边界向右移动,直到窗口大小值收缩到新大小为止。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_6-7.gif

OK,终于将流量控制问题解决得差不多了,你看着上面一个个小动图,给这个窗口起了一个更生动的名字:滑动窗口。

9、拥塞问题

但有的时候,不是 B 的接受能力不够,而是网络不太好,造成了网络拥塞。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_7-1.gif

拥塞控制与流量控制有些像,但流量控制是受 B 的接收能力影响,而拥塞控制是受网络环境的影响。

拥塞控制的解决办法依然是通过设置一定的窗口大小。只不过,流量控制的窗口大小是 B 直接告诉 A 的,而拥塞控制的窗口大小按理说就应该是网络环境主动告诉 A。

但网络环境怎么可能主动告诉 A 呢?只能 A 单方面通过试探,不断感知网络环境的好坏,进而确定自己的拥塞窗口的大小。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_7-2.gif

拥塞窗口大小的计算有很多复杂的算法,就不在本文中展开了(有兴趣可以深入阅读《[通俗易懂]深入理解TCP协议(下):RTT、滑动窗口、拥塞处理》)。

假如拥塞窗口的大小为  cwnd,上一部分流量控制的滑动窗口的大小为 rwnd,那么窗口的右边界受这两个值共同的影响,需要取它俩的最小值。

窗口大小 = min(cwnd, rwnd)

含义很容易理解:当 B 的接受能力比较差时,即使网络非常通畅,A 也需要根据 B 的接收能力限制自己的发送窗口。当网络环境比较差时,即使 B 有很强的接收能力,A 也要根据网络的拥塞情况来限制自己的发送窗口。正所谓受其短板的影响嘛~

10、连接问题

有的时候,B 主机的相应进程还没有准备好或是挂掉了,A 就开始发送数据包,导致了浪费。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_8-1.gif

这个问题在于:A 在跟 B 通信之前,没有事先确认 B 是否已经准备好,就开始发了一连串的信息。就好比你和另一个人打电话,你还没有"喂"一下确认对方有没有在听,你就巴拉巴拉说了一堆。

这个问题该怎么解决呢?

地球人都知道:三次握手嘛!

  • A:我准备好了(SYN)
  • B:我知道了(ACK),我也准备好了(SYN)
  • A:我知道了(ACK)

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_8-2.gif

A 与 B 各自在内存中维护着自己的状态变量,三次握手之后,双方的状态都变成了连接已建立(ESTABLISHED)。

虽然就只是发了三次数据包,并且在各自的内存中维护了状态变量,但这么说总觉得太 low,你看这个过程相当于双方建立连接的过程,于是你灵机一动,就叫它面向连接吧。

注意:这个连接是虚拟的,是由 A 和 B 这两个终端共同维护的,在网络中的设备根本就不知道连接这回事儿!

但凡事有始就有终,有了建立连接的过程,就要考虑释放连接的过程。

这就是网络编程中耳熟能详的四次挥手啦!

  • A:再见,我要关闭了(FIN)
  • B:我知道了(ACK)。给 B 一段时间把自己的事情处理完...
  • B:再见,我要关闭了(FIN)
  • A:我知道了(ACK)

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_8-3.gif

11、小结一下

以上讲述的,就是 TCP 协议的核心思想,上面过程中需要传输的信息,就体现在 TCP 协议的头部,这里放上最常见的 TCP 协议头解读的图。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_9-1.png

不知道你现在再看下面这句话,是否能理解:

TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。

“面向连接、可靠”,这两个词通过上面的讲述很容易理解,那什么叫做基于字节流呢?

很简单:TCP 在建立连接时,需要告诉对方 MSS(最大报文段大小)。

也就是说:如果要发送的数据很大,在 TCP 层是需要按照 MSS 来切割成一个个的 TCP 报文段 的。

切割的时候我才不管你原来的数据表示什么意思,需要在哪里断句啥的,我就把它当成一串毫无意义的字节,在我想要切割的地方咔嚓就来一刀,标上序号,只要接收方再根据这个序号拼成最终想要的完整数据就行了。

在我 TCP 传输这里,我就把它当做一个个的字节,也就是基于字节流的含义了。

网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?_10-1.png

12、写在最后

一提到 TCP,可能很多人都想起被三次握手和四次挥手所支配的恐惧。

但其实你跟着本文中的思路你就会发现,三次握手与四次挥手只占 TCP 所解决的核心问题中很小的一部分,只是因为它在面试中很适合作为知识点进行考察,所以在很多人的印象中就好像 TCP 的核心就是握手和挥手似的。

本文希望你能从问题出发,真正理解 TCP 所想要解决的问题,你会发现很多原理就好像生活常识一样顺其自然,并不复杂,希望你有收获~

最后,如果对TCP的理解仍存在疑惑,可以继续阅读以下精选的资料:

本文已同步发布于“即时通讯技术圈”公众号。

▲ 本文在公众号上的链接是:点此进入。同步发布链接是:http://www.52im.net/thread-3339-1-1.html

posted @ 2021-02-24 12:47 Jack Jiang 阅读(132) | 评论 (0)编辑 收藏

     摘要: 本文原题“如果让你来设计网络”,由闪客sun分享,转载请联系作者。1、引言网络编程能力对于即时通讯技术开发者来说是基本功,而计算机网络又是网络编程的理论根基,因而深刻准确地理解计算机网络知识显然能夯实你的即时通讯应用的实践品质。本文风格类似于52im社区里的《网络编程懒人入门》、《脑残式网络编程入门》两个系列,但通俗又不失内涵,简洁又不简陋,非常适合对计算机网络知识有向往但...  阅读全文

posted @ 2021-02-02 15:24 Jack Jiang 阅读(118) | 评论 (0)编辑 收藏

本文原题“高并发高性能服务器是如何实现的”,转载请联系作者。

1、系列文章引言

1.1 文章目的

作为即时通讯技术的开发者来说,高性能、高并发相关的技术概念早就了然与胸,什么线程池、零拷贝、多路复用、事件驱动、epoll等等名词信手拈来,又或许你对具有这些技术特征的技术框架比如:Java的Netty、Php的workman、Go的gnet等熟练掌握。但真正到了面视或者技术实践过程中遇到无法释怀的疑惑时,方知自已所掌握的不过是皮毛。

返璞归真、回归本质,这些技术特征背后的底层原理到底是什么?如何能通俗易懂、毫不费力真正透彻理解这些技术背后的原理,正是《从根上理解高性能、高并发》系列文章所要分享的。

1.2 文章源起

我整理了相当多有关IM、消息推送等即时通讯技术相关的资源和文章,从最开始的开源IM框架MobileIMSDK,到网络编程经典巨著《TCP/IP详解》的在线版本,再到IM开发纲领性文章《新手入门一篇就够:从零开发移动端IM》,以及网络编程由浅到深的《网络编程懒人入门》、《脑残式网络编程入门》、《高性能网络编程》、《不为人知的网络编程》系列文章。

越往知识的深处走,越觉得对即时通讯技术了解的太少。于是后来,为了让开发者门更好地从基础电信技术的角度理解网络(尤其移动网络)特性,我跨专业收集整理了《IM开发者的零基础通信技术入门》系列高阶文章。这系列文章已然是普通即时通讯开发者的网络通信技术知识边界,加上之前这些网络编程资料,解决网络通信方面的知识盲点基本够用了。

对于即时通讯IM这种系统的开发来说,网络通信知识确实非常重要,但回归到技术本质,实现网络通信本身的这些技术特征:包括上面提到的线程池、零拷贝、多路复用、事件驱动等等,它们的本质是什么?底层原理又是怎样?这就是整理本系列文章的目的,希望对你有用。

1.3 文章目录

从根上理解高性能、高并发(一):深入计算机底层,理解线程与线程池

从根上理解高性能、高并发(二):深入操作系统,理解I/O与零拷贝技术

从根上理解高性能、高并发(三):深入操作系统,彻底理解I/O多路复用

从根上理解高性能、高并发(四):深入操作系统,彻底理解同步与异步

从根上理解高性能、高并发(五):深入操作系统,理解高并发中的协程

从根上理解高性能、高并发(六):通俗易懂,高性能服务器到底是如何实现的》(* 本文

1.4 本篇概述

接上篇《从根上理解高性能、高并发(五):深入操作系统,理解高并发中的协程》,本篇是高性能、高并发系列的第6篇文章(也是完结篇)。

本篇是本系列文章的完结篇,你将能了解到,一个典型的服务器端是如何利用前5篇中讲解的各单项技术从而实现高性能高并发的。

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注。公众号上的链接是:点此进入

2、本文作者

应作者要求,不提供真名,也不提供个人照片。

本文作者主要技术方向为互联网后端、高并发高性能服务器、检索引擎技术,网名是“码农的荒岛求生”。感谢作者的无私分享。

3、正文引言

当你在阅读本篇文章的时候,有没有想过,服务器是怎么把这篇文章发送给你的呢?

说起来很简单:不就是一个用户请求吗?服务器根据请求从数据库中捞出这篇文章,然后通过网络发回去吗。

其实有点复杂:服务器端到底是如何并行处理成千上万个用户请求的呢?这里面又涉及到哪些技术呢?

这篇文章就是来为你解答这个问题的。

4、多进程

历史上最早出现也是最简单的一种并行处理多个请求的方法就是利用多进程

比如在Linux世界中,我们可以使用fork、exec等系统调用创建多个进程,我们可以在父进程中接收用户的连接请求,然后创建子进程去处理用户请求。

就像这样:

这种方法的优点就在于:

  • 1)编程简单,非常容易理解;
  • 2)由于各个进程的地址空间是相互隔离的,因此一个进程崩溃后并不会影响其它进程;
  • 3)充分利用多核资源。

多进程并行处理的优点很明显,但是缺点同样明显:

  • 1)各个进程地址空间相互隔离,这一优点也会变成缺点,那就是进程间要想通信就会变得比较困难,你需要借助进程间通信(IPC,interprocess communications)机制,想一想你现在知道哪些进程间通信机制,然后让你用代码实现呢?显然,进程间通信编程相对复杂,而且性能也是一大问题;
  • 2)我们知道创建进程开销是比线程要大的,频繁的创建销毁进程无疑会加重系统负担。

幸好,除了进程,我们还有线程。

5、多线程

不是创建进程开销大吗?不是进程间通信困难吗?这些对于线程来说统统不是问题。

什么?你还不了解线程,赶紧看看这篇《深入计算机底层,理解线程与线程池》,这里详细讲解了线程这个概念是怎么来的。

由于线程共享进程地址空间,因此线程间通信天然不需要借助任何通信机制,直接读取内存就好了。

线程创建销毁的开销也变小了,要知道线程就像寄居蟹一样,房子(地址空间)都是进程的,自己只是一个租客,因此非常的轻量级,创建销毁的开销也非常小。

我们可以为每个请求创建一个线程,即使一个线程因执行I/O操作——比如读取数据库等——被阻塞暂停运行也不会影响到其它线程。

就像这样:

但线程就是完美的、包治百病的吗,显然,计算机世界从来没有那么简单。

由于线程共享进程地址空间,这在为线程间通信带来便利的同时也带来了无尽的麻烦。

正是由于线程间共享地址空间,因此一个线程崩溃会导致整个进程崩溃退出,同时线程间通信简直太简单了,简单到线程间通信只需要直接读取内存就可以了,也简单到出现问题也极其容易,死锁、线程间的同步互斥、等等,这些极容易产生bug,无数程序员宝贵的时间就有相当一部分用来解决多线程带来的无尽问题。

虽然线程也有缺点,但是相比多进程来说,线程更有优势,但想单纯的利用多线程就能解决高并发问题也是不切实际的。

因为虽然线程创建开销相比进程小,但依然也是有开销的,对于动辄数万数十万的链接的高并发服务器来说,创建数万个线程会有性能问题,这包括内存占用、线程间切换,也就是调度的开销。

因此,我们需要进一步思考。

6、事件驱动:Event Loop

到目前为止,我们提到“并行”二字就会想到进程、线程。

但是:并行编程只能依赖这两项技术吗?并不是这样的!

还有另一项并行技术广泛应用在GUI编程以及服务器编程中,这就是近几年非常流行的事件驱动编程:event-based concurrency。

PS:搞IM服务端开发的程序员肯定不陌生,著名的Java NIO高性能网络编程框架Netty中EvenLoop 这个接口意味着什么(有关Netty框架的高性能原理可以读这篇《新手入门:目前为止最透彻的的Netty高性能原理和框架架构解析》)。

大家不要觉得这是一项很难懂的技术,实际上事件驱动编程原理上非常简单。

这一技术需要两种原料:

  • 1)event;
  • 2)处理event的函数,这一函数通常被称为event handler;

剩下的就简单了:你只需要安静的等待event到来就好,当event到来之后,检查一下event的类型,并根据该类型找到对应的event处理函数,也就是event handler,然后直接调用该event handler就好了。

That's it !

以上就是事件驱动编程的全部内容,是不是很简单!

从上面的讨论可以看到:我们需要不断的接收event然后处理event,因此我们需要一个循环(用while或者for循环都可以),这个循环被称为Event loop。

使用伪代码表示就是这样:

while(true) {

    event = getEvent();

    handler(event);

}

Event loop中要做的事情其实是非常简单的,只需要等待event的带来,然后调用相应的event处理函数即可。

注意:这段代码只需要运行在一个线程或者进程中,只需要这一个event loop就可以同时处理多个用户请求。

有的同学可以依然不明白:为什么这样一个event loop可以同时处理多个请求呢?

原因很简单:对于网络通信服务器来说,处理一个用户请求时大部分时间其实都用在了I/O操作上,像数据库读写、文件读写、网络读写等。当一个请求到来,简单处理之后可能就需要查询数据库等I/O操作,我们知道I/O是非常慢的,当发起I/O后我们大可以不用等待该I/O操作完成就可以继续处理接下来的用户请求。

现在你应该明白了吧:虽然上一个用户请求还没有处理完我们其实就可以处理下一个用户请求了,这也是并行,这种并行就可以用事件驱动编程来处理。

这就好比餐厅服务员一样:一个服务员不可能一直等上一个顾客下单、上菜、吃饭、买单之后才接待下一个顾客,服务员是怎么做的呢?当一个顾客下完单后直接处理下一个顾客,当顾客吃完饭后会自己回来买单结账的。

看到了吧:同样是一个服务员也可以同时处理多个顾客,这个服务员就相当于这里的Event loop,即使这个event loop只运行在一个线程(进程)中也可以同时处理多个用户请求。

相信你已经对事件驱动编程有一个清晰的认知了,那么接下来的问题就是,这个事件也就是event该怎么获取呢?

7、事件来源:IO多路复用

在《深入操作系统,彻底理解I/O多路复用》这篇文章中我们知道,在Linux/Unix世界中一切皆文件,而我们的程序都是通过文件描述符来进行I/O操作的,当然对于网络编程中的socket也不例外。

那我们该如何同时处理多个文件描述符呢?

IO多路复用技术正是用来解决这一问题的:通过IO多路复用技术,我们一次可以监控多个文件描述,当某个“文件”(实际可能是im网络通信中socket)可读或者可写的时候我们就能得到通知啦。

这样IO多路复用技术就成了event loop的原材料供应商,源源不断的给我们提供各种event,这样关于event来源的问题就解决了。

当然:关于IO多路复用技术的详细讲解请参见《深入操作系统,彻底理解I/O多路复用》,本文作为纲领性文章,就不再赘述了。

至此:关于利用事件驱动来实现并发编程的所有问题都解决了吗?event的来源问题解决了,当得到event后调用相应的handler,看上去大功告成了。

想一想还有没有其它问题?

8、问题:阻塞式IO

现在:我们可以使用一个线程(进程)就能基于事件驱动进行并行编程,再也没有了多线程中让人恼火的各种锁、同步互斥、死锁等问题了。

但是:计算机科学中从来没有出现过一种能解决所有问题的技术,现在没有,在可预期的将来也不会有。

那上述方法有什么问题吗?

不要忘了,我们event loop是运行在一个线程(进程),这虽然解决了多线程问题,但是如果在处理某个event时需要进行IO操作会怎么样呢?

在《深入操作系统,理解I/O与零拷贝技术》一文中,我们讲解了最常用的文件读取在底层是如何实现的,程序员最常用的这种IO方式被称为阻塞式IO。

也就是说:当我们进行IO操作,比如读取文件时,如果文件没有读取完成,那么我们的程序(线程)会被阻塞而暂停执行,这在多线程中不是问题,因为操作系统还可以调度其它线程。

但是:在单线程的event loop中是有问题的,原因就在于当我们在event loop中执行阻塞式IO操作时整个线程(event loop)会被暂停运行,这时操作系统将没有其它线程可以调度,因为系统中只有一个event loop在处理用户请求,这样当event loop线程被阻塞暂停运行时所有用户请求都没有办法被处理。你能想象当服务器在处理其它用户请求读取数据库导致你的请求被暂停吗?

因此:在基于事件驱动编程时有一条注意事项,那就是不允许发起阻塞式IO。

有的同学可能会问,如果不能发起阻塞式IO的话,那么该怎样进行IO操作呢?

PS:有阻塞式IO,就有非阻塞式IO。我们继续往下讨论。

9、解决方法:非阻塞式IO

为克服阻塞式IO所带来的问题,现代操作系统开始提供一种新的发起IO请求的方法,这种方法就是异步IO。对应的,阻塞式IO就是同步IO,关于同步和异步这两个概念可以参考《从根上理解高性能、高并发(四):深入操作系统,彻底理解同步与异步》。

异步IO时,假设调用aio_read函数(具体的异步IO API请参考具体的操作系统平台),也就是异步读取,当我们调用该函数后可以立即返回,并继续其它事情,虽然此时该文件可能还没有被读取,这样就不会阻塞调用线程了。此外,操作系统还会提供其它方法供调用线程来检测IO操作是否完成。

就这样,在操作系统的帮助下IO的阻塞调用问题也解决了。

10、基于事件驱动并行编程的难点

虽然有异步IO来解决event loop可能被阻塞的问题,但是基于事件编程依然是困难的。

首先:我们提到,event loop是运行在一个线程中的,显然一个线程是没有办法充分利用多核资源的,有的同学可能会说那就创建多个event loop实例不就可以了,这样就有多个event loop线程了,但是这样一来多线程问题又会出现。

另一点在于编程方面,在《从根上理解高性能、高并发(四):深入操作系统,彻底理解同步与异步》这篇文章中我们讲到过,异步编程需要结合回调函数(这种编程方式需要把处理逻辑分为两部分:一部分调用方自己处理,另一部分在回调函数中处理),这一编程方式的改变加重了程序员在理解上的负担,基于事件编程的项目后期会很难扩展以及维护。

那么有没有更好的方法呢?

要找到更好的方法,我们需要解决问题的本质,那么这个本质问题是什么呢?

11、更好的方法

为什么我们要使用异步这种难以理解的方式编程呢?

是因为:阻塞式编程虽然容易理解但会导致线程被阻塞而暂停运行。

那么聪明的你一定会问了:有没有一种方法既能结合同步IO的简单理解又不会因同步调用导致线程被阻塞呢?

答案是肯定的:这就是用户态线程(user level thread),也就是大名鼎鼎的协程(关于协程请详读本系列的上篇《从根上理解高性能、高并发(五):深入操作系统,理解高并发中的协程》,本文就不再赘述了)。

虽然基于事件编程有这样那样的缺点,但是在当今的高性能高并发服务器上基于事件编程方式依然非常流行,但已经不是纯粹的基于单一线程的事件驱动了,而是 event loop + multi thread + user level thread。

关于这一组合,同样值得拿出一篇文章来讲解,我们将在后续文章中详细讨论。

12、本文小结

高并发技术从最开始的多进程一路演进到当前的事件驱动,计算机技术就像生物一样也在不断演变进化,但不管怎样,了解历史才能更深刻的理解当下。希望这篇文章能对大家理解高并发服务器有所帮助。

附录:更多高性能、高并发文章精选

高性能网络编程(一):单台服务器并发TCP连接数到底可以有多少

高性能网络编程(二):上一个10年,著名的C10K并发连接问题

高性能网络编程(三):下一个10年,是时候考虑C10M并发问题了

高性能网络编程(四):从C10K到C10M高性能网络应用的理论探索

高性能网络编程(五):一文读懂高性能网络编程中的I/O模型

高性能网络编程(六):一文读懂高性能网络编程中的线程模型

高性能网络编程(七):到底什么是高并发?一文即懂!

以网游服务端的网络接入层设计为例,理解实时通信的技术挑战

知乎技术分享:知乎千万级并发的高性能长连接网关技术实践

淘宝技术分享:手淘亿级移动端接入层网关的技术演进之路

一套海量在线用户的移动端IM架构设计实践分享(含详细图文)

一套原创分布式即时通讯(IM)系统理论架构方案

微信后台基于时间序的海量数据冷热分级架构设计实践

微信技术总监谈架构:微信之道——大道至简(演讲全文)

如何解读《微信技术总监谈架构:微信之道——大道至简》

快速裂变:见证微信强大后台架构从0到1的演进历程(一)

17年的实践:腾讯海量产品的技术方法论

腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面

以微博类应用场景为例,总结海量社交系统的架构设计步骤

新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践

从新手到架构师,一篇就够:从100到1000万高并发的架构演进之路

本文已同步发布于“即时通讯技术圈”公众号。

▲ 本文在公众号上的链接是:点此进入。同步发布链接是:http://www.52im.net/thread-3315-1-1.html

posted @ 2021-01-25 16:36 Jack Jiang 阅读(183) | 评论 (0)编辑 收藏

     摘要: 本文原题“程序员应如何理解高并发中的协程”,转载请联系作者。1、系列文章引言1.1 文章目的作为即时通讯技术的开发者来说,高性能、高并发相关的技术概念早就了然与胸,什么线程池、零拷贝、多路复用、事件驱动、epoll等等名词信手拈来,又或许你对具有这些技术特征的技术框架比如:Java的Netty、Php的workman、Go的gnet等熟练掌握。但真正到了面视或者技术实践过程...  阅读全文

posted @ 2021-01-18 14:51 Jack Jiang 阅读(112) | 评论 (0)编辑 收藏

     摘要: 本文原题“从小白到高手,你需要理解同步与异步”,转载请联系作者。1、系列文章引言1.1 文章目的作为即时通讯技术的开发者来说,高性能、高并发相关的技术概念早就了然与胸,什么线程池、零拷贝、多路复用、事件驱动、epoll等等名词信手拈来,又或许你对具有这些技术特征的技术框架比如:Java的Netty、Php的workman、Go的gnet等熟练掌握。但真正到了面视或者技术实践...  阅读全文

posted @ 2021-01-12 14:15 Jack Jiang 阅读(117) | 评论 (0)编辑 收藏

本文原题“终于明白了,一文彻底理解I/O多路复用”,转载请联系作者。

1、系列文章引言

1.1 文章目的

作为即时通讯技术的开发者来说,高性能、高并发相关的技术概念早就了然与胸,什么线程池、零拷贝、多路复用、事件驱动、epoll等等名词信手拈来,又或许你对具有这些技术特征的技术框架比如:Java的Netty、Php的workman、Go的nget等熟练掌握。但真正到了面视或者技术实践过程中遇到无法释怀的疑惑时,方知自已所掌握的不过是皮毛。

返璞归真、回归本质,这些技术特征背后的底层原理到底是什么?如何能通俗易懂、毫不费力真正透彻理解这些技术背后的原理,正是《从根上理解高性能、高并发》系列文章所要分享的。

1.2 文章源起

我整理了相当多有关IM、消息推送等即时通讯技术相关的资源和文章,从最开始的开源IM框架MobileIMSDK,到网络编程经典巨著《TCP/IP详解》的在线版本,再到IM开发纲领性文章《新手入门一篇就够:从零开发移动端IM》,以及网络编程由浅到深的《网络编程懒人入门》、《脑残式网络编程入门》、《高性能网络编程》、《不为人知的网络编程》系列文章。

越往知识的深处走,越觉得对即时通讯技术了解的太少。于是后来,为了让开发者门更好地从基础电信技术的角度理解网络(尤其移动网络)特性,我跨专业收集整理了《IM开发者的零基础通信技术入门》系列高阶文章。这系列文章已然是普通即时通讯开发者的网络通信技术知识边界,加上之前这些网络编程资料,解决网络通信方面的知识盲点基本够用了。

对于即时通讯IM这种系统的开发来说,网络通信知识确实非常重要,但回归到技术本质,实现网络通信本身的这些技术特征:包括上面提到的线程池、零拷贝、多路复用、事件驱动等等,它们的本质是什么?底层原理又是怎样?这就是整理本系列文章的目的,希望对你有用。

1.3 文章目录

从根上理解高性能、高并发(一):深入计算机底层,理解线程与线程池

从根上理解高性能、高并发(二):深入操作系统,理解I/O与零拷贝技术

从根上理解高性能、高并发(三):深入操作系统,彻底理解I/O多路复用》(* 本文

《从根上理解高性能、高并发(四):深入操作系统,彻底理解同步与异步 (稍后发布..)》

《从根上理解高性能、高并发(五):高并发高性能服务器到底是如何实现的 (稍后发布..)》

1.4 本篇概述

接上篇《深入操作系统,理解I/O与零拷贝技术》,本篇是高性能、高并发系列的第3篇文章,上篇里我们讲到了I/O技术,本篇将以更具象的文件这个话题入手,带你一步步理解高性能、高并发服务端编程时无法回避的I/O多路复用及相关技术。

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注。公众号上的链接是:点此进入

2、本文作者

应作者要求,不提供真名,也不提供个人照片。

本文作者主要技术方向为互联网后端、高并发高性能服务器、检索引擎技术,网名是“码农的荒岛求生”,公众号“码农的荒岛求生”。感谢作者的无私分享。

3、什么是文件?

在正式展开本文的内容之前,我们需要先预习一下文件以及文件描述符的概念。

程序员使用I/O最终都逃不过文件这个概念。

在Linux世界中文件是一个很简单的概念,作为程序员我们只需要将其理解为一个N byte的序列就可以了:

b1, b2, b3, b4, ....... bN

实际上所有的I/O设备都被抽象为了文件这个概念,一切皆文件(Everything is File),磁盘、网络数据、终端,甚至进程间通信工具管道pipe等都被当做文件对待。

所有的I/O操作也都可以通过文件读写来实现,这一非常优雅的抽象可以让程序员使用一套接口就能对所有外设I/O操作。

常用的I/O操作接口一般有以下几类:

  • 1)打开文件,open;
  • 2)改变读写位置,seek;
  • 3)文件读写,read、write;
  • 4)关闭文件,close。

程序员通过这几个接口几乎可以实现所有I/O操作,这就是文件这个概念的强大之处。

4、什么是文件描述符?

在上一篇《深入操作系统,理解I/O与零拷贝技术》中我们讲到:要想进行I/O读操作,像磁盘数据,我们需要指定一个buff用来装入数据。

一般都是这样写的:

read(buff);

但是这里我们忽略了一个关键问题:那就是,虽然我们指定了往哪里写数据,但是我们该从哪里读数据呢?

从上一节中我们知道,通过文件这个概念我们能实现几乎所有I/O操作,因此这里少的一个主角就是文件。

那么我们一般都怎样使用文件呢?

举个例子:如果周末你去比较火的餐厅吃饭应该会有体会,一般周末人气高的餐厅都会排队,然后服务员会给你一个排队序号,通过这个序号服务员就能找到你,这里的好处就是服务员无需记住你是谁、你的名字是什么、来自哪里、喜好是什么、是不是保护环境爱护小动物等等,这里的关键点就是:服务员对你一无所知,但依然可以通过一个号码就能找到你。

同样的:在Linux世界要想使用文件,我们也需要借助一个号码,根据“弄不懂原则”,这个号码就被称为了文件描述符(file descriptors),在Linux世界中鼎鼎大名,其道理和上面那个排队号码一样。

因此:文件描述仅仅就是一个数字而已,但是通过这个数字我们可以操作一个打开的文件,这一点要记住。

有了文件描述符,进程可以对文件一无所知,比如文件在磁盘的什么位置、加载到内存中又是怎样管理的等等,这些信息统统交由操作系统打理,进程无需关心,操作系统只需要给进程一个文件描述符就足够了。

因此我们来完善上述程序:

int fd = open(file_name); // 获取文件描述符

read(fd, buff);

怎么样,是不是非常简单。

5、文件描述符太多了怎么办?

经过了这么多的铺垫,终于要到高性能、高并发这一主题了。

从前几节我们知道,所有I/O操作都可以通过文件样的概念来进行,这当然包括网络通信。

如果你有一个IM服务器,当三次握手建议长连接成功以后,我们会调用accept来获取一个链接,调用该函数我们同样会得到一个文件描述符,通过这个文件描述符就可以处理客户端发送的聊天消息并且把消息转发给接收者。

也就是说,通过这个描述符我们就可以和客户端进行通信了:

// 通过accept获取客户端的文件描述符

int conn_fd = accept(...);

Server端的处理逻辑通常是接收客户端消息数据,然后执行转发(给接收者)逻辑:

if(read(conn_fd, msg_buff) > 0) {

do_transfer(msg_buff);

}

是不是非常简单,然而世界终归是复杂的,当然也不是这么简单的。

接下来就是比较复杂的了。

既然我们的主题是高并发,那么Server端就不可能只和一个客户端通信,而是可能会同时和成千上万个客户端进行通信。这时你需要处理不再是一个描述符这么简单,而是有可能要处理成千上万个描述符。

为了不让问题一上来就过于复杂,我们先简单化,假设只同时处理两个客户端的请求。

有的同学可能会说,这还不简单,这样写不就行了:

if(read(socket_fd1, buff) > 0) { // 处理第一个

do_transfer();

}

if(read(socket_fd2, buff) > 0) { // 处理第二个

do_transfer();

在上一篇《深入操作系统,理解I/O与零拷贝技术》中我们讨论过,这是非常典型的阻塞式I/O,如果此时没有数据可读那么进程会被阻塞而暂停运行。这时我们就无法处理第二个请求了,即使第二个请求的数据已经就位,这也就意味着处理某一个客户端时由于进程被阻塞导致剩下的所有其它客户端必须等待,在同时处理几万客户端的server上。这显然是不能容忍的。

聪明的你一定会想到使用多线程:为每个客户端请求开启一个线程,这样一个客户端被阻塞就不会影响到处理其它客户端的线程了。注意:既然是高并发,那么我们要为成千上万个请求开启成千上万个线程吗,大量创建销毁线程会严重影响系统性能。

那么这个问题该怎么解决呢?

这里的关键点在于:我们事先并不知道一个文件描述对应的I/O设备是否是可读的、是否是可写的,在外设的不可读或不可写的状态下进行I/O只会导致进程阻塞被暂停运行。

因此要优雅的解决这个问题,就要从其它角度来思考这个问题了。

6、“不要打电话给我,有需要我会打给你”

大家生活中肯定会接到过推销电话,而且不止一个,一天下来接上十个八个推销电话你的身体会被掏空的。

这个场景的关键点在于:打电话的人并不知道你是不是要买东西,只能来一遍遍问你。因此一种更好的策略是不要让他们打电话给你,记下他们的电话,有需要的话打给他们,这样推销员就不会一遍一遍的来烦你了(虽然现实生活中这并不可能)。

在这个例子中:你,就好比内核,推销者就好比应用程序,电话号码就好比文件描述符,和你用电话沟通就好比I/O。

现在你应该明白了吧,处理多个文件描述符的更好方法其实就存在于推销电话中。

因此相比上一节中:我们通过I/O接口主动问内核这些文件描述符对应的外设是不是已经就绪了,一种更好的方法是,我们把这些感兴趣的文件描述符一股脑扔给内核,并霸气的告诉内核:“我这里有1万个文件描述符,你替我监视着它们,有可以读写的文件描述符时你就告诉我,我好处理”。而不是弱弱的问内核:“第一个文件描述可以读写了吗?第二个文件描述符可以读写吗?第三个文件描述符可以读写了吗?。。。”

这样:应用程序就从“繁忙”的主动变为了清闲的被动,反正文件描述可读可写了内核会通知我,能偷懒我才不要那么勤奋。

这是一种更加高效的I/O处理机制,现在我们可以一次处理多路I/O了,为这种机制起一个名字吧,就叫I/O多路复用吧,这就是 I/O multiplexing。

7、I/O多路复用(I/O multiplexing)

multiplexing一词其实多用于通信领域,为了充分利用通信线路,希望在一个信道中传输多路信号,要想在一个信道中传输多路信号就需要把这多路信号结合为一路,将多路信号组合成一个信号的设备被称为Multiplexer(多路复用器),显然接收方接收到这一路组合后的信号后要恢复原先的多路信号,这个设备被称为Demultiplexer(多路分用器)。

如下图所示:

回到我们的主题。

所谓I/O多路复用指的是这样一个过程:

  • 1)我们拿到了一堆文件描述符(不管是网络相关的、还是磁盘文件相关等等,任何文件描述符都可以);
  • 2)通过调用某个函数告诉内核:“这个函数你先不要返回,你替我监视着这些描述符,当这堆文件描述符中有可以进行I/O读写操作的时候你再返回”;
  • 3)当调用的这个函数返回后我们就能知道哪些文件描述符可以进行I/O操作了。

也就是说通过I/O多路复用我们可以同时处理多路I/O。那么有哪些函数可以用来进行I/O多路复用呢?

以Linux为例,有这样三种机制可以用来进行I/O多路复用:

  • 1)select;
  • 2)poll;
  • 3)epoll。

接下来我们就来介绍一下牛掰的I/O多路复用三剑客。

8、I/O多路复用三剑客

本质上:Linux上的select、poll、epoll都是阻塞式I/O,也就是我们常说的同步I/O。

原因在于:调用这些I/O多路复用函数时如果任何一个需要监视的文件描述符都不可读或者可写那么进程会被阻塞暂停执行,直到有文件描述符可读或者可写才继续运行。

8.1 select:初出茅庐

在select这种I/O多路复用机制下,我们需要把想监控的文件描述集合通过函数参数的形式告诉select,然后select会将这些文件描述符集合拷贝到内核中。

我们知道数据拷贝是有性能损耗的,因此为了减少这种数据拷贝带来的性能损耗,Linux内核对集合的大小做了限制,并规定用户监控的文件描述集合不能超过1024个,同时当select返回后我们仅仅能知道有些文件描述符可以读写了,但是我们不知道是哪一个。因此程序员必须再遍历一边找到具体是哪个文件描述符可以读写了。

因此,总结下来select有这样几个特点:

  • 1)我能照看的文件描述符数量有限,不能超过1024个;
  • 2)用户给我的文件描述符需要拷贝的内核中;
  • 3)我只能告诉你有文件描述符满足要求了,但是我不知道是哪个,你自己一个一个去找吧(遍历)。

因此我们可以看到,select机制的这些特性在高并发网络服务器动辄几万几十万并发链接的场景下无疑是低效的。

8.2 poll:小有所成

poll和select是非常相似的。

poll相对于select的优化仅仅在于解决了文件描述符不能超过1024个的限制,select和poll都会随着监控的文件描述数量增加而性能下降,因此不适合高并发场景。

8.3 epoll:独步天下

在select面临的三个问题中,文件描述数量限制已经在poll中解决了,剩下的两个问题呢?

针对拷贝问题:epoll使用的策略是各个击破与共享内存。

实际上:文件描述符集合的变化频率比较低,select和poll频繁的拷贝整个集合,内核都快被烦死了,epoll通过引入epoll_ctl很体贴的做到了只操作那些有变化的文件描述符。同时epoll和内核还成为了好朋友,共享了同一块内存,这块内存中保存的就是那些已经可读或者可写的的文件描述符集合,这样就减少了内核和程序的拷贝开销。

针对需要遍历文件描述符才能知道哪个可读可写这一问题,epoll使用的策略是“当小弟”。

在select和poll机制下:进程要亲自下场去各个文件描述符上等待,任何一个文件描述可读或者可写就唤醒进程,但是进程被唤醒后也是一脸懵逼并不知道到底是哪个文件描述符可读或可写,还要再从头到尾检查一遍。

但epoll就懂事多了,主动找到进程要当小弟替大哥出头。

在这种机制下:进程不需要亲自下场了,进程只要等待在epoll上,epoll代替进程去各个文件描述符上等待,当哪个文件描述符可读或者可写的时候就告诉epoll,epoll用小本本认真记录下来然后唤醒大哥:“进程大哥,快醒醒,你要处理的文件描述符我都记下来了”,这样进程被唤醒后就无需自己从头到尾检查一遍,因为epoll小弟都已经记下来了。

因此我们可以看到:在epoll这种机制下,实际上利用的就是“不要打电话给我,有需要我会打给你”这种策略,进程不需要一遍一遍麻烦的问各个文件描述符,而是翻身做主人了——“你们这些文件描述符有哪个可读或者可写了主动报上来”。

这种机制实际上就是大名鼎鼎的事件驱动——Event-driven,这也是我们下一篇的主题。

实际上:在Linux平台,epoll基本上就是高并发的代名词。

9、本文小结

基于一切皆文件的设计哲学,I/O也可以通过文件的形式实现,高并发场景下要与多个文件交互,这就离不开高效的I/O多路复用技术。

本文我们详细讲解了什么是I/O多路复用以及使用方法,这其中以epoll为代表的I/O多路复用(基于事件驱动)技术使用非常广泛,实际上你会发现但凡涉及到高并发、高性能的场景基本上都能见到事件驱动的编程方法,当然这也是下一篇我们要重点讲解的主题《从根上理解高性能、高并发(四):深入操作系统,彻底理解同步与异步》,敬请期待!

附录:更多高性能、高并发文章精选

高性能网络编程(一):单台服务器并发TCP连接数到底可以有多少

高性能网络编程(二):上一个10年,著名的C10K并发连接问题

高性能网络编程(三):下一个10年,是时候考虑C10M并发问题了

高性能网络编程(四):从C10K到C10M高性能网络应用的理论探索

高性能网络编程(五):一文读懂高性能网络编程中的I/O模型

高性能网络编程(六):一文读懂高性能网络编程中的线程模型

高性能网络编程(七):到底什么是高并发?一文即懂!

以网游服务端的网络接入层设计为例,理解实时通信的技术挑战

知乎技术分享:知乎千万级并发的高性能长连接网关技术实践

淘宝技术分享:手淘亿级移动端接入层网关的技术演进之路

一套海量在线用户的移动端IM架构设计实践分享(含详细图文)

一套原创分布式即时通讯(IM)系统理论架构方案

微信后台基于时间序的海量数据冷热分级架构设计实践

微信技术总监谈架构:微信之道——大道至简(演讲全文)

如何解读《微信技术总监谈架构:微信之道——大道至简》

快速裂变:见证微信强大后台架构从0到1的演进历程(一)

17年的实践:腾讯海量产品的技术方法论

腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面

以微博类应用场景为例,总结海量社交系统的架构设计步骤

新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践

从新手到架构师,一篇就够:从100到1000万高并发的架构演进之路

本文已同步发布于“即时通讯技术圈”公众号。

▲ 本文在公众号上的链接是:点此进入。同步发布链接是:http://www.52im.net/thread-3287-1-1.html

posted @ 2021-01-05 15:06 Jack Jiang 阅读(142) | 评论 (0)编辑 收藏

1、系列文章引言

1.1 文章目的

作为即时通讯技术的开发者来说,高性能、高并发相关的技术概念早就了然与胸,什么线程池、零拷贝、多路复用、事件驱动、epoll等等名词信手拈来,又或许你对具有这些技术特征的技术框架比如:Java的Netty、Php的workman、Go的nget等熟练掌握。但真正到了面视或者技术实践过程中遇到无法释怀的疑惑时,方知自已所掌握的不过是皮毛。

返璞归真、回归本质,这些技术特征背后的底层原理到底是什么?如何能通俗易懂、毫不费力真正透彻理解这些技术背后的原理,正是《从根上理解高性能、高并发》系列文章所要分享的。

1.2 文章源起

我整理了相当多有关IM、消息推送等即时通讯技术相关的资源和文章,从最开始的开源IM框架MobileIMSDK,到网络编程经典巨著《TCP/IP详解》的在线版本,再到IM开发纲领性文章《新手入门一篇就够:从零开发移动端IM》,以及网络编程由浅到深的《网络编程懒人入门》、《脑残式网络编程入门》、《高性能网络编程》、《不为人知的网络编程》系列文章。

越往知识的深处走,越觉得对即时通讯技术了解的太少。于是后来,为了让开发者门更好地从基础电信技术的角度理解网络(尤其移动网络)特性,我跨专业收集整理了《IM开发者的零基础通信技术入门》系列高阶文章。这系列文章已然是普通即时通讯开发者的网络通信技术知识边界,加上之前这些网络编程资料,解决网络通信方面的知识盲点基本够用了。

对于即时通讯IM这种系统的开发来说,网络通信知识确实非常重要,但回归到技术本质,实现网络通信本身的这些技术特征:包括上面提到的线程池、零拷贝、多路复用、事件驱动等等,它们的本质是什么?底层原理又是怎样?这就是整理本系列文章的目的,希望对你有用。

1.3 文章目录

1.4 本篇概述

接上篇《深入计算机底层,理解线程与线程池》,本篇是高性能、高并发系列的第2篇文章,在这里我们来到了I/O这一话题。你有没有想过,当我们执行文件I/O、网络I/O操作时计算机底层到底发生了些什么?对于计算机来说I/O是极其重要的,本篇将带给你这个问的答案。

2、本文作者

应作者要求,不提供真名,也不提供个人照片。

本文作者主要技术方向为互联网后端、高并发高性能服务器、检索引擎技术,网名是“码农的荒岛求生”,公众号“码农的荒岛求生”。感谢作者的无私分享。

3、不能执行I/O的计算机是什么?

相信对于程序员来说I/O操作是最为熟悉不过的了,比如:

  • 1)当我们使用C语言中的printf、C++中的"<<",Python中的print,Java中的System.out.println等时;
  • 2)当我们使用各种语言读写文件时;
  • 3)当我们通过TCP/IP进行网络通信时;
  • 4)当我们使用鼠标龙飞凤舞时;
  • 5)当我们拿起键盘在评论区指点江山亦或是埋头苦干努力制造bug时;
  • 6)当我们能看到屏幕上的漂亮的图形界面时等等。

以上这一切,都是I/O!

想一想:如果没有I/O计算机该是一种多么枯燥的设备,不能看电影、不能玩游戏,也不能上网,这样的计算机最多就是一个大号的计算器。

既然I/O这么重要,那么到底什么才是I/O呢?

4、什么是I/O?

I/O就是简单的数据Copy,仅此而已!

这一点很重要!

既然是copy数据,那么又是从哪里copy到哪里呢?

如果数据是从外部设备copy到内存中,这就是Input。

如果数据是从内存copy到外部设备,这就是Output。

内存与外部设备之间不嫌麻烦的来回copy数据就是Input and Output,简称I/O(Input/Output),仅此而已。

5、I/O与CPU

现在我们知道了什么是I/O,接下来就是重点部分了,大家注意,坐稳了。

我们知道现在的CPU其主频都是数GHz起步,这是什么意思呢?

简单说就是:CPU执行机器指令的速度是纳秒级别的,而通常的I/O比如磁盘操作,一次磁盘seek大概在毫秒级别,因此如果我们把CPU的速度比作战斗机的话,那么I/O操作的速度就是肯德鸡。

也就是说当我们的程序跑起来时(CPU执行机器指令),其速度是要远远快于I/O速度的。那么接下来的问题就是二者速度相差这么大,那么我们该如何设计、该如何更加合理的高效利用系统资源呢?

既然有速度差异,而且进程在执行完I/O操作前不能继续向前推进,那么显然只有一个办法,那就是等待(wait)。

同样是等待,有聪明的等待,也有傻傻的等待,简称傻等,那么是选择聪明的等待呢还是选择傻等呢?

假设你是一个急性子(CPU),需要等待一个重要的文件,不巧的是这个文件只能快递过来(I/O),那么这时你是选择什么事情都不干了,深情的注视着门口就像盼望着你的哈尼一样专心等待这个快递呢?还是暂时先不要管快递了,玩个游戏看个电影刷会儿短视频等快递来了再说呢?

很显然,更好的方法就是先去干其它事情,快递来了再说。

因此:这里的关键点就是快递没到前手头上的事情可以先暂停,切换到其它任务,等快递过来了再切换回来。

理解了这一点你就能明白执行I/O操作时底层都发生了什么。

接下来让我们以读取磁盘文件为例来讲解这一过程。

6、执行I/O时底层都发生了什么

在上一篇《深入计算机底层,理解线程与线程池》中,我们引入了进程和线程的概念。

在支持线程的操作系统中,实际上被调度的是线程而不是进程,为了更加清晰的理解I/O过程,我们暂时假设操作系统只有进程这样的概念,先不去考虑线程,这并不会影响我们的讨论。

现在内存中有两个进程,进程A和进程B,当前进程A正在运行。

如下图所示:

进程A中有一段读取文件的代码,不管在什么语言中通常我们定义一个用来装数据的buff,然后调用read之类的函数。

就像这样:

read(buff);

这就是一种典型的I/O操作,当CPU执行到这段代码的时候会向磁盘发送读取请求。

注意:与CPU执行指令的速度相比,I/O操作操作是非常慢的,因此操作系统是不可能把宝贵的CPU计算资源浪费在无谓的等待上的,这时重点来了,注意接下来是重点哦。

由于外部设备执行I/O操作是相当慢的,因此在I/O操作完成之前进程是无法继续向前推进的,这就是所谓的阻塞,即通常所说的block。

操作系统检测到进程向I/O设备发起请求后就暂停进程的运行,怎么暂停运行呢?很简单:只需要记录下当前进程的运行状态并把CPU的PC寄存器指向其它进程的指令就可以了。

进程有暂停就会有继续执行,因此操作系统必须保存被暂停的进程以备后续继续执行,显然我们可以用队列来保存被暂停执行的进程。

如下图所示,进程A被暂停执行并被放到阻塞队列中(注意:不同的操作系统会有不同的实现,可能每个I/O设备都有一个对应的阻塞队列,但这种实现细节上的差异不影响我们的讨论)。

这时操作系统已经向磁盘发送了I/O请求,因此磁盘driver开始将磁盘中的数据copy到进程A的buff中。虽然这时进程A已经被暂停执行了,但这并不妨碍磁盘向内存中copy数据。

注意:现代磁盘向内存copy数据时无需借助CPU的帮助,这就是所谓的DMA(Direct Memory Access)。

这个过程如下图所示:

让磁盘先copy着数据,我们接着聊。

实际上:操作系统中除了有阻塞队列之外也有就绪队列,所谓就绪队列是指队列里的进程准备就绪可以被CPU执行了。

你可能会问为什么不直接执行非要有个就绪队列呢?答案很简单:那就是僧多粥少,在即使只有1个核的机器上也可以创建出成千上万个进程,CPU不可能同时执行这么多的进程,因此必然存在这样的进程,即使其一切准备就绪也不能被分配到计算资源,这样的进程就被放到了就绪队列。

现在进程B就位于就绪队列,万事俱备只欠CPU。

如下图所示:

当进程A被暂停执行后CPU是不可以闲下来的,因为就绪队列中还有嗷嗷待哺的进程B,这时操作系统开始在就绪队列中找下一个可以执行的进程,也就是这里的进程B。

此时操作系统将进程B从就绪队列中取出,找出进程B被暂停时执行到的机器指令的位置,然后将CPU的PC寄存器指向该位置,这样进程B就开始运行啦。

如下图所示:

注意:接下来的这段是重点中的重点!

注意观察上图:此时进程B在被CPU执行,磁盘在向进程A的内存空间中copy数据,看出来了吗——大家都在忙,谁都没有闲着,数据copy和指令执行在同时进行,在操作系统的调度下,CPU、磁盘都得到了充分的利用,这就是程序员的智慧所在。

现在你应该理解为什么操作系统这么重要了吧。

此后磁盘终于将全部数据都copy到了进程A的内存中,这时磁盘通知操作系统任务完成啦,你可能会问怎么通知呢?这就是中断。

操作系统接收到磁盘中断后发现数据copy完毕,进程A重新获得继续运行的资格,这时操作系统小心翼翼的把进程A从阻塞队列放到了就绪队列当中。

如下图所示:

注意:从前面关于就绪状态的讨论中我们知道,操作系统是不会直接运行进程A的,进程A必须被放到就绪队列中等待,这样对大家都公平。

此后进程B继续执行,进程A继续等待,进程B执行了一会儿后操作系统认为进程B执行的时间够长了,因此把进程B放到就绪队列,把进程A取出并继续执行。

注意:操作系统把进程B放到的是就绪队列,因此进程B被暂停运行仅仅是因为时间片到了而不是因为发起I/O请求被阻塞。

如下图所示:

进程A继续执行,此时buff中已经装满了想要的数据,进程A就这样愉快的运行下去了,就好像从来没有被暂停过一样,进程对于自己被暂停一事一无所知,这就是操作系统的魔法。

现在你应该明白了I/O是一个怎样的过程了吧。

这种进程执行I/O操作被阻塞暂停执行的方式被称为阻塞式I/O,blocking I/O,这也是最常见最容易理解的I/O方式,有阻塞式I/O就有非阻塞式I/O,在这里我们暂时先不考虑这种方式。

在本节开头我们说过暂时只考虑进程而不考虑线程,现在我们放宽这个条件,实际上也非常简单,只需要把前图中调度的进程改为线程就可以了,这里的讨论对于线程一样成立。

7、零拷贝(Zero-copy)

最后需要注意的一点就是:上面的讲解中我们直接把磁盘数据copy到了进程空间中,但实际上一般情况下I/O数据是要首先copy到操作系统内部,然后操作系统再copy到进程空间中。

因此我们可以看到这里其实还有一层经过操作系统的copy,对于性能要求很高的场景其实也是可以绕过操作系统直接进行数据copy的,这也是本文描述的场景,这种绕过操作系统直接进行数据copy的技术被称为Zero-copy,也就零拷贝,高并发、高性能场景下常用的一种技术,原理上很简单吧。

PS:对于搞即时通讯开发的Java程序员来说,著名的高性能网络框架Netty就使用了零拷贝技术,具体可以读《NIO框架详解:Netty的高性能之道》一文的第12节。如果对于Netty框架很好奇但不了解的话,可以因着这两篇文章入门:《新手入门:目前为止最透彻的的Netty高性能原理和框架架构解析》、《史上最通俗Netty入门长文:基本介绍、环境搭建、动手实战》。

8、本文小结

本文讲解的是程序员常用的I/O(包括所谓的网络I/O),一般来说作为程序员我们无需关心,但是理解I/O背后的底层原理对于设计比如IM这种高性能、高并发系统是极为有益的,希望这篇能对大家加深对I/O的认识有所帮助。

接下来的一篇《从根上理解高性能、高并发(三):深入操作系统,彻底理解I/O多路复用》将要分享的是I/O技术的一大突破,正是因为它,才彻底解决了高并发网络通信中的C10K问题(见《高性能网络编程(二):上一个10年,著名的C10K并发连接问题),敬请期待!

附录:更多高性能、高并发文章精选

高性能网络编程(一):单台服务器并发TCP连接数到底可以有多少

高性能网络编程(二):上一个10年,著名的C10K并发连接问题

高性能网络编程(三):下一个10年,是时候考虑C10M并发问题了

高性能网络编程(四):从C10K到C10M高性能网络应用的理论探索

高性能网络编程(五):一文读懂高性能网络编程中的I/O模型

高性能网络编程(六):一文读懂高性能网络编程中的线程模型

高性能网络编程(七):到底什么是高并发?一文即懂!

以网游服务端的网络接入层设计为例,理解实时通信的技术挑战

知乎技术分享:知乎千万级并发的高性能长连接网关技术实践

淘宝技术分享:手淘亿级移动端接入层网关的技术演进之路

一套海量在线用户的移动端IM架构设计实践分享(含详细图文)

一套原创分布式即时通讯(IM)系统理论架构方案

微信后台基于时间序的海量数据冷热分级架构设计实践

微信技术总监谈架构:微信之道——大道至简(演讲全文)

如何解读《微信技术总监谈架构:微信之道——大道至简》

快速裂变:见证微信强大后台架构从0到1的演进历程(一)

17年的实践:腾讯海量产品的技术方法论

腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面

以微博类应用场景为例,总结海量社交系统的架构设计步骤

新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践

从新手到架构师,一篇就够:从100到1000万高并发的架构演进之路

本文已同步发布于“即时通讯技术圈”公众号。

▲ 本文在公众号上的链接是:点此进入。同步发布链接是:http://www.52im.net/thread-3280-1-1.html

posted @ 2020-12-28 14:42 Jack Jiang 阅读(129) | 评论 (0)编辑 收藏

     摘要: 本文原题“聊聊TCP连接耗时的那些事儿”,本次收录已征得作者同意,转载请联系作者。有少许改动。1、系列文章引言1.1 文章目的作为即时通讯技术的开发者来说,高性能、高并发相关的技术概念早就了然与胸,什么线程池、零拷贝、多路复用、事件驱动、epoll等等名词信手拈来,又或许你对具有这些技术特征的技术框架比如:Java的Netty、Php的workman、Go的nget等熟练掌...  阅读全文

posted @ 2020-12-23 16:19 Jack Jiang 阅读(212) | 评论 (0)编辑 收藏

本文由淘宝消息业务团队李历岷(花名骨来)原创分享,首次发表于公众号“淘系技术”,有修订和改动。

1、引言

本文来自淘宝消息业务团队的技术实践分享,分析了电商IM消息平台在非传统IM应用场景下的高发并、强互动群聊和直播业务中的技术特点,总结并分享了在这些场景下实现大量多对多实时消息分发投递的一些架构方面的设计实践。

目前,阿里的IM消息业务团队负责新零售领域 IM 消息平台的建设,通过 IM即时通讯产品(push、聊天机器人、单聊、群聊、消息号和聊天室)构建连接消费者和商家的沟通和触达渠道,每天处理百亿级的消费规模,支撑了直播互动、客服服务、商家群运营、品牌资讯、营销推送等电商领域 BC 互通的业务场景。

本文同步发布于:http://www.52im.net/thread-3252-1-1.html

2、技术背景

2020年双11,第一次改变节奏,从光棍节变成双节棍,从一个峰变成了两个峰,在新的挑战下,如何做好技术保障,做好技术支撑,所有技术人都进入了一个新的学习过程。

新的形势下,显著特点是时间跨度长、活动周期长以及用户互动玩法更多。从单用户CC分享到群内分享,以及直播间互动消息等,作为电商的IM消息平台,承接着整个互动的场地。

用户在整个“互动消息”场景下,可以进行实时分享、聊天、客服沟通、商家优惠、商家优惠活动和红包以及商品秒杀等。

“消息”作为用户和用户之间架起“连接”的重要桥梁,消息连接了用户和用户、用户和商家、用户和平台等。

如何做到高并发强互动下消息有序地呈现在用户面前,是消息团队亘古不变的主题,尤其是在用户的互动行为不确定性情况下,在面对不确定性的流量和规模时,系统应该怎样做到“丝般顺滑”的体验呢?本文将带着读者一起来进行深入探讨。

3、强互动消息场景的技术挑战

不同于传统社区IM消息平台,电商IM消息平台有自已的互动场景特点。

3.1 直播间

淘宝直播带来新的流量变化,百万在线已经成为常态化,大量的直播间7X24小时直播带货,用户在直播频道页频繁地进出不同的直播间。

有时候被某个直播间吸引,停在那里等着优惠、红包和宝贝,随着主播在直播间喊一嗓子,推送一张宝贝卡片,几十万人蜂拥而至秒杀。这样的场景每天都在上演 ...

3.2 互动PK群

主互动玩法“超级星秀猫瓜分20亿红包”开启的时候,意味着大量互动拉赞、分享到好友、分享到群成为流量的入口,无论淘口令/图片等。

大量的消息卡片被转发、二次转发带来的分享裂变效应,每天09:00和晚上22:00成为活跃的峰值,每个组队的用户拉群互赞,这样的场景从10月20日到11月11日持续每天固定时段上演 ...

3.3 不确定性流量

分享面板入口一次列表排布改变,营销活动的一次优惠推送,直播频道页一次运营活动都可能导致直播间/群的流量瞬间波动。

纷涌而至的用户带来了大量的互动行为,各种点击/分享/消息发送纷至沓来。

如何应对这样的流量和规模,作为平台系统,必须能够做到应对不确定性流量能力和机制,必须从流量入口到流量出口端到端考虑整体的全链路架构,是否存在单点瓶颈和缺口。

尤其在大促前期,系统架构梳理、强弱依赖梳理、上下游链路串联、破坏性压测、脉冲流量压测和全链路压测保障和优化,以及配合相关的流量控制、预案保护、业务流量隔离机制、资源调控等手段,才得以做到“不确定性流量”向“确定性流量”转变,才可以更大程度上保障系统高可用和极致用户体验。

4、强互动群聊中的消息架构实践

4.1 传统IM中“写扩散”架构的瓶颈

随着2018年淘系电商首推“双11合伙人计划”,更简单直接的双11玩法,给大众带来更多期待和惊喜。

尤其是盖楼互赞活动更是把“群聊”推上风口浪尖, 在手淘APP内部分享比在微信和钉钉等社交/企业软件分享更加便捷和高效,用户不停地在群内互动分享拉赞、通过好友助力提升盖楼积分。

基于传统的IM架构技术,尤其在群内聊天或者分享,每条消息按照群内人数进行写扩散,按照主互动500人群规模来计算,平均群大小320+,1:N的写入必然导致写入DB的RT以及存储压力,按照DB库承接120w/s写入速度,导致消息上行3K/s的极限,而实际参与互动分享的用户在峰值的时候远大于这部分互动分享和聊天消息流量。

其次集群的写入不可能完全给IM聊天消息,还有其它的营销活动、交易、物流等通知类型的消息。

基于传统IM的“写扩散”架构,在高并发、强互动场景下遇到了瓶颈,导致消息大量的延迟下推,影响最终用户体验。

4.2 “读扩散”架构的应用

基于写扩散架构的消息扩散比是1:N,那能否做到消息扩散比是1:1呢?

答案是肯定的:针对群内的消息可以分为“非个性化消息”和“个性化消息”,所谓“非个性化消息”就是大家看到都是一样的,应该只需要写一条数据,群内成员可以共享取这条数据(所谓“个性化消息”,指定某个成员发送的单边消息,譬如进群欢迎语等)。

在群内,99.99%都是“非个性化消息”,也就是可以从1:N压缩到1:1写入。

基于这个理论假设,需要考虑“读扩散”让每个用户的消息从对应的“群会话消息队列同步“数据,而不是从”用户队列同步“数据。

其中同步队列围绕队列offset偏移量进行,通过队列的自增syncId保证有序,每个客户端维护相应的队列的同步位点,采取“客户端存储位点的去中心化“方案,实现”下行消息的推拉“结合。

通过队列位点syncId进行比对,如果服务端消息队列syncId-客户端队列syncId=1,表示云端消息无空洞,否则携带客户端的队列和对应的syncId到云端重新同步区间数据,实现最终一致性。

传统的IM产品:腾讯QQ、腾讯微信、网易云通讯、抖音IM、钉钉IM、脉脉IM、支付宝IM。

PS:市面上APP80%都具备IM聊天能力,均采取写扩散简单模式进行云端消息同步。

4.3 “读扩散”、“写扩散”技术方案对比

4.3.1)写扩散技术:

优点:

  • 1)整体架构简洁,方案简单,维护用户同步队列实现数据同步机制;
  • 2)无论是单聊还是群聊等会话消息,写入用户维度同步队列,集中获取同步数据;
  • 3)推和拉情况下,存储模型和数据处理简单,且天然支持个性化数据。

缺点:

  • 1)群会话消息,天然存在1:N写入扩散比,存储压力N倍压力,在线用户收到消息延迟增大;
  • 2)多个群内消息队列混合在同步队列,无优先级处理能力,无法针对互动群等做隔离。

4.3.2)读扩散技术:

优点:

  • 1)降低写扩散N倍的DB存储压力,减少下行在线用户端到端扩散的RT时间;
  • 2)提升消息上行集群整体的吞吐量,用户体验更流畅;
  • 3)端到端实现会话级别的同步优先级队列,实现差异化服务。

缺点:

  • 1)整体架构偏复杂,需要维护每个动态会话消息同步队列,端侧需要实时感知新增的动态同步队列;
  • 2)客户端冷启动需要分散读取多个会话消息同步队列数据,对于存储会带来读QPS压力。

4.4 读写扩散混合模式

核心在于架构的演进推进“读写扩散混合模式“落地,结合两者的优点进行云端一体化数据同步技术,覆盖几千万互动群用户,保证整体峰值上行消息以及用户在群内端到端延迟体验,做到一条上行消息500ms以内到达群内其他用户端侧,让整体用户体验提升明显,群内强互动成为可能。

5、电商直播互动中的消息架构实践

5.1 技术挑战

电商直播呈现大直播若干个(大于30W同时在线)、中直播间几百个、小直播几万个这样分布,尤其是晚会和主播带来的热点直播间效应,对系统整体的IM消息架构存在热点带来严重挑战。

热点问题的产生需要从不确定性的流量来源说起。

直播间人员规模天然和群聊不一样(单个群<=500人),一个直播间可以突破40万-200万之间设备同时在线。

直播间在另一个层面是“特殊的群”,群维护了群和群成员强关系,直播间维护直播和设备之间的订阅弱关系,在扩散维度群按照500人进行分库分表切片模式分发下行消息,直播间需要以直播间几十万设备作为分段切片进行分发。同时直播间的指令消息如果不加以干预,会形成超大的流量热点和扩散问题。那么针对这个问题如何应对?

直播间互动全链路和核心处理节点简易时序图如下:

 

即:观众互动消息 -> 网关接入 -> 应用系统Buffer(秒级直播间维度消息) -> 编码、合并、批量消息流 -> 直播间维度设备扩散 -> 设备长连通道推送 -> 设备接收 -> 解码消息 -> 业务处理。

5.2 应对手段

5.2.1)充分利用直播间在线人数:

针对大直播间和小直播间区分对待,充分利用在线人数这个关键性指标。

譬如小直播间不做buffer队列处理(所谓buffer,就是维护topic维度的缓冲队列),支持N秒或消息条数的异步flush机制,实现削峰过程。

其中针对“可靠消息”进行持久化缓存存储,如果当前消息推送失败,重试3次或者下一条消息再次携带失败的可靠消息,整体按照推拉结合的方式,端侧做重复消息去重控制。

5.2.2)用户进出房间关系维护:

从用户第一次进直播间,发起订阅请求,最终通过大小直播间分片进行路由到指定的直播间,针对特殊大直播间提前做好集群分片。

或者通过每天的离线数据分析大直播间的账号,主播开播创建直播间的时候,提前做好分片确定性,在脉冲绑定关系的时候,流量分散到N台订阅集群进行绑定关系建立。

5.2.3)流控策略考虑:

流控不等于一刀切,而是针对不同的subType指令进行控制。

针对可靠消息(红包、优惠、宝贝卡片)等进行持久化存储,利用多次消息下推携带机制,同时兼顾端侧拉取机制,保证消息最终一致性且在3秒以内到达端侧。

5.2.4)一致性hash问题-热点直播间:

一致性Hash的架构必然存在热点问题,如果在直播间进行分散架构,必然存在指令风暴问题。基于此,需要进行Hash热点二次打散处理。

针对这样的热点问题技术上是可行的,热点问题散列到多台IP机器上进行流量承接,另外需要考虑直播间维度的统计数据分布式合并等问题,需要用到分布式锁并发写入统计数据,且直播维度数据合并计算,类似JDKStriped64原理。

6、“群聊”和“直播互动”的消息架构差异思考

因为早期两套系统各自演进应对不同的流量和产品需求,用户规模化和产品要求带来的差异化架构。

对比“群聊”和“直播间互动”两类互动场景,群聊基于消息、会话、会话视图以及附加消息存储和消息漫游能力进行整体设计。而对于直播间来说,围绕直播间大规模实时互动进行抽象设计,充分实现buffer/批量/订阅关系切片等机制。

对于关系来说:群关系是强关系,需要用户确认加群,才可以进群、退群、查看群好友等。对于直播间来说是弱关系,用户进直播、退出直播都是自动完成关系建立和取消,当然用户可以后续进入回放直播间。

因此:在功能上和模型设计上需要进一步思考,尤其现有回放直播间需要一套录制/回放指令机制,保存互动指令等关键性指令数据,而这点在消息IM场景完全支持了,包括用户可以漫游历史消息等。

技术的发展不会一尘不变,针对合适的场景进行差异化架构和设计才是最重要的,同样在应对不确定性流量这个场景下,解决问题的方式是不完全相同的,这个过程需要反复迭代和优化,围绕端到端用户体验这个核心目标进行技术演进才是最重要的价值选择和方向判断。

7、不确定性流量的消息QoS思考

不确定性的流量如何转为确定性流量,是技术必须要解决的“确定性”问题。

尤其面对流量需要确定性进行分解,分优先级保障不同的消息QoS能力是“高可用架构”永恒不变的主题,就像应用系统的限流,需要分场景进行流控一样。

基于这样的思考推演,QoS分级机制来承诺消息服务SLA,最终才可以做到隔离/优先级/差异化处理,完成整体的消息顺滑体验。

 

8、写在最后

面对不确定性的流量,需要进行流量再细分以及面向不同的场景采取不同的处理方案去应对,惯用的手段是指令合并处理、链路隔离和共享、租户存储隔离、流量打标机制区分场景来源等,以及有相对确定性的流量大脑进行观测,在峰值流量来临之前,需要具备流量感知和预警机制,做好流量的优先级划分,应急预案是快速失败限流还是慢速等待消费以及优先级控制等机制。

同时在系统层面,需要做到水平扩展能力,也就是能否通过弹性扩容的方式应对高流量的峰值,另外需要做到整体全链路合理的架构设计,避免“写入放大”以及“读取放大”的单点问题产生,需要针对入口流量和出口流量进行比对,是否可以做到上下游1:1的情况下,尽可能地避免单点瓶颈带来集群瓶颈乃至整体不可用。

“高可用架构“是技术人员永恒不变的主题之一,随着用户规模增长以及集中流量爆发的情形下,更需要敏锐地找到全链路单点问题,提前预判定义出面临的问题。然后给出合理的优化方案,最终灰度切流以及全链路压测验证保证整体方案有序推进,尤其面对的在线系统优化,好比开着飞机换轮胎一样。具备优化效果数据和监控是衡量每一步的预期收益,只有这样才可以做好“架构演进”,才可以持续交付更好的用户产品体验,赢得用户的喜爱,以及互动效果和产品流畅性才可以得到用户满意度提升。架构演进是个动态化的过程,需要持续投入和改进,推动全链路整体落地。

附录:更多相关资料

[1] 有关阿里巴巴的文章分享:

阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处

现代IM系统中聊天消息的同步和存储方案探讨

阿里技术分享:深度揭秘阿里数据库技术方案的10年变迁史

阿里技术分享:阿里自研金融级数据库OceanBase的艰辛成长之路

来自阿里OpenIM:打造安全可靠即时通讯服务的技术实践分享

钉钉——基于IM技术的新一代企业OA平台的技术挑战(视频+PPT) [附件下载]

阿里技术结晶:《阿里巴巴Java开发手册(规约)-华山版》[附件下载]

重磅发布:《阿里巴巴Android开发手册(规约)》[附件下载]

作者谈《阿里巴巴Java开发手册(规约)》背后的故事

《阿里巴巴Android开发手册(规约)》背后的故事

干了这碗鸡汤:从理发店小弟到阿里P10技术大牛

揭秘阿里、腾讯、华为、百度的职级和薪酬体系

淘宝技术分享:手淘亿级移动端接入层网关的技术演进之路

难得干货,揭秘支付宝的2维码扫码技术优化实践之路

淘宝直播技术干货:高清、低延时的实时视频直播技术解密

阿里技术分享:电商IM消息平台,在群聊、直播场景下的技术实践

[2] 有关IM架构设计的文章:

浅谈IM系统的架构设计

简述移动端IM开发的那些坑:架构设计、通信协议和客户端

一套海量在线用户的移动端IM架构设计实践分享(含详细图文)

一套原创分布式即时通讯(IM)系统理论架构方案

从零到卓越:京东客服即时通讯系统的技术架构演进历程

蘑菇街即时通讯/IM服务器开发之架构选择

腾讯QQ1.4亿在线用户的技术挑战和架构演进之路PPT

微信后台基于时间序的海量数据冷热分级架构设计实践

微信技术总监谈架构:微信之道——大道至简(演讲全文)

如何解读《微信技术总监谈架构:微信之道——大道至简》

快速裂变:见证微信强大后台架构从0到1的演进历程(一)

17年的实践:腾讯海量产品的技术方法论

移动端IM中大规模群消息的推送如何保证效率、实时性?

现代IM系统中聊天消息的同步和存储方案探讨

IM开发基础知识补课(二):如何设计大量图片文件的服务端存储架构?

IM开发基础知识补课(三):快速理解服务端数据库读写分离原理及实践建议

IM开发基础知识补课(四):正确理解HTTP短连接中的Cookie、Session和Token

WhatsApp技术实践分享:32人工程团队创造的技术神话

微信朋友圈千亿访问量背后的技术挑战和实践总结

王者荣耀2亿用户量的背后:产品定位、技术架构、网络方案等

IM系统的MQ消息中间件选型:Kafka还是RabbitMQ?

腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面

以微博类应用场景为例,总结海量社交系统的架构设计步骤

快速理解高性能HTTP服务端的负载均衡技术原理

子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践

知乎技术分享:从单机到2000万QPS并发的Redis高性能缓存实践之路

IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列

微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)

微信技术分享:微信的海量IM聊天消息序列号生成实践(容灾方案篇)

新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践

一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践

阿里技术分享:深度揭秘阿里数据库技术方案的10年变迁史

阿里技术分享:阿里自研金融级数据库OceanBase的艰辛成长之路

社交软件红包技术解密(一):全面解密QQ红包技术方案——架构、技术实现等

社交软件红包技术解密(二):解密微信摇一摇红包从0到1的技术演进

社交软件红包技术解密(三):微信摇一摇红包雨背后的技术细节

社交软件红包技术解密(四):微信红包系统是如何应对高并发的

社交软件红包技术解密(五):微信红包系统是如何实现高可用性的

社交软件红包技术解密(六):微信红包系统的存储层架构演进实践

社交软件红包技术解密(七):支付宝红包的海量高并发技术实践

社交软件红包技术解密(八):全面解密微博红包技术方案

社交软件红包技术解密(九):谈谈手Q红包的功能逻辑、容灾、运维、架构等

社交软件红包技术解密(十):手Q客户端针对2020年春节红包的技术实践

社交软件红包技术解密(十一):解密微信红包随机算法(含代码实现)

即时通讯新手入门:一文读懂什么是Nginx?它能否实现IM的负载均衡?

即时通讯新手入门:快速理解RPC技术——基本概念、原理和用途

多维度对比5款主流分布式MQ消息队列,妈妈再也不担心我的技术选型了

从游击队到正规军(一):马蜂窝旅游网的IM系统架构演进之路

从游击队到正规军(二):马蜂窝旅游网的IM客户端架构演进和实践总结

从游击队到正规军(三):基于Go的马蜂窝旅游网分布式IM系统技术实践

IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!

瓜子IM智能客服系统的数据架构设计(整理自现场演讲,有配套PPT)

阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处

微信后台基于时间序的新一代海量数据存储架构的设计实践

IM开发基础知识补课(九):想开发IM集群?先搞懂什么是RPC!

阿里技术分享:电商IM消息平台,在群聊、直播场景下的技术实践

>> 更多同类文章 ……

本文已同步发布于“即时通讯技术圈”公众号:

▲ 本文在公众号上的链接是:点此进入,原文链接是:http://www.52im.net/thread-3252-1-1.html

posted @ 2020-12-18 22:04 Jack Jiang 阅读(133) | 评论 (0)编辑 收藏

     摘要: 本文作者张彦飞,原题“图解Linux网络包接收过程”,内容有少许改动。1、引言因为要对百万、千万、甚至是过亿的用户提供各种网络服务,所以在一线互联网企业里面试和晋升后端开发同学的其中一个重点要求就是要能支撑高并发,要理解性能开销,会进行性能优化。而很多时候,如果你对网络底层的理解不深的话,遇到很多线上性能瓶颈你会觉得狗拿刺猬,无从下手。这篇文章将用图解的方式,从操作系统这一...  阅读全文

posted @ 2020-12-09 15:13 Jack Jiang 阅读(151) | 评论 (0)编辑 收藏

     摘要: 本文由朱益盛、杨晖、傅啸分享,来自IBM Developer社区,原题“使用 Java 开发兼容 IPv6 的网络应用程序”,本次收录时有改动。1、引言前几天,有个群友跟我讨论用 MobileIMSDK 写的IM服务端想支持IPv6的问题。因为众所周之的原因,IPv4早就不够用,现在国内从国家层面都在大力推广IPv6的普及,所以包括事业单位、国企在内,现在搞信息化...  阅读全文

posted @ 2020-12-07 19:31 Jack Jiang 阅读(178) | 评论 (0)编辑 收藏

     摘要: 本文由淘宝直播音视频算法团队原创分享,原题“5G时代|淘宝直播高画质低延时技术探索”,收录时有改动。1、引言目前,5G技术应用正在逐步推进,相比目前广泛使用的4G, 它具有更高的速率,更大的容量,同时延迟更低, 可靠性更高。在5G时代,得益于网络带宽的提升,视频未来将成为主流的传播媒介。越来越多的业务和应用将视频化、直播化。 大量互动的内容将通过5G以低延时的方式以视频的形...  阅读全文

posted @ 2020-11-26 15:41 Jack Jiang 阅读(176) | 评论 (0)编辑 收藏

     摘要: 原作者江成军,原题“还在被Java NIO虐?该试试Netty了”,收录时有修订和改动。1、阅读对象本文适合对Netty一无所知的Java NIO网络编程新手阅读,为了做到这一点,内容从最基本介绍到开发环境的配置,再到第一个Demo代码的编写,事无巨细都用详细的图文进行了说明。所以本文这对于新手来说帮助很大,但对于老司机来说,就没有必要了。老司机请绕道哦。PS:是的,用Ja...  阅读全文

posted @ 2020-11-18 14:35 Jack Jiang 阅读(249) | 评论 (0)编辑 收藏

     摘要: 本文作者Ahab,原题“视频相关的理论知识与基础概念”,收录时有修订和改动。1、引言随着移动互联网的普及,实时音视频技术已经在越来越多的场景下发挥重要作用,已经不再局限于IM中的实时视频聊天、实时视频会议这种功能,在远程医疗、远程教育、智能家居等等场景也司空见惯。虽然实时音视频技术的应用越来越普及,但对于程序员来说,这方面的技术门槛仍然存在(准备地说是仍然很高),想要在短时...  阅读全文

posted @ 2020-11-12 15:45 Jack Jiang 阅读(145) | 评论 (0)编辑 收藏

本文引用了沈剑《如何保证IM实时消息的“时序性”与“一致性”?》一文的图片和内容(由于太懒,图没重新画),原文链接在文末。

1、引言

本文接上篇《零基础IM开发入门(三):什么是IM系统的可靠性?》,讲解IM系统中消息时序的一致性问题。

所谓的一致性,在IM中通常指的是消息时序的一致性,那就是:

  • 1)聊天消息的上下文连续性;
  • 2)聊天消息的绝对时间序。

再具体一点,IM消息的一致性体现在:

  • 1)单聊时:要保证发送方发出聊天消息的顺序与接收方看到的顺序一致;
  • 2)群聊时:要保证所有群员看到的聊天消息,与发送者发出消息时的绝对时间序是一致的。

IM系统中消息时序的一致性问题是个看似简单,实则非常有难度的技术热点话题之一,本文尽量以通俗简显的文字为你讲解IM消息时序一致性问题的产品意义、发生原因、解决思路等

  • 学习交流:

- 即时通讯/推送技术开发交流5群:215477170 [推荐]

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK

(本文同步发布于:http://www.52im.net/thread-3189-1-1.html

2、系列文章

零基础IM开发入门(一):什么是IM系统?

零基础IM开发入门(二):什么是IM系统的实时性?

零基础IM开发入门(三):什么是IM系统的可靠性?

零基础IM开发入门(四):什么是IM系统的消息时序一致性?》(* 本文

《零基础IM开发入门(五):什么是IM系统的安全性? (稍后发布)》

《零基础IM开发入门(六):什么是IM系统的的心跳机制? (稍后发布)》

《零基础IM开发入门(七):如何理解并实现IM系统消息未读数? (稍后发布)》

《零基础IM开发入门(八):如何理解并实现IM系统的多端消息漫游? (稍后发布)》

3、消息时序的一致性,对于IM的意义

现如今,由于移动互联网的普及,现代人的实际社交关系,几乎完全是靠IM这种即时通讯社交工具所组织起来的,IM这种工具的重要性不言而喻。

IM在现代人的生活中,越来越重要,但也越来平常。现在想联系一个人,第一时间想到的不是打个电话,而是发个“微信”或“QQ”。是的,IM这玩意承载的意义越来越多。

消息时序的一致性问题,对于IM的意义,毫无疑问带来的不只是简简单单的所谓用户体验问题。我们来看看例子。

假设:你跟女神的表白正进入到关键阶段,聊着聊着就因为这烂IM,导致聊天消息前言不搭后语,此刻1000公里外你的女神真一脸蒙逼的盯着手机看你的“醉话”,后果是多么严重——这半年来的“舔狗”生活、忍辱负重,算是白白被程序员这群“格子衫”、“地中海”们给祸害了。

再往严重了说,就因为这烂IM,让你失去了这难得的借助女神优良基因,改造家族后代颜值的绝佳机会,无不让人痛心疾首。。。

上面这个例子,说的还只是单聊,如果是群聊,则问题可能还会被无限放大:试想一个技术交流群,正在激烈的争吵着“php是世界最好语言”这种话题的时候,忽然就想砸手机了,不是因为吵的太凶,而因为消息顺序全乱完全没法看,已经严重影响键盘侠们积极发表个人意见了。

4、凭什么说保证消息时序的一致性很困难?

4.1 基本认知

在普通IM用户的眼里,消息无非是从一台手机传递到另一台手机而已,保证时序有何困难?

是的,普通用户这么认为,从技术上讲,他只是单纯的将IM消息的收发过程理解为单线程的工作模式而已。

实际上,在IM这种高性能场景下,服务端为了追求高吞吐、高并发,用到了多线程、异步IO等等技术。

在这种情况下,“高并发”与“顺序”对于IM服务端来说,本来就是矛盾的,这就有点鱼与熊掌的味道了(两者很难兼得)。

所以,要实现IM场景下的消息时序一致性,需要做出权衡,而且要考虑的技术维度相当多。这就导致具体技术实施起来没有固定的套路,而由于开发者技术能力的参差不齐,也就使得很多IM系统在实际的效果上会有较大问题,对于用户而言也将直接在产品体验上反应出来。

下面将具体说明技术难点所在。

4.2 没有全局时钟

如上图所示,一个真正堪用的生产系统,显示不可能所有服务都跑在一台服务器上,分布式环境是肯定的。

那么:在分布式环境下,客户端+服务端后台的各种后台服务,都各自分布在不同的机器上,机器之间都是使用的本地时钟,没有一个所谓的“全局时钟”(也没办法做到真正的全局时钟),那么所谓的消息时序也就没有真正意义上的时序基准点。所以消息时序问题显然不是“本地时间”可以完全决定的。

4.3 多发送方问题

服务端分布式的情况下,不能用“本地时间”来保证时序性,那么能否用接收方本地时间表示时序呢?

遗憾的是,由于多个客户端的存在(比如群聊时),即使是一台服务器的本地时间,也无法表示“绝对时序”。

如上图所示:绝对时序上,APP1先发出msg1,APP2后发出msg2,都发往服务器web1,网络传输是不能保证msg1一定先于msg2到达的,所以即使以一台服务器web1的时间为准,也不能精准描述msg1与msg2的绝对时序。

4.4 多接收方问题

多发送方不能保证时序,假设只有一个发送方,能否用发送方的本地时间表示时序呢?遗憾的是,由于多个接收方的存在,无法用发送方的本地时间,表示“绝对时序”。

如上图,绝对时序上,web1先发出msg1,后发出msg2,由于网络传输及多接收方的存在,无法保证msg1先被接收到先被处理,故也无法保证msg1与msg2的处理时序。

4.5 网络传输与多线程问题

既然多发送方与多接收方都难以保证绝对时序,那么假设只有单一的发送方与单一的接收方,能否保证消息的绝对时序一致性呢?

结论是悲观的,由于网络传输与多线程的存在,这仍然不行。

如上图所示,web1先发出msg1、后发出msg2,即使msg1先到达(网络传输其实还不能保证msg1先到达),由于多线程的存在,也不能保证msg1先被处理完。

5、如何保证绝对的消息时序一致性?

通过上一章内容的总结,我们已经对IM中消息时序一致性问题所产生的缘由,有了较为深刻的认识。

从纯技术的角度来说,假设:

  • 1)只有一个发送方;
  • 2)一个接收方;
  • 3)上下游连接只有一条socket连接;
  • 4)通过阻塞的方式通讯。

这样的情况下,难道不能保证先发出的消息被先处理,进而被先展示给消息的接收者吗?

是的,可以!

但实际生产情况下不太可能出现这种IM系统,必竟单发送方、单接收方、单socket连接、阻塞方式,这样的IM一旦做出来,产品经理会立马死给你看。。。

6、实用的优化思路

6.1 一对一单聊的消息一致性保证思路

假设两人一对一聊天,发送方A依次发出了msg1、msg2、msg3三条消息给接收方B,这三条消息该怎么保证显示时序的一致性(发送与显示的顺序一致)?

我们知道,发送方A依次发出的msg1、msg2、msg3三条消息,到底服务端后,再由服务端中转发出时,这个顺序由于多线程的网络的问题,是有可能乱序的。

那么结果就可能是这样: 

如上图所示,会出现与发出时的消息时序不一致问题(收到的消息顺序是:msg3、msg1、msg2)。

不过,实际上一对一聊天的两个人,并不需要全局消息时序的一致(因为聊天只在两人的同一会话在发生),只需要对于同一个发送方A,发给B的消息时序一致就行了。

常见优化方案,在A往B发出的消息中,加上发送方A本地的一个绝对时序(比如本机时间戳),来表示接收方B的展现时序。

那么当接收方B收到消息后,即使极端情况下消息可能存在乱序到达,但因为这个乱序的时间差对于普通用户来说体感是很短的,在UI展现层按照消息中自带的绝对时序排个序后再显示,用户其实是没有太多感知的。

6.2 多对多群聊的消息一致性保证思路

假设N个群友在一个IM群里聊天,应该怎样保证所有群员收到消息的显示时序一致性呢?

首先:不能像一对聊天那样利用发送方的绝对时序来保证消息顺序,因为群聊发送方不单点,时间也不一致。

或许:我们可以利用服务器的单点做序列化。

如上图所示,此时IM群聊的发送流程为:

  • 1)sender1发出msg1,sender2发出msg2;
  • 2)msg1和msg2经过接入集群,服务集群;
  • 3)service层到底层拿一个唯一seq,来确定接收方展示时序;
  • 4)service拿到msg2的seq是20,msg1的seq是30;
  • 5)通过投递服务讲消息给多个群友,群友即使接收到msg1和msg2的时间不同,但可以统一按照seq来展现。

这个方法:

  • 1)优点是:能实现所有群友的消息展示时序相同;
  • 2)缺点是:这个生成全局递增序列号的服务很容易成为系统瓶颈。

还有没有进一步的优化方法呢?

从技术角度看:群消息其实也不用保证全局消息序列有序,而只要保证一个群内的消息有序即可,这样的话,“消息id序列化”就成了一个很好的思路。

上图这个方案中,service层不再需要去一个统一的后端拿全局seq(序列号),而是在service连接池层面做细小的改造,保证一个群的消息落在同一个service上,这个service就可以用本地seq来序列化同一个群的所有消息,保证所有群友看到消息的时序是相同的。

关于IM的系统架构下使用怎么样实现消息序列化,或者说全局消息ID的生成方案,这又是另一个很热门的技术话题。

有兴趣,可以深入阅读下面这个系列:

IM消息ID技术专题(一):微信的海量IM聊天消息序列号生成实践(算法原理篇)

IM消息ID技术专题(二):微信的海量IM聊天消息序列号生成实践(容灾方案篇)

IM消息ID技术专题(三):解密融云IM产品的聊天消息ID生成策略

IM消息ID技术专题(四):深度解密美团的分布式ID生成算法

IM消息ID技术专题(五):开源分布式ID生成器UidGenerator的技术实现

IM消息ID技术专题(六):深度解密滴滴的高性能ID生成器(Tinyid)

这个系列中,尤其微信的趋势递增ID生成思路(注意:趋势递增不是严格递增,趋势递增意味着中问有ID被跳过也没事),对于分布式IM的消息ID来说是非常切实可行的。

是的,对于IM系统来说,绝对意义上的时序很难保证,但通过服务端生成的单调递增消息ID的方式,利用递增ID来保证时序性,也是一个很可性的方案。

7、小结一下

IM系统架构下,消息的绝对时序是很困难的,原因多种多样,比如:没有全局时钟、多发送方、多接收方、多线程、网络传输不确定性等。

一对一单聊时,其实只需要保证发出的时序与接收的时序一致,就基本能让用户感觉不到乱序了。

多对多的群聊情况下,保证同一群内的所有接收方消息时序一致,也就能让用户感觉不到乱序了,方法有两种,一种单点绝对时序,另一种实现消息id的序列化(也就是实现一种全局递增消息ID)。

8、参考资料

[1] 如何保证IM实时消息的“时序性”与“一致性”?,作者:沈剑

[2] 一个低成本确保IM消息时序的方法探讨,作者:封宇

附录:更多IM开发热门技术点

移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”

移动端IM开发者必读(二):史上最全移动弱网络优化方法总结

现代移动端网络短连接的优化手段总结:请求速度、弱网适应、安全保障

移动端IM中大规模群消息的推送如何保证效率、实时性?

移动端IM开发需要面对的技术问题

开发IM是自己设计协议用字节流好还是字符流好?

请问有人知道语音留言聊天的主流实现方式吗?

如何保证IM实时消息的“时序性”与“一致性”?

一个低成本确保IM消息时序的方法探讨

IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?

IM群聊消息如此复杂,如何保证不丢不重?

谈谈移动端 IM 开发中登录请求的优化

移动端IM登录时拉取数据如何作到省流量?

浅谈移动端IM的多点登录和消息漫游原理

完全自已开发的IM该如何设计“失败重试”机制?

微信对网络影响的技术试验及分析(论文全文)

IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列

微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)

IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!

IM里“附近的人”功能实现原理是什么?如何高效率地实现它?

IM的扫码登录功能如何实现?一文搞懂主流应用的扫码登录技术原理

IM开发宝典:史上最全,微信各种功能参数和逻辑规则资料汇总

本文已同步发布于“即时通讯技术圈”公众号。

▲ 本文在公众号上的链接是:点此进入,原文链接是:http://www.52im.net/thread-3189-1-1.html

posted @ 2020-11-04 14:31 Jack Jiang 阅读(126) | 评论 (0)编辑 收藏

本文编写时引用了“聊聊IM系统的即时性和可靠性”一文的部分内容和图片,感谢原作者。

1、引言

上一篇《零基础IM开发入门(二):什么是IM系统的实时性?》讲到了IM系统的“立足”之本——“实时性”这个技术特征,本篇主要讲解IM系统中的“可靠性”这个话题,内容尽量做到只讲原理不深入展开,避开深层次的技术性探讨,确保通俗易懂。


阅读对象:本系列文章主要阅读对象为零IM基础的开发者或产品经理,目标是告诉你“IM系统是什么?”,尽量不深入探讨具体的技术实现,确保通俗易懂,老少皆宜。

如您想从技术维度系统学习IM技术并着手自已的IM开发(即解决“IM系统要怎么做?”这个疑问),请从此文开始:《新手入门一篇就够:从零开发移动端IM》。

学习交流:

- 即时通讯/推送技术开发交流5群:215477170[推荐]

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK

(本文同步发布于:http://www.52im.net/thread-3182-1-1.html

2、系列文章

零基础IM开发入门(一):什么是IM系统?

零基础IM开发入门(二):什么是IM系统的实时性?

零基础IM开发入门(三):什么是IM系统的可靠性?》(* 本文

零基础IM开发入门(四):什么是IM系统的消息时序一致性?

《零基础IM开发入门(五):什么是IM系统的安全性? (稍后发布)》

《零基础IM开发入门(六):什么是IM系统的的心跳机制? (稍后发布)》

《零基础IM开发入门(七):如何理解并实现IM系统消息未读数? (稍后发布)》

《零基础IM开发入门(八):如何理解并实现IM系统的多端消息漫游? (稍后发布)》

3、正文概述

一般来说,IM系统的消息“可靠性”,通常就是指聊天消息投递的可靠性(准确的说,这个“消息”是广义的,因为还存用户看不见的各种指令,为了通俗,统称“消息”)。

从用户行为来讲,消息“可靠性”应该分为两种类型:

  • 1)在线消息的可靠性:即发送消息时,接收方当前处于“在线”状态;
  • 2)离线消息的可靠性:即发送消息时,接收方当前处于“离线”状态。

从具体的技术表现来讲,消息“可靠性”包含两层含义:

  • 1)消息不丢:这很直白,发出去的消息不能像进了黑洞一样,一脸懵逼可不行;
  • 2)消息不重:这是丢消息的反面,消息重复了也不能容忍。

对于“消息不丢”这个特征来说,细化下来,它又包含两重含义:

  • 1)已明确被对方收到;
  • 2)已明确未被对方收到。

是的,对于第1)重含义好理解,第2)重含义的意思是:当对方没有成功收到时,你的im系统也必须要感知到,否则,它同样属于被“丢”范畴。

总之,一个成型的im系统,必须包含这两种消息“可靠性”逻辑,才能堪用,缺一不可。

消息的可靠性(不丢失、不重复)无疑是IM系统的重要指标,也是IM系统实现中的难点之一。本文以下文字,将从在线消息的可靠性和离线消息的可靠性进行讨论。

4、典型的在线消息收发流程

先看下面这张典型的im消息收发流程: 

是的,这是一个典型的服务端中转型IM架构。

所谓“服务端中转型IM架构”是指:一条消息从客户端A发出后,需要先经过 IM 服务器来进行中转,然后再由 IM 服务器推送给客户端B,这种模式也是目前最常见的 IM 系统的消息分发架构。

你可能会说,IM不可以是P2P模式的吗?是的,目前来说主流IM基本都是服务器中转这种方式,P2P模式在IM系统中用的很少。

原因是以下两个很明显的弊端:

  • 1)P2P模式下,IM运营者很容易被用户架空(无法监管到用户行为,用户涉黄了怕不怕?);
  • 2)P2P模式下,群聊这种业务形态,很难实现(我要在千人群中发消息给,不可能我自已来分发1000次吧)。

话题有点跑偏,我们回到正题:在上面这张图里,客户A发送消息到服务端、服务端中转消息给客户B,假设这两条数据链接中使用的通信协议是TCP,你认为在TCP所谓可靠传输协议加持下,真的能保证IM聊天消息的可靠性吗?

答案是否定的。我们继续看下节。

5、TCP并不能保证在线消息的“可靠性”

接上节,在一个典型的服务端中转型IM架构中,即使使用“可靠的传输协议”TCP,也不能保证聊天消息的可靠性。为什么这么说?

要回答这个问题,网上的很多文章,都会从服务端的角度举例:比如消息发送时操作系统崩溃、网络闪断、存储故障等等,总之很抽象,不太容易理解。

这次我们从客户端角度来理解,为什么使用了可靠传输协议TCP的情况下IM聊天消息仍然不可靠的问题。

具体来说:如何确保 IM 消息的可靠性是个相对复杂的话题,从客户端发送数据到服务器,再从服务器送达目标客户端,最终在 UI 成功展示,其间涉及的环节很多,这里只取其中一环「接收端如何确保消息不丢失」来探讨,粗略聊下我接触过的两种设计思路。

说到可靠送达:第一反应会联想到 TCP 的可靠性。数据的可靠送达是个通用性的问题,无论是网络二进制流数据,还是上层的业务数据,都有可靠性保障问题,TCP 作为网络基础设施协议,其可靠性设计的可靠性是毋庸置疑的,我们就从 TCP 的可靠性说起。

在 TCP 这一层:所有 Sender 发送的数据,每一个 byte 都有标号(Sequence Number),每个 byte 在抵达接收端之后都会被接收端返回一个确认信息(Ack Number), 二者关系为 Ack = Seq + 1。简单来说,如果 Sender 发送一个 Seq = 1,长度为 100 bytes 的包,那么 receiver 会返回一个 Ack = 101 的包,如果 Sender 收到了这个Ack 包,说明数据确实被 Receiver 收到了,否则 Sender 会采取某种策略重发上面的包。

第一个问题是:既然 TCP 本身是具备可靠性的,为什么还会出现消息接收端(Receiver)丢失消息的情况?

看下图一目了然:

▲ 上图引用自《从客户端的角度来谈谈移动端IM的消息可靠性和送达机制

一句话总结上图的含义:网络层的可靠性不等同于业务层的可靠性。

数据可靠抵达网络层之后,还需要一层层往上移交处理,可能的处理有:

  • 1)安全性校验;
  • 2)binary 解析;
  • 3)model 创建;
  • 4)写 db;
  • 5)存入 cache;
  • 6)UI 展示;
  • 7)以及一些边界问题:比如断网、用户突然退出登陆、磁盘已满、内存溢出、app奔溃、突然关机等等。

项目的功能特性越多,网络层往上的处理出错的可能性就越大。

举个最简单的场景为例子:消息可靠抵达网络层之后,写 db 之前 IM APP 崩溃(不稀奇,是 App 都有崩溃的可能),虽然数据在网络层可靠抵达了,但没存进 db,下次用户打开 App 消息自然就丢失了,如果不在业务层再增加可靠性保障(比如:后面要提到的网络层面的消息重发保障),那么意味着这条消息对于接收端来说就永远丢失了,也就自然不存在“可靠性”了。

从客户端角度理解IM的可能性以及解决办法,可以详细阅读:从客户端的角度来谈谈移动端IM的消息可靠性和送达机制》,本节引用的是该文中“4、TCP协议的可靠性之外还会出现消息丢失?”一节的文字。

6、为在线消息增加“可靠性”保障

那么怎样在应用层增加可靠性保障呢?

有一个现成的机制可供我们借鉴:TCP协议的超时、重传、确认机制。

具体来说就是:

  • 1)在应用层构造一种ACK消息,当接收方正确处理完消息后,向发送方发送ACK;
  • 2)假如发送方在超时时间内没有收到ACK,则认为消息发送失败,需要进行重传或其他处理。

增加了确认机制的消息收发过程如下: 

我们可以把整个过程分为两个阶段。

阶段1:clientA -> server

  • 1-1:clientA向server发送消息(msg-Req);
  • 1-2:server收取消息,回复ACK(msg-Ack)给clientA;
  • 1-3:一旦clientA收到ACK即可认为消息已成功投递,第一阶段结束。

无论msg-A或ack-A丢失,clientA均无法在超时时间内收到ACK,此时可以提示用户发送失败,手动进行重发。

阶段2:server -> clientB

  • 2-1:server向clientB发送消息(Notify-Req);
  • 2-2:clientB收取消息,回复ACK(Notify-ACk)给server;
  • 2-3:server收到ACK之后将该消息标记为已发送,第二阶段结束。

无论msg-B或ack-B丢失,server均无法在超时时间内收到ACK,此时需要重发msg-B,直到clientB返回ACK为止。

关于IM聊天消息的可靠性保障问的深入讨论,可以详读:IM消息送达保证机制实现(一):保证在线实时消息的可靠投递》,该文会深入讨论这个话题。

7、典型的离线消息收发流程

说完在线消息的“可靠性”问题,我们该了解一下离线消息了。

7.1 离线消息的收发也存在“不可靠性”

下图是一张典型的IM离线消息流程图:

如上图所示,和在线消息收发流程类似。

离线消息收发流程也可划分为两个阶段:

阶段1:clientA -> server

  • 1-1:clientA向server发送消息(msg-Req) ;
  • 1-2:server发现clientB离线,将消息存入offline-DB。

阶段2:server -> clientB

  • 2-1:clientB上线后向server拉取离线消息(pull-Req) ;
  • 2-2:server从offline-DB检索相应的离线消息推送给clientB(pull-res),并从offline-DB中删除。

显然:离线消息收发过程同样存在消息丢失的可能性。

举例来说:假设pull-res没有成功送达clientB,而offline-DB中已删除,这部分离线消息就彻底丢失了。

7.2 离线消息的“可靠性”保障

与在线消息收发流程类似,我们同样需要在应用层增加可靠性保障机制。

下图是增加了可靠性保障后的离线消息收发流程: 

与初始的离线消息收发流程相比,上图增加了1-3、2-4、2-5步骤:

  • 1-3:server将消息存入offline-DB后,回复ACK(msg-Ack)给clientA,clientA收到ACK即可认为消息投递成功;
  • 2-4:clientB收到推送的离线消息,回复ACK(res-Ack)给server;
  • 2-5:server收到res-ACk后确定离线消息已被clientB成功收取,此时才能从offline-DB中删除。

当然,上述的保障机制,还存在性能优化空间。

当离线消息的量较大时:如果对每条消息都回复ACK,无疑会大大增加客户端与服务器的通信次数。这种情况我们通常使用批量ACK的方式,对多条消息仅回复一个ACK。在某此后IM的实现中是将所有的离线消息按会话进行分组,每组回复一个ACK,假如某个ACK丢失,则只需要重传该会话的所有离线消息。

有关离线消息的可靠性保障机制的详细讨论,可以详读:IM消息送达保证机制实现(二):保证离线消息的可靠投递》、《IM开发干货分享:如何优雅的实现大量离线消息的可靠投递》,这两篇文章可以给你更深入具体的答案。

8、聊天消息重复的问题

上面章节中,通过在应用层加入重传、确认机制后,我们确实是杜绝了消息丢失的可能性。

但由于重试机制的存在,我们会遇到一个新的问题:那就是同一条消息可能被重复发送。

举一个最简单的例子:假设client成功收到了server推送的消息,但其后续发送的ACK丢失了,那么server将会在超时后再次推送该消息,如果业务层不对重复消息进行处理,那么用户就会看到两条完全一样的消息。

消息去重的方式其实非常简单,一般是根据消息的唯一标志(id)进行过滤。

具体过程在服务端和客户端可能有所不同:

  • 1)客户端 :我们可以通过构造一个map来维护已接收消息的id,当收到id重复的消息时直接丢弃;
  • 2)服务端 :收到消息时根据id去数据库查询,若库中已存在则不进行处理,但仍然需要向客户端回复Ack(因为这条消息很可能来自用户的手动重发)。

关于消息的去重问题,在一对一聊天的情况下,逻辑并不复杂,但在群聊模式下,会将问题复杂化,有关群聊消息不丢和去重的详细讨论,可以深入阅读:《IM群聊消息如此复杂,如何保证不丢不重?》。

9、本文小结

保证消息的可靠性是IM系统设计中很重要的一环,能不能做到“消息不丢”、“消息不重”,对用户的体验影响极大。

所谓“可靠的传输协议”TCP也并不能保障消息在应用层的可靠性。

我们一般通过在应用层的ACK应答和重传机制,来实现IM消息的可靠性保障。但由此带来的消息重复问题,需要我们额外进行处理,最简单的方法就是通过消息ID进行幂等去重。

关于IM系统消息可靠性的理论基础,我们就探讨到这里,有疑问的读者,可以在本文末尾留意,欢迎积极讨论。

10、参考资料

[1] IM消息送达保证机制实现(一):保证在线实时消息的可靠投递

[2] IM消息送达保证机制实现(二):保证离线消息的可靠投递

[3] IM开发干货分享:如何优雅的实现大量离线消息的可靠投递

[4] 从客户端的角度来谈谈移动端IM的消息可靠性和送达机制

[5] 聊聊IM系统的即时性和可靠性

[6] 学习笔记4——IM系统如何保证消息的可靠性

[7] IM群聊消息如此复杂,如何保证不丢不重?

附录:更多IM开发热门技术点

移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”

移动端IM开发者必读(二):史上最全移动弱网络优化方法总结

现代移动端网络短连接的优化手段总结:请求速度、弱网适应、安全保障

移动端IM中大规模群消息的推送如何保证效率、实时性?

移动端IM开发需要面对的技术问题

开发IM是自己设计协议用字节流好还是字符流好?

请问有人知道语音留言聊天的主流实现方式吗?

如何保证IM实时消息的“时序性”与“一致性”?

一个低成本确保IM消息时序的方法探讨

IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?

IM群聊消息如此复杂,如何保证不丢不重?

谈谈移动端 IM 开发中登录请求的优化

移动端IM登录时拉取数据如何作到省流量?

浅谈移动端IM的多点登录和消息漫游原理

完全自已开发的IM该如何设计“失败重试”机制?

微信对网络影响的技术试验及分析(论文全文)

IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列

微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)

IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!

IM里“附近的人”功能实现原理是什么?如何高效率地实现它?

IM的扫码登录功能如何实现?一文搞懂主流应用的扫码登录技术原理

IM消息ID技术专题(一):微信的海量IM聊天消息序列号生成实践(算法原理篇)

IM消息ID技术专题(二):微信的海量IM聊天消息序列号生成实践(容灾方案篇)

IM消息ID技术专题(三):解密融云IM产品的聊天消息ID生成策略

IM消息ID技术专题(四):深度解密美团的分布式ID生成算法

IM消息ID技术专题(五):开源分布式ID生成器UidGenerator的技术实现

IM消息ID技术专题(六):深度解密滴滴的高性能ID生成器(Tinyid)

IM开发宝典:史上最全,微信各种功能参数和逻辑规则资料汇总

本文已同步发布于“即时通讯技术圈”公众号。

▲ 本文在公众号上的链接是:点此进入,原文链接是:http://www.52im.net/thread-3182-1-1.html

posted @ 2020-10-29 14:09 Jack Jiang 阅读(105) | 评论 (0)编辑 收藏

     摘要: 本文原题“Node.js - 200 多行代码实现 Websocket 协议”,为了提升内容品质,有较大修订。1、引言最近正在研究 WebSocket 相关的知识,想着如何能自己实现 WebSocket 协议。到网上搜罗了一番资料后用 Node.js 实现了一个WebSocket协议服务器,倒也没有想象中那么复杂,除去注释语句和 console 语句后,大约 200 行代码...  阅读全文

posted @ 2020-10-21 14:41 Jack Jiang 阅读(171) | 评论 (0)编辑 收藏

     摘要: 本文原题“WebSocket:5分钟从入门到精通”,作者“程序猿小卡_casper”,原文链接见文末参考资料部分。本次收录时有改动。1、引言自从HTML5里的WebSocket出现后,彻底改变了以往Web端即时通讯技术的基础通道这个“痛点”(在此之前,开发者们不得不弄出了诸如:短轮询、长轮询、Comet、SSE等技术,可谓苦之...  阅读全文

posted @ 2020-10-14 14:40 Jack Jiang 阅读(296) | 评论 (0)编辑 收藏

     摘要: 本文由融云技术团队原创投稿,作者是融云WebRTC高级工程师苏道,转载请注明出处。1、引言在一个典型的IM应用里,使用实时音视频聊天功能时,视频首帧的显示,是一项很重要的用户体验指标。本文主要通过对WebRTC接收端的音视频处理过程分析,来了解和优化视频首帧的显示时间,并进行了总结和分享。(本文同步发布于:http://www.52im.net/thread-3169-1-1.html)2、什么是...  阅读全文

posted @ 2020-09-28 22:17 Jack Jiang 阅读(158) | 评论 (0)编辑 收藏

本文引用自“蚂蚁金服科技”公众号,原文由支付宝技术团队原创分享。 本次收录时有改动。

1、引言

最早接触2维码扫码功能,是在2011年,那会移动互联网正是起步阶段,大家都感觉智能手机可以更强大,但到底要做些什么功能,都是在探索和创新。2维码扫描功能就是这些创新功能之一。

当然,2维码扫描说到底还是图像识别,这种技术不是一般的公司能搞定的,所以大家用的最多的2维码扫描库就是ZXing。这个库,很多人应该非常熟悉,用过这个库的人,基本都记住了下面这个图片(ZXing的logo)。

▲ ZXing工程的logo

这个库的使用前题就是需要手机摄像头有自动对焦功能,那会手机成本还没现在这么低,所以自动对焦功能不是所有手机都具备,也就限制了2维码扫码功能在一些较低端的手机上的使用,同时也制约了扫码功能的普及。

后来的事情大家都知道了,微信这种社交IM越来越受欢迎,微信里可以用来扫码加好友的“扫一扫”功能,让2维码扫描功能几乎变成了IM软件的标配。

 

▲ 早期微信里的“扫一扫”功能

现在的微信,不仅可以扫2维友加好友,还可以扫码支付以及各种图像识别方面的功能,越做越丰富。2维码扫码功能从一个单一的图像识别技术,逐渐演变成了移动线联网的入口功能。

从去年开始,微信对2维码扫描功能进行了升级,不仅可以在UI上让用户知道被扫描2维码的中心点,还能同时识别最多3个2维码,相当强大。

 

▲ 现在的微信可以同时识别最多3个2维码(注意绿点)

如上图所示,原本大家都习以为常的2维码扫描功能,原来还可以做的这么友好。

正好看到支付宝分享的这篇2维码扫描优化文章,市面上真正分享这方面的技术的文章几乎没有,而2维码扫码加友作为IM中最常见的功能之一,对于即时通讯网的开发者来说,虽然不需要自已从底层开发,但了解这方面的知识,还是很有必要的。

本文要分享的是支付宝针对2维码扫描功能,在2维码残缺、变形、变色等等恶劣条件下,是如何提升扫码识别率、识别速度的技术实践总结。希望能带给你启发。

学习交流:

- 即时通讯/推送技术开发交流5群:215477170[推荐]

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK

(本文同步发布于:http://www.52im.net/thread-3150-1-1.html

2、技术背景

随着支付宝的线下场景不断扩大,收钱码、口碑、共享单车、充电宝、停车缴费等产品让我们的生活越来越便利。

二维码因为成本低、兼容性好成为了线上线上最主要的连接工具,也因此面临更多新的挑战。

因为二维码是一种点阵式信息编码方式,任何视觉上的缺损、弯曲以及光线作用都会极大的影响识别成功率,如果识别困难也就意味着用户可能选择放弃,影响支付体验也影响用户心智。

用户扫码体验的最关键的主要有以下几个因素:

  • 1)识别率:这是扫码服务的基础指标,识别率能直接体现识别能力,识别率如果无法提高意味着大量的用户将无法使用更便捷的服务;
  • 2)识别耗时:包括 app 启动耗时以及图像识别耗时,这是衡量一个用户从点击 app 到正确识别到内容耗时,每增加 1s,将有相当大量的用户放弃等待并离开;
  • 3)精准反馈:识别结果不仅需要及时反馈给用户,还需要非常精准,特别是在目前线下有多个二维码的场景下,需要避免用户二次操作。

本文将从以上三个方面,分享支付宝扫码技术团队是如何为用户打造一个又准又快又稳的极致扫码体验。

我们对用户反馈进行了大量统计分析,发现绝大部分识别失败都是因为二维码并不标准,并且很遗憾的是在使用我们早期的扫码版本进行识别率测试时发现识别率只有 60%。下面的文字,将首先从提高识别率的方向着手。

3、提高识别率策略1:优化桩点查找算法长宽比耐受

以往的扫码算法,检查长宽比例时允许差异 40%,但是由于使用前向误差,判断结果跟长宽的先后顺序相关,这会导致有些长宽比失调的码,横着扫不出来,但是旋转 90 度竖着却能扫出来了(^OMG^)。

优化策略总结:

  • 1)通过修改长宽比的判定规则,长宽比将不再受先后顺序影响;
  • 2)对于已知长度,修改规则将可接受的宽度范围扩大,增强长宽比的耐受。

在我们对比测试集中,识别率提高了 1% 左右。

4、提高识别率策略2:新增1:5:1桩点识别模式模式

在一张图片中,要找到二维码,关键在找二维码特征定位点: 

三个角的回字型图案,这就是二维码特征定位点。

中间区域的黑白色块比例是1:1:3:1:1:

以往的扫码算法,桩点识别是通过状态机 查找11311模式后 取中间位置确定x位置(此时扫描线在第一行11311比例处)在x位置纵向搜索11311模式, 确定y位置再以 (x,y) 位置横向搜索11311比例,修正x位置。

这种模式在桩点污损的情况下,识别能力较差只要在任何一次11311模式搜索中遇到干扰点,哪怕是一个像素的椒盐噪声也能使桩点查找失败。(支付宝蓝的桩点,会在蓝色区域产生大量噪点,导致识别率低下)

为此,我们新增了一种桩点识别方式。在状态机达到151模式的时候,开始尝试确认桩点。(此时扫描线在第一行151比例处)。

优化效果:

  • 1)新的查找方法将不再受桩点中心或边缘部分被污损的影响,支付宝蓝色桩点码识别率明显提升;
  • 2)修改后识别率整体提升了接近 1%,但识别失败的耗时有所提升。

5、提高识别率策略3:添加一种对角线过滤规则

在枚举所有可能桩点组合 O(N^3) 之前,对所有可疑桩点进行一次对角线检查过滤。由于桩点对角线也应该满足 11311模式 ,用这个规则做一次过滤可疑有效减少运算量,也就有效降低了识别成功和失败的耗时。

6、提高识别率策略4:基于 Logistic Regression 的二维码分类器

在以往的扫码算法中在拿到三个桩点后,基于夹角,长度偏差,单位长度查三个数值,用简单公式计算得到阈值,判断是否为可能的二维码,误判概率较大。

为此,我们引入机器学习中的逻辑回归算法模型。

基于支付宝丰富的二维码数据集,训练出逻辑回归模型,作为二维码分类器,明显降低了误判概率,也将明显降低无二维码时识别失败的耗时。

7、提高识别率策略5:修改跳行扫描的间隔数

由于输入的相机帧分辨率高,像素点多,运算量大,以往的扫码算法在水平跟垂直方向跳行采样进行计算。但在实际运算中,由于跳过了太多列,错过了11311模式中某些1位置的点,导致桩点查找失败。

我们通过将跳行计算行数修改为可配置项,通过线上 AB 灰度测试得到最合适的跳行策略,整体配置此跳行策略后,识别率得到明显提升。

上述优化在测试集的表现: 

综上优化:扫码核心识别能力,在7744张图片测试集上提高了6.95个百分点。

8、特殊策略优化

除此上述通用扫码优化之外,我们还对特殊场景扫码能力进行提高。

8.1 畸变?不怕不怕!

线下场景复杂多变。饮料瓶身上变形的二维码、超市小票卷起边角弯曲的二维码、路边小贩凹凸不平甚至折叠的二维码......这些畸变的二维码容易增加识别难度,甚至导致识别失败。

以往的扫码算法抗畸变策略中,先用透视变换关系建立映射关系。

优点是:适应性好,满足大多数应用场景。

不足也明显:对 Version 1 的码,因为映射关系退化为仿射变换,效果较差,手机必须和码平面平行才能方便识别。当物料表面不是平面的时候,效果较差。

优化策略:

  • 1)假设采样坐标系到二维码坐标系遵守一个更复杂的映射关系,并且假设物料表面的卷曲较小,通过使用二次函数可以较好的拟合这个映射关系;
  • 2)实际发票上的二维码版本普遍大于等于 7,高版本二维码具有多个辅助定位点,更利于构造二次映射表;
  • 3)基于以上推论,使用新的映射代替旧的透视变换,进行更精准的采样。

用新的策略,发票码这个场景的二维码识别能力提升明显。

▲注意:由于采用了增强算法,请对准二维码稍作等待

样本测试结果:

8.2 容错识别能力提升

商户或者供应商生成二维码后,通常会在二维码的中间部分贴上 Logo,这部分有可能会使二维码 Decode 时出错。

优化策略:

对于采样后拿到的 BitMatrix,对于中间部分一块矩形区域内的点,采用某些策略来改变中间点的值,使它能够通过容错边界的检查。目前采用两种策略,第一种是反转,第二种是每一个点随机取值。目前所取的矩形区域是长、宽的四分之一。

通过此项优化后,扫码的容错能力也得到明显提升。

9、更小的识别耗时

针对识别效率,我们使用了GPU计算二值化,降低识别单帧耗时。

所谓图像二值化就是将图像上的像素点的灰度值设置为 0 或 255,也就是将整个图像呈现出明显的只有黑和白的视觉效果。下图左边为原图,右边是二值化处理过的图。

在扫码算法解码前,有二值化计算,图像的二值化计算能使图像中数据量大为减少,并弱化图像模糊、颜色对比度不强、光线过强/太弱、图像污损等情况下其他信息的干扰,更利于检测识别。

传统算法是在 CPU 上进行二值化运算,非常消耗 CPU 资源,但其实 GPU 更擅长大规模并行计算,所以我们选择使用 GPU 来做二值化计算。在安卓平台上使用 RenderScript,iOS 平台上使用 Metal,都是很底层的框架。

1)iOS优化结果:统一电池、角度、光线等环境变量, 在iPhone6上测试扫码核心5种摄像头二值化算法。

表现如下: 

 

可以看出,在图像二值化方面 Metal 有相当高的优势,相比原来的单纯 CPU 处理快了接近 150%, 同时降低了近50个百分点的CPU资源。

2)Andriod优化结果:由于Android机型众多,我们抽取了线上数据,可以看到GPU 在二值化处理中显著降低了单帧耗时30%以上。 

10、算法分级、场景分类、科学调度

线下物料千奇百怪,扫码算法为了解决一些不理想的场景,如二维码有遮挡、污损、模糊或角度很不好的特殊情况,需要使用一些比较耗时但比较强大的算法,但普通情况不需要这些算法。

所以,我们对识码算法定了优先级,通过时间推移、跳帧触发等方式调度。

优先级:

  • 1)高优先级:每帧执行;
  • 2)中优先级:降帧率执行;
  • 3)低优先级:低帧率执行。

不同优先级的功能执行时机可配置。不同功能属于哪个优先级可配置

特殊场景算法能力:

  • 1)反色码识别能力;
  • 2)容错边界码识别能力;
  • 3)污损桩点识别能力等;
  • 4)条码识别能力。

附录:更多阿里团队分享的文章

社交软件红包技术解密(七):支付宝红包的海量高并发技术实践

阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处

来自阿里OpenIM:打造安全可靠即时通讯服务的技术实践分享

淘宝技术分享:手淘亿级移动端接入层网关的技术演进之路

阿里技术分享:深度揭秘阿里数据库技术方案的10年变迁史

阿里技术分享:阿里自研金融级数据库OceanBase的艰辛成长之路

作者谈《阿里巴巴Java开发手册(规约)》背后的故事

淘宝技术分享:手淘亿级移动端接入层网关的技术演进之路

《阿里巴巴Android开发手册(规约)》背后的故事

手机淘宝消息推送系统的架构与实践(音频+PPT) [附件下载]

重磅发布:《阿里巴巴Android开发手册(规约)》[附件下载]

阿里技术结晶:《阿里巴巴Java开发手册-v1.6.0-泰山版》[附件下载]

(本文同步发布于:http://www.52im.net/thread-3150-1-1.html

posted @ 2020-09-25 14:29 Jack Jiang 阅读(175) | 评论 (0)编辑 收藏

本文在编写时参考了博客作者“鹿呦呦”和在线课程“即时消息技术剖析与实战”的相关资料,一并表示感谢。

1、引言

随着移动互联网络的发展,IM技术的应用已经不仅限于聊天应用本身,它早已融入各种应用形态中,比如:直播中的主播互动、联网游戏中的玩家互动、外卖/打车应用中的实时位置共享、在线教育应用中的互动白板等。

在这些风格迥异的应用场景下,IM技术所呈现出来的功能形态虽有不同,但“实时性”这个技术特征并无区别。

那么,对于技术门外汉来说,到底什么是IM的“实时性”?该如何理解它?这就是本文想要讨论的主题。

区别于强大的原生应用,Web端的IM系统,在很长一段时间内想实现真正的“实时性”,是非常困难的,因为无法直接使用UDP、TCP通信协议,在HTML5中的WebSocket出现之前,Web端几乎没有真正意义上的“双向实时通信”这种技术存在。

正因为如此,理解Web端即时通信技术的演进,也就自然而然能循序渐进地体会到IM系统中的“实时性”了。所以本文将围绕Web端即时通讯技术,为你展开IM“实时性”这个话题。

友情提示:本系列文章侧重于理论概念的讲述,篇幅有限,点到即止,如需系统、深入、具体地学习IM技术的方方面面,请从此文入手:《新手入门一篇就够:从零开发移动端IM》(史诗级文章,适合从入门到放弃)。

学习交流:

- 即时通讯/推送技术开发交流5群:215477170 [推荐]

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK

(本文同步发布于:http://www.52im.net/thread-3143-1-1.html

2、系列文章目录

IM开发快速入门(一):什么是IM系统?

IM开发快速入门(二):什么是IM系统的实时性?》(* 本文

《IM开发快速入门(三):什么是IM系统的可靠性? (稍后发布)》

《IM开发快速入门(四):什么是IM系统的一致性? (稍后发布)》

《IM开发快速入门(五):什么是IM系统的安全性? (稍后发布)》

《IM开发快速入门(六):什么是IM系统的的心跳机制? (稍后发布)》

《IM开发快速入门(七):如何理解并实现IM系统消息未读数? (稍后发布)》

《IM开发快速入门(八):如何理解并实现IM系统的多端消息漫游? (稍后发布)》

3、短轮询技术

在早期的Web时代,技术的创造者们无法预见如今各种选进的技术应用形式,他们认为数据只是用来“看”的,也数据的获取基本就是“请求 -> 响应”这种一问一答形式。包括我们平时浏览的各种门户网站都是采用的“请求响应”模式。

这种依赖于用户“主动”请求的数据获取模式,如果想实现IM系统,是无法即时获得最新的聊天消息的,因为用户并不知道新消息什么时候到来,而服务端也没有办法主动通知用户。

在这个时期,虽然技术和思路都受当时技术水平的限制,但IM总不能不做吧。

于是,一种被称为“短轮询”的数据获取模式出现了。在“短轮询”模式下,IM客户端定时轮询服务端,以便让用户知道是否有新的聊天消息存在。

这种模式下,服务端收到请求后,即刻查询是否存在新消息,有就返回给客户端,没有则返回空并立即关闭连接。

相较于前面用户需要“手动”去刷新页面的方式,这种模式只是将用户的“手动”变为“自动”而已,技术本质并没有发生任何实质性改变。

短轮询这种模式,就好比旧时代一个等待重要邮件的人,他需要每天自已跑到邮局,主动去问是否有自己的信件,有就拿回家,如果没有,则第二天继续去问。一来一去,非常低效。

技术原理总结如下图所示:

短轮询这种模式有好处,也有坏处。

好处是:

  • 1)技术简单,容易实现;
  • 2)可维护性强,因为它没什么复杂的。

坏处是:

  • 1)因为无法预知数据是否存在,所以多数请求是无用的,浪费计算资源;
  • 2)为了提升实时性,高频率的请求会加大服务端的性能负载。

总结一下就是,短轮询这种模式对于IM技术大拿来说,显的非常low,因为技术实现实在是简单粗暴。

4、长轮询技术

正如你所见,用短轮询技术来保证IM的实时性,确实难说优雅。不过,这难不倒无所不能的程序员,一种被称为“长轮询”的数据获取模式出现了。

从技术上来说,长轮询实现的IM相较于短轮询最大的改进在于:短轮询情况下,服务端不管有没有新消息,请求结束就会立即断开连接。而长轮询时,如果本次请求没有新消息发生,糨不会马上断开连接并返回,而是会将本次连接“挂起”一段时间,如果在这段“挂起”时间内有新的聊天消息出现,就能马上读取并立即返回给客户端,接着结束本次连接。一段时间后又会再次发起请求,如此周而复始。

长轮询这种模式,拿上节等待邮件的这个例子来说,就好比收信的人每天到邮局去问是否有信件,如果没有,他不马上回家,而是在邮局待上一段时间,如果这段时间过去了,还是没有,就先回家,接着第二天再来。

技术原理总结如下图所示: 

长轮询的优点是:

  • 1)相较于短连询,一定程度降低了服务端请求负载;
  • 2)相较于短连询,实时性有提升,因为它是主动“等”消息。

长轮询的缺点是:

  • 1)长论询模式下,连接“挂起”的这段时间内,服务端需要配合开启单独的消息查询线程,仍然存在无用功;
  • 2)相较于短连询模式,在一次长轮询结束、下次轮询发起前的窗口期内,仍然存在“实时性”盲区。

实际上,在Web端即时通讯技术里,长轮询有个专业的术语叫“Comet”,有兴趣可以详细学习《Comet技术详解:基于HTTP长连接的Web端实时通信技术》。

5、轮询无法实现真正的“实时性”

对于Web端即时通讯技术来说,上面提到的无论是短轮询,还是长轮询,它们都存在“实时性”盲区。

我们回到上两节介绍的短轮询和长轮询技术原理图。

先看看短轮询这张图:

很明显,短轮询在每次轮询结束和下次轮询开始的间隔期内,是无法感知到新消息的,这也便形成了“实时性盲区”。换句话说,短轮询技术在“实时性盲区”内,无法做到“实时”。

再来看看长轮询:

跟短轮询道理一样,长轮询在每次轮询结束和下次轮询开始的间隔期,依然会形成“实时性盲区”。

要理解纠结轮询技术的实时性缺陷,就得了解它们背后的技术——HTTP协议了。

HTTP协议设计的目的,就是为了实现“请求--响应”这种模式的数据交互,也就是众所周之的“短连接”设计。而无论是短轮询还是长轮询,都跳不出HTTP的先天技术逻辑(请求--响应--断开)。

所以,归根到底,想要基于HTTP协议来实现IM,要达到真正的“实时性”,是相当勉强的。因为HTTP设计的目的,就是用“短连接”来简化传统TCP长连接通信带来的复杂性,而IM的实时性恰好要用到的又是TCP的长连接特性,所以这就是个悖论。

要真正实现Web端的IM“实时性”,肯定不能强行HTTP上做文章了,我们需要新的技术。

6、WebSocket让Web端IM真正的“实时性”变成可能

好消息是,HTML5中带来了WebSocket技术。WebSocket是真正的全双式双向通信技术(详见:《WebSocket从入门到精通,半小时就够!》)。

下图上旧式轮询技术跟WebSocket的对比图:

从上图可以看出:

  • 1)轮询技术一问一答,在下一个请求发起之前,存在“实时性”盲区;
  • 2)WebSocket一旦建立连接后,数据可以随时双向通信(即客户端可以随时向服务端发消息,服务端也可以随时通知客户端有新消息)。

举个例子就是:轮询技术相当于传统的邮件传递方法(你得自已去邮局问有没有新邮件),而WebSocket相当于现代的电话系统,只要你拨通后,随时可以实时收听到对方的声音,对方也能随时收听到你的声音。完美!

总结一下WebSocket 的优点是:

  • 1)真正的实时性:支持客户端与服务端真正的双向实时通信;
  • 2)大幅降低负载:少了轮询技术中高频率无用的请求,可大大降低服务端QPS压力;
  • 3)网络开销降低:一次连接,随时使用,再也不用轮询技术中每次发起HTTP请求(随之而来的是每次HTTP的大量冗余协议头信息等)。

7、本文小结

本文以Web端即时通讯技术的演进为例,从短轮询到长轮询,再到WebSocket,理论联系实际地讲解了Web端IM“实时性”的技术变迁,从而帮助读者理解IM中“实时性”这个最为关键的技术特征。

附录:更多Web端即时通讯资料

新手入门贴:史上最全Web端即时通讯技术原理详解

Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE

SSE技术详解:一种全新的HTML5服务器推送事件技术

Comet技术详解:基于HTTP长连接的Web端实时通信技术

新手快速入门:WebSocket简明教程

WebSocket详解(一):初步认识WebSocket技术

WebSocket详解(二):技术原理、代码演示和应用案例

WebSocket详解(三):深入WebSocket通信协议细节

WebSocket详解(四):刨根问底HTTP与WebSocket的关系(上篇)

WebSocket详解(五):刨根问底HTTP与WebSocket的关系(下篇)

WebSocket详解(六):刨根问底WebSocket与Socket的关系

socket.io实现消息推送的一点实践及思路

LinkedIn的Web端即时通讯实践:实现单机几十万条长连接

Web端即时通讯技术的发展与WebSocket、Socket.io的技术实践

Web端即时通讯安全:跨站点WebSocket劫持漏洞详解(含示例代码)

开源框架Pomelo实践:搭建Web端高性能分布式IM聊天服务器

使用WebSocket和SSE技术实现Web端消息推送

详解Web端通信方式的演进:从Ajax、JSONP 到 SSE、Websocket

MobileIMSDK-Web的网络层框架为何使用的是Socket.io而不是Netty?

理论联系实际:从零理解WebSocket的通信原理、协议格式、安全性

微信小程序中如何使用WebSocket实现长连接(含完整源码)

八问WebSocket协议:为你快速解答WebSocket热门疑问

快速了解Electron:新一代基于Web的跨平台桌面技术

一文读懂前端技术演进:盘点Web前端20年的技术变迁史

Web端即时通讯基础知识补课:一文搞懂跨域的所有问题!

Web端即时通讯实践干货:如何让你的WebSocket断网重连更快速?

WebSocket从入门到精通,半小时就够!

>> 更多同类文章 ……

本文已同步发布于“即时通讯技术圈”公众号:

▲ 本文在公众号上的链接是:点此进入,原文链接是:http://www.52im.net/thread-3143-1-1.html

posted @ 2020-09-18 13:56 Jack Jiang 阅读(107) | 评论 (0)编辑 收藏

1、引言

在中大型IM系统中,聊天消息的唯一ID生成策略是个很重要的技术点。不夸张的说,聊天消息ID贯穿了整个聊天生命周期的几乎每一个算法、逻辑和过程,ID生成策略的好坏有可能直接决定系统在某些技术点上的设计难易度。

有中小型IM场景下,消息ID可以简单处理,反正只要唯一就行,而中大型场景下,因为要考虑到分布式的性能、一致性等,所以要考虑的问题点又是另一回事。

总之就是,IM的消息ID生成这件事,可深可浅,看似简单但实际可探索的边界可以很大,这也是为什么即时通讯网为此专门整理了《IM消息ID技术专题》系列文章的原因。做技术所谓厚积薄发,了解的越多,你的技术可操作空间也就越大,希望随着这个系列文章的阅读,可以为你在ID生成这一块的技术选型带来更多有益的启发。

另外,因为即时通讯网主要关注的是即时通讯方面的系统开发,但并不意味着这个系统文章只适用于IM或消息推送等实时通信系统,它同样适用于其它需要唯一ID的应用中。

本文将要分享的是滴滴开源的分布式ID生成器Tinyid的技术原理、使用方法等等,希望能进一步为你打开这方面的技术视野。

学习交流:

- 即时通讯/推送技术开发交流5群:215477170[推荐]

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK

(本文同步发布于:http://www.52im.net/thread-3129-1-1.html

2、专题目录

本文是“IM消息ID技术专题”系列文章的第6篇,专题总目录如下:

IM消息ID技术专题(一):微信的海量IM聊天消息序列号生成实践(算法原理篇)

IM消息ID技术专题(二):微信的海量IM聊天消息序列号生成实践(容灾方案篇)

IM消息ID技术专题(三):解密融云IM产品的聊天消息ID生成策略

IM消息ID技术专题(四):深度解密美团的分布式ID生成算法

IM消息ID技术专题(五):开源分布式ID生成器UidGenerator的技术实现

IM消息ID技术专题(六):深度解密滴滴的高性能ID生成器(Tinyid)》(* 本文

3、什么是Tinyid?

Tinyid是滴滴用Java开发的一款分布式id生成系统,基于数据库号段算法实现。

Tinyid是在美团的ID生成算法Leaf的基础上扩展而来,支持数据库多主节点模式,它提供了REST API和Java客户端两种获取方式,相对来说使用更方便。不过,和美团的Leaf算法不同的是,Tinyid只支持号段一种模式(并不支持Snowflake模式)。(有关美团的Leaf算法,可以详读《IM消息ID技术专题(四):深度解密美团的分布式ID生成算法

Tinyid目前在滴滴客服部门使用,且通过tinyid-client方式接入,每天生成的是亿级别的id。性能上,据称单实例能达到1千万QPS。

它的开源地址是:

PS:滴滴在Tinyid工程页面写了一句话,“tinyid,并不是滴滴官方产品,只是滴滴拥有的代码”,我语文不好,这句该怎么理解呢?

4、Tinyid的主要技术特性

主要特性总结一下就是:

  • 1)全局唯一的long型ID:即id极限数量是2的64次方;
  • 2)趋势递增的id:趋势递增的意思是,id是递增但不一定是连续的(这跟微信的ID生成策略类似);
  • 3)提供 http 和 java-client 方式接入;
  • 4)支持批量获取ID;
  • 5)支持生成1,3,5,7,9…序列的ID;
  • 6)支持多个db的配置。

适用的场景:只关心ID是数字,趋势递增的系统,可以容忍ID不连续,可以容忍ID的浪费。

不适用场景:像类似于订单ID的业务,因生成的ID大部分是连续的,容易被扫库、或者推算出订单量等信息。

另外:微信的聊天消息ID生成算法也是基于号段、趋势递增这种逻辑,如果有兴趣,可以详见:《IM消息ID技术专题(一):微信的海量IM聊天消息序列号生成实践(算法原理篇)》。

5、Tinyid的技术优势

性能方面:

  • 1)http方式:访问性能取决于http server的能力,网络传输速度;
  • 2)java-client方式:id为本地生成,号段长度(step)越长,qps越大,如果将号段设置足够大,则qps可达1000w+。

可用性方面:

  • 1)当db不可用时,因为server有缓存,所以还可以使用一段时间;
  • 2)如果配置了多个db,则只要有1个db存活,则服务可用;
  • 3)使用tiny-client时,只要server有一台存活,则理论上server全挂,因为client有缓存,也可以继续使用一段时间。

6、Tinyid的技术原理详解

6.1 ID生成系统的技术要点

在简单系统中,我们常常使用db的id自增方式来标识和保存数据,随着系统的复杂,数据的增多,分库分表成为了常见的方案,db自增已无法满足要求。

这时候全局唯一的id生成系统就派上了用场,当然这只是id生成其中的一种应用场景。

那么,一个成熟的id生成系统应该具备哪些能力呢?

  • 1)唯一性:无论怎样都不能重复,id全局唯一是最基本的要求;
  • 2)高性能:基础服务尽可能耗时少,如果能够本地生成最好;
  • 3)高可用:虽说很难实现100%的可用性,但是也要无限接近于100%的可用性;
  • 4)易用性:能够拿来即用,接入方便,同时在系统设计和实现上要尽可能的简单。

6.2 Tinyid的实现原理

我们先来看一下最常见的id生成方式,db的auto_increment,相信大家都非常熟悉。

我也见过一些同学在实战中使用这种方案来获取一个id,这个方案的优点是简单,缺点是每次只能向db获取一个id,性能比较差,对db访问比较频繁,db的压力会比较大。

那么,是不是可以对这种方案优化一下呢?可否一次向db获取一批id呢?答案当然是可以的。

一批id,我们可以看成是一个id范围,例如(1000,2000],这个1000到2000也可以称为一个“号段”,我们一次向db申请一个号段,加载到内存中,然后采用自增的方式来生成id,这个号段用完后,再次向db申请一个新的号段,这样对db的压力就减轻了很多,同时内存中直接生成id,性能则提高了很多。

PS:简单解释一下什么是号段模式:

号段模式就是从数据库批量的获取自增ID,每次从数据库取出一个号段范围,例如 (1,1000] 代表1000个ID,业务服务将号段在本地生成1~1000的自增ID并加载到内存。

那么保存db号段的表该怎设计呢?我们继续往下看。

6.3 DB号段算法描述

如上表,我们很容易想到的是db直接存储一个范围(start_id,end_id],当这批id使用完毕后,我们做一次update操作,update start_id=2000(end_id), end_id=3000(end_id+1000),update成功了,则说明获取到了下一个id范围。仔细想想,实际上start_id并没有起什么作用,新的号段总是(end_id,end_id+1000]。

所以这里我们更改一下,db设计应该是这样的:

如上表所示:

  • 1)我们增加了biz_type,这个代表业务类型,不同的业务的id隔离;
  • 2)max_id则是上面的end_id了,代表当前最大的可用id;
  • 3)step代表号段的长度,可以根据每个业务的qps来设置一个合理的长度;
  • 4)version是一个乐观锁,每次更新都加上version,能够保证并发更新的正确性 。

那么我们可以通过如下几个步骤来获取一个可用的号段:

A、查询当前的max_id信息:select id, biz_type, max_id, step, version from tiny_id_info where biz_type='test';

B、计算新的max_id: new_max_id = max_id + step;

C、更新DB中的max_id:update tiny_id_info set max_id=#{new_max_id} , verison=version+1 where id=#{id} and max_id=#{max_id} and version=#{version};

D、如果更新成功,则可用号段获取成功,新的可用号段为(max_id, new_max_id];

E、如果更新失败,则号段可能被其他线程获取,回到步骤A,进行重试。

6.4 号段生成方案的简单架构

如上述内容,我们已经完成了号段生成逻辑。

那么我们的id生成服务架构可能是这样的:

如上图,id生成系统向外提供http服务,请求经过我们的负载均衡router,到达其中一台tinyid-server,从事先加载好的号段中获取一个id。

如果号段还没有加载,或者已经用完,则向db再申请一个新的可用号段,多台server之间因为号段生成算法的原子性,而保证每台server上的可用号段不重,从而使id生成不重。

可以看到:

  • 1)如果tinyid-server如果重启了,那么号段就作废了,会浪费一部分id;
  • 2)同时id也不会连续;
  • 3)每次请求可能会打到不同的机器上,id也不是单调递增的,而是趋势递增的(不过这对于大部分业务都是可接受的)。

6.5 简单架构的问题

到此一个简单的id生成系统就完成了,那么是否还存在问题呢?

回想一下我们最开始的id生成系统要求:高性能、高可用、简单易用。

在上面这套架构里,至少还存在以下问题:

  • 1)当id用完时需要访问db加载新的号段,db更新也可能存在version冲突,此时id生成耗时明显增加;
  • 2)db是一个单点,虽然db可以建设主从等高可用架构,但始终是一个单点;
  • 3)使用http方式获取一个id,存在网络开销,性能和可用性都不太好。

6.6 优化办法及最终架构

1)双号段缓存:

对于号段用完需要访问db,我们很容易想到在号段用到一定程度的时候,就去异步加载下一个号段,保证内存中始终有可用号段,则可避免性能波动。

2)增加多db支持:

db只有一个master时,如果db不可用(down掉或者主从延迟比较大),则获取号段不可用。实际上我们可以支持多个db,比如2个db,A和B,我们获取号段可以随机从其中一台上获取。那么如果A,B都获取到了同一号段,我们怎么保证生成的id不重呢?tinyid是这么做的,让A只生成偶数id,B只生产奇数id,对应的db设计增加了两个字段,如下所示

delta代表id每次的增量,remainder代表余数,例如可以将A,B都delta都设置2,remainder分别设置为0,1则,A的号段只生成偶数号段,B是奇数号段。通过delta和remainder两个字段我们可以根据使用方的需求灵活设计db个数,同时也可以为使用方提供只生产类似奇数的id序列。

3)增加tinyid-client:

使用http获取一个id,存在网络开销,是否可以本地生成id?

为此我们提供了tinyid-client,我们可以向tinyid-server发送请求来获取可用号段,之后在本地构建双号段、id生成,如此id生成则变成纯本地操作,性能大大提升,因为本地有双号段缓存,则可以容忍tinyid-server一段时间的down掉,可用性也有了比较大的提升。

4)tinyid最终架构:

最终我们的架构可能是这样的:

下面是更具体的代码调用逻辑:

如上图所示,下面是关于这个代码调用逻辑图的说明:

  • 1)nextId和getNextSegmentId是tinyid-server对外提供的两个http接口;
  • 2)nextId是获取下一个id,当调用nextId时,会传入bizType,每个bizType的id数据是隔离的,生成id会使用该bizType类型生成的IdGenerator;
  • 3)getNextSegmentId是获取下一个可用号段,tinyid-client会通过此接口来获取可用号段;
  • 4)IdGenerator是id生成的接口;
  • 5)IdGeneratorFactory是生产具体IdGenerator的工厂,每个biz_type生成一个IdGenerator实例。通过工厂,我们可以随时在db中新增biz_type,而不用重启服务;
  • 6)IdGeneratorFactory实际上有两个子类IdGeneratorFactoryServer和IdGeneratorFactoryClient,区别在于,getNextSegmentId的不同,一个是DbGet,一个是HttpGet;
  • 7)CachedIdGenerator则是具体的id生成器对象,持有currentSegmentId和nextSegmentId对象,负责nextId的核心流程。nextId最终通过AtomicLong.andAndGet(delta)方法产生。

具体的代码实现,有兴趣可以直接阅读源码:

7、Tinyid的最佳实践

1)tinyid-server推荐部署到多个机房的多台机器:

多机房部署可用性更高,http方式访问需使用方考虑延迟问题。

2)推荐使用tinyid-client来获取id,好处如下:

a、id为本地生成(调用AtomicLong.addAndGet方法),性能大大增加;

b、client对server访问变的低频,减轻了server的压力;

c、因为低频,即便client使用方和server不在一个机房,也无须担心延迟;

d、即便所有server挂掉,因为client预加载了号段,依然可以继续使用一段时间

注:使用tinyid-client方式,如果client机器较多频繁重启,可能会浪费较多的id,这时可以考虑使用http方式。

3)推荐db配置两个或更多:

db配置多个时,只要有1个db存活,则服务可用 多db配置,如配置了两个db,则每次新增业务需在两个db中都写入相关数据。

8、Tinyid该怎么调用?

关于怎么调用。鉴于篇幅原因,就不再具体去写了,有兴趣的话,可以读一下这篇《Tinyid:滴滴开源千万级并发的分布式ID生成器》。

9、参考资料

[1] 面试总被问分布式ID怎么办? 滴滴(Tinyid)甩给他

[2] Tinyid:滴滴开源千万级并发的分布式ID生成器

[3] tinyid工程中文readme

[4] 滴滴开源的Tinyid如何每天生成亿级别的ID?

附录:更多IM开发热门技术文章

新手入门一篇就够:从零开发移动端IM

移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”

移动端IM开发者必读(二):史上最全移动弱网络优化方法总结

从客户端的角度来谈谈移动端IM的消息可靠性和送达机制

现代移动端网络短连接的优化手段总结:请求速度、弱网适应、安全保障

移动端IM中大规模群消息的推送如何保证效率、实时性?

移动端IM开发需要面对的技术问题

开发IM是自己设计协议用字节流好还是字符流好?

请问有人知道语音留言聊天的主流实现方式吗?

IM消息送达保证机制实现(一):保证在线实时消息的可靠投递

IM消息送达保证机制实现(二):保证离线消息的可靠投递

如何保证IM实时消息的“时序性”与“一致性”?

一个低成本确保IM消息时序的方法探讨

IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?

IM群聊消息如此复杂,如何保证不丢不重?

谈谈移动端 IM 开发中登录请求的优化

移动端IM登录时拉取数据如何作到省流量?

浅谈移动端IM的多点登录和消息漫游原理

完全自已开发的IM该如何设计“失败重试”机制?

自已开发IM有那么难吗?手把手教你自撸一个Andriod版简易IM (有源码)

IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!

适合新手:从零开发一个IM服务端(基于Netty,有完整源码)

拿起键盘就是干:跟我一起徒手开发一套分布式IM系统

适合新手:手把手教你用Go快速搭建高性能、可扩展的IM系统(有源码)

IM里“附近的人”功能实现原理是什么?如何高效率地实现它?

IM开发基础知识补课(七):主流移动端账号登录方式的原理及设计思路

IM要做手机扫码登录?先看看微信的扫码登录功能技术原理

IM开发宝典:史上最全,微信各种功能参数和逻辑规则资料汇总

IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的

IM开发干货分享:如何优雅的实现大量离线消息的可靠投递

IM开发干货分享:有赞移动端IM的组件化SDK架构设计实践

>> 更多同类文章 ……

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文在公众号上的链接是:点此进入,原文链接是:http://www.52im.net/thread-3129-1-1.html

posted @ 2020-09-08 22:31 Jack Jiang 阅读(180) | 评论 (0)编辑 收藏

     摘要: 本文由小米信息技术团队研发工程师陈刚原创,原题“当我们在谈论高并发的时候究竟在谈什么?”,为了更好的内容呈现,即时通讯网收录时有修订和改动。1、引言在即时通讯网社区里,多是做IM、消息推送、客服系统、音视频聊天这类实时通信方面的开发者,在涉及到即时通讯技术时聊的最多的话题就是高并发、高吞吐、海量用户。代码还没开始写,就考虑万一哪天这IM用户量破百万、千万该怎么办的问题,是多...  阅读全文

posted @ 2020-09-03 23:08 Jack Jiang 阅读(215) | 评论 (0)编辑 收藏

本文内容编写时,参考了网上的资料,详见“参考资料”部分,感谢分享者。

1、引言

这个系列文章已经整理了10篇,但都没有涉及到具体的红包算法实现,主要有以下两方面原因。

一方面是各社交/IM产品中的红包功能同质化严重,红包算法的“可玩性”便是“核心竞争力所在”,这是同质化功能的差异化竞争思路,不会随便公开。

另一方面,市场上还存在各种抢红包插件这类灰产存在,一旦公开这些算法,很可能又被这帮插件开发者们搞出什么幺蛾子。

所以,这样的情况下,如果要做社交/IM产品中的红包功能,红包随便算法该怎么实现,基本上只能自已琢磨,很难找到大厂算法直接套用。

本着即时通讯网一贯的im知识传播精神,我收集整理并参考了大量的网上资料,综合了比较靠谱的信息来源,便有了本文。本文根据有限的资料,分享了微信红包随机算法实现中的一些技术要点,并整理了两种比较靠谱的红包算法实现思路(含可运行的实现代码),希望能给你的红包算法开发带来启发。

申明:本文资料整理自网络,仅供学习研究之用,如有不妥,请通知作者。

学习交流:

- 即时通讯开发交流5群:215477170 [推荐]

- 移动端IM开发入门文章:新手入门一篇就够:从零开发移动端IM

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK  [推荐]

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文在公众号上的链接是:点此进入,原文链接是:http://www.52im.net/thread-3125-1-1.html

2、系列文章

3、微信红包算法要点汇总

这是目前能找到的仅有的一份,有微信团队人员参与的微信红包算法技术要点的讨论资料。分享于2015年,差不多是微信红包刚火没多久,大概是微信技术团队的人当时没有现在这些技术之外的顾虑,所以作了有限的分享,资料难得,本次重新整理了一下,可以作为参考资料使用。以下是资料正文。

资料来源:来自InfoQ的某架构群的技术讨论,由朱玉华整理(个人博客是:zhuyuhua.com(目前已无法访问))。

资料背景:起因是有朋友在朋友圈咨询微信红包的架构,于是在微信团队成员参与讨论的情况下,我(指“朱玉华”)整理了这次讨论的技术要点,也就是下面的内容(内容为问答形式)。

3.1、算法实现的技术要点

问:微信的金额什么时候算?

答:微信金额是拆的时候实时算出来,不是预先分配的,采用的是纯内存计算,不需要预算空间存储。

为什么采取实时计算金额?原因是:实时效率更高,预算才效率低下。预算还要占额外存储。因为红包只占一条记录而且有效期就几天,所以不需要多大空间。就算压力大时,水平扩展机器是。

问:关于实时实时性,为什么明明抢到红包,点开后发现没有?

答:2014年的红包一点开就知道金额,分两次操作,先抢到金额,然后再转账。

2015年的红包的拆和抢是分离的,需要点两次,因此会出现抢到红包了,但点开后告知红包已经被领完的状况。进入到第一个页面不代表抢到,只表示当时红包还有。

问:关于分配算法,红包里的金额怎么算?为什么出现各个红包金额相差很大?

答:随机,额度在 0.01 和剩余平均值 2 之间。 例如:发 100 块钱,总共 10 个红包,那么平均值是 10 块钱一个,那么发出来的红包的额度在 0.01元~20元之间波动。

当前面 3 个红包总共被领了 40 块钱时,剩下 60 块钱,总共 7 个红包,那么这 7 个红包的额度在:0.01~(60/7 * 2)=17.14之间。

注意:这里的算法是每被抢一个后,剩下的会再次执行上面的这样的算法(Tim老师也觉得上述算法太复杂,不知基于什么样的考虑)。

这样算下去,会超过最开始的全部金额,因此到了最后面如果不够这么算,那么会采取如下算法:保证剩余用户能拿到最低1分钱即可。

如果前面的人手气不好,那么后面的余额越多,红包额度也就越多,因此实际概率一样的。

问:红包的设计

答:微信从财付通拉取金额数据过来,生成个数/红包类型/金额放到redis集群里,app端将红包ID的请求放入请求队列中,如果发现超过红包的个数,直接返回。根据红包的逻辑处理成功得到令牌请求,则由财付通进行一致性调用,通过像比特币一样,两边保存交易记录,交易后交给第三方服务审计,如果交易过程中出现不一致就强制回归。

问:并发性处理:红包如何计算被抢完?

答:cache会抵抗无效请求,将无效的请求过滤掉,实际进入到后台的量不大。cache记录红包个数,原子操作进行个数递减,到 0 表示被抢光。财付通按照 20万笔每秒入账准备,但实际还不到 8万每秒

问:通如何保持8w每秒的写入?

答:多主sharding,水平扩展机器。

问:数据容量多少?

答:一个红包只占一条记录,有效期只有几天,因此不需要太多空间。

问:查询红包分配,压力大不?

答:抢到红包的人数和红包都在一条cache记录上,没有太大的查询压力。

问:一个红包一个队列?

答:没有队列,一个红包一条数据,数据上有一个计数器字段。

问:有没有从数据上证明每个红包的概率是不是均等?

答:不是绝对均等,就是一个简单的拍脑袋算法。

问:拍脑袋算法,会不会出现两个最佳?

答:会出现金额一样的,但是手气最佳只有一个,先抢到的那个最佳。

问:每领一个红包就更新数据么?

答:每抢到一个红包,就cas更新剩余金额和红包个数。

问:红包如何入库入账?

答:数据库会累加已经领取的个数与金额,插入一条领取记录。入账则是后台异步操作。

问:入帐出错怎么办?比如红包个数没了,但余额还有?

答:最后会有一个take all操作。另外还有一个对账来保障。

问:既然在抢的时候有原子减了就不应该出现抢到了拆开没有的情况?

答:这里的原子减并不是真正意义上的原子操作,是Cache层提供的CAS,通过比较版本号不断尝试。

问:cache和db挂了怎么办?

答:主备 +对账。

问:为什么要分离抢和拆?

答:总思路是设置多层过滤网,层层筛选,层层减少流量和压力。

这个设计最初是因为抢操作是业务层,拆是入账操作,一个操作太重了,而且中断率高。 从接口层面看,第一个接口纯缓存操作,搞压能力强,一个简单查询Cache挡住了绝大部分用户,做了第一道筛选,所以大部分人会看到已经抢完了的提示。

问:抢到红包后再发红包或者提现,这里有什么策略吗?

答:大额优先入账策略。

针对上面的技术要点,有人还画了张原理图(这是网上能找到的相对清晰的版本): 

3.2、微信抢红包的过程模拟

针对上节中整理的资料,当有人在微信群里发了一个 N 人的红包、总金额 M 元,后台大概的技术逻辑如下。

3.2.1)发红包后台操作:

  • 1)在数据库中增加一条红包记录,存储到CKV,设置过期时间;
  • 2)在Cache(可能是腾讯内部kv数据库,基于内存,有落地,有内核态网络处理模块,以内核模块形式提供服务))中增加一条记录,存储抢红包的人数N。

3.2.2)抢红包后台操作:

  • 1)抢红包分为抢和拆:抢操作在Cache层完成,通过原子减操作进行红包数递减,到0就说明抢光了,最终实际进入后台拆操作的量不大,通过操作的分离将无效请求直接挡在Cache层外面。
  • 这里的原子减操作并不是真正意义上的原子减操作,是其Cache层提供的CAS,通过比较版本号不断尝试,存在一定程度上的冲突,冲突的用户会放行,让其进入下一步拆的操作,这也解释了为啥有用户抢到了拆开发现领完了的情况。
  • 2)拆红包在数据库完成:通过数据库的事务操作累加已经领取的个数和金额,插入一条领取流水,入账为异步操作,这也解释了为啥在春节期间红包领取后在余额中看不到。
  • 拆的时候会实时计算金额,其金额为1分到剩余平均值2倍之间随机数,一个总金额为M元的红包,最大的红包为 M * 2 /N(且不会超过M),当拆了红包后会更新剩余金额和个数。财付通按20万笔每秒入账准备,实际只到8万每秒。

4、微信红包算法模拟实现1(含代码)

根据上一节的微信红包随机算法技术要点资料,实现了一个算法,以下供参考。(注:本节内容引用自《微信红包随机算法初探》一文)

4.1、算法约定

算法很简单,跟微信的算法一样,不是提前算好,而是抢红包时计算。

即:金额随机,额度在0.01和剩余平均值*2之间。(参见上一节的 “关于分配算法,红包里的金额怎么算?为什么出现各个红包金额相差很大?” 内容)

4.2、代码实现

算法的逻辑主要是:

public static double getRandomMoney(RedPackage _redPackage) {

    // remainSize 剩余的红包数量

    // remainMoney 剩余的钱

    if(_redPackage.remainSize == 1) {

        _redPackage.remainSize--;

        return (double) Math.round(_redPackage.remainMoney * 100) / 100;

    }

    Random r     = newRandom();

    double min   = 0.01; //

    double max   = _redPackage.remainMoney / _redPackage.remainSize * 2;

    double money = r.nextDouble() * max;

    money = money <= min ? 0.01: money;

    money = Math.floor(money * 100) / 100;

    _redPackage.remainSize--;

    _redPackage.remainMoney -= money;

    return money;

}

LeftMoneyPackage数据结构如下:

class RedPackage {

    int remainSize;

    double remainMoney;

}

测试时初始化相关数据是:

static void init() {

    redPackage.remainSize  = 30;

    redPackage.remainMoney = 500;

}

附件是可以运行的完整Java代码文件:

(无法上传附件,如有需要请从此链接处下载:http://www.52im.net/thread-3125-1-1.html

4.3、测试结果

4.3.1 单次测试

按上述代码中的初始化数据(30人抢500块),执行了两次,结果如下:

//第一次

15.69   21.18   24.11   30.85   0.74    20.85   2.96    13.43   11.12   24.87   1.86    19.62   5.97    29.33   3.05    26.94   18.69   34.47   9.4 29.83   5.17    24.67   17.09   29.96   6.77    5.79    0.34    23.89   40.44   0.92

//第二次

10.44   18.01   17.01   21.07   11.87   4.78    30.14   32.05   16.68   20.34   12.94   27.98   9.31    17.97   12.93   28.75   12.1    12.77   7.54    10.87   4.16    25.36   26.89   5.73    11.59   23.91   17.77   15.85   23.42   9.77

第一次随机红包数据图表如下: 

▲ x轴为抢的顺序,y轴为抢到的金额

第二次随机红包数据图表如下:

▲ x轴为抢的顺序,y轴为抢到的金额

4.3.2 多次均值

重复执行200次的均值:

▲ x轴为抢的顺序,y轴为该次抢到金额的概率均值

重复执行2000次的均值: 

▲ x轴为抢的顺序,y轴为该次抢到金额的概率均值

从以上两张图的均值结果可以看出,这个算法中每一次能抢到的金额几率几乎是均等的,从随机性来说比较合理。

5、微信红包算法模拟实现2(含代码)

我对随机算法很感兴趣,正巧最近研究的方向有点偏随机数这块,所以也自己实现了一下微信的红包分发算法(算法要点参考的是本文第三节内容)。(注:本节内容引用自《微信红包算法的分析》一文)

5.1、代码实现

从第三节中可以了解到,微信并不是一开始就预分配所有的红包金额,而是在拆时进行计算的。这样做的好处是效率高,实时性。本次的代码中,红包具体是怎么计算的呢?请参见第4节中的“关于分配算法,红包里的金额怎么算?为什么出现各个红包金额相差很大?”。

那基于这个思想,可以写出一个红包分配算法:

/**

 * 并不完美的红包算法

 */

public static double rand(double money, int people, List<Double> l) {

    if(people == 1) {

        double red = Math.round(money * 100) / 100.0;

        l.add(red);

        return0;

    }

    Random random = newRandom();

    double min = 0.01;

    double max = money / people * 2.0;

    double red = random.nextDouble() * max;

    red = red <= min ? min : red;

    red = Math.floor(red * 100) / 100.0;

    l.add(red);

    double remain = Math.round((money - red) * 100) / 100.0;

    return remain;

}

算法整体思路很简单,就在在最后一个人的时候要注意,此时不进行随机数计算,而是直接将剩余金额作为红包。

5.2、第一次分析

采用上述算法,可以对用户的抢红包行为做分析。这里的模仿行为是:30 元的红包,10 人抢。操作 100 次。

可以得出如下结果: 

▲ x轴为抢的顺序,y轴为该次抢到金额

从上图中可以很轻易的看出来,越后抢的人,风险越大,同时收益也越大,有较大几率获得“手气最佳”。

那红包面值的分布性如何呢?

▲ x轴为抢的顺序,y轴为该次抢到金额重复 100 次后的平均值

从上图可以看出,都是比较接近平均值(3 元)的。

那重复 1000 次呢?

▲ x轴为抢的顺序,y轴为该次抢到金额重复 1000 次后的平均值

更接近了。。。

可以看出,这个算法可以让大家抢到红包面额在概率上是大致均等的。

5.3、不足之处

有人提出了这个问题: 

他接下来放了好几张他试验的截图。我这里取了一张,如果有兴趣,可以去知乎的问题里查看更多图片。

而此时,我哥们在和我的在讨论中,也告诉我,确实存在某个规律,可能让最后一个抢的人占有某些微小的优势,比如,多 0.01 的之类。

例如发 6 个,总额 0.09 的包,最后一个抢的有极大概率是 0.03。

然而我之前的代码却没办法体现出这一点。

比如 10 人拆 0.11 元的包,我的结果是:

可见以上代码还存在不足之处。

于是我就有一个猜测:

微信可能不是对全金额进行随机的,可能在派发红包之前,已经对金额做了处理,比如,事先减去(红包个数*0.01),之后在每个红包的随机值基础上加 0.01,以此来保证每个红包最小值都是 0.01。

这个猜测或许可以解开那位知友和我哥们这边的疑惑。

5.4、完善算法

在原先的基础上对代码进行简单的修正:

public static double rand(double money, int people, List<Double> l) {

    if(people == 1) {

        double red = Math.round(money * 100) / 100.0;

        l.add(red+0.01);

        return 0;

    }

    Random random = newRandom();

    double min = 0;

    double max = money / people * 2.0;

    double red = random.nextDouble() * max;

    red = red <= min ? min : red;

    red = Math.floor(red * 100) / 100.0;

    l.add(red+0.01);

    double remain = Math.round((money - red) * 100) / 100.0;

    return remain;

}

这个算法,在第一次调用时传入 money 的值是总金额减去红包数*0.01,大概像这样:

_money = _money - people * 0.01;

5.5、第二次分析

5.5.1 验证上次的不足之处

1)10 人抢 0.11 元的包:

2)2 人抢 0.03 元的包: 

3)6 人抢 0.09 的包:

5.5.2 修改后的代码会不会对已知结论造成影响?

30 元的红包,10 人抢,操作 100 次。

▲ x轴为抢的顺序,y轴为该次抢到金额 

▲ x轴为抢的顺序,y轴为该次抢到金额重复 100 次后的平均值

由上面两图可见,结论基本上没有改变。

5.6、结论

经过上述代码实践可知:

1)先抢后抢,金额期望都是相同的;

2)微信的红包算法很可能是预先分配给每人 0.01 的“底额”;

3)后抢者风险高,收益大。

5.7、补充

上几张后面测试的图,补充一下之前的观点,发 n 个红包,总金额是(n+1)*0.01,最后一个领的一定是手气最佳。

 

大家也可以试试。

以上,大概可以证明,微信红包是在分配前先给每个人 0.01 的最低金额的!

6、参考资料

[1] 微信红包随机算法初探

[2] 微信红包算法的分析

[3] 微信红包的架构设计简介

[4] 微信红包的随机算法是怎样实现的?

另外,知乎上对于微信红包算法的讨论问题很多人参与,有兴趣可以上去看看,或许会有更多启发:《微信红包的随机算法是怎样实现的?》。

附录:更多微信相关资源

IM开发宝典:史上最全,微信各种功能参数和逻辑规则资料汇总

微信本地数据库破解版(含iOS、Android),仅供学习研究 [附件下载]

(本文同步发布于:http://www.52im.net/thread-3125-1-1.html

posted @ 2020-08-26 14:32 Jack Jiang 阅读(198) | 评论 (0)编辑 收藏

本文由手机淘宝技术团队原创分享,吴志华(天施)、洪海(孤星)、陈虓将(仲升)等专家参与了本文创作,首次发表于公众号“淘系技术”,收录整理时有修订和改动。

1、引言

移动端网络的优化是超级APP们永恒的话题,而对于无线电商来说这更为重要,因为网络请求体验跟用户的购买行为息息相关。

手机淘宝从过去的HTTP API网关,到后来扛住双十一战场主要流量的自研高性能、全双工、安全的ACCS(阿里云通道服务),无论是基础架构的演进、网络调优、协议的优化、异地多活、网络调度上,都有不少宝贵的经验与大家分享,本文借此机会总结了整个技术演进过程。

* 阅读对象:本文属于移动端网络优化的深水区总结性文章,适合有一定移动端网络应用经验的开发者阅读(尤其对移动弱网有一定了解的),初学者如果没有相关知识积累的话,可以简单了解无需深入。如果你对移动弱网很有兴趣,可以进一步阅读本文末尾“附录”部分的推荐文章。

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文在公众号上的链接是:点此进入,原文链接是:http://www.52im.net/thread-3110-1-1.html

2、相关文章

Netty干货分享:京东京麦的生产级TCP网关技术实践总结

知乎技术分享:知乎千万级并发的高性能长连接网关技术实践

手机淘宝消息推送系统的架构与实践(音频+PPT) [附件下载]

3、技术背景

回想移动电商在双十一业务开始兴起的时候,当时双十一当天移动成交243亿占整体571亿的42.6%。

业务高速发展希望更多主动推送去触达用户,一些新的玩法和互动形式,需要连接买家与买家、买家与卖家、买家与达人,因为没有有效的通道能力,业务采取的是不停去轮询服务器,一来对服务器造成不必要的压力,二来对于用户手机的电量流量也是极大的浪费,关键在双十五这种大促的情况下,不必要的请求过大甚至会导致后端集群限流,从而影响到用户体验。

信息传播形态的变化的背后是移动化带来新的技术特征导致的结果。移动电商领域,手机淘宝一直是先行者。移动电商从最初的复制WEB的业务形态到移动特性不断涌现,更多的互动形式的出现,向社交化、娱乐化不断迈进的今天,一个单纯的商品的陈列架形式已经不能满足业务的需求。

业务上需要实时的触达用户,充分发挥移动的特性,将消费时间的碎片利用起来,事实也证明了用户的消费时间随着移动化的进程不断发生变化,逐步分布到全天的碎片时间中。同时货架形态也在向社区化、娱乐化的方向发展,这些都对网络层连接用户有了更高的要求。更多的媒体形态和展示方式,对网络层提出了更多元的要求。

大家可以关注到手机淘宝内的消息盒子这些产品都是业务求变的体现,业务的变化倒逼技术的前进。

4、移动网络环境的挑战性一直都存在

移动网络的速度随便3g、4g、5g的普及,速度有很大提升,但网络环境的多样性和差异性使移动网络的环境更加复杂,在过去双十一前还常遇到一些移动网络劫持的事情。网络劫持这块问题的排查效率很低,需要找到用户、复现现场,甚至找网工、运营商配合排查,一查就是几天过去。

同时在我们的舆情反馈上总是看到用户在说“某个页面加载中、页面打不开、请求很慢、打开某个功能很慢”,面对这些问题过去我们是没有太好的办法,只能猫抓耗子一桩桩去排雷很被动。很多网络的问题是偶现的,一旦错过现在就无从查起。

诸如此类的问题,背后的原因很多:

  • 1)运营商问题;
  • 2)机房部署原因;
  • 3)客户端SDK Bug;
  • 4)弱网和网络抖动;
  • 5)DNS劫持和数据篡改。

在PC时代,我们访问网站的接入条件是相对恒定的,所以在开发时很少考虑网络对用户体验的影响。但是移动APP则不然,尤其是在国内,基础的移动网络环境还不算太好,而且我们有很多用户的访问是发生在地铁、公交车这样的移动环境下,移动基站的频繁切换进一步增加了网络的不稳定。从手机淘宝的数据可以看出,我们每天活跃用户中有不少来自于弱网环境。如果端到云的连接不稳定、高延时,那么所有的用户体验都无从谈起。

基础网络的效率就像一辆列车,时延是火车的速度(启动时间),而带宽就像火车的车厢装载量,整个传输的物理链路就像火车的铁轨。目前现实条件下的移动网络条件非常复杂,我们的目标很简单,就是想让所有用户都能在手机淘宝获得流畅的体验。

下面这张图,能够让大家更加直观的了解国内的移动网络环境。描述了从用户到IDC的端到端的路由情况,不仅数据传输耗时长且丢包率高,同时安全性也是相当糟糕的,DNS劫持、内容劫持在中国就是家常便饭。

因此,我们在改善网络通道上有很多的事情可以去做,去探索突破运营商基础网络的限制,力争为用户创造极致的购物体验。

移动端的DNS问题相当普遍,可以详读以下专题文章:

全面了解移动端DNS域名劫持等杂症:原理、根源、HttpDNS解决方案等

美图App的移动端DNS优化实践:HTTPS请求耗时减小近半

百度APP移动端网络深度优化实践分享(一):DNS优化篇

移动端网络优化之HTTP请求的DNS优化

5、整体技术架构

为了满足移动电商业务高速发展的需求,我们决定打造一个世界级的网络接入服务,构建一个无线网络下”水、电、煤“ 一样的基础设施。

这样一个基础设施需要做到的四个目标:

  • 1)全双工;
  • 2)低延时;
  • 3)高安全;
  • 4)开放。

在这四个目标之上是围绕这个接入服务配套的运维体系,帮助最终用户取得良好的端上体验的同时,帮助开发者快速构建自己的业务。 

如上图所示,在整个接入服务上我们划分为两层:

  • 1)接入网关:负责连接的保持、消息的解析、消息的分发;
  • 2)应用网关:实现各种应用层协议:API、SYNC、RPC、PUSH等,在应用网关的背后是具体的业务系统。

同时我们建立了一个统一调度服务,而不是采用传统的DNS,调度服务是我们的控制中心,通过它我们可以强有力的指挥我们的客户端,并且不会受到DNS污染的影响。

与服务端的分层架构对应的是客户端的SDK,最底层的统一网络库SDK集中了我们对网络优化的策略,并向上为各个应用网关技术的SDK提供API。

基于上面的开放架构,业务方可以选择直接开放具体的后端服务对接不同的应用网关,不需要了解网络背后的细节,并通过应用网关如API网关提供的开发工具快速生成客户端代码。业务方也可以基于这个接入层设计自己的协议。

统一接入层集中管理了用户的设备、在线状态,并提供信息的双向传递能力。

如下图所示:

网关将致力于解决中间网络的通讯,为上层的服务提供高质量的双向通讯能力。

6、稳定性与容灾

稳定性与容灾是服务端中间件永恒的主题,统一接入层这样一个汇聚网关收益和风险是并存的,一旦这个入口故障了,波及的用户范围是不可想象的,如何做的更加稳定,是一个巨大的挑战。

6.1 网关架构的优化

对于一个统一网关来说,对接的业务网关的信息传递特点是不一样的,大部分的业务在全天都是比较平缓的,但是个别营销类业务会在短时间内发布海量的信息,这样的信息发布会抢占网关的大量资源,对于用户的正常访问会产生影响。

举个例子:push服务需要通过网关推送2亿条消息,而这些消息需要在短时间内全部推送完,而同时网关在为正常的用户的交互提供服务,海量信息的推送和正常的用户交互相互竞争资源,最终会造成正常用户的交互失败,对于业务来说,这是不可接受的。

基于上面的情况考虑,整个网关在布署上分为两个集群:

  • 1)一个集群处理常态的在线用户访问;
  • 2)一个集群处理海量信息的推送。

如下图所示,通过这样的方式,避免了业务形态不同,对统一网关的冲击,将不同的业务形态进行了隔离。

6.2 异地多活

在异地多活的整体方案中,统一网关承担了快速引导流量的职责,也是这一方案顺利实施的一个重要环节。

异地多活是一个多机房的整体方案,在多个地区同时存在对等的多个机房,以用户维度划分,多机房共同承担全量用户的流量;在单个机房发生故障时,故障机房的流量可以快速的被迁引到可用机房,减少故障的恢复时间。

6.2.1)无线接入层单元化的协商机制:

先看一下web端在这异地多活中的实现方式:

从上图可以看到,浏览器的业务器求会发给CDN,由CDN上保存的分发规则,向后续的单元机房分发。

无线端也这样做吗?

  • 1)客户端拥有强大的能力,可以做的更灵活;
  • 2)CDN的分发节点带来更多的机器成本;
  • 3)对于需要双工通讯能力的客户端,消息投递更为复杂。

这些是我们思考与WEB不同的地方,是不是能做些不一样的选择?

如上图所示, 我们借助了客户端的强大能力,利用协商的机制来完成用户的请求正确被分配到不同的单元。

含以下几点:

  • 1)客户端的请求始终带上当前用户归属单元的信息;
  • 2)当请求到达服务端时,服务端判断用户归属单元是否正确,不正确将用户重定向到正确的单元 ;
  • 3)当前请求由网关在服务端上通过跨单元调用保证业务的正确性;
  • 4)当客户端归属单元更新后,后续的请求都会发到正确的单元机房。

6.2.2)无线接入层单元化的旁路调度:

协商机制看起来很不错,这里一个重磅炸弹丢过来了,机房的入口网络断了!

如上图,外网不可用,协商的机会都没有故障单元的用户无法恢复,这时旁路的调度服务出场了。

如上图,我们设计的调度中心这时又承担了单元化的旁路调度职责,当app访问的单元无法访问的时候,app会访问不同单元的调度中心,询问用户的归属单元。通过这种方式取得可用的单元节点,将用户切到正确的单元。这个方案同样适用于单机房的接入层网关不可用的场景。

6.2.3)应用层网关不可用:

某个单元机房的应用层网关不可用,这时等待应用网关排查问题需要的时间比较久,为了达到最快的故障恢复,我们通过开关把修改接入层的转发规则,将流量切到可用的单元。

如下图所示: 

7、端到端网络优化

7.1 统一网络库

在做网络优化一开始,我们想做一个通用的网络库,这个网络库包含策略、httpDNS、SPDY协议等一切系统网络优化需要的方方面面。(如果你对httpDNS不甚了解,可以详读《全面了解移动端DNS域名劫持等杂症:原理、根源、HttpDNS解决方案等》)

上层api网关请求逻辑、推送逻辑、上传下载逻辑对于这样一个通用网络库来说都是业务。在分层上将通用网络库和上层应用逻辑分开、彻底解耦,对长期持续优化网络是很有必要。

如下图所示架构: 

这样架构上分离,可以让我们更专注更系统化去做无线网络优化。

统一网络库的几个重要特性:

  • 1)灵活控制客户端网络行为策略(建连、超时处理、请求协议、是否加密);
  • 2)包含HTTPDNS;
  • 3)支持异地多活;
  • 4)更细粒度控制和调度(域名级和域名下参数级)。

1、2、3、4均由网络调度中心的集群控制,我们希望这个可以做到与业务无关,去掉一些阿里的业务属性后,这个模块大家可以理解为HTTPDNS,可以理解我们在HTTPDNS之外做了大量网络优化的端到端的工作。

7.2 就近就快接入

基于网络库我们实现了一套智能学习的网络策略,智能学习客户端在不同网络环境下建连策略,用户重新回到这个网络环境会给出最优的策略进行快速连接,并定期去更新或淘汰本地cache的历史最优网络策略。

为了建立更加迅速在各自网络下穿透性更好,接入服务器支持了多种协议和端口,客户端建连时可以极速接入网络。

我们有一个重要指标是打开客户端30秒内网络请求成功率,就是关注连的快给用户体验带来的价值。

基于调度中心,我们搭建了一个智能大数据分析平台,将客户端在在网络请求过程中的数据如建连时间、首包收取时间、整包收取时间、ssl握手时间等重要指标收集上来 。根据这些指标分析出网络异常区域,调整我们的就近就快接入规则,甚至推动IDC建设和CDN的布点完善。

7.3 弱网优化和抗抖动

在弱网优化上我们尝试了QUIC协议,在网络延时较高、丢包严重情况下比TCP有更好表现。

线上手机淘宝灰度版本实测切换到QUIC后,平均RT收益有接近20%。考虑QUIC在移动网络可能存在穿透性问题,未来我们将采取SPDY为主,QUIC为辅助的模式来完善我们的网络链接策略。

现在QUIC协议在移动端应用的越来越广泛,有兴趣的话可详细以下文章:

网络编程懒人入门(十):一泡尿的时间,快速读懂QUIC协议

技术扫盲:新一代基于UDP的低延时网络传输层协议——QUIC详解

让互联网更快:新一代QUIC协议在腾讯的技术实践分享

七牛云技术分享:使用QUIC协议实现实时视频直播0卡顿!

同样在一些网络环境较差情况下,我们采取长短链接结合方式,在长链接遇到请求超时或穿透性较差情况,利用短链接HTTP短链接去请求数据(在移动网络环境下HTTP协议尤其HTTP1.0的穿透性是最好的),这样可以在一些极端情况下最大程度保证用户体验。

数据如下图:

网络切换和网络抖动情况下的技术优化也是一个很重要的方面,我们经常遇到移动设备网络切换和信号不稳定的情况,在这种情况我们怎么保证用户的体验?

针对这种情况我们的思路是有策略合理增加重试。我们对一个网络请求以是否发送到socket缓冲区作为分割,将网络请求生命周期划分为“请求开始到发送到 socket缓冲区”和“已经发送到socket缓冲区到请求结束”两个阶段。在阶段一内请求失败了,会根据业务需求帮助业务请求去做重试。阶段二请求失败只针对读操作提供重试能力。

设想一个场景:用户在进电梯发起一个刷新数据请求,进到电梯因为网络抖动的原因网络链接断了,这个时候我们能够合理策略去做重试,这样当用户离开电梯时很可能网络请求重试成功,帮助用户拉到了想要的数据,提升了用户体验和客户端的网络抗抖动能力。

7.4 加密传输1秒钟法则

众所周知的传统https的整个握手流程是非常重的,在网络质量不高的情况下,造成建连过慢,用户体验惨不能睹,甚至都无法完成安全握手。然而从安全的角度我们是需要一个安全的传输通道保护用户的隐私数据。

安全与网络这一对冲突放在我们的面前,需要在技术上有所突破,因此我们自建了一套slight-ssl的技术,参考了tls1.3的协议,通过合并请求,优化加密算法,运用session-ticket等策略,最终在安全和体验之间找到了一个平衡点,在基本不牺牲用户体验的基础上,达到了安全传输的目地, 同时还大幅度提升了服务端的性能。通过技术的创新,我们实现了无线网络加密传输下1秒钟法则。

关于TLS1.3在移动端的应用,也可以详读微信团队分享的这篇《微信新一代通信安全解决方案:基于TLS1.3的MMTLS详解》。

附录:有关移动端弱网方面的资料汇总

IM开发者的零基础通信技术入门(十一):为什么WiFi信号差?一文即懂!

IM开发者的零基础通信技术入门(十二):上网卡顿?网络掉线?一文即懂!

IM开发者的零基础通信技术入门(十三):为什么手机信号差?一文即懂!

IM开发者的零基础通信技术入门(十四):高铁上无线上网有多难?一文即懂!

现代移动端网络短连接的优化手段总结:请求速度、弱网适应、安全保障

聊聊iOS中网络编程长连接的那些事

移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”

移动端IM开发者必读(二):史上最全移动弱网络优化方法总结

全面了解移动端DNS域名劫持等杂症:原理、根源、HttpDNS解决方案等

美图App的移动端DNS优化实践:HTTPS请求耗时减小近半

百度APP移动端网络深度优化实践分享(一):DNS优化篇

百度APP移动端网络深度优化实践分享(二):网络连接优化篇

百度APP移动端网络深度优化实践分享(三):移动端弱网优化篇

爱奇艺移动端网络优化实践分享:网络请求成功率优化篇

美团点评的移动端网络优化实践:大幅提升连接成功率、速度等

5G时代已经到来,TCP/IP老矣,尚能饭否?

微信Mars:微信内部正在使用的网络层封装库,即将开源

如约而至:微信自用的移动端IM网络层跨平台组件库Mars已正式开源

谈谈移动端 IM 开发中登录请求的优化

腾讯原创分享(一):如何大幅提升移动网络下手机QQ的图片传输速度和成功率》 

腾讯原创分享(二):如何大幅压缩移动网络下APP的流量消耗(下篇)》 

腾讯原创分享(三):如何大幅压缩移动网络下APP的流量消耗(上篇)》 

IM开发者的零基础通信技术入门(十一):为什么WiFi信号差?一文即懂!

IM开发者的零基础通信技术入门(十二):上网卡顿?网络掉线?一文即懂!

IM开发者的零基础通信技术入门(十三):为什么手机信号差?一文即懂!

IM开发者的零基础通信技术入门(十四):高铁上无线上网有多难?一文即懂!

>> 更多同类文章 ……

(本文已同步发布于:http://www.52im.net/thread-3110-1-1.html

posted @ 2020-08-19 13:49 Jack Jiang 阅读(196) | 评论 (0)编辑 收藏

1、引言

最近在从头重写 MobileIMSDK 的TCP版,自已组织TCP数据帧时就遇到了字节序大小端问题。所以,借这个机会单独整理了这篇文章,希望能加深大家对字节序问题的理解,加强对IM这种基于网络通信的程序在数据传输这一层的知识掌控情况。

程序员在写应用层程序时,一般不需要考虑字节序问题,因为字节序跟操作系统和硬件环境有关,而我们编写的程序要么不需要跨平台(比如只运行在windows),要么需要跨平台时会由Java这种跨平台语言在虚拟机层屏蔽掉了。

但典型情况,当你编写网络通信程序,比如IM聊天应用时,就必须要考虑字节序问题,因为你的数据在这样的场景下要跨机器、跨网络通信,必须解决不同系统、不同平台的字节序问题。

* 阅读对象:本文属于计算机基础知识,特别适合从事网络编程方面工作(比如IM这类通信系统)的程序员阅读。面视时,面视官一般都会聊到这个知识点。

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文在公众号上的链接是:点此进入 ,原文链接是:http://www.52im.net/thread-3101-1-1.html

2、什么是字节序?

字节序,是指数据在内存中的存放顺序,当字节数大于1时需要考虑(只有一个字节的情况下,比如char类型,也就不存在顺序问题啦)。

从下图中,可以直观的感受到什么是字节序问题: 

上图片改编自《C语言打印数据的二进制格式-原理解析与编程实现

3、字节序的分类

字节序常被分为两类:

  • 1)Big-Endian(大端字节序):高位字节排放在内存的低地址端,低位字节排放在内存的高地址端(这是人类读写数值的方法);
  • 2)Little-Endian(小端字节序):低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

举个具体的例子,0x1234567 的大端字节序和小端字节序写法如下:

如上图所示:大端小端字节序最小单位1字节,即8bit;大端字节序就是和我们平时写法的顺序一样,从低地址到高地址写入0x01234567;而小端字节序就是和我们平时的写法反过来,因为字节序最小单位为1字节,所以从低地址到高地址写入0x67452301。

4、为什么会存在大端、小端字节序问题?

4.1 比较合理的解释

一个比较合理的解释是说:计算机中电路优先处理低位字节,效率比较高,因为计算机都是从低位开始的,所以计算机内部处理都是小端字节序。

而人类人类读写数值的方法,习惯用大端字节序,所以除了计算机的内部处,其他的场理合都是大端字节序,比如:网络传输和文件储存时都是用的大端字节序(关于网络字节序,会在后面继续展开说明)。

大小端字节序问题,最有可能是跟技术算硬件或软件的创造者们,在技术创立之初的一些技术条件或个人习惯有关。

所以大小端问题,体现在实际的计算机工业应用来上,不同的操作系统和不同的芯片类型可能都会有不同。

4.2 常见的操作系统和芯片使用的字节序

具体来说:DEC和Intel的机器(X86平台)一般采用小端,IBM、Motorola(Power PC)、Sun的机器一般采用大端。

当然,这不代表所有情况。有的CPU即能工作于小端, 又能工作于大端,比如:Arm、Alpha、摩托罗拉的PowerPC。 

而且,具体这类CPU是大端还是小端,和具体设置也有关。如:Power PC支持小端字节序,但在默认配置时是大端字节序。

一般来说:大部分用户的操作系统(如:Windows、FreeBsd、Linux)是小端字节序。少部分,如:Mac OS 是大端字节序。

4.3 如何判断用的是什么字节序?

怎么判断我的计算机里使用的是大端还是小端字节序呢?

下面的这段代码可以用来判断计算机是大端的还是小端。判断的思路是:确定一个多字节的值(下面使用的是4字节的整数),将其写入内存(即赋值给一个变量),然后用指针取其首地址所对应的字节(即低地址的一个字节),判断该字节存放的是高位还是低位,高位说明是Big endian,低位说明是Little endian。

#include <stdio.h>

int main ()

{

  unsigned int x = 0x12345678;

  char*c = (char*)&x;

  if(*c == 0x78) {

    printf("Little endian");

  } else{

    printf("Big endian");

  }

  return 0;

}

5、“大端”、“小端”名字由来

根据网上的资料,据说名字的由来跟乔纳森·斯威夫特的著名讽刺小说《格列佛游记》有关。

书中的故事是这样的:一般来说,大家都认为吃鸡蛋前,原始的方法是打破鸡蛋较大的一端。可是当今皇帝的祖父小时候吃鸡蛋,一次按古法打鸡蛋时碰巧将一个手指弄破了,因此他的父亲,当时的皇帝,就下了一道敕令,命令全体臣民吃鸡蛋时打破鸡蛋较小的一端,违令者重罚。

小人国内部分裂成Big-endian和Little-endian两派,区别在于一派要求从鸡蛋的大头把鸡蛋打破,另一派要求从鸡蛋的小头把鸡蛋打破。

小人国国王改变了打开鸡蛋的方位与理由,并由此招致了修改法律、引发战争和宗教改革等一序列事件的发生。

《格列佛游记》中的这则故事,原本是借以讽刺英国的政党之争。而在计算机工业中,也借用了这个故事来代指大家在数据储存字节顺序中的分歧,并把“大端”(Big-endian)、“小端”(Little-endian)的名字,沿用到了计算机中。 

上图片改编自《“字节序”是个什么鬼?

或许,借用这个故事来命名大小端字节序问题,无非就是想告诉大家,所谓的“大端”、“小端”实际上可能无关计算机性能,更多的只是创造者们在创立计算机之初,代入了个人的一些约定俗成的习惯而已。

6、什么是网络字节序?

6.1 字节序问题给网络通信带来的困扰

对于搞网络通信应用(比如IM、消息推送、实时音视频)开发的程序员来说,自已写通信底层的话是一定会遇到大小端问题的,对于网络字节序这个知识点是一定要必知必会。(当然,你要是很没追求的认为,反正我公司就让租租第3方,能用就行,具体通底层怎么写我才不想掉头发去考虑那么多。。。。 那哥也救不了你。。

上面所说的大小端字节序都是在说计算机自己,也被称作主机字节序。同型号计算机上写的程序,在相同的系统上面运行总归是没有问题。

但计算机网络的出现让大小端问题变的复杂化了,因为每个计算机都有自己的主机字节序。不同计算机之间通过网络通信时:我“说”的你听不懂,你“说”我也听不懂,这可怎么办?

6.2 TCP/IP协议强行约定了字节序方案

好消息是,TCP/IP协议很好的解决了这个问题,TCP/IP协议规定使用“大端”字节序作为网络字节序。

这样,即使不使用大端的计算机也没有关系,因为发送数据的时候可以将自己的主机字节序转换为网络字节序(即“大端”字节序),对接收到的数据转换为自己的主机字节序。这样一来,也就达到了与CPU、操作系统无关,实现了网络通信的标准化。

具体的原理就是:

  • 1)TCP/IP协议会把接收到的第一个字节当作高位字节看待,这就要求发送端发送的第一个字节是高位字节;
  • 2)而在发送端发送数据时,发送的第一个字节是该数值在内存中的起始地址处对应的那个字节。

也就是说,该数值在内存中的起始地址处对应的那个字节就是要发送的第一个高位字节(即:高位字节存放在低地址处)。由此可见,多字节数值在发送之前,在内存中就是以大端法存放的。

所以说,网络字节序就是大端字节序。

6.3 主机字机序到网络字节序的转换

那么,为了程序的兼容,程序员们每次发送和接受数据都要进行转换,这样做的目的是保证代码在任何计算机上执行时都能达到预期的效果。

通信时的这种常用的操作,Socket API这一层,一般都提供了封装好的转换函数,方便程序员使用。比如从主机字节序到网络字节序的转换函数:htons、htonl(C语言中常用),从网络字节序到主机字节序的转换函数:ntohs、ntohl(C语言中常用)。当然,也可以编写自己的转换函数。

7、实践中的大小端字节序处理

在我编写MobileIMSDK的TCP版时(MobileIMSDK是我开源的IM通信层库),同样遇到了大小端字节序问题。

以MobileIMSDK的iOS端拼装网络数据收发的代码为例:

如上图代码所示,注意以下两个大小端转换函数的使用:

  • 1)第27行“CFSwapInt32HostToBig”函数:网络发出数据之前,先将主机字节序转为网络字节序(即大端字节序);
  • 2)第53行“CFSwapInt32BigToHost”函数:收到原始网络数据后,转为主机字节序后就可以在程序中正常使用了。

如果对网络大小端转换这方面的实践感兴趣,可以自已去下载MobileIMSDK源码试一试:https://github.com/JackJiang2011/MobileIMSDK

8、参考资料

[1] “字节序”是个什么鬼?

[2] 大小端及网络字节序

[3] C语言打印数据的二进制格式-原理解析与编程实现

附录:系列文章

本文是系列文章中的第9篇,本系列大纲如下:

脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手

脑残式网络编程入门(二):我们在读写Socket时,究竟在读写什么?

脑残式网络编程入门(三):HTTP协议必知必会的一些知识

脑残式网络编程入门(四):快速理解HTTP/2的服务器推送(Server Push)

脑残式网络编程入门(五):每天都在用的Ping命令,它到底是什么?

脑残式网络编程入门(六):什么是公网IP和内网IP?NAT转换又是什么鬼?

脑残式网络编程入门(七):面视必备,史上最通俗计算机网络分层详解

脑残式网络编程入门(八):你真的了解127.0.0.1和0.0.0.0的区别?

脑残式网络编程入门(九):面试必考,史上最通俗大小端字节序详解》(本文)

(本文同步发布于:http://www.52im.net/thread-3101-1-1.html

posted @ 2020-08-13 14:58 Jack Jiang 阅读(143) | 评论 (0)编辑 收藏

本文作者网易智慧企业web前端开发工程师马莹莹。为了提升内容质量,收录时有修订和改动。

1、引言

在一个完善的即时通讯IM应用中,WebSocket是极其关键的一环,它为基于Web的即时通讯应用提供了一种全双工的通信机制。但为了提升IM等实际应用场景下的消息即时性和可靠性,我们需要克服WebSocket及其底层依赖的TCP连接对于复杂网络情况下的不稳定性,即时通讯的开发者们通常都需要为其设计一套完整的连接保活、验活以及断片网重连方案。

就断网重连而言,其重连响应速度将严重影响了上层应用的“即时性”和用户体验。试想打开网络一分钟后,微信的网络不能即时感知到socket连接的恢复,无法即时收发聊天消息的话,是不是很崩溃?

因此,如何在复杂网络场景下,更即时快速地感知网络变动,并快速恢复WebSocket的可用性,就变得尤为重要。本文将基于笔者的开发实践,分享WebSocket在不同状态下、不同的网络状态下,应该如何实现快速断网重连。

* 阅读对象:本文适合有过IM底层网络实际开发经验,或者对底层网络实现有较深了解的开发者阅读。如果对底层网络了解甚少,建议跳过本文,直接阅读网络本文末尾附录部分的基础后再回头来看。

* 内容点评:本文内容没有高大上,但比较干货,实用性较高,内容也很通俗,建议可详细阅读。文中虽讲的是WebSocket,但思想可以延伸应用到基于TCP协议的同类技术中。

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文在公众号上的链接是:点此进入原文链接是:http://www.52im.net/thread-3098-1-1.html

2、预备知识

本文中将要分享的内容是基于实践总结,如果你对Web端的即时通讯知识还一头雾水,务必先读:《新手入门贴:史上最全Web端即时通讯技术原理详解》、《Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE》。

限于篇幅,本文不会深究WebSocket技术细节,如有兴趣请系统学习:

3、快速了解WebSocket

Websocket诞生于2008年,在2011年成为国际标准,现在所有的浏览器都已支持(详见《新手快速入门:WebSocket简明教程》)。它是一种全新的应用层协议,是专门为web客户端和服务端设计的真正的全双工通信协议,可以类比HTTP协议来了解websocket协议。

图片引用自《WebSocket详解(四):刨根问底HTTP与WebSocket的关系(上篇)

它们的不同点:

  • 1)HTTP的协议标识符是http,WebSocket的是ws;
  • 2)HTTP请求只能由客户端发起,服务器无法主动向客户端推送消息,而WebSocket可以;
  • 3)HTTP请求有同源限制,不同源之间通信需要跨域,而WebSocket没有同源限制。

它们的相同点:

  • 1)都是应用层的通信协议;
  • 2)默认端口一样,都是80或443;
  • 3)都可以用于浏览器和服务器间的通信;
  • 4)都基于TCP协议。

两者和TCP的关系图:

图片引用自《新手快速入门:WebSocket简明教程

有关Http和WebSocket的关系,可以详读:

有关WebSocket和Socket的关系,可以详读:《WebSocket详解(六):刨根问底WebSocket与Socket的关系》.

4、WebSocket重连过程拆解

首先考虑一个问题,何时需要重连?

最容易想到的是WebSocket连接断了,为了接下来能收发消息,我们需要再发起一次连接。

但在很多场景下,即便WebSocket连接没有断开,实际上也不可用了。

比如以下场景:

  • 1)设备切换网络;
  • 2)链路中间路由崩溃(常识是一条socket连接对应的网络通路上,会存在很多路由设备);
  • 3)链路的前端出口不可用(比如家庭WiFi中,网络连接正常,但实际运营商的宽带已经欠费被停机);
  • 4)服务器负载持续过高无法响应等。

这些场景下的WebSocket都没有断开,但对上层来说,都没办法正常的收发数据了。

因此在重连前,我们需要一种机制来感知连接是否可用、服务是否可用,而且要能快速感知,以便能够快速从不可用状态中恢复。

一旦感知到了连接不可用,那便可以弃旧图新了,弃用并断开旧连接,然后发起一次新连接。这两个步骤看似简单,但若想达到快,且不是那么容易的。

首先:是断开旧连接,对客户端来说,如何快速断开?协议规定客户端必须要和服务器协商后才能断开WebSocket连接,但是当客户端已经联系不上服务器、无法协商时,如何断开并快速恢复?

其次:是快速发起新连接。此快非彼快,这里的快并非是立即发起连接,立即发起连接会对服务器带来不可预估的影响。重连时通常会采用一些退避算法,延迟一段时间后再发起重连。但如何在重连间隔和性能消耗间做出权衡?如何在“恰当的时间点”快速发起连接?

带着这些疑问,我们来细看下这三个过程:

5、快速重连关键1:快速感知何时需要重连

5.1 场景

需要重连的场景可以细分为三种:

  • 1)连接明确断开了;
  • 2)连接没断但是不可用了;
  • 3)连接对端的服务不可用了。

对于第一种场景:这很简单,连接直接断开了,肯定需要重连了。

对于后两者:无论是连接不可用,还是服务不可用,对上层应用的影响都是不能再收发即时消息了。

5.2 心跳包主动探测网络可用性

所以从上面这个角度出发,感知何时需要重连的一种简单粗暴的方法就是通过心跳包超时:发送一个心跳包,如果超过特定的时间后还没有收到服务器回包,则认为服务不可用,如下图中左侧的方案(这种方法最直接)。

那如果想要快速感知呢,就只能多发心跳包,加快心跳频率。但是心跳太快对移动端流量、电量的消耗又会太多,所以使用这种方法没办法做到快速感知,可以作为检测连接和服务可用的兜底机制。

5.3 被动监听网络状态改变

如果要检测连接不可用,除了用心跳检测,还可以通过判断网络状态来实现,因为断网、切换wifi、切换网络是导致连接不可用的最直接原因,所以在网络状态由offline变为online时,大多数情况下需要重连下,但也不一定,因为webscoket底层是基于TCP的,TCP连接不能敏锐的感知到应用层的网络变化,所以有时候即便网络断开了一小会,对WebSocket连接是不会有影响的,网络恢复后,仍然能够正常地进行通信。

因此在网络由断开到连接上时,立即判断下连接是否可用,可以通过发一个心跳包判断,如果能够正常收到服务器的心跳回包,则说明连接仍是可用的,如果等待超时后仍没有收到心跳回包,则需要重连,如上图中的右侧。这种方法的优点是速度快,在网络恢复后能够第一时间感知连接是否可用,不可用的话可以快速执行恢复,但它只能覆盖应用层网络变化导致WebSocket不可用的情况。

5.4 小结

综上所述:

  • 1)定时发送心跳包检测的方案贵在稳定,能够覆盖所有场景,但速度不即时(心跳间隔是固定的);
  • 2)判断网络状态的方案速度快,无需等待心跳间隔,较为灵敏,但覆盖场景较为局限。

因此,我们可以结合两种方案:

  • 1)定时以不太快的频率发送心跳包,比如40s/次、60s/次等,具体可以根据应用场景来定;
  • 2)然后在网络状态由offline变为online时立即发送一次心跳,检测当前连接是否可用,不可用的话立即进行恢复处理。

这样在大多数情况下,上层的应用通信都能较快从不可用状态中恢复,对于少部分场景,有定时心跳作为兜底,在一个心跳周期内也能够恢复。

6、快速重连关键2:快速断开旧连接

通常情况下,在发起下一次连接前,如果旧连接还存在的话,应该先把旧连接断开。

这样做的目的:

  • 1)一来可以释放客户端和服务器的资源;
  • 2)二来可以避免之后误从旧连接收发数据。

我们知道WebSocket底层是基于TCP协议传输数据的,连接两端分别是服务器和客户端,而TCP的TIME_WAIT状态是由服务器端维持的,因此在大多数正常情况下,应该由服务器发起断开底层TCP连接,而不是客户端。

也就是说:

  • 1)要断开WebSocket连接时,如果是服务器收到指示要断开WebSocket,那它应该立即发起断开TCP连接;
  • 2)如果是客户端收到指示要断开WebSocket,那它应该发信号给服务器,然后等待底层TCP连接被服务器断开或直至超时。

那如果客户端想要断开旧的WebSocket,可以分为WebSocket连接可用和不可用两种情况来讨论。

具体如下:

  • 1)当旧连接可用时,客户端可以直接给服务器发送断开信号,然后服务器发起断开连接即可;
  • 2)当旧连接不可用时,比如客户端切换了wifi,客户端发送了断开信号,但是服务器收不到,客户端只能迟迟等待,直至超时才能被允许断开。

超时断开的过程相对来说是比较久的,那有没有办法可以快点断开?

上层应用无法改变只能由服务器发起断开连接这种协议层面的规则,所以只能从应用逻辑入手,比如在上层通过业务逻辑保证旧连接完全失效,模拟连接断开,然后在发起新连接,恢复通讯。

这种方法相当于尝试断开旧连接不行时,直接弃之,然后就能快速进入下一流程,所以在使用时一定要确保在业务逻辑上旧连接已完全失效。

比如:

  • 1)保证丢掉从旧连接收到所有数据;
  • 2)旧连接不能阻碍新连接的建立
  • 3)旧连接超时断开后不能影响新连接和上层业务逻辑等等。

7、快速重连关键3:快速发起新连接

有IM开发经验的同学应该有所了解,遇到因网络原因导致的重连时,是万万不能立即发起一次新连接的,否则当出现网络抖动时,所有的设备都会立即同时向服务器发起连接,这无异于黑客通过发起大量请求消耗网络带宽引起的拒绝服务攻击,这对服务器来说简直是灾难(即:服务端雪崩效应)。

所以在重连时通常采用一些退避算法,延迟一段时间再发起重连,如下图中左侧的流程。

如果要快速连上呢?最直接的做法就是缩短重试间隔,重试间隔越短,在网络恢复后就能越快的恢复通讯。但是太频繁的重试对性能、带宽、电量的消耗就比较严重。

如何在这之间做一个较好的权衡呢?

  • 1)一种比较合理的方式是随着重试次数增多,逐渐增大重试间隔;
  • 2)另一方面监听网络变化,在网络状态由offline变为online这种比较可能重连上的时刻,适当地减小重连间隔。

上述第2)种方案,如上图中的右侧所示,随重试次数的增多,重连间隔也会变大。这两种方式配合使用,更为合理。

除此之外,还可以结合业务逻辑,根据成功重连上的可能性适当的调整间隔,如网络未连接时或应用在后台时重连间隔可以调大一些,网络正常的状态下可以适当调小一些等等,加快重连上的速度。

8、本文小结

最后总结一下。

本文将WebSocket断网重连逻辑细分为三个步骤:

  • 1)确定何时需要重连;
  • 2)断开旧连接;
  • 3)发起新连接。

然后分别分析了在WebSocket的不同状态下、不同的网络状态下,如何快速完成这个三个步骤。

过程具体总结就是:

  • 1)首先:通过定时发送心跳包的方式检测当前连接是否可用,同时监测网络恢复事件,在恢复后立即发送一次心跳,快速感知当前状态,判断是否需要重连;
  • 2)其次:正常情况下由服务器断开旧连接,与服务器失去联系时直接弃用旧连接,上层模拟断开,来实现快速断开;
  • 3)最后:发起新连接时使用退避算法延迟一段时间再发起连接,同时考虑到资源浪费和重连速度,可以在网络离线时调大重连间隔,在网络正常或网络由offline变为online时缩小重连间隔,使之尽可能快地重连上。

以上就是我关于如何实现WebSocket快速重连的技术分享,欢迎留言与我探讨。

9、参考资料

[1] RFC 6455 文档

[2] 新手快速入门:WebSocket简明教程

[3] WebSocket详解(四):刨根问底HTTP与WebSocket的关系(上篇)

[4] WebSocket详解(五):刨根问底HTTP与WebSocket的关系(下篇)

[5] WebSocket详解(六):刨根问底WebSocket与Socket的关系

附录:更多Web端即时通讯资料

新手入门贴:史上最全Web端即时通讯技术原理详解

Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE

SSE技术详解:一种全新的HTML5服务器推送事件技术

Comet技术详解:基于HTTP长连接的Web端实时通信技术

socket.io实现消息推送的一点实践及思路

LinkedIn的Web端即时通讯实践:实现单机几十万条长连接

Web端即时通讯技术的发展与WebSocket、Socket.io的技术实践

Web端即时通讯安全:跨站点WebSocket劫持漏洞详解(含示例代码)

开源框架Pomelo实践:搭建Web端高性能分布式IM聊天服务器

使用WebSocket和SSE技术实现Web端消息推送

详解Web端通信方式的演进:从Ajax、JSONP 到 SSE、Websocket

MobileIMSDK-Web的网络层框架为何使用的是Socket.io而不是Netty?

理论联系实际:从零理解WebSocket的通信原理、协议格式、安全性

微信小程序中如何使用WebSocket实现长连接(含完整源码)

八问WebSocket协议:为你快速解答WebSocket热门疑问

快速了解Electron:新一代基于Web的跨平台桌面技术

一文读懂前端技术演进:盘点Web前端20年的技术变迁史

Web端即时通讯基础知识补课:一文搞懂跨域的所有问题!

Web端即时通讯实践干货:如何让你的WebSocket断网重连更快速?

>> 更多同类文章 ……

(本文同步发布于:http://www.52im.net/thread-3098-1-1.html

posted @ 2020-08-05 15:36 Jack Jiang 阅读(211) | 评论 (0)编辑 收藏

本文由有赞技术团队原创分享,原题“有赞 APP IM SDK 组件架构设计”,即时通讯网收录时有修订和改动,感谢原作者的无私分享。

1、引言

本文主要以Android客户端为例,记录了有赞旗下 App 中使用自研 IM,并将IM提炼成组件化SDK的设计思路。此项工作由有赞移动开发组 IM SDK 团队共同讨论完成。

 

在有赞产品中,存在大量需要交易双方沟通交流的场景,比如,客户咨询商家产品信息,售前售后简单的答疑和维权等。另外,有赞业务还存在一些特殊的复杂场景,如供应商、分销商、客户三方之间需要同步沟通,会同时存在多种沟通角色。

此时需要较为完善的即时通信(IM)解决方案,但是由于有赞针对不同的商户和使用场景有多个APP,APP自行实现IM功能代价较大,且维护起来人力分散,于是,IM SDK项目便应运而生了,APP 通过接入此给件化SDK,可以快速实现IM基本功能。

学习交流:

- 即时通讯/推送技术开发交流5群:215477170[推荐]

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文在公众号上的链接是:https://mp.weixin.qq.com/s/ANp1kuj65Ww5RpABl2M9RQ,原文链接是:http://www.52im.net/thread-3088-1-1.html

2、相关文章

从游击队到正规军(一):马蜂窝旅游网的IM系统架构演进之路

从游击队到正规军(二):马蜂窝旅游网的IM客户端架构演进和实践总结》(* 推荐

从游击队到正规军(三):基于Go的马蜂窝旅游网分布式IM系统技术实践

一套海量在线用户的移动端IM架构设计实践分享(含详细图文)

从零到卓越:京东客服即时通讯系统的技术架构演进历程

一套原创分布式即时通讯(IM)系统理论架构方案

蘑菇街即时通讯/IM服务器开发之架构选择

自已开发IM有那么难吗?手把手教你自撸一个Andriod版简易IM (有源码)

适合新手:从零开发一个IM服务端(基于Netty,有完整源码)

拿起键盘就是干:跟我一起徒手开发一套分布式IM系统

3、设计目标

本次IM组件化SDK的设计目标有以下几点:

  • 1)IM 主流程稳定可用:消息传输具有高可靠性;
  • 2)UI 组件直接集成进入SDK,并支持可定制化;
  • 3)富媒体发送集成进入SDK,并可按需定制需要的富媒体类型;
  • 4)实现消息传输层SDK,与带有UI的SDK的功能分离,业务调用方既可以使用消息传输SDK,处理消息,然后自行处理UI,也可以使用带有UI组件的SDK,一步实现较为完备的IM功能。

4、整体结构

下图中简要描述了有赞客户端中IM系统的基本结构 : 

如上图所示,各分层的职责分工如下:

  • 1)消息通道层:维护Socket长连接作为消息通道,消息收发流程主要在这一层中完成;
  • 2)持久化层:主要将消息存入数据库中,富媒体文件存入文件缓存中,方便第二次展示消息时候,从本地加载,而不是网络层获取;
  • 3)逻辑处理层:完成各种消息相关的逻辑处理,如排序,富媒体文件的预处理等;
  • 4)UI显示层:将数据在UI上进行呈现。

5、设计要点1:Socket长连接的创建与维护

IM SDK 所有数据收发流程,均通过Socket长连接完成,如何维护一个稳定Socket通道,是IM系统是否稳定的重要一环。 

下面描述下Socket通道几个重要的流程。

1)创建流程(连接) :

如图上所示,当IM SDK初始化后,业务调用连接请求接口,会开始连接的创建过程,创建成功后,会完成鉴权操作,当创建和鉴权都完成后,会开启消息收发线程,为了维持长连接,会有心跳机制,特别的,会开启一个心跳轮询线程。

2)心跳机制 :

心跳机制,是IM系统设计中的常见概念,简单的解释就是每隔若干时间发送一个固定信息给服务端,服务端收到后及时回复一个固定信息,如果服务端若干时间内没有收到客户端心跳信息则视客户端断开,同理如果客户端若干时间没有收到服务端心跳回值则视服务端断开。 

 

当长连接创建成功后,会开启一个轮询线程,每隔一段时间发送心跳消息给服务器端,以维持长连接。

有关IM心跳方面的专项文章,请见:

手把手教你用Netty实现网络通信程序的心跳机制、断线重连机制

为何基于TCP协议的移动端IM仍然需要心跳保活机制?

移动端IM实践:实现Android版微信的智能心跳机制

移动端IM实践:WhatsApp、Line、微信的心跳策略分析

一文读懂即时通讯应用中的网络心跳包机制:作用、原理、实现思路等

正确理解IM长连接的心跳及重连机制,并动手实现(有完整IM源码)

一种Android端IM智能心跳算法的设计与实现探讨(含样例代码)

手把手教你用Netty实现网络通信程序的心跳机制、断线重连机制

3)重连流程 :

重连被触发时,如果该次连接成功,退出重连。反之重连失败后,会判断当前重连的次数是否超过预期值(这里设为6次),并对重连次数计数,如果超过就会退出重连,反之休眠预设的时间后再次进行重连操作。

重连触发条件分为三种:

  • a. 主动连接不成功(主动连接Socket,如果连接失败,会触发重连机制);
  • b. 网络被主动断开(正常建立连接,操作过程中,网络被断开,通过系统广播触发重连);
  • c. 服务器没响应,心跳没回值(服务端心跳预设时间内没回值,客户端认为服务端已经断开,触发重连)。

有关重连机制的深入学习,可以阅读以下两篇:

4)网络状态判断:

TCP API并没有提供一个可靠的方法判断当前长连接通道状态,isConnected()和isClosed()仅仅告诉你当前的Socket状态,不是是长连接断开是一回事。 isConnected()告诉你是否Socket与Romote host保持连接,isClosed()告诉你是否Socket被关闭。 

假如你判断长连接通道是否被关闭,只能通过和流操作相关的以下方法:

  • a. read() return -1;
  • b. readLine() return null;
  • c. readXXX() throw EOPException for any other XXX;
  • d. write 将抛出IOException: Broken pipe(通道被关闭)。

所以SDK封装isConnected(方法的时候,是根据这几种情况综合判断当前的通道状态,而不是仅仅通过Socket.isConnected()或者Socket.isClosed()。

6、设计要点2:消息发送流程

消息发送流程主要有两大类:

1)一类是IM相关数据的请求,例如:历史消息列表,会话列表等;

2)另一类是IM消息的发送,主要是文字消息。

富媒体消息发送,会将富媒体文件先上传服务器后,拿到文件URL, 通过文字消息,将此URL发给接收方,接收方下载后进行UI展示)。 

以上两类消息发送,均使用上图的流程进行发送,可通过发送回调感知请求的结果。

如上图所示,消息发送流程,需要先封装消息请求,在通过发送队列发送至服务器,发送前,在将请求id和对应回调存入本地Map数据结构中。

if(requestCallBack != null) { 

  mCallBackMap.put(requestId, requestCallBack);

}

之后接收服务器推送消息(此消息带有发送请求时的请求id),在本地的Map数据找到请求id对应的回调,然后通过回调返回服务器推送过来的数据。 

请求可以通过泛型指定返回值类型,SDK中会自行解析服务器数据返回的数据,直接返回给业务调用方model对象,方便使用。(目前支持json格式的数据解析)

private void IMResponseOnSuccess(String requestid, String response) { 

        if(mCallBackMap != null) {

           IMCallBack callBack = mCallBackMap.get(requestid);

           if(callBack == null) {

               return;

           }

           if(callBack instanceofJsonResultCallback) {

               finalJsonResultCallback resultCallback = (JsonResultCallback) callBack;

               if(resultCallback.mType == String.class) {

                   callBack.onResponse(response);

               } else{

                   Object object = newGson().fromJson(response, resultCallback.mType);

                   callBack.onResponse(object);

               }

               removeCallBack(requestid);

           }

        }

}

如下的示例中,展示了一个获取会话列表的请求,可以看出目前的请求封装,和一些第三方的的网络库类似,使用起来较为方便。

RequestApi requestApi = new RequestApi(IMConstant.REQ_TYPE_GET_CONVERSATION_LIST, Enums Manager.IMType.IM_TYPE_WSC.getRequestChannel());

 

requestApi.addRequestParams("limit", 100); 

requestApi.addRequestParams("offset", 0);

 

IMEngine.getInstance().request(requestApi, newJsonResultCallback<List<ConversationEntity>>() { 

    @Override

    publicvoidonResponse(List<ConversationEntity> response) {

        mSwipeRefreshLayout.setRefreshing(false);

        mAdapter.mDataset.clear();

        mAdapter.mDataset.addAll(response);

        mAdapter.notifyDataSetChanged();

    }

 

    @Override

    publicvoidonError(intstatusCode) {

        //do something

    }

});

可以看出,该请求直接返回了一个会话类型的List集合,业务方可直接使用。

7、设计要点3:消息接收流程

消息的监听流程主要使用了一个全局监听的方式来进行,需要先注册监听器,监听器中有默认的回调。

public interface IMListener { 

    /**

     * 连接成功

     */

    void connectSuccess();

 

    /**

     * 连接失败

     */

    void connectFailure(EnumsManager.DisconnectType type);

 

    /**

     * 鉴权成功

     */

    void authorSuccess();

 

    /**

     * 鉴权失败

     */

    void authorFailure();

 

    /**

     * 接收数据成功

     */

    void receiveSuccess(int reqType, String msgId, String requestChannel, String message, int statusCode);

 

    /**

     * 接收数据失败

     */

    void receiveError(int reqType, String msgId, String requestChannel, int statusCode);

}

该监听器中可以接收如下类型的消息:

  • 1)Socket连接状态的返回结果;
  • 2)鉴权状态的返回结果,(鉴权流程因有赞业务需要);
  • 3)接收的IM消息,或者其他类型的返回消息。可根据消息类型进行后续的分发处理。

业务如需使用此全局监听器,需要自行实现此接口,并在业务初始化时,注册此监听器即可。SDK中会根据注册的监听器,在读取到服务器推送消息后,直接通过监听器到回调进行分发。

private void distributeData(IMEntity imEntity) { 

        if(mIMListener != null&& imEntity != null) {

       // 省略部分逻辑代码

       ……

       if(status == Response.SUCCESS) {

           switch(responseModel.reqType) {

               caseIMConstant.REQ_TYPE_AUTH: // 鉴权成功

                   mIMListener.authorSuccess();

                   return;

 

               caseIMConstant.REQ_TYPE_OFFLINE: //  服务端踢客户端下线

                   mIMListener.connectFailure(EnumsManager.DisconnectType.SERVER);

                   break;

 

               caseIMConstant.REQ_TYPE_HEARTBEAT: // 心跳成功

               caseIMConstant.REQ_TYPE_RECEIVER_MSG: // 收到回调消息

                   handleMessageID(responseModel.body);

                   break;

               default:

                   break;

           }

           mIMListener.receiveSuccess(responseModel.reqType, msgId, responseModel

                   .requestChannel, responseModel.body, 0);

       } else{

           mIMListener.receiveError(responseModel.reqType, msgId, responseModel

                   .requestChannel, status);

       }

   }

}

部分接收消息,如心跳,多端登录时被踢下线通知等,sdk内部会自行处理,业务基本无感知。

8、设计要点4:可定制化的UI

随着公司规模的扩大与业务线的快速迭代,可能新的业务也需要 IM 这个功能,众所周知,IM UI 功能的嵌入会占据大量的开发与调试时间, 为了解决这个痛点,决定将 IM UI 部分抽成一个 Library,实现可定制与单独维护,做到真正的敏捷开发与快速迭代。

8.1 UIKit设计 

IM UIKit暴露相应的api接口,业务方注入相应的功能定制项,针对UI的点击回调通过EventBus总线post分发,减少了业务方与UIKit的耦合,底层业务方通过MVP模式对View与Model进行解耦。

定制项一般通过如下几种方式。

1)XML(定制业务信息,资源信息,显示条数,各个业务功能开关等):

<?xml version="1.0" encoding="utf-8"?>

<resources>

    <stylename="limit">

        <!--每屏展示的条数-->

        <itemname="swiplimit">5</item>

        ......

    </style>

    ......

    ......

    <stylename="itembox">

        <itemname="showvoice">true</item>

        ......

        ......

        <itemname="more"show="true">

            <more>

                <iconstyle="mipmap">im_plus_image</icon>

                <itemname>测试</itemname>

                <callback>false</callback>

            </more>

             ......

             ......

            <more>

                <iconstyle="mipmap">ic_launcher</icon>

                <itemname>测试</itemname>

                <callback>true</callback>

            </more>

        </item>

        ......

        ......

    </style>

</resources>

2)Style(定制UI背景,气泡颜色,字体大小等):

<?xml version="1.0" encoding="utf-8"?>

<resources>

    <!--im 聊天背景-->

    <stylename="imui_background">

        <itemname="android:background">@android:color/holo_red_dark</item>

    </style>

    ......

    ......

 

    <!--气泡背景-->

    <stylename="bubble_background">

        <itemname="android:background">@mipmap/bubble_right_green</item>

    </style>

 

       <!--背景和和字段颜色定制-->

    <stylename="bg_and_textcolor"parent="bubble_background">

        <itemname="android:textColor">@android:color/holo_red_dark</item>

    </style>

    ......

    ......

</resources>

3)Model定制(传入预设的定制Model模板填入相应参数,UIKit里面做相应解析):

public class Entity {

    publicString action1;

    publicString action2;

    publicString aciton3;

    ......

}

8.2 UIKit 支持的富媒体类型

除了文字消息之外,现在主流的IM系统中也支持各种富媒体发送,在有赞IM SDK UIKit中,目前也支持几种富媒体发送。 以下是发送流程图和两类常见富媒体消息简介。

  • 1)语音消息:除了使用常见的录制和解码播放的技术之外。还利用了 AudioManager 中 requestAudioFocus,abandonAudioFocus 相关方法,实现了录制和播放语音消息,如果有第三方播放音乐,会自动暂停,录制和播放语音消息结束后,声音会自动播放。
  • 2)图片消息:通过七牛服务器设置了缩略图,接收方收到消息后,会先下载缩略图,当用户再点击进入图片详情页时,会下载大图,Andorid客户端使用Picasso加载库加载图片,并做本地缓存。

9、设计要点5:UI 中聊天会话数据加载策略

参考业界主流的IM系统方案,用户聊天时,需要将已经发送和接收到的聊天信息保存到本地,而不是每次都拉取历史数据。以达到节约流量和无网络状态下也查看数据的效果。

为此IM SDK持久化层的数据库中,也实现了简单存储加载机制,下面描述典型的数据加载场景。

1)IM会话首次请求数据流程:

2)IM下拉获取历史数据流程: 

3)IM单条消息发送持久化方案:

4)IM单条数据重发流程: 

10、设计不足之处

1)消息回执:

当前的设计方案中,没有消息回执的机制,也就是说接受方收到消息后,不会返回服务器收到消息的通知,服务器无法判断消息是否推送成功,这样在突然断网,网络模式切换,或者弱网环境下,会影响消息的到达率。 

一种可行的设计方式是,发送方增加已送到和未送达的状态,接收方收到消息后,给服务器返回已收到消息的通知,服务器再推送给发送方该状态,如果没有收到接收方回执,服务器可尝试重新推送。发送方接受到接收方的收到回执后,更新发送状态已发送,如果未收到,则显示未送达。为了防止接收方回执丢失,接收方接收消息时候,可维护本地去重队列。

2)本地请求超时的判断:

本地发起的请求,没有用定时器,完全依赖服务器返回或者出现Socket通道异常后上抛的通知作为超时判断,部分场景可能覆盖不到,需要对请求增加固定的超时处理机制,固定时候未收到请求,即认为超时。

* 推荐学习:针对以上两点不足,感兴趣的读者,可以研究一下MobileIMSDK开源工程源码https://github.com/JackJiang2011/MobileIMSDK,MobileIMSDK已经实现了完整的消息送达保证机制(包括:ACK回执、重传、去重、超时判定等等)。

(本文同步发布于:http://www.52im.net/thread-3088-1-1.html

posted @ 2020-07-29 14:09 Jack Jiang 阅读(151) | 评论 (0)编辑 收藏

     摘要: 本文引用自公众号“开发的猫”,本次收录时有改动,感谢原作者“开发的猫”的分享。1、引言随着移动网络速度越来越快、质量越来越来,实时音视频技术已经在各种应用场景下全面开花,语音通话、视频通话、视频会议、远程白板、远程监控等等。实时音视频技术的开发也越来越受到重视,但是由于音视频开发涉及知识面比较广,入门门槛相对较高,让许许多多开发者望而生畏。虽然网上有...  阅读全文

posted @ 2020-07-24 14:13 Jack Jiang 阅读(225) | 评论 (0)编辑 收藏

1、点评

IM聊天消息的可靠投递,是每个线上产品都要考虑的IM热点技术问题。

IM聊天消息能保证可靠送达,对于用户来说,就好比把钱存在银行不怕被偷一样,是信任的问题。试想,如果用户能明显感知到聊天消息无法保证送达,谁还愿意来用你的APP?谁也不希望自已的话就像浮云一样随风飘逝。

必竟用IM聊天,虽然很多时候是费话,但总有关键时刻存在——比如向女神表白(哪怕明知被拒),作为合格的舔狗一定不希望女神错过这条消息。

所以,消息的可靠投递是每款IM产品和立足之本,也是IM开发者们孜孜不倦追求的技术目标。

本文作者将以自已IM开发过程中的真实总结,分享针对大量离线聊天消息,在确保用户端体验不降级的前提下,保证离线消息的可靠投递。

学习交流:

- 即时通讯/推送技术开发交流5群:215477170[推荐]

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文在公众号上的链接是:https://mp.weixin.qq.com/s/T2w9h_AN_T2UnqNdVikX0Q,原文链接是:http://www.52im.net/thread-3069-1-1.html

 

2、本文作者 

fzully(柳林勇):2005年数学系毕业,先后就职于福建新大陆、福建富士通、北京世纪奥通。长期从事服务端软件开发,涉及SIP服务器、内核RTP转送、电信级AAA认证系统、IM即时通讯系统等。在分布式高性能系统设计有多年经验积累。

本作者的另一篇:《IM群聊消息的已读未读功能在存储空间方面的实现思路探讨》也已被即时通讯网收录并整理发布,有兴趣可以前往阅读。

3、相关文章

从客户端的角度来谈谈移动端IM的消息可靠性和送达机制

移动端IM中大规模群消息的推送如何保证效率、实时性?

IM消息送达保证机制实现(一):保证在线实时消息的可靠投递

IM消息送达保证机制实现(二):保证离线消息的可靠投递》(* 强烈推荐

如何保证IM实时消息的“时序性”与“一致性”?

一个低成本确保IM消息时序的方法探讨

IM群聊消息如此复杂,如何保证不丢不重?》(* 强烈推荐

移动端IM登录时拉取数据如何作到省流量?》(* 强烈推荐

完全自已开发的IM该如何设计“失败重试”机制?

IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的》(* 强烈推荐

4、正文引言

暗恋女神良久,终于鼓起勇气决定向女神写一封情书。但如何表达才能感动女神?自感才疏学浅,于是通读四书五经、熟背唐诗宋词、遍览四大名著,已然腹有诗书气自华。一周末冥思苦想整日才写就一首七言律诗,虽无惊天地泣鬼神之势,但诚挚的爱念在字里行间里流淌,亦歌亦诗,相信会感动到女神,手机欣然发出。

发出一秒后,手心冒汗,感觉脸颊发烫,心脏像受惊吓的野兔一样快速跳动,就像第一次看见女神那时的感觉。闭着眼睛,想象女神看到消息时的情形,她是否也期盼我的表白?看到消息时是否心跳加速、小脸绯红?

一分钟后,紧盯手机屏幕,等待、期盼女神回复。

时间一分一秒地逝去,等一分钟像等一年一样漫长。

一小时后,仍然杳无音讯,难道她没看到消息么?或许在忙什么而没留意手机吧!

一天过去了,坐立不安,等待是一种痛苦的煎熬,期待和煎熬在心中交织翻滚,有几个瞬间甚至希望女神赶快拒绝自己,好让自己解脱!茶饭无味,失眠多天,整日魂不守舍。

一个月过去了,死心。

半年后,女神出嫁,婚礼那天前去祝福。席间亦随众觥筹交错,略有醉意,向女神敬酒:祝福你,但愿以后能遇见像你这样的女人。女神先是愣住、收起笑容,低下头,目光无神地看着大红地毯,长叹一声,言:我等你表白,等了一年!空气凝滞了几秒,女神强作欢颜:从今往后,各自安好吧,干杯!

我转身踱回到座位,拿起手机,打开那个App,看着曾经发出的情书,一切仿佛还在昨日,但故事脚本已被别人书写,欲哭无泪,叹老天为何如此捉弄我?为何我发的消息女神没收到啊!

失魂落魄地回到家里,从冰箱里拿出几瓶罗斯福10号来麻醉自己,在酒精强烈的作用下,迷迷入睡。

第二天醒来,我明白了一个道理:对IM系统而言,消息必达永远摆在第一位!

以上是胡说八道,以下开始正文。。。

5、用全量离线消息实现消息必达

我们在重构IM系统时,需解决上一代设计的痛点之一就是确保消息必达。

5.1 离线消息实现消息必达的流程

自然而然地会想到这么做——即由服务端为每个人保存一个“离线消息列表”。

具体的思路是这样:

  • 1)当用户在线时,由IMS主动确保消息下发且收到客户端的应答确认时,才认为消息送达客户端,相应地把消息从“离线消息列表”移除;
  • 2)如果客户端没有发回应答确认,IM服务端会再发送。

以此来确保消息一定送到客户端,看起来是很符合逻辑。当时调查过市面上多款IM,行为基本如此。

5.2 海啸般的离线消息

5.2.1)和平时期:

重构后的IM上线,内部测试及在公网运行,离线消息的工作一直很正常。

5.2.2)被签到签死了:

后来,为某客户部署的私有环境,其用户量达几十万,其中的一个组织接近三万人,全员群也接近三万人;还有,底下的部门也有相应的群组,几百到几千人群不等。

“报到”、“签到”。。。大量的类似消息被发到几千、几万人的群内,然后如果有人一两天没上线,或者被加入到多个组织内,等到其上线时,几万条离线消息像海啸一般涌来,您想象一下:手机用户刚登陆的几分钟内,是什么场景?

用户真的很无辜:我不就是登陆了一下App,叮叮咚咚响了几分钟,还卡,还发热。。。

客户端承受不起大规模离线消息的轰炸,怎么办?

5.3 临时运用方案

  • 1)对若干大组织的全员群,对非管理员禁言;
  • 2)通知所有用户不要在大群签到。

我承认,这确实不算是个正经方案。。。

6、远离全量离线消息

我承认,一开始设计离线消息时,真没想到是这样的使用场景。对于大多数IM的开发者,或许不会碰到这种场景(但凡事住最坏的可能性想,总是没错的)。

6.1 放弃以离线消息的形式实现消息必达

我开始思考什么是消息必达,以前的想法是:把用户该收的消息都送到其客户端,是消息必达。

后来,给消息必达下了新的定义:

  • 1)用户有新消息时,确保让用户知道;
  • 2)当用户要查看这些消息时,确保其可一条不漏地看到。

打个比方:

  • 1)客户要把钱给您,不必送到您家里才算送到;
  • 2)而是转账到您的银行账户上,并告知您;
  • 3)当您要用钱时,直接从银行账户上消费即可。

从此,不会在用户上线时向其发送大量离线消息(即全量推送)。

6.2 以会话列表为基础来实现消息必达

客户端在上线时,先从服务端更新会话列表,也就是你通常在每个IM客户端的首页看到的这个(如下图所示)。

上图引用自《IM开发快速入门(一):什么是IM系统?

每一个会话列表项包含如下信息(此处简化了与本文无关的成员变量):

{

        // 会话对象的角色类型,比如私聊、群聊、系统通知、业务通知。。。

        uint32  session_role;

        // 会话对象的ID

        uint32        session_id;

        // 会话时间戳,用于消息同步;

        // 指会话的最后操作时间,比如清除角标的时间,与会话最后一条的消息时间未必一致

        uint64 session_timestamp;

        // true表示新增或更新,false表示被删除

        boolis_add;

 

        // 当is_add=false时,忽略以下信息

 

        // 仅用于显示角标的未读数量,当用户查看该会话后清零,且客户端多端同步

        uint32 new_msg_count;

        // 会话的最后一条消息

        MessageItem         latest_msg;

        // 跳转消息的时间戳,即new_msg_count的最旧1条消息的时间

        uint64 goto_timestamp;

}

为方便讨论,假设以下前提:

  • 1)周五傍晚18:00下班,我关闭App,我是9527;
  • 2)有1小姐姐向我发了5条消息留言,约我周末去海边玩,她是杨幂3306;
  • 3)然后,另1小姐姐也向我发了33条消息留言,内容我不便透露,她是景甜5672;
  • 4)严正声明:我跟她们很清白,其实我喜欢的是6379。

对,既然是假设,假一点也无妨。

我下班回到家,看到手机有通知栏消息,打开App将会发生哪些事呢?

App和IM后端的交互:

1)登录后,App以18:00填充参数latest_session_time,向IMS获取会话列表(其实不是以下线时间18:00,但这样更易理解);

2)IM后端检查发现我从18:00开始,有2个会话更新了,于是向App发送应答,以增量形式携带2个会话项:杨幂3306,景甜5672。其中景甜5672的会话项信息如下:

{

        uint32  session_role = Role_User; //表示私聊

        uint32        session_id = 5672; //景甜的ID

        uint64  session_timestamp = 1594464295335672; //最后一条消息的时间戳,微秒

        boolis_add = true; // true表示是更新项

        uint32  new_msg_count = 33; // 景甜向我发了33条消息

        MessageItem         latest_msg = "房号是0520"; //最后1条消息,结构体MessageItem简略不表

        uint64  goto_timestamp = 1594463697556677; // 向我发的33条消息的最早1条的时间

}

3)App收到步骤2的应答,我在App的会话列表窗口里,能看到2项更新,景甜发来的未读消息数33条,杨幂的是5条,如下图所示:

 

4)点开景甜5672的会话,App将向IMS发起同步消息的请求,获取最新的10条聊天消息(为了显示一屏):

{

        uint32  session_role = Role_User; //表示私聊

        uint32        session_id = 5672; //景甜的ID

        uint64        begin_time  = 1594464295335672; //步骤2返回的session_timestamp

        uint64        end_time  = 1594434153444222; //景甜上午向我发的最后一条消息的时间

        uint32        max_pieces = 10; //本次最多取10条,PC屏幕大则不妨取20条

}

5)IM后端收到步骤4请求,将返回33条新消息的最后10条给App,呈现聊天窗口内,且聊天窗口上方有一个tip:“↑ 33条新消息”,如下图所示:

 
 

6)我可以向上翻动聊天记录,那么App将持续向IMS获取第2批同步消息;或者也可以点击tip:“↑ 33条新消息”,直接跳转到33条消息的最旧一条,这样支持从最旧的消息向新的翻看。

相比于客户端简单地被动接收服务端的离线通知方式,这种设计使得客户端的处理逻辑更复杂。

主要体现在:

  • 1)客户端向服务端取的同步消息是未必完整,这些存在客户端的消息,在时间区间上可能不连续的;
  • 2)客户端需要知道不同消息之间是否有断代,如果有则需要向服务端查询同步消息来merge本地信息,使其连续,即客户端要实现消息融合。

我的建议:用C++实现一个统一的底层imsdk库,来负责这些共通的消息处理和存储。避免各客户端(Windows,iOS,Android等)各自实现这些逻辑,减少工作量,也降低各端不一致的风险。

6.3 以会话列表为基础与用全量离线消息的方案对比

6.3.1)用全量离线消息实现的方案优缺点:

实现原理:由IM服务端确保消息送达客户端,客户端存储后发回确认。

方案优点:逻辑简单。

在聊天消息不同数量级时的表现:

  • a. 离线消息量不多(如几百条):没有效率问题,且消息全部达到客户端本地,方便进行查找等动作;
  • b. 离线消息量巨大(如几万条):用户登录瞬间CS间瞬时流量大,客户端瞬时要存储、更新的数据量巨大,可能出现卡顿、假死等情况。

6.3.2)用会话列表为基础的方案优缺点:

实现原理:客户端先同步会话列表,由用户驱动不定次获取同步消息。

方案缺点:逻辑复杂,客户端增加不少工作。

在聊天消息不同数量级时的表现:

  • a. 离线消息量不多(如几百条):没优势;
  • b. 离线消息量巨大(如几万条):登录时交互数据小,对IM后端、客户端、用户体验,都比较友好。

7、多终端条件下,如何得到完整消息履历?

由于同一个用户的每个终端,其会话最后更新时间、每个会话的最后一条时间可能都不一样,参照上一节的实现思路,可以得到解决方案。

具体如下:

  • 1)参照第6.2章节的“App和IM后端的交互”第1个步骤,可取到不同的增量变化的会话列表项;
  • 2)参照第6.2章节的“App和IM后端的交互”第4个步骤,可取到任一区间的同步消息,得到完整消息。

8、离线消息是否就彻底废弃了?

有若干情况,仍然需要保留离线消息,以确保消息送达。

比如以下情形:

  • 1)别人向我发送离线文件:这种情况下不能依赖同步消息来获取。因为不以离线消息通知的话,用户在没有拉取到对应的同步消息前,是不知道有离线文件的;
  • 2)撤回消息:即使接收者不拉取同步,仍然要保证在上线后其数据在第一时间被撤回。注意:这里可能存在多端撤回问题;
  • 3)用户在线时的消息下发:由于用户在线时,IM后端向客户端发送消息可能碰到网络抖动等情况,导致消息下发失败,这些消息先可以直接存在离线消息队列,IM后端可在收到客户端的心跳包时重发消息。相当于维护了一个在线消息的离线队列。

9、本文结语

曾经有一段真挚的爱情摆在我面前,如果时间倒流到半年前,我会选择一个靠谱的IM来发送消息,也许故事的脚本就由自己书写——是否要整一个时光倒流的版本,抱得美人归的那种?

不整了不整了,我得不到女神,你们才欢喜,我太了解你们了。。。各位爷欢喜就好。

附录:IM开发干货系列文章

本文是系列文章中的第26篇,总目录如下:

IM消息送达保证机制实现(一):保证在线实时消息的可靠投递

IM消息送达保证机制实现(二):保证离线消息的可靠投递

如何保证IM实时消息的“时序性”与“一致性”?

IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?

IM群聊消息如此复杂,如何保证不丢不重?

一种Android端IM智能心跳算法的设计与实现探讨(含样例代码)

移动端IM登录时拉取数据如何作到省流量?

通俗易懂:基于集群的移动端IM接入层负载均衡方案分享

浅谈移动端IM的多点登陆和消息漫游原理

IM开发基础知识补课(一):正确理解前置HTTP SSO单点登陆接口的原理

IM开发基础知识补课(二):如何设计大量图片文件的服务端存储架构?

IM开发基础知识补课(三):快速理解服务端数据库读写分离原理及实践建议

IM开发基础知识补课(四):正确理解HTTP短连接中的Cookie、Session和Token

IM群聊消息的已读回执功能该怎么实现?

IM群聊消息究竟是存1份(即扩散读)还是存多份(即扩散写)?

IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列

一个低成本确保IM消息时序的方法探讨

IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!

IM里“附近的人”功能实现原理是什么?如何高效率地实现它?

IM开发基础知识补课(七):主流移动端账号登录方式的原理及设计思路

IM开发基础知识补课(八):史上最通俗,彻底搞懂字符乱码问题的本质

IM的扫码登功能如何实现?一文搞懂主流应用的扫码登陆技术原理

IM要做手机扫码登陆?先看看微信的扫码登录功能技术原理

IM开发基础知识补课(九):想开发IM集群?先搞懂什么是RPC!

IM开发实战干货:我是如何解决大量离线聊天消息导致客户端卡顿的

IM开发干货分享:如何优雅的实现大量离线消息的可靠投递》(* 本文)

另外,如果您是IM开发初学者,强烈建议首先阅读《新手入门一篇就够:从零开发移动端IM》。

(本文同步发布于:http://www.52im.net/thread-3069-1-1.html

posted @ 2020-07-21 13:58 Jack Jiang 阅读(254) | 评论 (0)编辑 收藏

本文在编写时参考了博客作者“鹿呦呦”和在线课程“即时消息技术剖析与实战”的相关资料,一并表示感谢。

1、系列文章引言

IM系统看似简单(没错,很多土老板认为开发个qq和微信也就是几万块钱的事... ),实责是众多技术的应用合体,包括网络编程、移动开发、后端开发、高并发、高可用、高安全等技术范畴,再加上多端使用不同的编程语言,想要凑齐一个典型的IM产品技术栈那也不是个容易事。

而对于IM开发入门者来说,想要在众多的IM技术术语和概念中找到学习的方向和需要的资料,那也是件很让人抓狂的事。如果看到不该看的技术深水区文章,直接从入门到放弃——被活活吓退,那也是相当悲剧的。

本系列文章将尽量从理论概念入手,通俗易懂的梳理IM中的基础技术概念和热门技术点,希望能帮你理清看似一团乱麻的IM知识体系,助你找到清晰的IM技术学习方向,来日工资翻倍、迎娶白富美也未必不可能!

 

友情提示:本系列文章侧重于理论概念的讲述,篇幅有限,点到即止,如需系统、深入、具体地学习IM技术的方方面面,请从此文入手:《新手入门一篇就够:从零开发移动端IM》(史诗级文章,适合从入门到放弃)。

学习交流:

- 即时通讯/推送技术开发交流5群:215477170[推荐]

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM

2、系列文章目录

IM开发快速入门(一):什么是IM系统?》(* 本文

《IM开发快速入门(二):什么是IM系统的实时性? (稍后发布)》

《IM开发快速入门(三):什么是IM系统的可靠性? (稍后发布)》

《IM开发快速入门(四):什么是IM系统的一致性? (稍后发布)》

《IM开发快速入门(五):什么是IM系统的安全性? (稍后发布)》

《IM开发快速入门(六):什么是IM系统的的心跳机制? (稍后发布)》

《IM开发快速入门(七):如何理解并实现IM系统消息未读数? (稍后发布)》

《IM开发快速入门(八):如何理解并实现IM系统的多端消息漫游? (稍后发布)》

3、本文内容概述

本文将带你快速了解一个主流IM系统的应用场景、典型架构、技术特点和功能组成,帮你快速建立对IM系统的主观认知。

如果你不想从技术的角度理解IM原理,可以尝试阅读此文:《知识科普:IM聊天应用是如何将消息发送给对方的?(非技术篇)》。

本文已收入即时通讯网的入门纲领性文章《新手入门一篇就够:从零开发移动端IM》。

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文在公众号上的链接是:https://mp.weixin.qq.com/s/h7L4UGHRl7qI1bi8WyZ5iw,原文链接是:http://www.52im.net/thread-3065-1-1.html

4、IM的应用场景

IM其实并不局限于聊天、社交这类“典型”应用中,实际上它已经广泛运用于我们身边形形色色的软件中。

聊天、直播、在线客服、物联网等所有需要实时互动、高实时性的场景等等,都需要应用到 IM 技术。

下面这些场景是我们大家都熟悉的,都用到了IM技术:

  • 1)微信、qq、钉钉等主流IM应用:这是IM技术的典型应用场景;
  • 2)微博、知乎等社区应用:它们利用IM技术实现了用户私信等点对点聊天;
  • 3)抖音、快手等直播/短视频应用:它们利用IM技术实现了与主播的实时互动;
  • 4)米家等智能家居物联网应用:利用IM技术实现实时控制、远程监控等;
  • 5)滴滴、Uber等共享家通类应用:利用IM技术实现位置共享;
  • 6)在线教育类应用:利用IM技术实现在线白板。 

5、IM的典型架构

一个典型的IM架构类似于下图这样:

本图引用自《即时消息技术剖析与实战》学习笔记1——IM系统的架构》一文

如上图所示,IM架构中的各分层职责如下:

  • 1)客户端:作为与服务端进行消息收发通信的终端;
  • 2)接入层:也叫网关层,为客户端收发消息提供入口;
  • 3)逻辑层:负责IM系统各功能的核心逻辑实现;
  • 4)存储层:负责IM系统相关数据的持久化存储,包括消息内容、账号信息、社交关系链等;
  • 5)第三方服务:保证APP在未打开或后台运行时也能收到消息通知(这主要是第第3方消息推送服务)。

尤其对于“接入层”,它的职责最为关键,具体是:

  • 1)保持海量用户连接;
  • 2)解析协议,对传输内容进行编解码;
  • 3)维护客户端的连接(也叫“Session”);
  • 4)推送消息。

以下文章适合IM架构设计入门,有兴趣可以读一读:

6、IM技术的特点

IM技术的特点主要就是以下4点: 

▶ 1)实时性:

对于IM系统,“实时”二字是精髓,也是这项技术存在关键意义所在。它保证的是消息的实时触达。

举个例子:如果跟你的好友微信或qq聊天,我发的消息他不能即时收到,或者他发的信息你也不知道什么时候能收到,这基本上也就没法聊下去了(干吗不痛快打个电话呢)。

▶ 2)可靠性:

保证消息的不丢失和不重复,是IM系统的另一个关键技术特点。试想,当你在用qq或微信跟女朋友聊天,好不容易鼓起勇气向“她”表白,结果这消息要是丢包了,那肯定得卸载应用了,搞不好砸手机都有可能。当然,好话不说二遍,消息重复也同样恼人。

以下文章对消息的不丢/不重问题进行了深入探讨,有兴趣可以详读:

▶ 3)一致性:

对于单聊消息而言,保证同一个设备的时间顺序、不同设备的漫游同步,也是相当重要的一环。

IM系统中的消息交互,就到底就是人跟人在“说话”,前言不搭理后言、或者胡言乱语式的消息展现,那不是人疯了就是程序疯了,总之就是没法再聊下去了。

以下文章对消息时序问题进行了深入探讨,有兴趣可以详读:

▶ 4)安全性:

保证数据传输安全、数据存储安全、消息内容安全,也是IM系统必不可少的特性。尤其在私聊场景下,如果不能做到安全性,聊天的体验跟被人偷窥的感觉是没有区别的。

以下文章对IM的安全问题进行了深入探讨,有兴趣可以详读:

7、IM的功能组成

浅显的角度讲,一个典型的IM功能组成,无非就是以下5样:

  • 1)联系人列表;
  • 2)聊天界面;
  • 3)消息发送通道;
  • 4)消息接收通道;
  • 5)消息存储;
  • 6)消息未读数。

我们一样一样来说说各自的用途。

▶ 1)联系人列表:

这个很好理解,使用IM系统的第一步,就是要解决“跟谁聊”的问题。从功能表象上来说,联系人列表也就是社交关系列表,无非就是个信息列表界面,有什么特殊的地方?

联系人列表看似简单,实际上它是一系列IM系统的社交关系确立动作的结果体现。

要想建立联系人列表,你可能需要实现以下逻辑:

  • 1)怎么能找到想要聊天的人?(需要实现随机查找?精确查找?)
  • 2)怎么决定要不要跟这个人聊?(需要实现对方的个人信息查看)
  • 3)开始发出好友请求;
  • 4)被请求的一方,还可以决定是“同意”还是“拒绝”(“同意”该怎么处理?“拒绝”又该怎么处理?)。

总的来说,联系人列表的建立,是一个IM系统聊天关系确立的表现,不可或缺。

▶ 2)聊天界面: 

聊天界面看似很平常,实际它就是IM系统客户端的核心功能所在,所有主要的IM功能都是通过它展现。

它应该具备的能力有:

  • 1)各种聊天功能按钮:语音留言、图片、文字、表情、文件、实时电话、实时视频等;
  • 2)各种聊天消息显示:各种消息都有不同的UI显示元素和处理逻辑;
  • 3)流畅的使用体验:大量不同类型的消息显示时,不能卡顿;
  • 4)即时显示聊天消息:网络线程收到的消息,要马上在UI上显示出来;
  • 5)历史消息的加载:上次聊过的内容也得显示出来吧。

以上只是简单罗列,这看似简单的聊天界面,能把上面列表的事情做好,工作量也不小吧。

▶ 3)消息发送通道:

下图是一个典型的IM消息收发通道示意: 

如上图所示,消息发送通道这个比较好懂,最浅显易懂的理解就是用tcp或udp,建立socket长连接,需要发消息的时候,wirte一下就过去了,好简单!

但,事情往往不是想象的这么简单:

  • 1)如何保证这条socket长连接时一直处于可用的状态?
  • 2)当socket长连接不可用时,用户此时发送的消息该怎么处理?
  • 3)怎么保证发送的消息不丢?
  • 4)怎么保证发送的消息不复重?
  • 5)怎么保证发送的消息乱序?
  • 6)当对方不在线时,发送的消息去哪了?
  • 7)发送的消息,能保证实时送到?

这么一说,事情还挺多(那不废话吗。。。)。

▶ 4)消息接收通道:

正如上节中的消息收发通道示意图所示,消息接收通道也很好理解,对方通过消息发送通道write的消息,我得收到并显示啊。

要实现一个可靠的消息接收通道,也并非易事:

  • 1)如何保证socket长连接通道能随时处于良好的边接状态(随时接收对方write的消息);
  • 2)当socket长连接断开时,对方发送消息该怎么实现?
  • 3)当socket恢复连接时,怎么恢复之前的聊天现场?
  • 4)当我收到对方的消息时,对方怎么知道我已经收到了?
  • 5)当重复收到对方的消息时,该怎么处理?
  • 6)当收到的消息时序有错乱,该怎么处理?

▶ 5)消息存储:

消息存储这个功能好理解,聊天的消息如果存储,下次再聊的时候就不知道之前聊过什么,做不到这一点,这个IM系统的聊天体验好不起来。

那么,哪些情况下需要进行消息存储呢:

  • 1)对方不在线时:聊天消息应该存储(这叫离线消息存储);
  • 2)对方在线时:聊天消息也要存到本地存储(这叫消息缓存);
  • 3)对方在线或不在线时:聊天消息都要存到服务端(用于实现多设备的消息漫游和同步)。

具体要存储的内容和时机也就上面这几样。

但技术落到实处,要做的事情同样少不了:

  • 1)离线消息该怎么多久?
  • 2)图片、短视频、大文件这类的离线消息,多媒体文件该怎么存(有可能量会很大)?
  • 3)当本地的消息积累太多时,怎么能保证本地存储的性能?
  • 4)当应用更新、升级或异常时,怎么能保证本地存储的完整性(不被破坏)?
  • 5)怎么能保证多设备消息能不丢、不重、不乱?

这么多需要考虑的内容,也挺让人抓狂。

下图是一个IM系统的典型存储架构设计,了解一下:

本图引用自《现代IM系统中聊天消息的同步和存储方案探讨》一文

存储是IM系统的基石,以下文章可以深入阅读:

▶ 6)消息未读数:

消息未读数?看起来也就是那个所有IM应用都有的未读小红点嘛。是的,看起来也好简单!

然而,消息未读数功能的实现也一样不简单:

  • 1)未读数是客户端实现还是服务端实现?
  • 2)会话未读和总未读怎么保持一致?
  • 3)多终端情况下,怎么保证未读数的一致性(我在这台设备上读没读,那台设备怎么知道的?)?

是的,看起来就这么简简单单的3件事,但深入思考一下,还真的简单不起来。

8、本文小结

IM系统的应用场景已经不单单是IM聊天应用这一种形态,它已经融入到互联网应用的方方面面,必竟谁都想自已的应用具备“实时”交互这种能力,因为体验太好了。

IM系统典型架构无非就是网络接入层、业务逻辑层、数据存储层,除开网络接入层,其它各层其实跟普通的应用系统看起来差别并不是太大。

IM系统的技术特点来说,就是实时性、可靠性、一致性、安全性,除了实时性对于多数应用来说并不关心,其它的指标也很好理解。

IM系统的功能组成上,联系人列表用于数据模型的建立、聊天界面承载了IM系统的终端展现、消息的收发通道用于实现“实时”这个特性、存储和未读数看似不是必须但用户体验上确必不可少。

附录:更多IM开发资料汇总

[1] 有关IM架构设计的文章:

浅谈IM系统的架构设计

简述移动端IM开发的那些坑:架构设计、通信协议和客户端

一套海量在线用户的移动端IM架构设计实践分享(含详细图文)

一套原创分布式即时通讯(IM)系统理论架构方案

从零到卓越:京东客服即时通讯系统的技术架构演进历程

蘑菇街即时通讯/IM服务器开发之架构选择

腾讯QQ1.4亿在线用户的技术挑战和架构演进之路PPT

微信后台基于时间序的海量数据冷热分级架构设计实践

微信技术总监谈架构:微信之道——大道至简(演讲全文)

如何解读《微信技术总监谈架构:微信之道——大道至简》

快速裂变:见证微信强大后台架构从0到1的演进历程(一)

17年的实践:腾讯海量产品的技术方法论

移动端IM中大规模群消息的推送如何保证效率、实时性?

现代IM系统中聊天消息的同步和存储方案探讨

WhatsApp技术实践分享:32人工程团队创造的技术神话

微信朋友圈千亿访问量背后的技术挑战和实践总结

王者荣耀2亿用户量的背后:产品定位、技术架构、网络方案等

IM系统的MQ消息中间件选型:Kafka还是RabbitMQ?

腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面

以微博类应用场景为例,总结海量社交系统的架构设计步骤

快速理解高性能HTTP服务端的负载均衡技术原理

子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践

IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列

微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)

微信技术分享:微信的海量IM聊天消息序列号生成实践(容灾方案篇)

新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践

一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践

社交软件红包技术解密(一):全面解密QQ红包技术方案——架构、技术实现等

社交软件红包技术解密(二):解密微信摇一摇红包从0到1的技术演进

社交软件红包技术解密(三):微信摇一摇红包雨背后的技术细节

社交软件红包技术解密(四):微信红包系统是如何应对高并发的

社交软件红包技术解密(五):微信红包系统是如何实现高可用性的

社交软件红包技术解密(六):微信红包系统的存储层架构演进实践

社交软件红包技术解密(七):支付宝红包的海量高并发技术实践

社交软件红包技术解密(八):全面解密微博红包技术方案

社交软件红包技术解密(九):谈谈手Q红包的功能逻辑、容灾、运维、架构等

社交软件红包技术解密(十):手Q客户端针对2020年春节红包的技术实践

从游击队到正规军(一):马蜂窝旅游网的IM系统架构演进之路

从游击队到正规军(二):马蜂窝旅游网的IM客户端架构演进和实践总结

IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!

瓜子IM智能客服系统的数据架构设计(整理自现场演讲,有配套PPT)

阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处

从游击队到正规军(三):基于Go的马蜂窝旅游网分布式IM系统技术实践

微信后台基于时间序的新一代海量数据存储架构的设计实践

IM开发基础知识补课(九):想开发IM集群?先搞懂什么是RPC!

>> 更多同类文章 ……

[2] IM开发热门综合文章:

新手入门一篇就够:从零开发移动端IM

移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”

移动端IM开发者必读(二):史上最全移动弱网络优化方法总结

从客户端的角度来谈谈移动端IM的消息可靠性和送达机制

IM开发基础知识补课:正确理解前置HTTP SSO单点登录接口的原理

移动端IM中大规模群消息的推送如何保证效率、实时性?

移动端IM开发需要面对的技术问题

开发IM是自己设计协议用字节流好还是字符流好?

请问有人知道语音留言聊天的主流实现方式吗?

IM消息送达保证机制实现(一):保证在线实时消息的可靠投递

IM消息送达保证机制实现(二):保证离线消息的可靠投递

如何保证IM实时消息的“时序性”与“一致性”?

一个低成本确保IM消息时序的方法探讨

IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?

IM群聊消息如此复杂,如何保证不丢不重?

谈谈移动端 IM 开发中登录请求的优化

移动端IM登录时拉取数据如何作到省流量?

浅谈移动端IM的多点登录和消息漫游原理

完全自已开发的IM该如何设计“失败重试”机制?

通俗易懂:基于集群的移动端IM接入层负载均衡方案分享

微信对网络影响的技术试验及分析(论文全文)

即时通讯系统的原理、技术和应用(技术论文)

开源IM工程“蘑菇街TeamTalk”的现状:一场有始无终的开源秀

如约而至:微信自用的移动端IM网络层跨平台组件库Mars已正式开源

全面掌握移动端主流图片格式的特点、性能、调优等

子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践

IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列

微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)

自已开发IM有那么难吗?手把手教你自撸一个Andriod版简易IM (有源码)

融云技术分享:解密融云IM产品的聊天消息ID生成策略

IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!

适合新手:从零开发一个IM服务端(基于Netty,有完整源码)

拿起键盘就是干:跟我一起徒手开发一套分布式IM系统

适合新手:手把手教你用Go快速搭建高性能、可扩展的IM系统(有源码)

IM里“附近的人”功能实现原理是什么?如何高效率地实现它?

IM要做手机扫码登录?先看看微信的扫码登录功能技术原理

IM消息ID技术专题(一):微信的海量IM聊天消息序列号生成实践(算法原理篇)

IM消息ID技术专题(二):微信的海量IM聊天消息序列号生成实践(容灾方案篇)

IM消息ID技术专题(三):解密融云IM产品的聊天消息ID生成策略

IM消息ID技术专题(四):深度解密美团的分布式ID生成算法

IM消息ID技术专题(五):开源分布式ID生成器UidGenerator的技术实现

IM开发宝典:史上最全,微信各种功能参数和逻辑规则资料汇总

IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的

IM开发快速入门(一):什么是IM系统?

>> 更多同类文章 ……

(本文同步发布于:http://www.52im.net/thread-3065-1-1.html

posted @ 2020-07-09 14:16 Jack Jiang 阅读(376) | 评论 (0)编辑 收藏

1、引言

IM系统中,特别是在企业应用场景下,消息的已读未读状态是一个强需求。

以阿里的钉钉为例,钉钉的产品定位是用于商务交流,其“强制已读回执”功能,让职场人无法再“假装不在线”、“假装没收到”。更有甚者,钉钉的群聊“强制已读回执”功能,甚至能够知道谁读了消息,谁没有读消息(老板的福音啊)。

 

▲ 钉钉里的群聊消息已读未读功能效果

功能看起来很酷,但用起来是一言难尽(上班族心里苦.... )。实际上,技术实现也并不容易。

那么,对于已读未读状态:

  • 1)如果是私聊:消息的阅读状态比较容易实现,在性能和存储上也不存在问题;
  • 2)如果是群聊:考虑到存储和处理性能,特别当处于一个云环境时,如何高效地处理群聊的已读未读状态是一个非常值得探讨的话题。

这里提到的“高效”含3个方面:

  • 1)存储空间;
  • 2)处理速度;
  • 3)传输字节数。

本文将从服务端的角度来探讨已读未读状态,在具体的技术实现上对于存储空间占用方面的思路差异。能力有限,权当个人笔记,欢迎交流。

学习交流:

- 即时通讯/推送技术开发交流5群:215477170[推荐]

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文在公众号上的链接是:https://mp.weixin.qq.com/s/yUkKPOBsdqLlxiFrGmwFRQ,原文链接是:http://www.52im.net/thread-3054-1-1.html

2、内容点评

在收录本文前,Jack Jiang建议原作者对某些具体的技术点进行更深入的分享,但因作者工作较忙,本文中的某些关键技术点未来的及作进一步展开。

所以,本文可以作为IM聊天消息(主要是群聊)中已读未读功能的基本实现思路方面的参考,但不建议盲目迷信文中的结论或方案,避免被一些不够具体的技术指标而误导。

3、相关文章

如果你还想了解更多有关IM群聊中已读未读功能的实现逻辑,可以进一步阅读干货文章《IM群聊消息的已读回执功能该怎么实现?》(强烈推荐)。

如果你对IM中的已读未读功能有产品方面的痛点困惑,可以参考一下微信对已读未读功能的设计定位,详见《IM热门功能思考:为什么微信里没有消息“已读”功能?》。

更多IM群聊技术方面的文章详见文本附录部分。

4、已读未读状态交互流程

发送者发送的IM聊天消息,在接收者阅读消息后,是否要求阅读者通知已读,可能是由系统配置、组织配置、群组配置等决定,也可能由发送者根据业务需求决定。以下的讨论,均假设消息需要已读未读状态。

客户端与服务端之间,关于阅读状态的命令只需3个,每个命令含请求和应答。

4.1 通知消息已读(私聊、群聊通用)

当小宝阅读了一条或若干条消息,需向服务端发送消息已读通知:“众爱卿发的x+y+z消息,朕已阅”。

服务端收到小宝的已读通知时,需完成以下事项:

  • 1)存储消息的已读状态;
  • 2)返回应答给小宝;
  • 3)向已读列表的消息的原始发送者通知消息已读。

对于第“3)”步:

  • 1)私聊的场合,比较好理解,就是发送给私聊的对方;
  • 2)群聊的场合,可很不一样:因为小宝发送的已读消息列表,可能是由众爱卿发送的。考虑这种假设:张三、李四、王五发出的群聊消息,被小宝一下都阅读了,那么小宝发出的已读通知包含的消息列表,需要被IMS分解成3个已读通知(3个不同的消息列表),分别通知给张三、李四、王五,通知内容是“爱卿(不含'"众")发的这些消息,朕已阅”。

下面是大致的逻辑流程图: 

4.2 查询消息的未读人数(私聊、群聊通用)

消息的发送者,加载消息列表到聊天窗口时,可能需要展示消息是否被已读。

对群聊而言,显示的信息可能是n人未读的提示,那么需要向服务端查询消息的未读人数,由于客户端可能在UI显示自己发出的多条消息,需支持一次请求查询多条消息。

以未读人数的方式来表示消息的阅读状态,统一了私聊、群聊的查询,使得客户端-服务端间的接口更简单,同时使客户端的实现逻辑更统一。

就像下面这样:

  • 1)对于私聊:如果未读人数n>0,表示消息未读;
  • 2)对于群聊:直接显示n人未读即可,当然,当n等于0时表示全部已读。

4.3 查询群消息的已读、未读人员清单(群聊)

当客户端希望显示某一条群聊消息的已读、未读人员列表,需向服务端发起查询。

大致的逻辑流程图如下:

5、几种具体的已读未读状态存储思路探讨

5.1 基本约定

群聊的阅读状态比私聊复杂,因此这里着重讨论群聊的阅读状态。

假设群成员数是n,各个客户端立即IM服务端发送已读通知。服务端需存储每个人的阅读状态,包括那些未读的成员。由于群的成员清单可能变化,比如今天增加了一个成员,则昨天发的消息、与今天发的消息,其接收者列表不一样。

即:

  • 1)同一个群的不同消息,对应的接收者列表可能不一样。
  • 2)换言之,每一条消息都需要记录完整的接收者列表和已读人员列表。

为了方便讨论,本章假设群成员有640人为前提。

5.2 存储思路1

每一条消息都维护:

  • 1)接收人员列表receiver_list;
  • 2)已读人员列表read_list。

具体是:

  • 1)IM Server收到一条消息时,用全体群成员构建receiver_list;
  • 2)IM Server收到群成员对这条消息的已读通知时,将此成员加入到read_list。

客户端获取此消息的数据:

  • 1)当需要获取未读人数时,用receiver_list的个数减去read_list的个数;
  • 2)当需要获取已读、未读人员列表时,需用receiver_list减去read_list得到未读人员列表。

那么,思路1每条消息的存储空间是:

640个ID + 不定数量的已读人员ID

5.3 存储思路2

每一条消息维护:

  • 1)未读人员列表unread_list;
  • 2)已读人员列表read_list。

具体是:

  • 1)IM Server收到一条消息时,用全体群成员构建unread_list;
  • 2)IM Server收到群成员对这条消息的已读通知时,将此成员从unread_list移出,同时加入到read_list。

客户端获取此消息的数据:

  • 1)当需要获取未读人数时,直接计算unread_list的个数;
  • 2)当需要获取已读、未读人员列表时,直接返回unread_list和read_list。

那么,思路2每条消息的存储空间是:

未读人员ID + 已读人员ID,合计640个ID

思路2的实现,占用的空间是案1的0.5倍~1.0倍。即案2占用的空间少,但在每次收到客户端的已读通知时,比案1多了一个操作:从unread_list进行减员。

5.4 存储思路3(我的实现)

5.4.1)探讨5.2节、5.3节的不足:

5.2节、5.3节这两种思路,都能满足功能需求,但存在巨大的存储浪费。

该群有640人,如果群内聊天每天有1024条消息,人员ID以4字节存储计算,那么为该群每天的消息阅读状态需要消耗的空间是:

5.2节思路1:1024 * (640 * 4 + 已读人数 * 4),范围是 2.5MB ~ 5MB;

5.3节思路2:1024 * 640 * 4,等于2.5MB。

这仅仅是一个群在一天之内产生的阅读状态数据,如果是在云平台运行,单此功能消耗的空间,呵呵~~

题外话:如果成员不是用4字节整型存储,而改用字符串,比如"1123356777",那就更可观了。

5.4.2)如何减少存储空间:

考虑群成员并非时时刻刻都在变化,多数情况下,群成员的列表是相对稳定的,今天的和上周(甚至更久以前)的列表甚至可能是一样的,那么有可能几百条消息,甚至几万条消息对应的群成员列表是相同的。

因此,引出本文的重点思想:

考虑让不同的消息共用群成员列表,即把消息的阅读状态与群成员列表分开存储,并记录它们之间的关联。

假定平均每1024条消息共用一个群成员列表,发了1024条消息后,群成员变化了,此后需要用新的群成员列表。

那么这一千条消息的阅读状态所占用的空间是:

群成员列表空间 + 1024条消息的阅读状态:640 * 4 + 1024 * 每条消息的阅读状态所占空间

在具备群成员列表的前提下,如何减少每条消息的阅读状态所占空间?

很自然会想到用bit来表示已读人员,因为一个32位整型可表示32个人的已读状态。bit的顺序只需与群成员列表的顺序一致即可。

当一条消息没有人已读时,阅读状态占用0字节;当群内每个人都阅读时,占用的空间最大,即640 / 32 = 20字节。

因此优化之后,这一千条消息的阅读状态所占用的空间,范围是2.5KB ~ (2.5KB + 1024 * 20B),即2.5KB ~ 22.5KB,此数值与5.2节思路1、5.3节思路2对比,有了极大幅度地下降。

如下图所示:

该表格的前提条件:

  • 1)一个群有640人;
  • 2)该群连续1024条消息对应的群成员列表是稳定的。

退一步考虑,哪怕这1024条消息对应的群成员列表不稳定,中间变化了10次,那么也仅会多出2.5KB * 10即25KB的存储空间,与案1、案2相比仍然有极大优势。

6、如何提高已读未读状态的处理速度

小宝往公司群发了一条消息我来给大家介绍一下新来的女同事,大家立即、马上、瞬间、闪电般地查看消息,感觉迟1秒就会失去秒杀女神的机会一样,意味着一瞬间会有N多条已读通知发送到IMS。

对这些消息的处理流程是一样的:

  • 1)可合并这些操作以批量形式进行存储、转发;
  • 2)由于存储消息的阅读状态是一个设置bit的过程,所以不存在互斥的问题,即使在分布式环境也可以放心操作;
  • 3)消息对应的成员列表信息可临时缓存在内存对象内,以减少查询IO,提高效率。

附录:更多IM群聊技术文章

快速裂变:见证微信强大后台架构从0到1的演进历程(一)

如何保证IM实时消息的“时序性”与“一致性”?

IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?

IM群聊消息如此复杂,如何保证不丢不重?

微信后台团队:微信后台异步消息队列的优化升级实践分享

移动端IM中大规模群消息的推送如何保证效率、实时性?

现代IM系统中聊天消息的同步和存储方案探讨

关于IM即时通讯群聊消息的乱序问题讨论

IM群聊消息的已读回执功能该怎么实现?

IM群聊消息究竟是存1份(即扩散读)还是存多份(即扩散写)?

一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践

[技术脑洞] 如果把14亿中国人拉到一个微信群里技术上能实现吗?

IM群聊机制,除了循环去发消息还有什么方式?如何优化?

网易云信技术分享:IM中的万人群聊技术方案实践总结

阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处

IM群聊消息的已读未读功能在存储空间方面的实现思路探讨

>> 更多同类文章 ……

(本文同步发布于:http://www.52im.net/thread-3054-1-1.html

posted @ 2020-07-02 14:04 Jack Jiang 阅读(247) | 评论 (0)编辑 收藏

1、引言

IM在Android上的保活问题经常在即时通讯网的论坛和技术群里被讨论,自从Android 8.0后系统大大降低了后台运行应用的保活容忍度(详见《Android P正式版即将到来:后台应用保活、消息推送的真正噩梦》),保活从黑科技横行的时代进入了技术蛮荒阶段,真要实现保活,技术难度越来越大。

不过话说回来,既然用黑科技进行保活是Andriod技术的逆潮流,那何不回头是岸,做个“良民”?

本文将以某款线上的IM产品为例,介绍它是如何引导用户在多款主流机型上加白名单的,并分享了该款IM中已制作完成的多达7款主流Andriod机型的详细加白FAQ页面资源(含完整HTML+图片),方便您进行参考、学习和研究,希望能为你的应用开发带来帮助。

特别申明:本文示例中的资源来自某款真实的IM产品,仅供学习和研究,请勿用作非法用途,如有侵权,请告之于我。

学习交流:

- 即时通讯/推送技术开发交流5群:215477170[推荐]

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文在公众号上的链接是:https://mp.weixin.qq.com/s/JqWloZLBYicpxElVL_HKYw ,原文链接是:http://www.52im.net/thread-3033-1-1.html

2、Android保活,变的越来越不可能了

IM产品在Android上的保活问题从早期的系统版本到现在,从未有人停止过尝试。即时通讯通讯网也随着Andriod系统版本的升级,持续整理了很多篇相关文章,比如下面这些(文章的顺序按照Android系统的版本从低到高)。

上面这些文章,我们可以看到,自从Android 8.0(即Andriod P)以后,IM以及其它需要在后台保活的产品,存活难度越来越高,黑科技几乎都不起作用了。

于是,一些技术从牛们只能从更深的Android系统层面尝试突破系统的保活限制,比如这两篇:《史上最强Android保活思路:深入剖析腾讯TIM的进程永生技术》、《Android进程永生技术终极揭密:进程被杀底层原理、APP应对被杀技巧》。

正如上面两篇文章,为了跟系统作斗争,可谓斗智斗勇。但Android系统的历史进程终究无人能阻挡,越来越严格的保活限制已经是Android官方及各大手机厂商的共识。

好吧,之前费尽心机折腾的各种黑科技,如今就像浮云一样。。。

 

3、死磕保活?别做梦了,回头是岸

正如上节所述,鉴于Andriod保活变的越来越不可能,很多原本靠黑科技保活的产品,开始重新审视保活技术实现,到底是把保活黑科技这条路走到黑,还是回归Android官方最佳实践(乖乖引导用户手动设置白名单)?

我个人认为,后者是保活技术发展的必然结果,就像之前分享的这篇文章里所做的尝试一样:《2020年了,Android后台保活还有戏吗?看我如何优雅的实现!》,规范地引导用户“加白”。

放弃“黑科技”,并不意味着技术不行,回归“良民”,反而变的一身轻松。

 

4、调用系统代码引导用户加白名单,也不完美

之前整理的《2020年了,Android后台保活还有戏吗?看我如何优雅的实现!》一文,是按照不同的机型,自动适配代码并在代码中调用系统的加白名单设置功能。

比如像下面这样的代码调用:

▲ 以下代码引用自《2020年了,Android后台保活还有戏吗?看我如何优雅的实现!

会弹出这样一个窗口:

这个方法确实不错,但因为机型不同、同机型的ROOM版本不同,代码的兼容处理,可能会相当复杂,所以方法虽好,但也并不能一劳永逸的解决所有问题。

5、应用内提供更多机型的“加白”FAQ帮助,是一个补充办法

正如上节所示,调用系统代码引导用户加白名单确实算的上“优雅”,但在不同的机型、同机型的不同系统版本上,可能差异很大,代码兼容性是个头疼的问题,总之这不是个百分百完美的办法。

这就需要一个补充手段,比如我们可以针对大量不同的机型,针对它的最行或最常用系统版本,在应用内以FAQ帮助网页的方式,为用户提供帮助。

比如可以在手机里打开像下面这样FAQ网页页面:

至少能在调用系统代码无法实现的情况下,可以让用户自主找到解决问题的办法。而这便是本文要分享,下节内容会以一个市面上做的比较好的IM应用为例,为你提供一个完整示例。

6、一个完整的“加白”FAQ帮助示例

最近发现的一款市面上的IM应用(此产品跟即时通讯网无任何关系,仅仅是作为技术研究参考对象而已),它内置的“加白”FAQ帮助就很完善。

以下是从该款IM中截下来的图: 

 

以下是该款IM应用中的运行演示视频(点此打开视频链接):

 

目前该应用中FAQ帮助已覆盖7款主流Andriod机,以下是完整示例页面链接:

可以看到,这款IM里的“加白”FAQ做的还是比较细、覆盖的机型也比较典型, 如果你有类似的想法或需求,完全可以参考这款产品的实现。尤其在一些特定的场景(比如企业内部的IM等)下,这种方式还是能解决大部分终端用户的问题的。

7、覆盖7款主流机型的“加白”FAQ页面静态资源(附件下载)

我整理了上节中提到的这款IM产品中的全部“加白”FAQ帮助页面静态资源,覆盖7款主流Andriod机型,如果你也需要同样的东西,可以参考这份完整的示例实现,打包到手机中使用之。

以下是这份静态资源示例的内容(图太长,已截掉了一部分): 

以下是这份静态资源示例的打包附件:

请从原文附件中下载:http://www.52im.net/thread-3033-1-1.html

附录:更多精品资源汇总

[1] 精品源码下载:

Java NIO基础视频教程、MINA视频教程、Netty快速入门视频 [有源码]

轻量级即时通讯框架MobileIMSDK的iOS源码(开源版)[附件下载]

开源IM工程“蘑菇街TeamTalk”2015年5月前未删减版完整代码 [附件下载]

微信本地数据库破解版(含iOS、Android),仅供学习研究 [附件下载]

NIO框架入门(四):Android与MINA2、Netty4的跨平台UDP双向通信实战 [附件下载]

NIO框架入门(三):iOS与MINA2、Netty4的跨平台UDP双向通信实战 [附件下载]

NIO框架入门(二):服务端基于MINA2的UDP双向通信Demo演示 [附件下载]

NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示 [附件下载]

用于IM中图片压缩的Android工具类源码,效果可媲美微信 [附件下载]

高仿Android版手机QQ可拖拽未读数小气泡源码 [附件下载]

一个WebSocket实时聊天室Demo:基于node.js+socket.io [附件下载]

Android聊天界面源码:实现了聊天气泡、表情图标(可翻页) [附件下载]

高仿Android版手机QQ首页侧滑菜单源码 [附件下载]

开源libco库:单机千万连接、支撑微信8亿用户的后台框架基石 [源码下载]

分享java AMR音频文件合并源码,全网最全

微信团队原创Android资源混淆工具:AndResGuard [有源码]

一个基于MQTT通信协议的完整Android推送Demo [附件下载]

Android版高仿微信聊天界面源码 [附件下载]

高仿手机QQ的Android版锁屏聊天消息提醒功能 [附件下载]

高仿iOS版手机QQ录音及振幅动画完整实现 [源码下载]

Android端社交应用中的评论和回复功能实战分享[图文+源码]

Android端IM应用中的@人功能实现:仿微博、QQ、微信,零入侵、高可扩展[图文+源码]

仿微信的IM聊天时间显示格式(含iOS/Android/Web实现)[图文+源码]

Android版仿微信朋友圈图片拖拽返回效果 [源码下载]

[2] 精品文档和工具下载:

计算机网络通讯协议关系图(中文珍藏版)[附件下载]

史上最全即时通讯软件简史(精编大图版)[附件下载]

重磅发布:《阿里巴巴Android开发手册(规约)》[附件下载]

阿里技术结晶:《阿里巴巴Java开发手册(规约)-终极版》[附件下载]

基于RTMP协议的流媒体技术的原理与应用(技术论文)[附件下载]

独家发布《TCP/IP详解 卷1:协议》CHM版 [附件下载]

良心分享:WebRTC 零基础开发者教程(中文)[附件下载]

MQTT协议手册(中文翻译版)[附件下载]

经典书籍《UNIX网络编程》最全下载(卷1+卷2、中文版+英文版)[附件下载]

音视频开发理论入门书籍之《视频技术手册(第5版)》[附件下载]

国际电联H.264视频编码标准官方技术手册(中文版)[附件下载]

Apache MINA2.0 开发指南(中文版)[附件下载]

网络通讯数据抓包和分析工具 Wireshark 使用教程(中文) [附件下载]

最新收集NAT穿越(p2p打洞)免费STUN服务器列表 [附件下载]

高性能网络编程经典:《The C10K problem(英文)》[附件下载]

即时通讯系统的原理、技术和应用(技术论文)[附件下载]

技术论文:微信对网络影响的技术试验及分析[附件下载]

华为内部3G网络资料: WCDMA系统原理培训手册[附件下载]

网络测试:Android版多路ping命令工具EnterprisePing[附件下载]

Android反编译利器APKDB:没有美工的日子里继续坚强的撸

一款用于P2P开发的NAT类型检测工具 [附件下载]

两款增强型Ping工具:持续统计、图形化展式网络状况 [附件下载]

Android保活从入门到放弃:乖乖引导用户加白名单吧(附7大机型加白示例)

[3] 精选视频、演讲PPT下载:

美图海量用户的IM架构零基础演进之路(PPT)[附件下载]

开源实时音视频工程WebRTC的架构详解与实践总结(PPT+视频)[附件下载]

QQ空间百亿级流量的社交广告系统架构实践(视频+PPT)[附件下载]

海量实时消息的视频直播系统架构演进之路(视频+PPT)[附件下载]

YY直播在移动弱网环境下的深度优化实践分享(视频+PPT)[附件下载]

QQ空间移动端10亿级视频播放技术优化揭秘(视频+PPT)[附件下载]

RTC实时互联网2017年度大会精选演讲PPT [附件下载]

微信分享开源IM网络层组件库Mars的技术实现(视频+PPT)[附件下载]

微服务理念在微信海量用户后台架构中的实践(视频+PPT)[附件下载]

移动端IM开发和构建中的技术难点实践分享(视频+PPT)[附件下载]

网易云信的高品质即时通讯技术实践之路(视频+PPT)[附件下载]

腾讯音视频实验室:直面音视频质量评估之痛(视频+PPT)[附件下载]

腾讯QQ1.4亿在线用户的技术挑战和架构演进之路PPT[附件下载]

微信朋友圈海量技术之道PPT[附件下载]

手机淘宝消息推送系统的架构与实践(音频+PPT)[附件下载]

如何进行实时音视频的质量评估与监控(视频+PPT)[附件下载]

Go语言构建高并发消息推送系统实践PPT(来自360公司)[附件下载]

网易IM云千万级并发消息处理能力的架构设计与实践PPT [附件下载]

手机QQ的海量用户移动化实践分享(视频+PPT)[附件下载]

钉钉——基于IM技术的新一代企业OA平台的技术挑战(视频+PPT)[附件下载]

微信技术总监谈架构:微信之道——大道至简(PPT讲稿)[附件下载]

Netty的架构剖析及应用案例介绍(视频+PPT)[附件下载]

声网架构师谈实时音视频云的实现难点(视频采访)

滴滴打车架构演变及应用实践(PPT讲稿)[附件下载]

微信海量用户背后的后台系统存储架构(视频+PPT)[附件下载]

在线音视频直播室服务端架构最佳实践(视频+PPT)[附件下载]

从0到1:万人在线的实时音视频直播技术实践分享(视频+PPT)[附件下载]

微信移动端应对弱网络情况的探索和实践PPT[附件下载]

Android版微信从300KB到30MB的技术演进(PPT讲稿)[附件下载]

从零开始搭建瓜子二手车IM系统(PPT)[附件下载]

极光分享:高并发海量消息推送系统架构演进(视频+PPT)[附件下载]

微信红包系统可用性设计实践(PPT) [附件下载]

微信红包数据架构演变(PPT) [附件下载]

百度网盘千万节点的P2P架构设计(PPT) [附件下载]

瓜子IM智能客服系统的数据架构设计(PPT) [附件下载]

基于C++构建微信客户端跨平台开发框架(PPT) [附件下载]

(本文同步发布于:http://www.52im.net/thread-3033-1-1.html

posted @ 2020-06-24 13:55 Jack Jiang 阅读(355) | 评论 (0)编辑 收藏

     摘要: 1、引言好久没写技术文章了,今天这篇不是原理性文章,而是为大家分享一下由笔者主导开发实施的IM即时通讯聊天系统,针对大量离线消息(包括消息漫游)导致的用户体验问题的升级改造全过程。文章中,我将从如下几个方面进行介绍:1)这款IM产品的主要业务及特点;2)IM系统业务现状和痛点;3)升级改造之路;4)消息ACK逻辑的优化。下述内容都是根据笔者开发IM的亲身经历总结下来的宝贵经验,干货满满,期待你的点...  阅读全文

posted @ 2020-06-17 13:47 Jack Jiang 阅读(247) | 评论 (0)编辑 收藏

1、内容点评

本文以轻松幽默的语气,讲解了视频编解码的一些基本常识,并以爱奇艺为例,讲述了视频编解码技术在国内的发展以及未来的一些展望。

▼ 阅读本文需要有一些音视频编解码技术的基础,否则请先阅读以下文章:

即时通讯音视频开发(一):视频编解码之理论概述

即时通讯音视频开发(二):视频编解码之数字视频介绍

即时通讯音视频开发(三):视频编解码之编码基础

即时通讯音视频开发(十九):零基础,史上最通俗视频编码技术入门

本文并未就具体的视频编解码概念进行深入的讨论,目的是尽可能以浅显易懂的方式让读者轻松了解本文标题想呈现的内容。如果您想深入了解视频编解码技术,可继续阅读以下文章。

▼ 以下是比较深入的视频编解码相关文章:

即时通讯音视频开发(四):视频编解码之预测技术介绍

即时通讯音视频开发(五):认识主流视频编码技术H.264

即时通讯音视频开发(十三):实时视频编码H.264的特点与优势

即时通讯音视频开发(十七):视频编码H.264、VP8的前世今生

即时通讯音视频开发(十八):详解音频编解码的原理、演进和应用选型

移动端实时音视频直播技术详解(四):编码和封装

[观点] WebRTC应该选择H.264视频编码的四大理由

▼ 爱奇艺技术产品团队还分享了其它两篇文章,有兴趣也可以读一读:

爱奇艺移动端网络优化实践分享:网络请求成功率优化篇

爱奇艺技术分享:爱奇艺Android客户端启动速度优化实践总结

欢迎关注“即时通讯技术圈”,更多好文会同步发布在公众号:

(本文同步发布于:http://www.52im.net/thread-3028-1-1.html

2、正文引言

初夏最火的造型是什么?不少人可能会脱口而出——“淡黄的长裙,蓬松的头发。。。”

就连我这个上古时期的老年人,都开始每周四和周六准时点开爱奇艺首页,吸一口青春美少女们。原因无他,后疫情灰色时期,还有什么能比漂亮小姐姐的灿烂笑容更能让人感觉到人间值得呢?而我上一次真情实感追完的的女团选秀,可能要追溯到……《超级女声》。

 

不管自己pick的姐姐妹妹能不能顺利出道,至少今天在屏幕上欣赏她们的颜,绝对是一件超幸福的事儿。既不用忍受电视的“马赛克画质”,还能随时随地掏出手机来欣赏妹妹们今日份的可爱。

 

不知道大家有没有同一种快乐,那就是用4G网络看蓝光1080P,已经没有“流量焦虑”了,出现缓冲旋转“小菊花”的情况的几率也在悄然减少。这种观看体验的优化,除了通信网络环境的改变之外,缔造这种视觉快乐的一项关键技术——视频编码,就像是宇宙中的暗物质——鲜为人知,但十分重要。

简单来说:视频编码技术的升级,能够让你用更少的流量、更低的带宽、更快的速度,看更高清晰度的视频画面。比如《青春有你2》溢出屏幕的元气少女感,是不是比2005年的朦胧美更令人心动呢?

 

习惯了1080P高清的用户,绝对不愿意再回到480P的自动马赛克时代了。那么,视频编码到底在高清视频的烹制中,发挥了怎样神奇的化学反应?搞秀之余,不妨来了解一下视频编码的前世今生,看看无数科技巨头如何围绕它撕得花红柳绿。

3、曾今的视频编码标准,是IT巨头们“跑马圈地”的游戏

曾今的视频编码标准,巨头们争相“入股”,视频编码标准有何诱人之处?

思科、微软、苹果、谷歌、奈飞等这些大名鼎鼎的巨头,为什么都觉得视频编码技术这波“入股不亏”,非要pick助其出道?

回答这个问题之前,有必要先简单解释一下,视频编码技术究竟是干什么的?

简单来说:就是将视频压缩成一定的格式,去除掉其中的空间冗余和时间冗余,形成更适合存储和传输的码流。之所以需要被编码压缩,最主要原因就是原始视频的数据量过于庞大。高清视频文件往往高达1G以上,如果本地播放也就算了,一旦需要将其上传、分享给他人,传输网络和存储设备都扛不住那么巨大的数据量。

所以视频平台就需要对所播放的文件进行重构:

  • 1)通过压缩编码将数据量变小,以方便传输;
  • 2)再依靠解压缩在用户端解码,将视频图像还原出来。

简单总结就是:编码效率决定了你的手机能下载多少部高清视频。

 

如果你经历过一晚上不关机终于下好一部2G大小的视频,却发现播放器弹出一行大字——“该视频无法播放”的绝望,那一定会鼓掌欢迎编解码技术的更新。

 

当然,一些“有流量任性”的小伙伴也会选择直接用4G网络在线观看视频,这时候视频大小可就没什么影响了吧?

这么想可就太天真了。视频编码技术越强,传输需要占用的带宽也就更小,就像堤坝没有加固,但冲击河道的流水变小了,自然也就不会出现出水口淤塞的问题,看视频卡顿、掉帧的事故也就不会上演了。

比如此次疫情期间,网络流量比例不断增加,为互联网服务商带来极大的挑战,传统的解决方案就是在网络带宽有限的前提下,降低视频的清晰度。如,YouTube、亚马逊旗下Prime Video、Netflix等一众互联网视频提供商都在欧洲、印度、澳大利亚等国家降低了视频画质,用户只能默认收看标清视频。

可是,吃惯了高清这盘珍馐,哪个用户还愿意忍受低画质的粗茶淡饭呢?为了留住挑剔的内容食客们,平台们不得不想尽办法,提升对视频的烹饪技艺。

那么,让我们得以随时随地追剧追综的视频编解码技术,到底有哪些呢?

目前,国际上主要有几种主流的视频编解码标准(国际上音视频编码技术的标准化),包括VPx(