Netty框架问题记录1--多线程下批量发送消息导致消息被覆盖
业务背景
项目是基于Netty实现的实时课堂项目,课堂中老师需要对试卷进行讲解,则老师向服务器发送一个打开试卷信息的请求,服务器获取试卷信息,将试卷信息发送给所有的客户端(学生和老师)。
发送给学生的时候需要在试卷信息中加上本人得分的信息。
实现方式大致如下:
Paper paper = getPaper(paperId); // 根据试卷ID获取试卷详细信息
for(Client client : allClients){
paper.setMyScore(getMyScore(client.getUserId())); //根据userId获取本人得分
client.send(paper); //向客户端发送数据
}
结果:学生A收到的得分是学生B的得分,也就是发送给clientA的paper数据被发送给clientB的paper数据给覆盖了,因为paper对象是同一个
原因分析:
虽然发送给所有客户端的信息都是paper对象,但是是在for循环里面执行的send方法,也就是说理论上应该是clientA的send方法执行完了之后才会执行clientB的send方法,也就是说理论上应该是学生A收到的paper信息之后学生B才会收到paper信息。
所以得出的结论猜想就是send方法不是同步执行的,而是异步的。追踪代码进行分析
第四行的代码client.send(paper) 实际就是调用了Channel的writeAndFlush方法
追踪到AbstractChannel的实现如下:
@Override
public ChannelFuture writeAndFlush(Object msg) {
return pipeline.writeAndFlush(msg);
}
执行了ChannelPipeline的writeAndFlush方法,跟踪实现类DefaultChannelPipeline的实现如下:
@Override
public final ChannelFuture writeAndFlush(Object msg) {
return tail.writeAndFlush(msg);
}
执行的是ChannelHandlerContext的writeAndFlush方法,跟踪实现类AbstractChannelHandlerContext实现如下:
@Override
public ChannelFuture writeAndFlush(Object msg) {
return writeAndFlush(msg, newPromise());
}
执行了内部的writeAndFlush方法,继续跟踪如下:
@Override
public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
if (msg == null) {
throw new NullPointerException("msg");
} if (isNotValidPromise(promise, true)) {
ReferenceCountUtil.release(msg);
// cancelled
return promise;
} write(msg, true, promise); return promise;
}
write方法如下:
private void write(Object msg, boolean flush, ChannelPromise promise) {
AbstractChannelHandlerContext next = findContextOutbound();
final Object m = pipeline.touch(msg, next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) { //判断当前线程是否是EventLoop线程
if (flush) {
next.invokeWriteAndFlush(m, promise);
} else {
next.invokeWrite(m, promise);
}
} else {
AbstractWriteTask task;
if (flush) {
task = WriteAndFlushTask.newInstance(next, m, promise);
} else {
task = WriteTask.newInstance(next, m, promise);
}
safeExecute(executor, task, promise, m);
}
}
跟踪到这里终于有所发现了,方法逻辑大致如下:
1.获取channelPipeline中的head节点
2.获取当前channel的eventLoop对象
3.判断当前channel的eventLoop对象中的线程是否是当前线程
4.如果是EventLoop线程,则直接执行writeAndFlush方法,也就是执行写入并且刷新到channelSocket中去
5.如果不是EventLoop线程,则会创建一个AbstractWriteTask,然后将这个task添加到这个channel的eventLoop中去
分析到这里就可以总结问题的所在了,如果执行channel的writeAndFlush的线程不是work线程池中的线程,那么就会先将这个发送消息封装成一个task,然后添加到这个channel所属的eventLoop中的阻塞队列中去,
然后通过EventLoop的循环来从队列中获取任务来执行。一旦task添加到队列中完成,write方法就会返回。那么当下一个客户端再执行write方法时,由于msg内容是同一个对象,就会将前一个msg的内容给覆盖了。
从而就会出现发送给多个客户端的内容不同,但是接收到的内容是相同的内容。而本例中,执行channel的write方法的线程确实不是eventLoop线程,因为我们采用了线程池来处理业务,当channel发送数据给服务器之后,
服务器解析channel中发送来的请求,然后执行业务处理,而执行业务的操作是采用线程池的方式实现的,所以最终通过channel发送数据给客户端的时候实际的线程是线程池中的线程,而并不是channel所属的EventLoop中的线程。
总结:
Netty中的work线程池中的EventLoop并不是一个纯粹的IO线程,除了有selector轮询IO操作之外,还会处理系统的Task和定时任务。
系统的task是通过EventLoop的execute(Runnable task)方法实现,EventLoop内部有一个LinkedBlockingQueue阻塞队列保存task,task一般都是由于用户线程发起的IO操作。
每个客户端有一个channel,每一个channel会绑定一个EventLoop,所以每个channel的所以IO操作默认都是由这个EventLoop中的线程来执行。然后用户可以在自定义的线程中执行channel的方法。
当用户线程执行channel的IO操作时,并不会立即执行,而是将IO操作封装成一个Task,然后添加到这个channel对应的EventLoop的队列中,然后由这个EventLoop中的线程来执行。所以channel的所有IO操作最终还是
由同一个EventLoop中的线程来执行的,只是发起channel的IO操作的线程可以不是任何线程。
采用将IO操作封装成Task的原因主要是防止并发操作导致的锁竞争,因为如果不用task的方式,那么用户线程和IO线程就可以同时操作网络资源,就存储并发问题,所以采用task的方式实现了局部的无锁化。
所以线程池固然好用,netty固然强大,但是如果没有深入理解,稍有不慎就可能会出现意想不到的BUG。
Netty框架问题记录1--多线程下批量发送消息导致消息被覆盖的更多相关文章
- 基于Java Netty框架构建高性能的部标808协议的GPS服务器
使用Java语言开发一个高质量和高性能的jt808 协议的GPS通信服务器,并不是一件简单容易的事情,开发出来一段程序和能够承受数十万台车载接入是两码事,除去开发部标808协议的固有复杂性和几个月长周 ...
- PHP 命令行模式实战之cli+mysql 模拟队列批量发送邮件(在Linux环境下PHP 异步执行脚本发送事件通知消息实际案例)
源码地址:https://github.com/Tinywan/PHP_Experience 测试环境配置: 环境:Windows 7系统 .PHP7.0.Apache服务器 PHP框架:ThinkP ...
- 基于Java Netty框架构建高性能的Jt808协议的GPS服务器(转)
原文地址:http://www.jt808.com/?p=971 使用Java语言开发一个高质量和高性能的jt808 协议的GPS通信服务器,并不是一件简单容易的事情,开发出来一段程序和能够承受数十万 ...
- 【转】彻底搞透Netty框架
本文基于 Netty 4.1 展开介绍相关理论模型,使用场景,基本组件.整体架构,知其然且知其所以然,希望给大家在实际开发实践.学习开源项目方面提供参考. Netty 是一个异步事件驱动的网络应用程序 ...
- 基于netty框架的Socket传输
一.Netty框架介绍 什么是netty?先看下百度百科的解释: Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开 ...
- ASP.NET MVC Filters 4种默认过滤器的使用【附示例】 数据库常见死锁原因及处理 .NET源码中的链表 多线程下C#如何保证线程安全? .net实现支付宝在线支付 彻头彻尾理解单例模式与多线程 App.Config详解及读写操作 判断客户端是iOS还是Android,判断是不是在微信浏览器打开
ASP.NET MVC Filters 4种默认过滤器的使用[附示例] 过滤器(Filters)的出现使得我们可以在ASP.NET MVC程序里更好的控制浏览器请求过来的URL,不是每个请求都会响 ...
- Java进阶专题(十五) 从电商系统角度研究多线程(下)
前言 本章节继上章节继续梳理:线程相关的基础理论和工具.多线程程序下的性能调优和电商场景下多线程的使用. 多线程J·U·C ThreadLocal 概念 ThreadLocal类并不是用来解决 ...
- 记录在linux下的wine生活
记录在linux下的windows生活 本篇内容涉及QQ.微信.Office的安装配置 QQ: 到deepin下载轻聊版. 如果安装了crossover,那么将其中opt/cxoffice/suppo ...
- 关于多线程情况下Net-SNMP v3 版本导致进程假死情况的跟踪与分析
1.问题描述 在使用net-snmp对交换机进行扫描的时候经常会出现进程假死的情况(就是进程并没有死掉,但是看不到它与外界进行任何的数据交互).这时候不知道进程内部发生了什么,虽然有日志信息,但进程已 ...
随机推荐
- DFS(单词方阵)
思路: 先把地图二维字符数组存进去之后,遍历寻找到一个‘y’,然后我们可以设置一个八个方向的方向数组,让‘y’的坐标,遍历加上方向坐标,找到’i‘然后沿着这个方向,dfs下去,每次寻找到正确的,然后建 ...
- Linux系统管理第四次作业 磁盘管理 文件系统
1.为主机新增两块30GB的SCSI硬盘 2.划分3个主分区,各5GB,剩余空间作为扩展分区 [root@localhost ~]# fdisk /dev/sdb 欢迎使用 fdisk (util-l ...
- hdu3033 I love sneakers! 分组背包变形(详解)
这个题很怪,一开始没仔细读题,写了个简单的分组背包交上去,果不其然WA. 题目分析: 分组背包问题是这样描述的:有K组物品,每组 i 个,费用分别为Ci ,价值为Vi,每组物品是互斥的,只能取一个或者 ...
- Web 之 Cookie
Cookie Cookie实际上是一小段的文本信息.客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie.客户端浏览器会把Cookie保存起来.当浏 ...
- Java算法之 二分搜寻法 ( 搜寻原则的代表)
为什么80%的码农都做不了架构师?>>> 二分搜寻法 ( 搜寻原则的代表) 1.二分查找又称折半查找,它是一种效率较高的查找方法. 2.二分查找要求:(1)必须采用顺序存储结构 ...
- CF思维联系–CodeForces - 222 C Reducing Fractions(数学+有技巧的枚举)
ACM思维题训练集合 To confuse the opponents, the Galactic Empire represents fractions in an unusual format. ...
- Flutter 系统是如何实现ExpansionPanelList的
老孟导读:Flutter组件有一个很大的特色,那就是很多复杂的组件都是通过一个一个小组件拼装而成的,今天就来说说系统的ExpansionPanelList是如何实现的. 在了解ExpansionPan ...
- JSP+Servlet+JDBC+C3P0实现的人力资源管理系统
项目简介 项目来源于:https://github.com/ruou/hr 本系统基于JSP+Servlet+C3P0+Mysql.涉及技术少,易于理解,适合JavaWeb初学者学习使用. 难度等级: ...
- STL下<algorithm>下的sort函数
定义: sort函数用于C++中,对给定区间所有元素进行排序,默认为升序,也可进行降序排序.sort函数进行排序的时间复杂度为nlog2n,比冒泡之类的排序算法效率要高,sort函数包含在头文件为#i ...
- 第六章第二十题(计算一个字符串中字母的个数)(Count the letters in a string) - 编程练习题答案
*6.20(计算一个字符串中字母的个数)编写一个方法,使用下面的方法头计算字符串中的字母个数: public static int countLetters(String s) 编写一个测试程序,提示 ...