«

Kotlin-Netty-Akka-IM项目--version1.0

ZealSinger 发布于 阅读:84 项目


项目想法来源

问题来源

大二的时候,在一个项目中写过一个聊天模块,但是那个时候用的技术栈是SpringBoot+WebSocket的技术实现的。那个时候刚刚学了Netty,原本打算用Netty,但是之所以没有用,是因为涉及到了团队成员技术+项目时间成本+个人时间等多方面影响。

来回顾一下原本的这个聊天系统:

失物招领WebSocket在线聊天研发记录.pdf

其实整体架构就是如下,很简单的一个架构,用户A,B通过WebSocket长连接服务器,对于每一个连接服务器自然是存在一个Session会话,服务器这边使用SessionMap即key-value键值对的方式维护全局的连接,即(session_A , 用户A的唯一标识ID)

这里其实对于是(session_A , userId_A) 还是  (userId_A , session_A)即谁Key谁Value的问题进行过一定的思考
考虑到会有多端登录的情况,所以用session作为key是方便一点的,即可以出现(session_A_客户端,userId_A) 和 (session_A_PC端,userId_A)

当然,userId作为Key也不是不行,可以采用(userId_A,session_A_lists) value采用集合的形式,也是可以的

基于上述的存储和管理,那么我们整个的消息发送和接收逻辑就是:

A,B将session数据注册到WebSocket服务器,A要给B发送消息,即将消息发送到WebSocket服务器,服务器这边到sessionMap中查找目标session,如果存在则当在线消息主动推送,如果不在则当离线消息暂存处理,后续再推送

image-20250608150128286

先不说这个方案的功能局限性,单从拓展性和并发承受性而言就很一般了,首先WebScoket服务器只有一台,内部使用concurrentHashMap进行线程安全的记录,当用户量大的时候,内存开销就很大,除此之外,如果WebSocket服务器到底了单点瓶颈,自然需要扩充服务,那么我们肯定是部署多个WebSocket服务器,客户连接任意一个或者通过负载均衡连接到当前情况最优的一个WebSocket服务器,那么就会出现A连接WS服务器1而B连接到了WS服务器2,由于WS服务器内的sessionMap是JVM级别的,自然是无法跨机器交流的,这样就导致A给B的消息全部都是离线消息(因为在WS服务器1上不可能找到B的session),但是实际B是在线的,自然是不可以的。也就是说,基于session存储的非分布式性导致很难横向拓展,横向拓展需要再想办法形成分布式session,就需要引入别的中间件or组件

解决想法

Netty

直接用Netty处理,这个肯定是行的,但是其实这个说法本身就是一个不完整的说法,我们整个IM系统最笼统的分层,那么其实就是一个网络处理 + 信息处理两层,纯粹的Netty只能解决网络处理这一个问题,在系统内部对于消息的处理,路由,存储等功能只能很普通的进行处理,然后每个节点内部依旧存在着分布式Session的问题,当需要分布式的情况下,每个Netty服务器找寻另外一台服务器上的节点的时候,也需要定制Netty的对于节点间信息传递的处理方式,总体而言是不够的

AKKA+Netty

最近学了AKKA,发现AKKA这种基于Actor模型的,本质上就是一种消息推送的方式,对高并发,事件驱动类型,实时系统上有着天然的优势,而IM系统本身也是一个高并发连接+消息驱动+分布式需求+状态管理+实时性的场景,两者本身就是完全契合的

对于上述我们遇到的问题,如果采用AKKA的方式,那么横向拓展将不会存在任何问题,因为基于AKKA本身的集群可通信特性,每一个User可以对应成为一个UserActor,A给B发消息那么其实也就是UserA-Acotr给UserB-Actor发送消息,这个属于是AKKA的基础功能了。

从并发性能上而言:Actor 模型天然适合高并发,Netty 的 NIO 模型可支持百万级连接(单机)

从拓展性上而言:AKKA 集群支持动态扩缩容,但需手动管理节点;Netty 可水平扩展,但需结合负载均衡

AKKA + Netty 在性能和分布式能力上具有显著优势,但开发复杂度较高,并且没有安全性上的支持,适合对性能要求极高的定制化场景

以至于发送性能和处理性能,Netty作为网络层,早就是Java网络通信的标杆框架,性能绝对没得话说;AKKA作为Actor模型,在处理上也是一个高性能框架,两者结合,性能上没得说

所以我打算AKKA+Netty的方式进行此次IM系统的编写和改造,即对于我们的整个通讯服务,采用Netty作为网络接入层,然后每一个用户对应的不再是用session作为标识,而是每个user对应一个UserActor,利用AKKA的actor cluster相关的sharding和routing功能进行跨服务器交互

本个项目的核心也就是IM的聊天功能(后续的文件发送等等以后再拓展)

相关设计和架构

节点上下线通知

image-20250717145546220

点对点发送

SendRoute和ReceiveRoute流程图

点对点发送其实就是分为了本地投递(目标用户和自己处于统一个server节点)和广播投递(目标用户和自己处于不同的server节点)

整体流程图

本地投递

本地处理的时候

广播投递

需要广播的时候

自发送(自己给自己发送)

为了适应分布式环境,保证单一职责原则,开闭原则和统一接口原则,对于自己给自己发送消息的场景,通过sendRoute进行转发而非直接判断目标对象后发到自己的信箱中+防死循环设计,增加了消息跳转但是提高了拓展性,一致性,整体清晰度,完整的状态管理,且自己给自己发送也是一个相对低频的场景

image-20250717145558980

image-20250717145605741

未来版本计划

(1)目前只支持WebSocket连接方式,TCP方式还在写ing

(2)对于AKKA集群种子节点的维护和寻找有待解决,目前种子节点属于在配置中提前配好,无法动态寻找获取,需要进行修改

(3)后端Kotlin的特性还是不够明显,感觉目前只能算是稍微甜一点的Java,可修改和改进的地方应该还有不少

(4)对于群聊的处理方式后续可能会需要进行修改(每个用户是Actor,由于每个用户的信箱内容的不同,会导致处理上的时间不一致的问题),可能参考一下AKKA官方提供的顺序保证机制和Erlang/OTP的相关机制

(5)用KMP将前端界面实现IOS,安卓端,Web端跨平台(长期计划)

Kotlin 编程 项目