基于 Netty,从零开发 IM(四):编码实践篇(系统优化)
本文由作者“大白菜”分享,有较多修订和改动。注意:本系列是给 IM 初学者的文章,IM 老油条们还望海涵,勿喷!
1、引言
前两篇《编码实践篇(单聊功能)》、《编码实践篇(群聊功能)》分别实现了控制台版本的 IM 单聊和群聊的功能。
通过前两篇这两个小案例来体验的只是 Netty 在 IM 系统这种真实的开发实践,但对比在真实的 Netty 应用开发当中,本系列的案例是非常的简单的,主要目的其实是让大家可以更好地了解其原理,从而写出更高质量的 Netty 代码。
不过,虽然 Netty 的性能很高,但是也不能保证随意写出来的项目就是性能很高的,所以本篇将主要讲解几个基于 Netty 的 IM 系统的优化实战技术点。
学习交流:
移动端 IM 开发入门文章:《新手入门一篇就够:从零开发移动端 IM》
开源 IM 框架源码:https://github.com/JackJiang2011/MobileIMSDK(备用地址点此)
(本文同步发布于:http://www.52im.net/thread-3988-1-1.html)
2、写在前面
建议你在阅读本文之前,务必先读本系列的前三篇《IM 系统设计篇》、《编码实践篇(单聊功能)》、《编码实践篇(群聊功能)》。
最后,在开始本文之前,请您务必提前了解 Netty 的相关基础知识,可从本系列首篇《IM 系统设计篇》中的“知识准备”一章开始。
3、系列文章
本文是系列文章的第 3 篇,以下是系列目录:
《基于 Netty,从零开发 IM(一):IM 系统设计篇》《基于 Netty,从零开发 IM(二):编码实践篇(单聊功能)》《基于 Netty,从零开发 IM(三):编码实践篇(群聊功能)》《基于 Netty,从零开发 IM(四):编码实践篇(系统优化)》(* 本文)
4、基于 Netty 的 IM 系统常见优化方向
常见优化方向脑图:
我们逐条详细解释一下这些优化的目的:
1)心跳检测:主要是避免连接假死现象;2)连接断开:则删除通道绑定属性、删除对应的映射关系,这些信息都是保存在内存当中的,如果不删除则造成资源浪费;3)性能问题:用户 ID 和 Channel 的关系绑定存在内存当中,比如:Map,key 是用户 ID,value 是 Channel,如果用户量多的情况(客户端数量过多),那么服务端的内存将被消耗殆尽;4)性能问题:每次服务端往客户端推送消息,都需从 Map 里查找到对应的 Channel,如果数量较大和查询频繁的情况下如何保证查询性能;5)安全问题:HashMap 是线程不安全的,并发情况下,我们如何去保证线程安全;6)身份校验:如何 LoginHandler 是负责登录认证的业务 Handler,AuthHandler 是负责每次请求时校验该请求是否已经认证了,这些 Handler 在链接就绪时已经被添加到 Pipeline 管道当中,其实,我们可以采用热插拔的方式去把一些在做业务操作时用不到的 Handler 给剔除掉。以上是基于 Netty 的 IM 系统开发当中,需要去注意的技术优化点,当然还有很多其他的细节,比如:线程池这块,需要大家慢慢去从实战中积累。
5、本篇优化方向
本篇主要的优化内容主要是在第二篇单聊功能和第三篇群聊功能的基础上继续完善几点。
具体的优化方向如下:
1)无论客户端还是服务端都分别只有一个 Handler,这样的话,业务越来越多,Handler 里面的代码就会越来越臃肿,我们应该想办法把 Handler 拆分成各个独立的 Handler;2)如果拆分的 Handler 很多,每次有连接进来,那么都会触发 initChannel () 方法,所有的 Handler 都得被 new 一遍,我们应该把这些 Handler 改成单例模式(不需要每次都 new,提高效率);3)发送消息时,无论是单聊还是群聊,对方不在线,则把消息缓存起来,等待其上线再推送给他;4)连接断开时,无论是主动和被动,需要删除 Channel 属性、删除用户和 Channel 映射关系。
6、业务拆分以及单例模式优化
6.1 概述主要优化细节如下:
1)自定义 Handler 继承 SimpleChannelInboundHandler,那么解码的时候,会自动根据数据格式类型转到相应的 Handler 去处理;2)@Shareable 修饰 Handler,保证 Handler 是可共享的,避免每次都创建一个实例。6.2 登录 Handler 优化 @ChannelHandler.Sharable
public class ClientLogin2Handler extends SimpleChannelInboundHandler<LoginResBean> {//1.构造函数私有化,避免创建实体
}
6.3 消息发送 Handler 优化 @ChannelHandler.Sharable
public class ClientMsgHandler extends SimpleChannelInboundHandler<MsgResBean> {//1.构造函数私有化,避免创建实体
}
6.4 initChannel 方法优化.handler(newChannelInitializer<SocketChannel>() {@Override
});
6.5 小结这种业务拆分以及单例模式优优化是 Netty 开发当中很常用的,可以更好的维护基于 Netty 的代码并提高应用性能。
7、数据缓存优化
为了提高用户体验,在发送消息(推送消息)时,如果接收方不在线,则应该把消息缓存起来,等对方上线时,再推送给他。
7.1 数据缓存到集合//1.定义一个集合存放数据(真实项目可以存放数据库或者 redis 缓存),这样数据比较安全。
private List<Map<Integer,String>> datas=new ArrayList<Map<Integer,String>>();
//2.服务端推送消息
private void pushMsg(MsgReqBean bean,Channel channel){Integer touserid=bean.getTouserid();
}
7.2 上线推送 private void login(LoginReqBean bean, Channel channel){Channel c=map.get(bean.getUserid());
}
8、连接断开事件处理优化
如果客户端网络故障导致连接断开了(非主动下线),那么服务端就应该能监听到连接的断开,且此时应删除对应的 map 映射关系。但是映射关系如果没有删除掉,将导致服务器资源没有得到释放,进而影响客户端的下次同一个账号登录以及大量的客户端掉线时性能。
8.1 正确写法实例:
public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {//映射关系
}
8.2 错误写法 Channel 断开,服务端监听到连接断开事件,但是此时 Channel 所绑定的属性已经被移除掉了,因此这里无法直接获取的到 userid。
实例:
public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {//映射关系
}
9、本篇小结
本篇内容还是相对容易理解的,主要是优化前面两篇实现的 IM 聊天功能,优化内容是业务 Handler 的拆分以及使用单例模式、接受人不在线则缓存数据、等其上线再推送、监听连接断开删除对应的映射关系。
限于篇幅,本系列文章文章没办法真正讲解开发一个完整 IM 系统所涉及的方方面面,如果有兴趣,可以继续阅读更有针对性的 IM 开发文章,比如 IM 架构设计、IM 通信协议、IM 通信安全、群聊优化、弱网优化、网络保活等。
10、参考资料
[1] 新手入门:目前为止最透彻的的 Netty 高性能原理和框架架构解析
[2] 理论联系实际:一套典型的 IM 通信协议设计详解
[3] 浅谈 IM 系统的架构设计
[4] 简述移动端 IM 开发的那些坑:架构设计、通信协议和客户端
[5] 一套海量在线用户的移动端 IM 架构设计实践分享(含详细图文)
[6] 一套原创分布式即时通讯(IM)系统理论架构方案
[7] 一套高可用、易伸缩、高并发的 IM 群聊、单聊架构方案设计实践
[8] 一套亿级用户的 IM 架构技术干货(上篇):整体架构、服务拆分等
[9] 从新手到专家:如何设计一套亿级消息量的分布式 IM 系统
[10] 基于实践:一套百万消息量小规模 IM 系统技术要点总结
[11] 探探的 IM 长连接技术实践:技术选型、架构设计、性能优化
[12] 拿起键盘就是干,教你徒手开发一套分布式 IM 系统
[13] 万字长文,手把手教你用 Netty 打造 IM 聊天
[14] 基于 Netty 实现一套分布式 IM 系统
[15] SpringBoot 集成开源 IM 框架 MobileIMSDK,实现即时通讯 IM 聊天功能
(本文同步发布于:http://www.52im.net/thread-3988-1-1.html)
评论