1. 一个EventLoopGroup当中会包含一个或多个EventLoop。
  2. 一个EventLoop在它的整个生命周期当中都只会与唯一一个Thread进行绑定。
  3. 所有由EventLoop所处理的各种I/O事件都将在它所关联的那个Thread上进行处理。
  4. 一个Channel在它的整个生命周期中只会注册在一个EventLoop上。
  5. 一个EventLoop在运行过程中,会被分配给一个或多个Channel。
  6. 同一个Channel提交的任务执行顺序和提交顺序是一样的(先进去的先出来,任务队列)。

重要结论:在netty的实现当中一定是线程安全的,基于此我们可以存储存储一个channel的引用,并且在需要向远程端点发送数据时,通过这个引用来调用Channel相应的方法;即便当时有很多线程在使用它也不会出现多线程问题,而且消息一定会按照顺序发送出去。

重要结论:我们在业务开发中,不要将长时间执行的耗时任务放入到EventLoop的执行队列中,因为它将会一直阻塞该线程所对应的所有Channel上的其他执行任务,如果我们需要进行阻塞调用或是耗时的操作(实际开发中很常见),那么我们就需要使用一个专门的EventExecutor(业务线程池)。

通常会有2种实现方式:
1、在ChannelHandler的回调方法中,使用自己定义的业务线程池,这样就可以实现异步调用。

2、借助于netty提供的向ChannelPipeLine添加ChannelHandler时调用的addLast方法来传递EventExecutor。
说明:默认情况下(调用addLast(handler)),ChannelHandler中的回调方法都是由I/O线程所执行,如果调用了ChannelPipeline addLast(EventExecutorGroup group, ChannelHandler… handlers);方法,那么ChannelHandler中的回调方法就是由参数中的group线程组来执行。

netty的异步:

从上图可以看到,ChannelPromise继承了Promise 接口,而Promise是可以写的(writable),什么是可以写的,之前的Future都是get,isSuccess之类的方法,在ChannelPromise里边可以看到setSuccess(Void result)【setSuccess只能写一次,下一次写报错】之类的写方法。ChannelPromise字面意思是承诺的意思,不管是成功还是失败会承诺给你一个结果。

JDK所提供的Future只能通过手工方式检查执行结果,而这个操作是会阻塞的;Netty则对ChannelFuture进行了增强,这里涉及到的是观察者模式,通过ChannelFutureListener以回调的方式来获取执行结果,去除了手工检查阻塞的操作,值得注意的是:ChannelFutureListener的operationcomplete方法是由I/O线程执行的,因此要注意的是不要再这里执行耗时操作,否则需要需要通过另外的线程或线程池来执行。

举例:jdk的Future得到返回结果是使用get或者isDone获取,而这两个方式是阻塞的,即使是用超时时间的方法如果时间到了获取不到也是返回null,这些事情都是开发人员自己做的,而Netty解决了这个弊端,netty通过在Future上加入了监听器的模式,注册到Future上若干Listner,Future持有Channel,当某一个事件发生的时候,Future调用对应的Listner的方法,方法入参会有当前Future的引用,所以在Listener里边就会得到Future的Channel,之后在Listener里边得到Channel的数据进行处理,这也是上边说的不要再Listener的方法里边处理耗时的业务的原因。

再说一下ChannelHandler,ChannelHandler有入栈和出栈的Handler,就拿ChannelInboundHandlerAdapter 来说,我们要写一个入栈处理器,需要必须重写接口里边的所有方法,但是我们只用一部分方法,而Adapter是一种适配器模式,会把所有方法实现,我们在用的时候直接用适配的类(要么重写要么直接使用)去实现业务逻辑就可以了,大大方便了开发者以及减轻来了开发者的工作量。

1
2
3
4
5
6
7
8
9
10
11
12
public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {
...略
public ChannelInboundHandlerAdapter() {
}
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelRegistered();
}
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelUnregistered();
}
...略
}

ChannelInboundHandlerAdapter 的具体实现类有SimpleChannelInboundHandler,他和ChannelInboundHandlerAdapter 有什么区别呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 大专栏  netty源码分析(十八)Netty底层架构系统总结与应用实践
18
19
20
21
22
23
24
25
26
27
public abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter
{
...略
protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception;
//开发者必须实现该方法,因为是静态的(模板设计模式)
...略 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean release = true;
try {
if (acceptInboundMessage(msg)) {
@SuppressWarnings("unchecked")
I imsg = (I) msg;//强制转换
channelRead0(ctx, imsg);//暴露给开发者的接口,带有泛型
} else {
release = false;
ctx.fireChannelRead(msg);
}
} finally {
if (autoRelease && release) {
ReferenceCountUtil.release(msg);//引用数减一,将资源释放掉,因此消息的引用我们不要再外围引用,
//因为消息在这里被释放掉了
}
}
} }

很直观就是加了一个泛型I,I就是接受的消息的类型,比如String,Object等,而在ChannelInboundHandlerAdapter 里边四需要把消息 强制类型转换的,这是他们最大的区别。除此之外SimpleChannelInboundHandler会对消息执行ReferenceCountUtil.release(Object)和ReferenceCountUtil.retain(Object) 分别是释放一个消息引用和保持一个消息引用(流到下一个handler).
我们一般会使用ChannelInboundHandlerAdapter 和SimpleChannelInboundHandler处理入栈数据。
实际应用:

ReferenceCountUtil的release方法:

1
2
3
4
5
6
public static boolean release(Object msg) {
if (msg instanceof ReferenceCounted) {
return ((ReferenceCounted) msg).release();
}
return false;
}

最终使用的是ReferenceCounted类操作的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* A reference-counted object that requires explicit deallocation.
* <p>
* When a new {@link ReferenceCounted} is instantiated, it starts with the reference count of {@code 1}.
* {@link #retain()} increases the reference count, and {@link #release()} decreases the reference count.
* If the reference count is decreased to {@code 0}, the object will be deallocated explicitly, and accessing
* the deallocated object will usually result in an access violation.
* </p>
* 当一个ReferenceCounted被实例化的时候,它的引用数是1,retain()增加一个引用次数,release()减少一个引用次数,如果引用数量是0
* 的时候,这个对象将会被显示的回收,去访问的一个被回收的对象通常的结果是访问违法常规的。
* <p>
* If an object that implements {@link ReferenceCounted} is a container of other objects that implement
* {@link ReferenceCounted}, the contained objects will also be released via {@link #release()} when the container's
* reference count becomes 0.
* </p>
* 如果一个一个实现了ReferenceCounted的类的对象最为一个容器,并且容器里边有若干对象,那么在容器外部被引用的次数为0的时候,随着容器的回收,
* 容器内部的对象也会被回收。
*/
public interface ReferenceCounted {
....略
}

netty源码分析(十八)Netty底层架构系统总结与应用实践的更多相关文章

  1. ABP源码分析十八:UI Inputs

    以下图中描述的接口和类都在Abp项目的Runtime/Validation, UI/Inputs目录下的.在当前版本的ABP(0.83)中这些接口和类并没有实际使用到.阅读代码时可以忽略,无需浪费时间 ...

  2. jQuery 源码分析(十八) ready事件详解

    ready事件是当DOM文档树加载完成后执行一个函数(不包含图片,css等),因此它的触发要早于load事件.用法: $(document).ready(fun) ;fun是一个函数,这样当DOM树加 ...

  3. Vue.js 源码分析(十八) 指令篇 v-for 指令详解

    我们可以用 v-for 指令基于一个数组or对象来渲染一个列表,有五种使用方法,如下: <!DOCTYPE html> <html lang="en"> & ...

  4. Netty源码分析 (八)----- write过程 源码分析

    上一篇文章主要讲了netty的read过程,本文主要分析一下write和writeAndFlush. 主要内容 本文分以下几个部分阐述一个java对象最后是如何转变成字节流,写到socket缓冲区中去 ...

  5. Netty源码分析第1章(Netty启动流程)---->第4节: 注册多路复用

    Netty源码分析第一章:Netty启动流程   第四节:注册多路复用 回顾下以上的小节, 我们知道了channel的的创建和初始化过程, 那么channel是如何注册到selector中的呢?我们继 ...

  6. Netty源码分析第1章(Netty启动流程)---->第5节: 绑定端口

    Netty源码分析第一章:Netty启动步骤 第五节:绑定端口 上一小节我们学习了channel注册在selector的步骤, 仅仅做了注册但并没有监听事件, 事件是如何监听的呢? 我们继续跟第一小节 ...

  7. Netty源码分析第1章(Netty启动流程)---->第3节: 服务端channel初始化

    Netty源码分析第一章:Netty启动流程   第三节:服务端channel初始化 回顾上一小节的initAndRegister()方法: final ChannelFuture initAndRe ...

  8. netty源码分析系列文章

    netty源码分析系列文章 nettynetty源码阅读netty源码分析  想在年终之际将对netty研究的笔记记录下来,先看netty3,然后有时间了再写netty4的,希望对大家有所帮助,这个是 ...

  9. Netty源码分析-- 处理客户端接入请求(八)

    这一节我们来一起看下,一个客户端接入进来是什么情况.首先我们根据之前的分析,先启动服务端,然后打一个断点. 这个断点打在哪里呢?就是NioEventLoop上的select方法上. 然后我们启动一个客 ...

随机推荐

  1. 实现Action

    实现Action 对于开发者来说,Action才是应用的核心,开发者需要提供大量的Action类,并在Struts.xml文件中配置Action.Action类中包含了用户请求的处理逻辑,Action ...

  2. python全局灰度线性变换——自由设定图像灰度范围

    全局线性变换的公式是s = (r-a)*(d-c)/(b-a)+c,其中a.b是原图片的灰度最小值和最大值,c.d是变换后的灰度值的最小值和最大值.r是当前像素点的灰度值,s是当前像素点变换后的灰度值 ...

  3. request请求生命周期

    request请求生命周期 一.request请求分析 1.1. request数据请求 # views.py from rest_framework.views import APIView fro ...

  4. memset为int型数组初始化问题

    头文件:#include <string.h>memset() 函数用来将指定内存的前n个字节设置为特定的值,其原型为:    void * memset( void * ptr, int ...

  5. 翻译——1_Project Overview, Data Wrangling and Exploratory Analysis-checkpoint

    为提高提高大学能源效率进行建筑能源需求预测 本文翻译哈佛大学的能源分析和预测报告,这是原文 暂无数据源,个人认为学习分析方法就足够 内容: 项目概述 了解数据 探索性分析 使用不同的机器学习方法进行预 ...

  6. 17.3.13---join函数

    1-----Python中有join()和os.path.join()两个函数,具体作用如下:     join():    连接字符串数组.将字符串.元组.列表中的元素以指定的字符(分隔符)连接生成 ...

  7. windows10使用npm安装vue、vue-cli

    从网上下载了一个免费的vue.js前端模板,准备和Django整合出一个项目出来,然后发现前端代码都是.vue文件,已经整合过.html,很容易,感觉这个.vue的前端稍微复杂一些 本文主要参考博客及 ...

  8. 如何写JS库,JS库写法

    前言: 现在javascript库特别多,其写法各式各样,总结几种我们经常见到的,作为自己知识的积累.而目前版本的 JavaScript 并未提供一种原生的.语言级别的模块化组织模式,而是将模块化的方 ...

  9. shell_切割日志

    可以修改的:1.日志存放目录:logdir='/data/logs/'2.每个类型日志保留个数:savefiles=30 #!/bin/bashnum=$(date -d"+1 day ag ...

  10. drf框架与postman初始

    drf框架 全称:django-rest framework 知识点 """ 1.接口:什么是接口.restful接口规范 2.CBV生命周期源码 - 基于restful ...