一篇关于Netty相关的梳理总结

一篇关于Netty的梳理总结

  • 一、Netty
    • [1.1 什么是netty?为什么要用netty](#1.1 什么是netty?为什么要用netty)
    • [1.2 Netty是什么?](#1.2 Netty是什么?)
  • 二、Netty关于网络基础
    • [2.1 线程池](#2.1 线程池)
    • [2.2 线程池线程的生命周期和状态](#2.2 线程池线程的生命周期和状态)
    • [2.3 为什么要使用线程池](#2.3 为什么要使用线程池)
    • [2.4 简述线程池原理,FixedThreadPool用的阻塞队列是什么](#2.4 简述线程池原理,FixedThreadPool用的阻塞队列是什么)
    • [2.5 并发篇](#2.5 并发篇)
    • [2.6 网络篇](#2.6 网络篇)
      • [2.6.1 TCP的三次连接四次握手](#2.6.1 TCP的三次连接四次握手)
      • [2.6.2 浏览器发出一个请求经历了那些步骤](#2.6.2 浏览器发出一个请求经历了那些步骤)
      • [2.6.3 HTTPS是如何保证安全传输的](#2.6.3 HTTPS是如何保证安全传输的)
  • 三、Netty入门(进入正题)
    • [3.1 IO、NIO、AIO、BIO简单说明:](#3.1 IO、NIO、AIO、BIO简单说明:)
    • [3.2 Reactor事件响应编程:](#3.2 Reactor事件响应编程:)
    • [3.3 Netty的线程模型是怎么样的](#3.3 Netty的线程模型是怎么样的)
    • [3.4 Netty的高性能体现在哪些方面](#3.4 Netty的高性能体现在哪些方面)
    • [3.5 epoll和poll的区别](#3.5 epoll和poll的区别)
  • 四、Netty性能调优
    • [4.1 问题点](#4.1 问题点)
    • [4.2 Netty的性能优化](#4.2 Netty的性能优化)
      • [4.2.1 Netty 代码里面的优化](#4.2.1 Netty 代码里面的优化)
      • [4.2.2 JVM层面相关性能优化](#4.2.2 JVM层面相关性能优化)
  • 五、用Netty实现单机百万TCP长连接
  • 六、Nginx搭建Netty负载均衡
    • [6.1 编辑nginx配置文件](#6.1 编辑nginx配置文件)
    • [6.2 测试nginx反向代理](#6.2 测试nginx反向代理)
  • 七、Netty时间轮算法HashedWheelTimer
    • [7.1 HashedWheelTimer](#7.1 HashedWheelTimer)
    • [7.2 多层时间轮](#7.2 多层时间轮)
  • 八、使用方法

一、Netty

1.1 什么是netty?为什么要用netty

复制代码
 netty是jboss提供的一个java开源框架,netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可用性的网络服务器和客户端程序。也就是说netty是一个基于nio的编程框架,使用netty可以快速的开发出一个网络应用。

 由于java 自带的nio api使用起来非常复杂,并且还可能出现 Epoll Bug,这使得我们使用原生的nio来进行网络编程存在很大的难度且非常耗时。但是netty良好的设计可以使开发人员快速高效的进行网络应用开发。

Netty是一个基于Java NIO(非阻塞IO)实现的网络通信框架,提供了高性能、低延迟的网络通信能力。在使用Netty开发网络应用时,我们需要考虑性能优化和调试问题。

1.2 Netty是什么?

1)本质:JBoss做的一个Jar包

2)目的:快速开发高性能、高可靠性的网络服务器和客户端程序

3)优点:提供异步的、事件驱动的网络应用程序框架和工具

通俗的说:一个好使的处理Socket的东东


二、Netty关于网络基础

2.1 线程池

线程池的处理流程:

线程池执行任务--->核心线程是否已满--->未满--->创建核心线程执行--->已满--->任务队列是否已满--->未满--->将任务放入到队列中--->已满最大线程数是否达到--->未达到--->创建临时线程执行--->已达到--->根据拒绝策略处理任务

线程池的底层工作原理:

线程池内部是通过队列+线程实现的

线程池的线程复用原理:

核心原理是线程池对Thread就行了封装,而不是每次调用Thread.start() 方法,让每个线程去执行"循环任务"检查是否有任务需要被执行,而是调用run方法

线程池中阻塞队列的作用?为什么是先添加队列而不是先创建最大线程?

阻塞队列作用:保存任务,阻塞线程和唤醒线程,使得线程进入wait状态,释放cpu资源

为什么要添加队列而不是先创建最大线程:任务积压,临时线程工,如果还不够那么就是执行线程池拒绝策略,为的就是不去频繁的创建线程和回收线程

2.2 线程池线程的生命周期和状态

复制代码
5种状态:创建、就绪、运行、阻塞和死亡状态

新建状态(New) 新创建了一个线程对象。

就绪状态(Runnable) 线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

运行状态(Running) 就绪状态的线程获取了CPU,执行程序代码。

阻塞状态(Blocked) 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

阻塞的情况分三种:等待阻塞、同步阻塞、其他阻塞

等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。

同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。

其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。

当sleep()状态超时、oin()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

2.3 为什么要使用线程池

降低资源消耗、提高响应速度 、提高线程的可管理性

相关参数:

corePoolSize 代表核心线程数,

maxinumPoolSize 代表最大线程数,

keepAliveTime、unit 表示超出核心线程数之外的线程的空间存活时间,

workQueue 用来存放待执行的任务,

ThreadFactory 实际是一个线程工厂,用来生产线程执行任务,

Handler 任务拒绝策略(第一种调用shutdown方法够关闭线程池,第二种是达到最大线程数)

守护线程:为所有非守护线程提供服务的线程,任何一个守护线程都是JVM中所以非守护线程(用户线程)的保姆,例如GC垃圾回收线程就是一个经典的守护线程

线程直接如何进行通讯:

线程直接可以通过共享内存或基于网络来进行通信

如果是通过共享内存来进行通信,则需要考虑并发问题,什么时候阻塞,什么时候唤醒

像Java中的wait(),notify()就是阻塞和唤醒

通过网络就比较简单了,通过网络连接将通信数据发送给对象,当然也要考虑到并发问题,处理方式就是加锁等方式

如何查看线程死锁

可以通过jstack命令就行查看,jstack命令中会显示发生了死锁的线程

或者两个线程去操作数据库时,数据发生了死锁,这是可以查询数据库的死锁情况


powershell 复制代码
 1、查询是否锁表
 show open tables where In_use > 0 ;
 2、查询进程
 show processlist ;
 3、查看正在锁的事务
 select * from information_schema.INNODB_LOCKS ;
 4、查看等待锁的事务
 select * from information_schema.INNODB_LOCKS_WAITS ;

2.4 简述线程池原理,FixedThreadPool用的阻塞队列是什么

线程池内部是通过队列+线程实现的,当我们利用线程池执行任务时:

  • 1.如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

  • 2.如果此时线程池中的数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列。

  • 3如果此时线程池中的数量大于等于corePooSize,冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。

  • 4如果此时线程池中的数量大于rrePooSizeOuee满、并中的数量maxmumpodSize那 andr所定的路来外理务

  • 5.当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过kepAliveTime线程将被终止,这样,线程池可以动态的调整池中的线程数

    FixedThreadPool代表定长线程池,底层用的LinkedBlockinqQueue,表示无界的阻塞队列。

2.5 并发篇

2.5.1.并发三大特性(三要素)

原子性,可见性,有序性

  • 原子性 可分割的操作,多个步骤要保证同时成功或同时失败 关键字:synchronized

  • 可见性 一个线程对共享变量的修改,另一个线程能立马看到 关键字:volatile、synchronized

  • 有序性 程序执行的顺序和代码顺序保持一致 关键字:volatile、synchronized、final

分析: i++

1、将 i 从内存读到工作内存中的副本中

2、+1的运算

3、将结果写入工作内存

4、将工作内存的值刷回主存(什么时候刷如由系统决定,不确定的)

串行在时间上不可能发生重叠,前一个任务没搞定,下一个任务就只能等着

并行在时间上是重叠的,两个任务在同一时刻互不干扰的同事执行

并发允许两个任务彼此干扰,统一时间点、只有一个任务运行,交替执行

2.6 网络篇

2.6.1 TCP的三次连接四次握手

说明:全双工,面向连接的字节流,可靠的面向连接传输服务

标志位:SEQ 表示请求序列号,ACK 表示确认序列号,SYN 表示发起一个新连接,FIN 表示释放一个连接。

在建立TCP连接时,需要通过三次握手来建立,过程是:

第1次握手建立连接时,客户端向服务器发送 SYN 报文(SEQ=x,SYN=1),并进入 SYN_SENT 状态,等待服务器确认

第2次握手实际上是分两部分来完成的,即 SYN+ACK(请求和确认)报文。

2.1服务器收到了客户端的请求,向客户端回复一个确认信息(ACK=x+1)。

2.2服务器再向客户端发送一个 SYN 包(SEQ=y)建立连接的请求,此时服务器进入 SYN_RECV 状态,

第3次握手,是客户端收到服务器的回复(SYN+ACK 报文)。此时,客户端也要向服务器发送确认包(ACK)。

此包发送完毕客户端和服务器进入 ESTABLISHED 状态,完成 3 次握手

在断开TCP连接时,需要通过四次回收来断开,过程是:

1.客户端向服务端发送FIN

2.服务端接收FIN后,向客户端发送ACK,表示我接收到了断开连接的请求,客户端你可以不发数据了,不过服务端这边可能还有数据正在处理

3.服务端处理完所有数据后,向客户端发送FIN,表示服务端现在可以断开连接

4.客户端收到服务端的FIN,向服务端发送ACK,表示客户端也会断开连接了

2.6.2 浏览器发出一个请求经历了那些步骤

1.浏览器解析用户输入的URL,生成一个HTTP格式的请求

2.先根据URL域名从本地的hosts文件查找是否有映射IP,如果没有就将域名发送给电脑配置的DNS进行域名解析,得到IP地址

3.浏览器通过操作系统将请求通过四层网络协议发送出去

4.途中可能会经过各种路由器、交换机、最终到达服务器

5.服务器收到请求后,根据请求所指定的端口,将请求传递给绑定了该端口的应用程序,比如8080被tomcat占用了

6.Tomcat接收到请求数据后,按照http协议的格式进行解析,解析得到所要访问的servlet

7.然后servlet来处理这个请求,如果是springmvc中的DispatchServlet,那么则会找到对应的controller中的方法,并执行该方法得到的结果

8.Tomcat得到响应结果后封装成HTTP响应的格式,并再次通过网络发送给浏览器所在的服务器

9.浏览器所在的服务器拿到结果后再传递给浏览器,浏览器则负责解析并渲染

2.6.3 HTTPS是如何保证安全传输的

https通过使用对称加密,非对称加密,数字证书等方式来保证数据的安全传输

三、Netty入门(进入正题)

3.1 IO、NIO、AIO、BIO简单说明:

IO: BIO阻塞IO Read和Write是阻塞的NIO: IO多路复用 非阻塞IO Reactor线程模型Netty:Netty是建立在NIO基础之上,Netty在NIO之上又提供了更高层次的抽象。在Netty里面,Accept连接可以使用单独的线程池去处理,读写操作又是另外的线程池来处理。Accept连接和读写操作也可以使用同一个线程池来进行处理。而请求处理逻辑既可以使用单独的线程池进行处理,也可以跟放在读写线程一块处理。线程池中的每一个线程都是NIO线程。用户可以根据实际情况进行组装,构造出满足系统需求的高性能并发模型。

3.2 Reactor事件响应编程:

1、获取selector对事件进行分发(读的和写的)2、通过接口回调提供channelRead方法(读取客户端发送的数据)-从服务器收到数据后调用2、通过接口回调提供channelActive方法(回调客户端的方法)-客户端连接服务器后被调用

3.3 Netty的线程模型是怎么样的

Netty同时支持Reactor单线程模型、Reactor多线程模型和Reactor主从多线程模型,用户可根据启动参数配置在这三种模型之间切换。服务端启动时,通常会创建两个NioEventLoopGroup实例,对应了两个独立的Reactor线程池,bossGroup负责处理客户端的连接请求,

workerGroup负责处理1/0相关的操作,执行系统Task、定时任务Task等。用户可根据服务端引导类ServerBootstrap配置参数选择Reactor线程模型,进而最大限度地满足用户的定制化需求。

3.4 Netty的高性能体现在哪些方面

1、NIO模型,用最少的资源做更多的事情。2、内存零拷贝,尽量减少不必要的内存拷贝,实现了更高效率的传输。3、内存池设计,申请的内存可以重用,主要指直接内存。内部实现是用一颗二又查找树管理内存分配情况.4、串行化处理读写:避免使用锁带来的性能开销。即消息的处理尽可能再同一个线程内完成,期间不进行线程切换,这样就避免了多线程竞争和同步锁。表面上看,串行化设计似乎CPU利用率不高,并发程度不够。但是,通过调整NIO线程池的线程参数,可以同时启动多个串行化的线程并行运行,这种局部无锁化的串行线程设计相比一个队里-多个工作线程模型性能更优5、高性能序列化协议:支持protobuf等高性能序列化协议。6、高效并发编程的体现: volatile的大量、正确使用,CAS和原子类的广泛使用;线程安全容器的使用;通过读写锁提升并发性能。

3.5 epoll和poll的区别

1、select模型,使用的是数组来存储Socket连接文件描述符,容量是固定的,需要通过轮询来判断是否发生了IO事件

2、poll模型,使用的是链表来存储Soket连接文件描述符,容量是不固定的,同样需要通过轮询来判断是否发生了IO事件

3、epoll横型,epoll和poll完全不同的,epoll是一种事件通知模型,当发生了IO事件时,应用程序才进行IO操作,不需要像poll模型那样主动去轮询

四、Netty性能调优

4.1 问题点

1、Netty的性能是靠什么来决定的

2、Netty负载均衡

3、Netty优化,连接优化

4.2 Netty的性能优化

4.2.1 Netty 代码里面的优化

1、设置合理的线程数

对于线程池的调优,主要集中在用于接收海量设备TCP连接、TLS握手的 Acceptor线程池( Netty通常叫 boss NioEventLoop Group)上,以及用于处理网络数据读写、心跳发送的1O工作线程池(Nety通常叫 work Nio EventLoop Group)上。

对于Nety服务端,通常只需要启动一个监听端口用于端侧设备接入即可,但是如果服务端集群实例比较少,甚至是单机(或者双机冷备)部署,在端侧设备在短时间内大量接入时,需要对服务端的监听方式和线程模型做优化,以满足短时间内(例如30s)百万级的端侧设备接入的需要。

服务端可以监听多个端口,利用主从 Reactor线程模型做接入优化,前端通过SLB做4层门7层负载均衡。

2、心跳优化

要能够及时检测失效的连接,并将其剔除,防止无效的连接句柄积压,导致OOM等问题

设置合理的心跳周期,防止心跳定时任务积压,造成频繁的老年代GC(新生代和老年代都有导致STW的GC,不过耗时差异较大),导致应用暂停

使用Nety提供的链路空闲检测机制,不要自己创建定时任务线程池,加重系统的负担,以及增加潜在的并发安全问题。

从技术层面看,要解决链路的可靠性问题,必须周期性地对链路进行有效性检测。目前最流行和通用的做法就是心跳检测。心跳检测机制分为三个层面

复制代码
2.1、TCP层的心跳检测,即TCP的 Keep-Alive机制,它的作用域是整个TCP协议栈。
2.2、协议层的心跳检测,主要存在于长连接协议中,例如MQTT。
2.3、应用层的心跳检测,它主要由各业务产品通过约定方式定时给对方发送心跳消息实现。

心跳检测的目的就是确认当前链路是否可用,对方是否活着并且能够正常接收和发送消息。作为高可靠的NIO框架,Nety也提供了心跳检测机制。一般的心跳检测策略如下。

2.1、连续N次心跳检测都没有收到对方的Pong应答消息或者Ping请求消息,则认为链路已经发生逻辑失效,这被称为心跳超时。

2.2、在读取和发送心跳消息的时候如果直接发生了IO异常,说明链路已经失效,这被称为心跳失败。无论发生心跳超时还是心跳失败,都需要关闭链路,由客户端发起重连操作,保证链路能够恢复正常。

Nety提供了三种链路空闲检测机制,利用该机制可以轻松地实现心跳检测

2.1读空闲,链路持续时间T没有读取到任何消息。

2.2写空闲,链路持续时间T没有发送任何消息

2.3读写空闲,链路持续时间T没有接收或者发送任何消息

2.4对于百万级的服务器,一般不建议很长的心跳周期和超时时长

3、接收和发送缓冲区调优

在一些场景下,端侧设备会周期性地上报数据和发送心跳,单个链路的消息收发量并不大,针对此类场景,可以通过调小TCP的接收和发送缓冲区来降低单个TCP连接的资源占用率 当然对于不同的应用场景,收发缓冲区的最优值可能不同,用户需要根据实际场景,结合性能测试数据进行针对性的调优

4、合理使用内存池

5、IO线程和业务线程分离

4.2.2 JVM层面相关性能优化

1、确定GC优化目标

2、确定服务端内存占用

3、GC优化过程

4、操作系统优化

首先就是要突破操作系统的限制在Linux平台上,无论编写客户端程序还是服务端程序,在进行高并发TCP连接处理时,最高的并发数量都要受到系统对用户单一进程同时可打开文件数量的限制(这是因为系统为每个TCP连接都要创建一个socket句柄,每个socket句柄同时也是一个文件句柄)。对于想支持更高数量的TCP并发连接的通讯处理程序,就必须修改Linux对当前用户的进程同时打开的文件数量。可使用ulimit命令查看系统允许当前用户进程打开的文件数限制:$ ulimit -n 1024软限制(soft limit)😗是指Linux在当前系统能够承受的范围内进一步限制用户同时打开的文件数;硬限制(hardlimit)😗是根据系统硬件资源状况(主要是系统内存)计算出来的系统最多可同时打开的文件数量。

第一步,修改/etc/security/limits.conf文件,在文件中添加如下行:

powershell 复制代码
 soft nofile 1000000
 hard nofile 1000000
第二步,修改/etc/pam.d/login文件,在文件中添加如下行:

 session required /lib/security/pam_limits.so
第三步,查看Linux系统级的最大打开文件数限制,使用如下命令:

 [root@VM_0_15_centos ~]# cat /proc/sys/fs/file-max
 98566
如何修改这个系统最大文件描述符的限制呢?修改sysctl.conf文件

 vi /etc/sysctl.conf
在末尾添加

 fs.file_max = 1000000
立即生效

 sysctl -p

五、用Netty实现单机百万TCP长连接

关键概念1、句柄:ulimit -n 这里一般显示的是65535,代表一个进程能够打开的最大文件数,一条TCP连接,对应Linux系统里面是一个文件,最大连接数会受限于这个数字,我们要做百万连接,所以需要修改这个值。打开 /etc/security/limits.conf文件中配置如下两行:

powershell 复制代码
 hard nofile 1000000
 soft nofile 1000000

soft和hard为两种限制方式,其中soft表示警告的限制,hard表示真正限制,nofile表示打开的最大文件数。4.2、修改客户机设置

powershell 复制代码
 cat /proc/sys/net/ipv4/ip_local_port_range  

值为32768 61000

大概也就是共61000-32768=28232个端可用,单个IP对外只能发送28232个TCP请求。以管理员身份,把端口的范围区间增到最大

powershell 复制代码
echo "1024 65535"> /proc/sys/net/ipv4/ip_local_port_range

查看服务器端口的链接数:

powershell 复制代码
 lsof -i:8888 | wc -l

六、Nginx搭建Netty负载均衡

参考资源:传送地址

Nginx搭建Netty负载均衡

参考链接:利用Nginx的stream实现Netty的TCP负载均衡

笔记记录一下用Nginx实现netty的负载均衡学习过程。

6.1 编辑nginx配置文件

在conf/nginx.conf中增加下面的内容

java 复制代码
 stream {
    upstream netty_server {
        server 127.0.0.1:8301 weight=1;
        server 127.0.0.1:8302 weight=1;
  }
  server {
    listen 8300;
    proxy_pass netty_server;
  }
 }

其中server 127.0.0.1:8301 server 127.0.0.1:8302 分别对应两个netty server的IP 和端口号

listen 8300 为对外开放的端口号,对应client示例代码的端口号

6.2 测试nginx反向代理

启动server

powershell 复制代码
服务器启动成功,监听端口/127.0.0.1:8301
服务器启动成功,监听端口/[0:0:0:0:0:0:0:0]:8302

重新启动nginx

powershell 复制代码
 ./nginx -s reload

启动多个client进程(同一份代码复制多份,分别启动),模拟多个不同的客户端

一个server打印下面的消息

powershell 复制代码
 收到链接:/127.0.0.1:51424
 收到链接:/127.0.0.1:51516

另一个server打印下面的消息

powershell 复制代码
收到链接:/127.0.0.1:49992

说明反向代理成功

七、Netty时间轮算法HashedWheelTimer

7.1 HashedWheelTimer

时间轮算法借助时钟的思想,可以将时间轮看作一个时钟,上面有刻度,每个刻度代表多少时间,每个刻度上放着若干个任务。

Netty中的HashedWheelTimer

时间轮其实就是一种环形的数据结构,可以想象成时钟,分成很多格子,

一个格子代码一段时间(这个时间越短,Timer的精度越高)。

并用一个链表报错在该格子上的到期任务,同时一个指针随着时间一格一格转动,

并执行相应格子中的到期任务。任务通过取摸决定放入那个格子。

假设一个格子是1秒,则整个wheel能表示的时间段为8s,假如当前指针指向1,

此时需要调度一个3s后执行的任务,显然应该加入到(1+3=4)的方格中,指针再走3次就可以执行了;

如果任务要在10s后执行,应该等指针走完一个round零2格再执行,

因此应放入3,同时将round(1)保存到任务中。

检查到期任务时应当只执行round为0的,格子上其他任务的round应减1。

与java中的Hashmap很像。其实就是HashMap的哈希拉链算法,

只不过多了指针转动与一些定时处理的逻辑。

所以其相关的操作和HashMap也一致:

添加任务:O(1)

删除/取消任务:O(1)

过期/执行任务:

最差情况为O(n)->也就是当HashMap里面的元素全部hash冲突,退化为一条链表的情况。

平均O(1)->显然,格子越多,每个格子上的链表就越短,这里需要权衡时间与空间。

7.2 多层时间轮

如果任务的时间跨度很大,数量很大,单层的时间轮会造成任务的round很大,

单个格子的链表很长。这时候可以将时间轮分层,类似于时钟的时分秒3层。

多层的时间轮造成的算法复杂度的进一步提升。

单层时间轮只需增加每一轮的格子就能解决链表过长的问题。

因此,更倾向使用单层的时间轮,netty4中时间轮的实现也是单层的。

一.HashedWheelTimer是什么?

时间轮是一种非常惊艳的数据结构。其在Linux内核中使用广泛,是Linux内核定时器的实现方法和基础之一。 换句话说时间轮是一种高效来利用线程资源来进行批量化调度的一种调度模型。把大批量的调度任务全部都绑定到同一个的调度器上面,使用这一个调度器来进行所有任务的管理(manager),触发(trigger)以及运行(runnable)。能够高效的管理各种延时任务,周期任务,通知任务等等 而HashedWheelTimer则是使用了时间轮这种数据结构,它是Netty内部的一个工具类,最开始主要用来优化I/O超时的检测,本文将详细分析HashedWheelTimer的使用及原理。

二.能干什么?为什么需要这个东西?

优点其实笔者认为其最大的优点就是可以在一个线程中动态的添加定时(延时)任务像我们经常使用Timer,ScheduledExecutorService,Spring的Scheduled这些都是无法做到这一点的,一旦某个线程开始执行某个定时任务,都是无法再去动态添加的

那某些场景,比如说有很多小的定时任务,难道每一个都去起一个线程处理吗?那数量多的话对程序势必影响很大,浪费资源,这个时候就可以考虑HashedWheelTimer了.

而在netty中,因为其可能管理上百万的连接,每一个连接都会有很多超时任务。比如发送超时、心跳检测间隔等,如果每一个定时任务都启动一个Timer,不仅低效,而且会消耗大量的资源。所以创造了这个工具类.

适用场景:

java 复制代码
* 心跳检测(客户端探活)
* 会话、请求是否超时
* 消息延迟推送
* 业务场景超时取消(订单、退款单等)片

八、使用方法

Netty时间轮算法HashedWheelTimer

java 复制代码
	/**
	  * @author: 可乐加糖
	  * @create: 2023-03-24 09:06
	  * @Description: Netty时间轮算法HashedWheelTimer
	  */
	 public class HashedWheelTimerTest {
	 
	     private static final Logger logger = 	LoggerFactory.getLogger(HashedWheelTimerTest.class);
	 
	     public static void main(String[] args) {
	         doStartTask();
	         //doDoCloseTask();
	    }
    
   /**
     * 时间轮算法HashedWheelTimer
     * 1.构建对象,添加定时任务
     */
    public static void doStartTask() {
        //在一个格子里面的并不会区分的很细,而会依次顺序执行,所以适用于对时间精度要求不高的任务
        //构建时间轮对象
        HashedWheelTimer timer = new HashedWheelTimer(5, TimeUnit.SECONDS, 10);
        // 设置同步
        CountDownLatch latch = new CountDownLatch(2);

        //添加定时任务1,延迟2s执行
        timer.newTimeout((TimerTask) timeout -> {
            System.out.println("任务1执行");
            System.out.println("线程名称:"+Thread.currentThread().getName());
       },2,TimeUnit.SECONDS);

        //添加定时任务2,延迟2s执行
        timer.newTimeout((TimerTask) timeout -> {
            System.out.println("任务2执行");
            System.out.println("线程名称:"+Thread.currentThread().getName());
       },5,TimeUnit.SECONDS);

        //等待定时任务执行完毕后,将时间轮内部工作线程停止,这里只是粗略的等待,也可以使用CountDownLatch
        try {
            //latch.await(2,TimeUnit.SECONDS);
            Thread.sleep(10000);
       } catch (InterruptedException e) {
            throw new RuntimeException(e);
       }
        timer.stop();
   }

  /**
   * 时间轮算法HashedWheelTimer
   * 2.取消某个定时任务
   */
  public static void doDoCloseTask(){
      //构建时间轮对象
      HashedWheelTimer timer = new HashedWheelTimer(1, TimeUnit.SECONDS, 10);
      //添加定时任务1
      Timeout newTimeout = timer.newTimeout((TimerTask) timeout -> {
          System.out.println("任务3执行");
          System.out.println("线程名称:" + Thread.currentThread().getName());

     }, 5, TimeUnit.SECONDS);

      //现在又想取消掉这个任务
      if(!newTimeout.isExpired()){
          newTimeout.cancel();
     }
 }
相关推荐
mghio8 小时前
Dubbo 中的集群容错
java·微服务·dubbo
Asthenia04128 小时前
Spring AOP 和 Aware:在Bean实例化后-调用BeanPostProcessor开始工作!在初始化方法执行之前!
后端
Asthenia04129 小时前
什么是消除直接左递归 - 编译原理解析
后端
Asthenia04129 小时前
什么是自上而下分析 - 编译原理剖析
后端
Asthenia041210 小时前
什么是语法分析 - 编译原理基础
后端
Asthenia041210 小时前
理解词法分析与LEX:编译器的守门人
后端
uhakadotcom10 小时前
视频直播与视频点播:基础知识与应用场景
后端·面试·架构
Asthenia041211 小时前
Spring扩展点与工具类获取容器Bean-基于ApplicationContextAware实现非IOC容器中调用IOC的Bean
后端
bobz96511 小时前
ovs patch port 对比 veth pair
后端