Netty 是一个异步的、基于事件驱动的网络应用框架,用以快速开发高性能、高可靠性的网络 IO 程序。

一、异步模型

同步I/O : 需要进程去真正的去操作I/O;

异步I/O:内核在I/O操作完成后再通知应用进程操作结果。

怎么去理解同步和异步?

  • 同步:比如服务端发送数据给客户端,客户端中的处理器(继承一个入站处理器即可),可以去重写 channelRead0 方法,那么该方法触发的时候,其实必须得服务器有消息发过来,客户端才能去读写,两者必须是有先后顺序,这就是所谓的同步
  • 异步:客户端在服务端发送数据来之前就已经返回数据给了用户,但客户端已经告诉服务端数据到了要通过订阅的方式(大名鼎鼎的观察者模式),文章最后已经附上传送门,理解设计模式

比如上一篇关于NettyAttributeKeyAttributeMap的原理和使用,这里不妨讲讲它的缺点

二、异步模型存在的问题

使用流程

Step1 使用 AttributeKey 设置 key 值和 k-v 对,为 channel 获取 值做准备

创建一个处理器 NettyClientHandler 继承 SimpleChannelInboundHandler<RpcResponse>,它已经实现了 入站处理器相关的功能,只要重写它的 channelRead0 方法即可

public class NettyClientHandler extends SimpleChannelInboundHandler<RpcResponse> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, RpcResponse msg) throws Exception {
try {
AttributeKey<RpcResponse> key = AttributeKey.valueOf(msg.getRequestId());
ctx.channel().attr(key).set(msg);
ctx.channel().close();
} finally {
ReferenceCountUtil.release(msg);
}
}
}

记得将该 处理器 加入到 客户端 bootStraphandler()方法中,需要 通过默认的 初始化器new ChannelInitializer<SocketChannel>()(也是一个处理器)去初始化处理器链,我是通过匿名内部类去重写 initChannel 方法的,最后addLast() 刚刚自己写的处理器即可。

创建服务器和客户端,这里不再赘述,这篇文章对刚入门的帮助不大,可到文章最后取经拿服务端和客户端。

Step2 使用 channel 的 attr 方法,获取 k-v 值

客户端这里NettyClient 通过用户调用 sendRequest() 方法,去向服务端发送信息,返回值是服务端发回的消息,我们都知道,信息都是在处理器获取的,也就是在channelRead0方法中,所以我们要在sendRequest()方法中,获取服务端传来的值,通过下面代码获取

@Override
public Object sendRequest(RpcRequest rpcRequest) throws RpcException {
// 通过 host 和 port 获取 channel
//省略
// 写入 channel 让 服务端 去 读 request
channel.writeAndFlush(rpcRequest);
// 获取 k-v 对
RpcResponse rpcResponse = channel.attr(key).get();
}

相信你们当中有一部分发觉了异样,sendRequest()方法和channelRead0()不会同步,就是说你发送数据后,会立马执行到 获取 k-v 的代码,不能阻塞住等待 channelRead0()方法把 k-vset 进去

最后测试到,客户端拿不到值,总是为null

那怎么保持使用异步操作,并且可以顺利拿到值呢?

那么就得通过future来实现,就是先返回值,但值还是没有的,后面让用户自己用future的方法get阻塞拿值,说白了,还是要去同步,只是同步由CPU转到了用户自己手中,慢慢品

三、使用CompletableFuture 解决异步问题

CompletableFuture 使用方法

CompletableFuture<RpcResponse> resultFuture = new CompletableFuture<>();
/**complete 执行结束后,状态发生改变,则 说明 值已经传到了,complete 是 (被观察者)
通知类的通知方法,通知 观察者 ,get 方法将 不再阻塞,可以获取到值
*/
resultFuture .complete(msg);
/**获取 正确结果,get 是阻塞操作,所以 先把 resultFuture 作为 返回值 返回,再 get
获取值
*/
RpcResponse rpcResponse = resultFuture.get();
// 获取 错误结果, 抛 异常 处理
resultFuture.completeExceptionally(future.cause());

所以我们要做的就是在channelRead0()中 做 complete(),最后 用户直接 get得到数据即可,只要把sendRequest()方法的返回类型改为CompletableFuture 就可以了。

简单来说就是通过使用这个CompletableFuture,让 response不至于返回后是null,因为我们自己new了一个CompletableFuture类,这个类会被通知,并把结果告知给它

需要注意的是,在 客户端的sendRequest()方法拿到的 CompletableFuture<RpcResponse> 和在channelRead0()拿到的必须为同一个,可以设计成单例模式,这里是很泛化的单例,通用

public class SingleFactory {

private static Map<Class, Object> objectMap = new HashMap<>();

private SingleFactory() {}

/**
* 使用 双重 校验锁 实现 单例模式
* @param clazz
* @param <T>
* @return
*/
public static <T> T getInstance(Class<T> clazz) {
Object instance = objectMap.get(clazz);
if (instance == null) {
synchronized (clazz) {
if (instance == null) {
try {
instance = clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
}
return clazz.cast(instance);
} }

下面这样实现是因为涉及到多个客户端并发访问同一个服务器,设计的原因如下:

  • 如果是同一个客户端要采用发起多个线程去请求服务端,设计时如果多个线程的rpcRequest请求id一样,那么要考虑线程安全
  • 如果是不同客户端发起请求服务端,又要保证线程之间对CompleteFuture是线程安全的,确保性能,不能用让所有线程共享同一个 CompleteFuture,这样通知会变为不定向,不可用,因此考虑使用map暂时缓存所有CompleteFuture,更加高效
public class UnprocessedRequests {

/**
* k - request id
* v - 可将来获取 的 response
*/
private static ConcurrentMap<String, CompletableFuture<RpcResponse>> unprocessedResponseFutures = new ConcurrentHashMap<>(); /**
* @param requestId 请求体的 requestId 字段
* @param future 经过 CompletableFuture 包装过的 响应体
*/
public void put(String requestId, CompletableFuture<RpcResponse> future) {
System.out.println("put" + future);
unprocessedResponseFutures.put(requestId, future);
} /**
* 移除 CompletableFuture<RpcResponse>
* @param requestId 请求体的 requestId 字段
*/
public void remove(String requestId) {
unprocessedResponseFutures.remove(requestId);
} public void complete(RpcResponse rpcResponse) {
CompletableFuture<RpcResponse> completableFuture = unprocessedResponseFutures.remove(rpcResponse.getRequestId());
completableFuture.complete(rpcResponse);
System.out.println("remove" + completableFuture);
}
}

传送门:

设计模式:https://gitee.com/fyphome/git-res/tree/master/design-patterns

或者:https://github.com/Fyupeng/java/tree/main/design_patterns

服务端和客户端的实现:https://github.com/Fyupeng/java/tree/main/NettyPro/src/main/java/com/fyp/netty/groupchat

四、结束语

评论区可留言,可私信,可互相交流学习,共同进步,欢迎各位给出意见或评价,本人致力于做到优质文章,希望能有幸拜读各位的建议!

与51cto同步:https://blog.51cto.com/fyphome

与csdn同步:https://blog.csdn.net/F15217283411



交流技术,寻求同志。

—— 嗝屁小孩纸 QQ:1160886967

Netty之非阻塞处理的更多相关文章

  1. Netty基础系列(2) --彻底理解阻塞非阻塞与同步异步的区别

    引言 在进行I/O学习的时候,阻塞和非阻塞,同步和异步这几个概念常常被提及,但是很多人对这几个概念一直很模糊.要想学好Netty,这几个概念必须要掌握清楚. 同步和异步 同步与异步的区别在于,异步基于 ...

  2. suging闲谈-netty 的异步非阻塞IO线程与业务线程分离

    前言 surging 对外沉寂了一段时间了,但是作者并没有闲着,而是针对于客户的需要添加了不少功能,也给我带来了不少外快收益, 就比如协议转化,consul 的watcher 机制,JAVA版本,sk ...

  3. NIO学习笔记,从Linux IO演化模型到Netty—— 究竟如何理解同步、异步、阻塞、非阻塞

    我的观点 首先,分开各自理解. 1. 同步:描述两个(或者多个)个体之间的协调关系. 比如,单线程中,methodA调用了methodB,methodB返回后,methodA才往下执行,那么称A同步调 ...

  4. Java IO(3)非阻塞式输入输出(NIO)

    在上篇<Java IO(2)阻塞式输入输出(BIO)>的末尾谈到了什么是阻塞式输入输出,通过Socket编程对其有了大致了解.现在再重新回顾梳理一下,对于只有一个“客户端”和一个“服务器端 ...

  5. 一文读懂阻塞、非阻塞、同步、异步IO

    介绍 在谈及网络IO的时候总避不开阻塞.非阻塞.同步.异步.IO多路复用.select.poll.epoll等这几个词语.在面试的时候也会被经常问到这几个的区别.本文就来讲一下这几个词语的含义.区别以 ...

  6. nio 阻塞 非阻塞 同步 异步

    https://mp.weixin.qq.com/s/5SKgdkC0kaHN495psLd3Tg 说在前面 上篇NIO相关基础篇二,主要介绍了文件锁.以及比较关键的Selector,本篇继续NIO相 ...

  7. 简易非阻塞http服务器

    说明         需要理解阻塞和非阻塞的区别,特别要注意非阻塞和异步不是一个概念,这个很容易弄错.云盘里面netty的书会讲这几个方面的区别,nodejs深入浅出关于异步编程章节里面       ...

  8. I/O模型(同步、非同步、阻塞、非阻塞)总结

    I/O:同步(synchronous).异步(asynchronous).阻塞(blocking).非阻塞(nonblocking) 1.I/O内部机制 出于安全考虑,用户程序(用户态)是没办法直接操 ...

  9. Java网络编程和NIO详解5:Java 非阻塞 IO 和异步 IO

    Java网络编程和NIO详解5:Java 非阻塞 IO 和异步 IO Java 非阻塞 IO 和异步 IO 转自https://www.javadoop.com/post/nio-and-aio 本系 ...

随机推荐

  1. C#特性(属性)Attribute

    先明确一个概念: 元数据..NET中元数据是指程序集中的命名空间.类.方法.属性等信息.这些信息是可以通过Reflection读取出来的. 再来看个例子: #define BUG //#define ...

  2. XStart远程连接Linux图形化界面

    转至:https://zhuanlan.zhihu.com/p/337791712 场景: 因在Linux中安装Oracle11g 需要调用Oracle的图形化界面,此时在宿主机上安装了 Xmanag ...

  3. omnet++:官方文档翻译总结(四)

    学习翻译自:Adding Statistics Collection - OMNeT++ Technical Articles Part 5 - Adding Statistics Collectio ...

  4. css样式之浮动

    什么是浮动? 添加了浮动的的元素会脱离正常的文档流. 浮动的特点: 1.可以让块级元素排在同一排 2.可以让行属性标签支持所有的css样式 3.遇到相邻的浮动元素或者父级元素会停下来 4.浮动会影响其 ...

  5. think php 验证器及验证场景的使用

    1:在需要验证的模块名下建一个validate文件夹 ,文件夹下建议个验证的 名称   Goods.php 2:打开Goods.php <?php namespace app\api\valid ...

  6. Lua在Nginx的应用

    当 Nginx 标准模块和配置不能灵活地适应系统要求时,就可以考虑使用 Lua 扩展和定制 Nginx 服务.OpenResty集成了大量精良的 Lua 库.第三方模块,可以方便地搭建能够处理超高并发 ...

  7. php 23种设计模型 - 模板方法模式

    模板模式 模板模式准备一个抽象类,将部分逻辑以具体方法以及具体构造形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑.不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现.先制 ...

  8. numpy: np.logical_and/or/not (逻辑与/或/非)+python3-曲线拟合(polyfit/polyval)

    可以用拟合两个变量之间的关系,然后根据一个变量,去推测出另外一个变量的推测值

  9. 问题排查利器:Linux 原生跟踪工具 Ftrace 必知必会

    本文地址:https://www.ebpf.top/post/ftrace_tools TLDR,建议收藏,需要时查阅. 如果你只是需要快速使用工具来进行问题排查,包括但不限于函数调用栈跟踪.函数调用 ...

  10. Window7环境下安装Scrapy 方法

    Window7环境下安装Scrapy Scrapy在CPython(默认Python实现)和PyPy(从PyPy 5.9开始)下运行Python 2.7和Python 3.4或更高版本. 如果您使用的 ...