Go语言进阶(二十四)Goim长连接TCP编程

Goim长连接TCP编程

概览

image-20211228150314511

Comet

长连接层,主要是监控外网 TCP/Websocket端口,并且通过设备 ID 进行绑定 Channel 实现,以及实现了 Room 合适直播等大房间消息广播。

Logic

逻辑层,监控连接 Connect、Disconnect 事件,可自定义鉴权,进行记录 Session 信息(设备 ID、ServerID、用户 ID),业务可通过设备 ID、用户 ID、RoomID、全局广播进行消息推送。

Job

通过消息队列的进行推送消峰处理,并把消息推送到对应 Comet 节点

各个模块之间通过 gRPC 进行通信。

协议设计

主要以包/针方式:

  • Package Length,包长度
  • Header Length,头长度
  • Protocol Version,协议版本
  • Operation,操作码
  • Sequence 请求序号 ID
  • Body,包内容

Operation:

  • Auth
  • Heartbeat
  • Message

Sequence:

  • 按请求、响应对应递增 ID

image-20211228150605818

边缘节点

Comet 长连接连续节点,通常部署在距离用户比较近,通过 TCP 或者 Websocket 建立连接,并且通过应用层 Heartbeat 进行保活检测,保证连接可用性。

image-20211228151017880

节点之间通过云 VPC 专线通信,按地区部署分布。

国内:

  • 华北(北京)
  • 华东(上海、杭州)
  • 东中(武汉)
  • 华南(广州、深圳)
  • 华西(四川)

国外:

  • 日本、美国、欧洲

负载均衡

长连接负载均衡比较特殊,需要按一定的负载算法进行分配节点,可以通过 HTTPDNS 方式,请求获致到对应的节点 IP 列表,例如,返回固定数量 IP,按一定的权重或者最少连接数进行排序,客户端通过 IP 逐个重试连接;

  • Comet 注册 IP 地址,以及节点权重,定时 Renew当前节点连接数量;
  • Balancer 按地区经纬度计算,按最近地区(经纬度)提供 Comet 节点 IP 列表,以及权重计算排序;
  • BFF 返回对应的长连接节点 IP,客户端可以通过 IP直接连;
  • 客户端 按返回 IP 列表顺序,逐个连接尝试建立长连接

image-20211228151215014

心跳保活机制

长连接断开的原因:

  • 长连接所在进程被杀死
  • NAT 超时
  • 网络状态发生变化,如移动网络 & Wifi 切换、断开、重连
  • 其他不可抗因素(网络状态差、DHCP 的租期等等 )

高效维持长连接方案

  • 进程保活(防止进程被杀死)
  • 心跳保活(阻止 NAT 超时)
  • 断线重连(断网以后重新连接网络)

自适应心跳时间

  • 心跳可选区间,[min=60s,max=300s]
  • 心跳增加步长,step=30s
  • 心跳周期探测,success=current + step、fail=current – step

用户鉴权和 Session 信息

用户鉴权,在长连接建立成功后,需要先进行连接鉴权,并且绑定对应的会话信息;

Connect,建立连接进行鉴权,保存 Session 信息:

  • DeviceID,设备唯一 ID
  • Token,用户鉴权 Token,认证得到用户 ID
  • CometID,连接所在 comet 节点

Disconnect,断开连接,删除对应 Session 信息:

  • DeviceID,设备唯一 ID
  • CometID,连接所在 Comet 节点
  • UserID,用户 ID

Session,会话信息通过 Redis 保存连接路由信息:

  • 连接维度,通过 设备 ID 找到所在 Comet 节点
  • 用户维度,通过 用户 ID 找到对应的连接和 Comet所在节点

image-20211228151540134

Comet

Comet 长连接层,实现连接管理和消息推送:

  • Protocol,TCP/Websocket 协议监听;
  • Packet,长连接消息包,每个包都有固定长度;
  • Channel,消息管道相当于每个连接抽象,最终TCP/Websocket 中的封装,进行消息包的读写分发;
  • Bucket,连接通过 DeviceID 进行管理,用于读写锁拆散,并且实现房间消息推送,类似 Nginx Worker;
  • Room,房间管理通过 RoomID 进行管理,通过链表进行Channel 遍历推送消息;

image-20211228152030196

每个 Bucket 都有独立的 Goroutine 和读写锁优化

Bucket

维护当前消息通道和房间的信息,有独立的 Goroutine 和 读写锁优化,用户可以自定义配置对应的 buckets 数量,在大并发业务上尤其明显。

image-20211228152234202

Room

结构也比较简单,维护了的房间的通道 Channel, 推送消息进行了合并写,即 Batch Write, 如果不合并写,每来一个小的消息都通过长连接写出去,系统 Syscall 调用的开销会非常大,Pprof 的时候会看到网络 Syscall 是大头。

image-20211228152246531

Channel

一个连接通道。Writer/Reader 就是对网络 Conn 的封装,cliProto 是一个 Ring Buffer,保存 Room 广播或是直接发送过来的消息体。

image-20211228152255198

内存优化主要几个方面

  • 一个消息一定只有一块内存:使用 Job 聚合消息,Comet 指针引用。
  • 一个用户的内存尽量放到栈上:内存创建在对应的用户 Goroutine 中。
  • 内存由自己控制:主要是针对 Comet 模块所做的优化,可以查看模块中各个分配内存的地方,都使用了内存池。

image-20211228152826958image-20211228152833700

模块优化也分为以下几个方面

消息分发一定是并行的并且互不干扰:

  • 要保证到每一个 Comet 的通讯通道必须是相互独立的,保证消息分发必须是完全并列的,并且彼此之间互不干扰。

并发数一定是可以进行控制的:

  • 每个需要异步处理开启的 Goroutine(Go 协程)都必须预先创建好固定的个数,如果不提前进行控制,那么 Goroutine 就随时存在爆发的可能。

全局锁一定是被打散的:

  • Socket 链接池管理、用户在线数据管理都是多把锁;打散的个数通常取决于 CPU,往往需要考虑 CPU 切换时造成的负担,并非是越多越好。

image-20211228153013045

Logic

Logic 业务逻辑层,处理连接鉴权、消息路由,用户会话管理;

主要分为三层:

  • sdk,通过 TCP/Websocket 建立长连接,进行重连、心跳保活;
  • goim,主要负责连接管理,提供消息长连能力;
  • backend,处理业务逻辑,对推送消息过虑,以及持久化相关等;

image-20211228153102371

Job

业务通过对应的推送方式,可以对连接设备、房间、用户 ID 进行推送,通过 Session 信息定位到所在的Comet 连接节点,并通过 Job 推送消息;

通过 Kafka 进行推送消峰,保证消息逐步推送成功;

支持的多种推送方式:

  • Push(DeviceID, Message)
  • Push(UserID, Message)
  • Push(RoomID, Message)
  • Push(Message)

image-20211228153154054

唯一 ID 设计

唯一 ID,需要保证全局唯一,绝对不会出现重复的 ID,且 ID 整体趋势递增。

通常情况下,ID 的设计主要有以下几大类:

  • UUID
  • 基于 Snowflake 的 ID 生成方式
  • 基于申请 DB 步长的生成方式
  • 基于数据库多主集群模式
  • 基于 Redis 或者 DB 的自增 ID生成方式
  • 特殊的规则生成唯一 ID

基于步长递增的分布式 ID 生成器(不建议)

可以生成基于递增,并且比较小的唯一 ID;服务主要分为:

  • 通过 gRPC 通信,提供 ID 生成接口,并且携带业务标记,为不同业务分配 ID;
  • 部署多个 id-server 服务,通过数据库进行申请 ID步长,并且持久化最大的 ID,例如,每次批量取1000到内存中,可减少对 DB 的压力;
  • 数据库记录分配的业务 MAX_ID 和对应 Step ,供Sequence 请求获取;

image-20211228153740739