Jack Jiang

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

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

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

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

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

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

posted @ 2021-03-29 22:36 Jack Jiang 阅读(247) | 评论 (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 阅读(197) | 评论 (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 阅读(458) | 评论 (0)编辑 收藏

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

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

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

posted @ 2021-03-03 13:02 Jack Jiang 阅读(224) | 评论 (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 阅读(307) | 评论 (0)编辑 收藏

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

posted @ 2021-02-02 15:24 Jack Jiang 阅读(221) | 评论 (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 阅读(272) | 评论 (0)编辑 收藏

仅列出标题
共47页: First 上一页 21 22 23 24 25 26 27 28 29 下一页 Last 
Jack Jiang的 Mail: jb2011@163.com, 联系QQ: 413980957, 微信: hellojackjiang