详解netty长连接网关请求处理模型

想要支持海量的客户端请求,首先要有一套高效的请求处理模型。本文以开源项目SONA为例,详解如何基于netty设计请求处理模型,帮助读者动手实践。本文最后附上开源项目地址。


背景

Sona 平台是一个搭建语音房产品的全端解决方案,包含了房间管理、实时音视频、房间IM、长连接网关等能力。其中最基础核心的就是长连接网关,作为网关,首先要支持处理海量的客户端请求,要做到这一点,需要设计一套稳定高效的请求处理模型。


一、Netty 处理模型

详解netty长连接网关请求处理模型_第1张图片

Netty 使用 主从Reactor模型

bossGroup 负责处理 accept 事件 , workerGroup 负责处理 read 、write 事件。

其中 ChannelPipeline 是一个双向链表,netty 里面定义了十几种事件,触发之后会顺序调用所有 ChannelHandler 的指定方法,ChannelHandler的调用都是由 workerGroup中的同一个 eventloop 线程执行,不存在线程之间的切换。 

这种无锁化的设计,避免了上下文切换,在海量请求的情况下能提供很高的性能。但是也存在风险,如果执行某个 ChannelHandler 出现了阻塞,会拖累这个 eventloop 线程所负责的其他请求。

在实际场景中,ChannelHandler里面一般都会执行一些 IO 操作,比如RPC调用,MQ等,无法保证不会出现阻塞的情况。所以很多高性能的分布式框架中都会使用三层处理模型,额外增加一个业务线程池,将耗时的IO操作放在这个业务线程池里面执行,这样就不会阻塞 workerGroup 中的线程了。

二、sona-gateway 处理模型

详解netty长连接网关请求处理模型_第2张图片

1、NettyServerHandler

pipeline 中除了 encode 和 decode ,只实现了一个 NettyServerHandler ,避免过多的handler 影响执行效率。

NettyServerHandler 里面使用装饰器模式实现了不同分工的 handler

AccessChannelHandler 基于 IP 的权限校验,不符合的连接直接拒绝
CatReportChannelHandler cat 数据打点上报
IdleChannelHandler 心跳和探测消息、握手等任务处理
DispatchChannelHandler 线程池分发请求
MercuryServerHandler 最上层的业务数据处理 handler

在上面也提到过,netty 里面的 ChannelHandler 有十几种事件,但对于我们来说,其实只需要关注其中的几种事件。因此,在NettyServerHandler中做了一层映射

channelActive connect 建立连接
channelInactive disconnect 断开连接
channelRead receive 读取到数据
write send 写入数据
exceptionCaught caught 异常捕获

NettyChannel 是我对 netty 底层 channel 的封装,业务层直接操作 NettyChannel,无需关心底层channel的处理,屏蔽了netty的使用细节。里面实现了对连接的维护管理以及消息发送的优化

2、MercuryServerHandler

MercuryServerHandler 中会根据请求中的 Command 命令,将当前请求路由到对应的业务 handler 中处理,并处理 request-response 模型。

详解netty长连接网关请求处理模型_第3张图片

开源版本只提供了登录、房间相关的处理器,详细可见github 的wiki 文档:

https://github.com/BixinTech/sona/wiki/%E9%95%BF%E8%BF%9E%E6%8E%A5%E7%BD%91%E5%85%B3#%E9%95%BF%E9%93%BE-command-%E8%AF%B7%E6%B1%82

用户可以基于gateway中提供的扩展,自定义请求处理器,实现自己的业务。

3、DispatchChannelHandler

这里着重介绍一下 DispatchChannelHandler ,前面说了这个 handler 是用于线程池分发请求的。

一般情况下,其实用 Jdk里面提供的线程池就可以了,比如说 Dubbo ,通过 spi 的方式提供了4种线程池,服务端默认使用 fixed ,客户端默认使用 cached。但底层还是直接使用的ThreadPoolExecutor

对于 IM 场景下,需要严格保证单个 channel 的处理顺序,但不同channel 之间可以不用保证顺序。

所以这里我是自己实现了一个 线程池 OrderedChannelExecutor

  1. 每个channel 都会生成一个 channelId (这里并没有使用 Netty里面默认的 id,而是通过一定规则生成的,主要是因为某些业务处理上的需要)

  2. 通过对 channelId 做一致性哈希,将这个channel 中的所有请求都路由到同一个 MpscQueue 里面。(MpscQueue 是 JCTools里面提供的 多生产者单消费者模式的 lock free 队列,能保证并发安全并且性能极高,Netty 底层使用的队列就是这个)

  3. MpscQueue 会从线程池里挑选一个线程去执行任务,只有当前任务处理完成,才会再从队列里 poll 下一个任务执行

  4. 只要MpscQueue 里面有数据,就会一直霸占这个线程,等队列的任务都执行完了,才会将它放回线程池中

目前 MpscQueue 和线程数量是 1:1 ,每个队列都能拿到一个线程,不需要任何等待

设计的时候,没有参考 netty eventloop 那样将 Selector 和线程 强绑定,因为我觉得线程是比较珍贵的资源,生产机器的配置是4核8g,线程多了性能也不一定能提升,而队列相对来说还好,只是耗点内存,项目中在节省内存方面也做了很多优化,大量使用池化技术,8g内存完全足够了,所以队列数量是可以大于线程数量的。

当时想着后面实现一个分级队列,支持队列按照优先级划分,优先级越高则有更高的概率优先执行,优先级低的在系统负载过大时,则允许延迟处理、丢弃或者快速失败 。不过之前压测,单机可以支持的 qps 远超预期,目前业务上还远远没有达到这种量级,就暂时搁置了。


总结

本文详细介绍了SONA长连接网关中的请求处理模型,在后续的系列文章中会对网关中的其他技术细节进行详细的介绍。

目前sona已经在比心的github仓库上开源,仓库地址:

GitHub - BixinTech/sona: Sona 平台是一个搭建语音房产品的全端解决方案,包含了房间管理、实时音视频、房间IM、长连接网关等能力。Sona 平台是一个搭建语音房产品的全端解决方案,包含了房间管理、实时音视频、房间IM、长连接网关等能力。 - GitHub - BixinTech/sona: Sona 平台是一个搭建语音房产品的全端解决方案,包含了房间管理、实时音视频、房间IM、长连接网关等能力。https://github.com/BixinTech/sona

欢迎你访问我们的项目,有任何想交流的想法可以留言联系我们。

往期阅读:

从0到1快速了解netty长连接网关协议_聊天室程序猿的博客-CSDN博客

比心聊天室的架构演进_聊天室程序猿的博客-CSDN博客

你可能感兴趣的:(SONA聊天室,后端,java,websocket,实时音视频)