长连接服务简介

微聊,是 58 一款聊天工具,目前已经接入 58 的大部分产品。及时准确数据传输,是对一款聊天工具最基本的要求。长连接服务就是在客户端到服务端之间建立一条全双工的数据通路,实现客户端和服务之间逻辑收发数据,在线离线等功能。

角色

  1. 长连接服务在整个微聊系统中,位于客户端与后台逻辑之间;

  2. 整个后台服务最重要的直接对外接口之一 (另一个是短连接请求的 Nginx );

  3. 长连接服务对外和对内的均采用 TCP 连接。

系统瓶颈

  1. 长连接服务主要功能是收发数据,保持在线,使用的系统资源主要包括:CPU,内存,网卡;

  2. 当连接非常活跃的时候,大量数据接收与发送,会用到更多的 CPU 和网卡;当大量用户在线的时候,需要维持这些连接,保持会话,需要用到大量内存;

  3. 考虑到微聊的实际场景,App 端占有很大的比例,由于手机的网络环境相对 PC 来说,不太稳定,需要处理大量连接的新建与断开,所以系统对 CPU 使用率比较高。

设计难点

  1. 设计单台物理机连接数 100W 的处理能力;

  2. CPU 资源充分利用,线程的分配;

  3. 内存合理分配,数据结构选择;

  4. 异步化,剥离业务逻辑和网络 IO 之间的相互依赖。

架构设计

架构的设计需要考虑 TCP 连接管理与应用层协议解析与处理相分离,以下是系统主要的功能模块:

  1. TCP 连接层 (黑色虚线框)- 连接保持,session 管理等,实现了 TCP 和 TLS 层;

  2. BlayServer - 逻辑层服务,逻辑层节点管理,协议解析等;

  3. ClientServer - 客户端服务,客户端协议解析;

  4. Protocol - Http, WebSocket ,Protocol 协议封装;

  5. Tools - JSON ,log,config,crypto。

线程管理

为了充分地使用 CPU,需要合理的进行线程规划。整个长连接服务使用事件驱动,包括:定时器事件和 IO 事件(listen fd,socket fd, pipe fd), 所以线程规划就是合理给这些事件分配线程。

线程优化

  1. 大量连接 Socket 需要平均分配到各个线程;

  2. 新的连接请求量比较大,Listen 线程压力较大,需要考虑多线程处理。

这里主要涉及到以下 3 种 fd 的线程分配:

  1. 监听 fd,包括监听逻辑层的连接请求和监听客户端的连接请求,并且支持启动多个监听端口,每个端口多个线程同时工作(可配置),整体会按顺序分配到线程;

  2. 连接 fd,包括逻辑层的连接和客户端的连接,采用 fd 取余线程数,保证平均分配到所有线程;

  3. pipe fd, 负责本线程管道中数据的读取,每个线程一个。

这样使得每个线程的 CPU 使用率相当。

线程间通信

客户端 fd 和 listen fd,采用不同的规则分配到同一组线程中,当客户端的数据需要发给逻辑层,或是逻辑层数据需要发给客户端,就存在客户端连接和逻辑层的连接存在同一线程或不同线程两种情况,不同线程之间传输数据需要用到线程通信。解决不同线程访问资源,传输数据主要考虑了下面几种方式:

  1. 加锁 (比如 session 读写锁,从而让 session 可以在多线程中操作) - 会导致多个线程竞争资源,阻塞线程;

  2. 共享内存,需要加锁保证原子性,需要线程设置定时器做可读性检测,实现起来比较复杂;

  3. 我们采用操作系统提供的管道 -pipe 解决线程间通信的问题。

pipe 通信协议:

  1. close session,一个线程需要关闭另一个线程上的连接,比如:收到逻辑层需要把某个用户踢下线的命令;

  2. send to session, 一个线程给别一个线程上的连接发送数据,比如:给某个用户推送消息;

  3. 应用层事件,应用层事件跨线程转发,一种通用的跨线程调用。

内存管理

在长连接服务工作过程中,会有大量会话不停创建和销毁,在会话过程中,又会有大量长度不等的数据通信,长时间稳定的服务需要合理高效的内存使用。长连接服务中对内存的使用,包括以下几个方面:操作系统 TCP 协议栈内存使用,服务中 session 管理,读写数据缓存 (Buffer) 等。

说明:

  1. 其中最主要的内存使用是 TCP 协议栈,包括 TCP 和读写缓存,其它内存使用体现在 session 存储和 session 读写 buffer;

  2. 由于在 TCP 上层实现了更加逻辑友好的 Buffer(后面会详细讲到),实际部署中可以把 TCP 协议栈中的读写 Buffer 设置成比较小的空间,比如设置为 1k。

session 静态内存模型

当一个客户端在线,服务端会生成一个 session 保存该连接的状态,一些逻辑信息,以及 socket 信息等。 大量的会话保持,需要服务端合理的管理这些 session。

考虑以下几种存储结构:

  1. Hash - 能够实现比较快速存取,需要自己实现 Hash 算法,经常分配释放内存会导致内存碎片;

  2. 固定数组 - 需要一开始分配大块内存,不支持动态扩展,但存取快速,不会有内存碎片;

  3. 动态数据 - 动态扩展,存取快速,无内存碎片,但扩展的时候会有大块内存分配与拷贝。

长连接服务采用数组存储 session:

  1. 预分配 100W session 全量的空间,相当于 100Wsession 的数组(一共 418M),大小可接受,并且有容量使用监控,防止空间满了建立连接失败;

  2. 直接使用 session 对应的 fd 作为下标,实现存取 O(1);

  3. 由于 fd 分配策略是从小到大分配空闲的,所以可以保证数组在当前连接数以下的空间是饱和的,空间利用率比较高。

Buffer

在 Socket 读写过程中,会遇到以下几个问题:

  1. 当向 Socket 中写数据的时候,如果当前不足以写下要写的所有数据,那么会写入部分数据,剩下的数据需要在 Socket 可写的时候继续写入,以保证数据的连贯性,这里需要有一个保存并记录需要写入的数据的数据结构,并且需要保证写入的数据的先后顺序,先入先出;

  2. 当从 Socket 中读数据的时候,TCP 只保证数据流的顺序性,并不知道应用层协议包大小,所以需要从 TCP 流中分离出一个个应用层协议包,在解决拆包和粘包问题的过程中,会遇到数据包不够的情况下,需要等待后续数据的读入,直到读到的数据构成一个完整的应用层协议包,然后把就个协议整体返回给上层;

  3. 在发送和接收的过程中,需要支持大小不一样的应用层数据包(聊天内容为一个字或是 1M 的字),所以这里需要一个可以发送拆解大应用层包的缓存队列。

为了满足以上功能和要求,我们设计了 Buffer:

  1. 分配与释放采用固定存储单元防止产生内存碎片;

  2. 动态扩展的双向循环链表(队列);

  3. 对外提供了连续数据存取接口。

TCP 拆包流程

TCP 是面向字符流的传输,TCP 保证了传输数据的顺序性和可靠性,当接收到字符流的时候,如何从字符流中分离出一段段上层协议,是 TCP 拆包应该考虑的问题。

如上图所示,逻辑层需要实现 getProtocolSize 和 receiveCallback 两个接口,前者通过参数中传入的数据判断出当前应用层协议包大小,后者是返回应用层协议包的回调。 当 Socket 读事件发生时,首先从 Socket 中读取数据,写入 buffer 中,然后,调用 Buffer 的预读接口(只是返回队列头部的只读指针,并不拷贝数据),调用 getProtocolSize 接口,由逻辑层返回应用层协议包大小(只有应用层逻辑才知道自己该识别哪种协议),再根据该大小,从 Buffer 队列中读出协议包,最后通过 receiveCallback 返回给上层处理。

长连接保活由于 TCP 自身的断开确认机制,如果一条 TCP 连接中间网络断开,此时客户端和服务端物理网络的断开导致了客户端和服务端都没有办法通知对方连接的断开,这样,服务端和客户端就会存在死连接,造成假在线,占用的资源得不到有效的回收。长连接保活主要有下面两种方式:

  1. tTCP Keepalive,通过设置 Keepalive 参数,TCP 协议栈会在超过一定时间没有数据交互的时候,发送 Keepalive 探测包,如果连接几次都没有收到回包,则断开连接。 优点是,TCP 协议栈提供的功能,更加稳定,并且占用较少的带宽;

  2. 采用应用层心跳包的方式,客户端定时向服务端发送心跳包,服务端收到心跳包后立即进行回包,客户端如果没有检测到回包,则断开连接;服务端检测超时还没有收到心跳包,则断开连接。优点主要是应用层有感知,可控,并且可以带些业务数据,比如时间戳。

微聊长连接,考虑到多端支持(Android, iOS, Web 等),兼容老版本实现方式,应用层协议主要使用 http,提供了 http-chunk 和 http-long-polling,两种 Http 接入方式,这样就限制了客户端上发数据,所以我们采用了服务端下发心跳,和 Keepalive 结合的方式,实现服务端和客户端的保活, 如下图:

  1. 服务端通过开启 TCP Keepalive 进行保活,当一条连接超过一定时间没有活动, 服务端会发 Keepalive 包,如果连续 3 次都没收到回包,服务端就会认为这是一个死连接,从而关闭它;

  2. 而对于客户端,服务端会定时下发心跳包,客户端通过监测心跳包来判别当前连接是否工作正常,如果不能正常收到心跳包,则会重新建立新的连接。

容量控制

在 TCP 连接建立到通信的流程中,为了防止一些恶意连接与攻击,长连接服务做了容量控制,如果体现在下面几个方面:

  1. 客户端建立 TCP 连接到 TLS 握手再到发出登录数据返回登录结果,这个过程是连贯的,如果客户端停在中间的某一步而不往下进行,就会一直占着服务端资源,针对这种情况,服务端增加了定时控制,在 TCP 连接之后 30s,还没有收到登录请求,服务端会主动断开连接;

  2. 在服务中统计了正在进行 TLS 握手,正在进行登录校验的连接数量,分别设置上限,防止当同时出现大量请求的时候,对后端服务的冲击;

  3. 增加了会话通信过程中 Buffer 内存使用量的上限,防止对端不接收数据,导致服务端数据积压;

  4. 增加 IP 统计服务,当建立连接后,会将新连接的 IP 等信息发送到 IP 统计服务,对整个服务的客户端 IP 情况作统计监控,增加 IP 黑名单功能。

总结

  1. 长连接服务是微聊的基础服务,稳定性尤其重要;

  2. 在稳定性的基础上,通过 TLS 握手优化等,不断提高建立连接的速度,更好的应对断网,弱网等复杂的外网环境;

  3. 通过支持更多的应用层协议,提高多端多设备的接入能力;

  4. 通过监控,不断挖掘潜在在安全威胁,同时预防常见的网络安全问题。

这篇分享主要在长连接服务的整体架构,线程,内存分配等一些普遍问题技术选型方面进行整体性的介绍,随着业务的接入与用户的增长,长连接服务也伴随着新的挑战,在稳定性,高并发,高效率,安全性方面的探索与提高永远没有尽头。

最后,欢迎对分布式长连接服务,Linux 内核网络协议栈感兴趣的同学一起交流。

作者介绍

赵忠生,来自 58 集团 TEG 基础服务部,后端工程师,专注分布式高可用架构设计。本文转载自 58 架构师公众号。

58 集团面向亿级用户 IM 长连接服务设计与实践相关推荐

  1. 微信亿级用户异常检测框架的设计与实践

    微信亿级用户异常检测框架的设计与实践 参考文章: (1)微信亿级用户异常检测框架的设计与实践 (2)https://www.cnblogs.com/qcloud1001/p/8351385.html ...

  2. Kylin 在满帮集团千亿级用户访问行为分析中的应用

    2019 年 7 月 12 日,国内首届以 Apache Kylin 为主题的大数据领域的前沿盛会 Kylin Data Summit 在上海落幕.在本次大会的制造业分论坛上,来自满帮集团的陈雅婕的分 ...

  3. hive建立内部表映射hbase_快手 HBase 在千亿级用户特征数据分析中的应用与实践...

    分享嘉宾:陈杨 快手 编辑整理:Hoh Xil 内容来源:BigData NoSQL 12th Meetup 出品社区:DataFun 注:欢迎转载,转载请注明出处. 快手建设 HBase 差不多有2 ...

  4. 手机淘宝:亿级用户APP的快速运维交付实践

    作者简介: 倪生华 淘宝网  资深技术专家 花名玄黎,12年加入淘宝无线事业部,经历了手淘从几十万日活到现在亿级日活的过程,一直负责手淘端研发运维等工程效率相关的工作,团队负责开发的支撑体系,很好的解 ...

  5. 亿级用户中心的设计与实践

    -     前言    - 用户中心是互联网最为基础的核心系统,随着业务和用户的增长,势必会带来不断的挑战.如何在亿级的情况下保证系统的高可用,高性能以及高安全,本文能够给你一套实践方案. 注1:本文 ...

  6. Hologres如何支持亿级用户UV计算

    简介: 本文将介绍阿里云Hologres如何基于RoaringBitmap进行UV等高复杂度计算的方案,实现亿级用户万级标签亚秒级分析,帮助用户从Kylin平滑迁移到Hologres,实现更实时.开发 ...

  7. 亿级用户百TB级数据的 AIOps 技术实践之路(增强版)

    作者简介 周荣,华为消费者BG云运维部 AIOps 负责人,GOPS 2018 深圳站金牌讲师,07年加入华为,先后分别负责下一代智能网.中间件平台.运维工具等产品的研发与规划,在分布式系统.大数据分 ...

  8. 12月16日vivo开发者大会:揭秘 vivo 互联网服务亿级用户的技术架构演进之路

    PART ONE 摘要 2021 vivo开发者大会,将于2021年12月16日在线上直播.vivo 开发者大会是 vivo 一年一度面向科技.互联网行业及合作伙伴举办的大型会议. PART TWO ...

  9. 24 亿级用户超级 APP 背后的全技术大揭秘

    进入十亿级用户"APP 俱乐部",可以说是很多做 APP 的公司梦寐以求的目标. 2017 年,刚刚成立 2 年的茄子科技(海外 SHAREit Group)交出了一份亮眼的出海成 ...

  10. 巧用 maxTimeMS 服务端超时,避免承载亿级用户的腾讯云数据库MongoDB服务雪崩

    腾讯云数据库MongoDB作为一款基于开源社区MongoDB版本的文档数据库产品,其承载着公司内外包括微信.看点.QQ音乐在内的亿级用户重量级APP产品.在某些场景的使用过程中,用户在客户端请求超时后 ...

最新文章

  1. java怎么自动提示关键词_Eclipse 实现关键字自动补全功能
  2. IOS UISearchDisplayController 点击搜索出现黑条问题解决方案
  3. 百度智能云一周连签三个新基建大单,“非对称竞争”优势凸显?
  4. 功能对等四个原则_佛山房屋加固工程需遵循的原则与步骤
  5. Struts2--DomainModel接收参数---使用广泛!!!
  6. ABP vNext微服务架构详细教程——结束语
  7. linux malloc free 内存碎片_内存申请malloc/new与内存释放free/delete的区别
  8. Survey Admin 示例:实现 Microsoft .NET 基于角色的安全性(转)
  9. kettle 插入更新 数据增量_kettle基于时间戳增量更新
  10. 面向组学大数据的生物信息学研究
  11. 经典查找算法 --- R树
  12. 金蝶KIS专业版如何做盘点
  13. deprecate(反对) 关于依赖版本低的问题
  14. 计算机家庭组无法访问,Win7共享文件夹无法访问解决方法
  15. 深度学习制作自己的样本
  16. python-长宽不同多张图片生成一列长图
  17. App详细测试流程及测试点
  18. 建立RADIUS认证服务器
  19. 关键词查询优惠券列表接口,拼多多超级搜索接口
  20. 2022.11.6 第二十九次周报

热门文章

  1. 新一代 FlinkSQL 平台,重新定义 Apache Flink 开发
  2. java计算机毕业设计ssm+vue高校科研管理系统
  3. C语言中bzero函数
  4. zip知识点的部分总结!
  5. LwIP协议栈源码说明
  6. 学习数据库系统概论这一篇就够了
  7. Android中利用ActivityGroup制作首页框架
  8. 瑞友天翼应用虚拟化系统服务器lP,瑞友天翼应用虚拟化系统 V6.0.6发版
  9. web一键返回顶端html代码,CSS-返回顶部代码_html/css_WEB-ITnose
  10. 【国产化电脑】如何有效避免违规外联