Java NIO
三个核心对象
在NIO中有三个核心对象需要掌握:缓冲区(Buffer)、选择器(Selector)和通道(Channel)。
缓冲区
缓冲区实际上是一个容器对象,更直接地说,其实就是一个数组,在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,它也是写入缓冲区的;任何时候访问NIO中的数据,都是将它放到缓冲区中。而在面向流I/O系统中,所有数据都是直接写入或者直接将数据读取到Stream对象中。在NIO中,所有的缓冲区类型都继承于抽象类Buffer,最常用的就是ByteBuffer
buffer必须要进行翻转。flip()
在缓冲区中,最重要的属性有下面三个,它们一起合作完成对缓冲区内部状态的变化跟踪。
● position:指定下一个将要被写入或者读取的元素索引,它的值由get()/put()方法自动更新,在新创建一个Buffer对象时,position被初始化为0。
● limit:指定还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
● capacity:指定了可以存储在缓冲区中的最大数据容量,实际上,它指定了底层数组的大小,或者至少是指定了准许我们使用的底层数组的容量。
缓冲区的种类
- 只读缓冲区
- 直接缓冲区:allocateDirect()方法
选择器
- 传统的Client/Server模式会基于TPR(Thread per Request),服务器会为每个客户端请求建立一个线程,由该线程单独负责处理一个客户请求。这种模式带来的一个问题就是线程数量的剧增,大量的线程会增大服务器的开销。
- 大多数的实现为了避免这个问题,都采用了线程池模型,并设置线程池中线程的最大数量,这又带来了新的问题,如果线程池中有200个线程,而有200个用户都在进行大文件下载,会导致第201个用户的请求无法及时处理,即便第201个用户只想请求一个几KB大小的页面。
- NIO中非阻塞I/O采用了基于Reactor模式的工作方式,I/O调用不会被阻塞,而是注册感兴趣的特定I/O事件,如可读数据到达、新的套接字连接等,在发生特定事件时,系统再通知我们。NIO中实现非阻塞I/O的核心对象是Selector,Selector是注册各种I/O事件的地方,而且当那些事件发生时,就是Seleetor告诉我们所发生的事件
使用NIO中非阻塞I/O编写服务器处理程序,大体上可以分为下面三个步骤。
(1)向Selector对象注册感兴趣的事件。(必须配置为非阻塞)
(2)从Selector中获取感兴趣的事件。
(3)根据不同的事件进行相应的处理
Netty
参考:https://weread.qq.com/web/reader/f3832ad071d38030f380cedkc20321001cc20ad4d76f5ae
https://blog.youkuaiyun.com/qq_33347239/article/details/104081653
高性能之道
(1)I/O传输模型:用什么样的通道将数据发送给对方,是BIO、NIO还是AIO,I/O传输模型在很大程度上决定了框架的性能。
(2)数据协议:采用什么样的通信协议,是HTTP还是内部私有协议。协议的选择不同,性能模型也就不同。一般来说内部私有协议比公有协议的性能更高。
(3)线程模型:线程模型涉及如何读取数据包,读取之后的编解码在哪个线程中进行,编解码后的消息如何派发等方面。线程模型设计得不同,对性能也会产生非常大的影响。
具体的
-
异步非阻塞通信
-
零拷贝
-
内存池
-
高效的Reactor线程模型
-
无锁化的串行设计理念
-
高效的并发编程,Netty的高效并发编程主要体现在如下几点。
- (1)volatile关键字的大量且正确的使用。
- (2)CAS和原子类的广泛使用。
- (3)线程安全容器的使用。
- (4)通过读写锁提升并发性能。
-
对高性能的序列化框架的支持
-
灵活的TCP参数配置能力
详细介绍见下文
ByteBuf
ByteBuf的基本分类AbstractByteBuf有众多子类,大致可以从三个维度来进行分类,分别如下。
- Pooled:池化内存,就是从预先分配好的内存空间中提取一段连续内存封装成一个ByteBuf,分给应用程序使用。
- Unsafe:是JDK底层的一个负责I/O操作的对象,可以直接获得对象的内存地址,基于内存地址进行读写操作。
- Direct:堆外内存,直接调用JDK的底层API进行物理内存分配,不在JVM的堆内存中,需要手动释放。
零拷贝
有三种形式的零拷贝的体现:
- 直接复制形式,利用sendfile。
- CompositeChannelBuffer类的
- 堆外直接内存的使用
方式1
数据可以直接从read buffer 读缓存区传输到套接字缓冲区,也就是省去了将操作系统的read buffer 拷贝到程序的buffer,以及从程序buffer拷贝到socket buffer的步骤,直接将read buffer拷贝到socket buffer。JDK NIO中的的transferTo() 方法就能够让您实现这个操作,这个实现依赖于操作系统底层的sendFile()实现的:
linux 2.1 内核开始引入了sendfile函数,用于将文件通过socket传输。
Linux中的sendfile()以及Java NIO中的FileChannel.transferTo()方法都实现了零拷贝的功能,而在Netty中也通过在FileRegion中包装了NIO的FileChannel.transferTo()方法实现了零拷贝。
方式2
而在Netty中还有另一种形式的零拷贝,即Netty允许我们将多段数据合并为一整段虚拟数据供用户使用,而过程中不需要对数据进行拷贝操作,这也是我们今天要讲的重点。在stream-based transport(如TCP/IP)的传输过程中,数据包有可能会被重新封装在不同的数据包中。因此在实际应用中,很有可能一条完整的消息被分割为多个数据包进行网络传输,而单个的数据包对你而言是没有意义的,只有当这些数据包组成一条完整的消息时你才能做出正确的处理,而Netty可以通过零拷贝的方式将这些数据包组合成一条完整的消息供你来使用。而此时,零拷贝的作用范围仅在用户空间中。
下面我们就来讲讲与Zero Copy直接相关的CompositeChannelBuffer类。 ###CompositeChannelBuffer类 CompositeChannelBuffer类的作用是将多个ChannelBuffer组成一个虚拟的ChannelBuffer来进行操作。为什么说是虚拟的呢,因为CompositeChannelBuffer并没有将多个ChannelBuffer真正的组合起来,而只是保存了他们的引用,这样就避免了数据的拷贝,实现了Zero Copy。
方式3
Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。
内存池
指的是池化内存
为了尽量重复利用缓冲区内存,Netty设计了一套基于内存池的缓冲区重用机制。
Arena代表1个内存区域,为了优化内存区域的并发访问,netty中内存池是由多个Arena组成的数组,分配时会每个线程按照轮询策略选择1个Arena进行内存分配。
1个Arena由两个PoolSubpage数组和多个ChunkList组成。两个PoolSubpage数组分别为tinySubpagePools和smallSubpagePools。多个ChunkList按照双向链表排列,每个ChunkList里包含多个Chunk,每个Chunk里包含多个Page(默认2048个),每个Page(默认大小为8k字节)由多个Subpage组成。
每个Arena由如下几个ChunkList构成:
PoolChunkList qInit:存储内存利用率0-25%的chunk
PoolChunkList q000:存储内存利用率1-50%的chunk
PoolChunkList q025:存储内存利用率25-75%的chunk
PoolChunkList q050:存储内存利用率50-100%的chunk
PoolChunkList q075:存储内存利用率75-100%的chunk
PoolChunkList q100:存储内存利用率100%的chunk
所以就可以按照最坏匹配和最近匹配原则分配内存。
灵活的TCP参数配置能力
合理设置TCP参数在某些场景下对性能的提升具有显著的效果,例如SO_RCVBUF和SO_SNDBUF。如果设置不当,对性能的影响也是非常大的。下面我们总结一下对性能影响比较大的几个配置项。
(1)SO_RCVBUF和SO_SNDBUF:通常建议值为128KB或者256KB。
(2)SO_TCPNODELAY:Nagle算法通过将缓冲区内的小封包自动相连,组成较大的封包,阻止大量小封包的发送阻塞网络,从而提高网络应用效率。但是对于延时敏感的应用场景需要关闭该优化算法。
(3)软中断:如果Linux内核版本支持RPS(2.6.35版本以上),开启RPS后可以实现软中断,提升网络吞吐量。RPS会根据数据包的源地址、目的地址,以及源端口和目的端口进行计算得出一个Hash值,然后根据这个Hash值来选择软中断CPU的运行。从上层来看,也就是说将每个连接和CPU绑定,并通过这个Hash值在多个CPU上均衡软中断,提升网络并行处理性能。
Bootstrap
客户端
在Netty中,Channel相当于一个Socket的抽象,它为用户提供了关于Socket状态(是连接还是断开)及对Socket的读、写等操作。每当Netty建立了一个连接,都创建一个与其对应的Channel实例。
NioSocketChannel的创建
Bootstrap是Netty提供的一个便利的工厂类,可以通过它来完成客户端或服务端的Netty初始化。
(1)EventLoopGroup:不论是服务端还是客户端,都必须指定EventLoopGroup。在这个例子中,指定了NioEventLoopGroup,表示一个NIO的EventLoopGroup。
(2)ChannelType:指定Channel的类型。因为是客户端,所以使用了NioSocketChannel。
(3)Handler:设置处理数据的Handler
channel()方法是传入工厂想创建的channel的模板
Channel的实例化过程其实就是调用ChannelFactory的newChannel()方法,而实例化的Channel具体类型又和初始化Bootstrap时传入的channel()方法的参数相关。因此对于客户端的Bootstrap而言,创建的Channel实例就是NioSocketChannel。
客户端Channel的初始化
NioSocketChannel初始化所做的工作内容。
-
调用NioSocketChannel.newSocket(DEFAULT_SELECTOR_PROVIDER)打开一个新的Java NioSocketChannel。
-
初始化AbstractChannel(Channel parent)对象并给属性赋值,具体赋值的属性如下。
- id:每个Channel都会被分配一个唯一的id。
- parent:属性值默认为null。
- unsafe:通过调用newUnsafe()方法实例化一个Unsafe对象,它的类型是AbstractNioByteChannel.NioByteUnsafe内部类。
- pipeline:是通过调用new DefaultChannelPipeline(this)新创建的实例。
- AbstractNIOChannel中被赋值的属性如下。
- ch:被赋值为Java原生SocketChannel,即NioSocketChannel的newSocket()方法返回的Java NIO SocketChannel。
- readInterestOp:被赋值为SelectionKey.OP_READ。
- ch:被配置为非阻塞,即调用ch.configureBlocking(false)方法。(4)NioSocketChannel中被赋值的属性:config=newNioSocketChannelConfig(this,socket.socket())。
Unsafe属性的初始化
在实例化NioSocketChannel的过程中,Unsafe就特别关键。Unsafe其实是对Java底层Socket操作的封装,因此,它实际上是沟通Netty上层和Java底层的重要桥梁
[外链图片转存中…(img-mP78hg9B-1596526402363)]
ChannelPipeline的初始化
Eachchannel has its own pipeline and it is created automatically when a newchannel is created 在实例化一个Channel时,必然都要实例化一个ChannelPipeline。
- DefaultChannelPipeline的构造器需要传入一个Channel,而这个Channel其实就是我们实例化的NioSocketChannel对象,DefaultChannelPipeline会将这个NioSocketChannel对象保存在Channel属性中。
- DefaultChannelPipeline中还有两个特殊的属性,即Head和Tail
- DefaultChannelPipeline中维护了一个以AbstractChannelHandlerContext为节点元素的双向链表,这个链表是Netty实现Pipeline机制的关键
EventLoop的初始化
要点:
- Netty对Selector有重构,创建一个新的Selector就会调用openSelector()
回到最开始的ChatClient用户代码中,我们在一开始就实例化了一个NioEventLoopGroup对象
- NioEventLoopGroup有几个重载的构造器,不过内容都没有太大的区别,最终都调用父类MultithreadEventLoopGroup的构造器
- NioEventLoopGroup实质是个线程池
- 如果我们传入的线程数nThreads是0,那么Netty会设置默认的线程数DEFAULT_EVENT_LOOP_THREADS(cpu数量*2)
关键点在于MultithreadEventExecutorGroup中维护了一个EventExecutor类型的数组,名字叫chileren。
[外链图片转存中…(img-wR94IdyQ-1596526402365)]
最后总结一下整个EventLoopGroup的初始化过程。
(1)EventLoopGroup(其实是MultithreadEventExecutorGroup)内部维护一个类型为EventExecutor的children数组,其大小是nThreads,这样就构成了一个线程池。
(2)我们在实例化NioEventLoopGroup时,如果指定线程池大小,则nThreads就是指定的值,反之是CPU核数×2。
(3)在MultithreadEventExecutorGroup中调用newChild()象方法来初始化children数组。
(4)newChild()方法是在NioEventLoopGroup中实现的,它返回一个NioEventLoop实例。
(5)初始化NioEventLoop对象并给属性赋值,具体赋值的属性如下。
● provider:就是在NioEventLoopGroup构造器中,调用SelectorProvider.provider()方法获取的SelectorProvider对象。
● selector:就是在NioEventLoop构造器中,调用provider.openSelector()方法获取的Selector对象。
将Channel注册到Selector
当Channel初始化后,紧接着会调用group().register()方法来向Selector注册Channel。继续跟踪的话,会发现其调用链如下图所示。
-
Channel的注册过程所做的工作就是将Channel与对应的EventLoop进行关联。因此,在Netty中,每个Channel都会关联一个特定的EventLoop,并且这个Channel中的所有I/O操作都是在这个EventLoop中执行的
-
当关联好Channel和EventLoop后,会继续调用底层Java NIO的SocketChannel对象的register()方法,将底层Java NIO的SocketChannel注册到指定的Selector中。通过这两步,就完成了Netty对Channel的注册过程。
Handler的添加过程
Netty有一个强大和灵活之处就是基于Pipeline的自定义Handler机制。
客户端发起连接请求
-
客户端通过调用Bootstrap的connect()方法进行连接。在connect()方法中进行一些参数检查,并调用doConnect()方法
-
在doConnect()方法中,eventLoop线程会调用Channel的connect()方法,而这个Channel的具体类型实际就是NioSocketChannel,前面已经分析过。继续跟踪channel.connect()方法,我们发现它调用的是DefaultChannelPipeline的connect()方法
-
找到创建Socket连接的关键代码继续跟踪,其实调用的就是AbstractNioUnsafe的connect()方法。
-
在这个connect()方法中,又调用了doConnect()方法。
[外链图片转存中…(img-giiDC5Ue-1596526402367)]
上面代码的功能是,首先获取Java NIO的SocketChannel,然后获取NioSocketChannel的newSocket()方法返回的SocketChannel对象;再调用SocketChannel的connect()方法完成Java NIO底层的Socket连接。
-
服务端
- 在客户端初始化的时候,我们初始化了一个EventLoopGroup对象,而在服务端的初始化时,我们设置了两个EventLoopGroup:一个是bossGroup,另一个是workerGroup。
- NioServerSocketChannel的默认构造器中,调用父类构造方法时传入的参数是SelectionKey.OP_ACCEPT。作为对比,在客户端的Channel初始化时,传入的参数是SelectionKey.OP_READ。
- childGroup的register()方法就是将workerGroup中的某个EventLoop和NioSocketChannel进行关联
Netty解决JDK空轮询Bug
大家应该早就听说过臭名昭著的Java NIO epoll的Bug,它会导致Selector空轮询,最终导致CPU使用率达到100%。官方声称JDK 1.6的update18修复了该问题,但是直到JDK 1.7该问题仍旧存在,只不过该Bug发生概率降低了一些而已,并没有被根本解决。出现此Bug是因为当Selector轮询结果为空时,没有进行wakeup或对新消息及时进行处理,导致发生了空轮询,CPU使用率达到了100%
- 在Netty中最终的解决办法是:创建一个新的Selector,将可用事件重新注册到新的Selector中来终止空轮询
- 如果每次轮询消耗的时间为0s,且重复次数超过512次,则调用rebuildSelector()方法,即重构Selector
- 实际上,在rebuildSelector()方法中,主要做了以下三件事情。(1)创建一个新的Selector。(2)将原来Selector中注册的事件全部取消。(3)将可用事件重新注册到新的Selector,并激活。
Netty对Selector中KeySet的优化
- Netty对Selector有重构,创建一个新的Selector就会调用openSelector()方法
- 利用反射机制,获取JDK底层的Selector的Class对象,用反射方法从Class对象中获得两个属性:selectedKeys和publicSelectedKeys,这两个属性就是用来存储已注册事件的。然后,将这两个对象重新赋值为Netty创建的SelectedSelectionKeySet
- 在SelectedSelectionKeySet中禁用了remove()方法、contains()方法和iterator()方法,只保留了add()方法,而且底层存储结构用的是数组SelectionKey[] keys。那么,Netty为什么要这样设计呢?主要目的还是简化我们在轮询事件时的操作
Handler的添加过程
跟EventLoopGroup一样,服务端的Handler也有两个:一个是通过handler()方法设置的Handler,另一个是通过childHandler()方法设置的childHandler。通过前面的bossGroup和workerGroup的分析,其实我们可以在这里大胆地猜测:Handler与accept过程有关,即Handler负责处理客户端新连接接入的请求;而childHandler就是负责和客户端连接的I/O交互。
客户端与服务端主要区别
(1)在服务端NioServerSocketChannel对象的Pipeline中添加了Handler对象和ServerBootstrapAcceptor对象。
(2)当有新的客户端连接请求时,会调用ServerBootstrapAcceptor的channelRead()方法创建此连接对应的NioSocketChannel对象,并将childHandler添加到NioSocketChannel对应的Pipeline中,而且将此Channel绑定到workerGroup中的某个EventLoop中。
(3)Handler对象只在accept()阻塞阶段起作用,它主要处理客户端发送过来的连接请求。
(4)childHandler在客户端连接建立以后起作用,它负责客户端连接的I/O交互。
[外链图片转存中…(img-F2ZpN3H2-1596526402367)]
EventLoop
在 Netty 中, 一个 EventLoop 需要负责两个工作, 第一个是作为 IO 线程, 负责相应的 IO 操作;
第二个是作为任务线程, 执行 taskQueue 中的任务
黄金总结
https://blog.youkuaiyun.com/jeffleo/article/details/71616620
总结:
1、一个EventLoopGroup 内部 维护了一个EventLoop数组,EventLoop实际上是一个Executor,大小为实例化时传入或者是默认的处理器核心数*2,Executor中是用来执行Runnable的
2、一个EventLoop中有一个 Selector 属性,SocketChannel会注册到EventLoop的 Selector 中去
3、一个客户端链接,会使用一个EventLoop来去处理
3、注册事件,读事件,写事件都会在ChannelPipeline中传递,从HeadHandler到TailHandler
4、一个NioEventLoop会和一个线程联系,Channel的IO操作,如监听,读,写是在EventLoop中run方法完成的,而run方法内部是通过Unsafe类完成的,而UnSafe类最终又会调用父类AbstractNioByteChannel来处理读写连接的事件,并且会把该事件在pipeline中传递
解释:
(1)设计一个专门的线程Acceptor,用于监听客户端的TCP连接请求。
(2)客户端连接的I/O操作都由一个特定的NIO线程池负责。每个客户端连接都与一个特定的NIO线程绑定,因此在这个客户端连接中的所有I/O操作都是在同一个线程中完成的。
(3)客户端连接有很多,但是NIO线程数是比较少的,因此一个NIO线程可以同时绑定到多个客户端连接中。
三种Reactor的线程模型和NioEventLoopGroup有什么关系呢?其实,不同的设置NioEventLoopGroup的方式对应了不同的Reactor线程模型。
-
单线程模型在Netty中的应用代码如下。
实际上,当传入一个group时,bossGroup和workerGroup就是同一个NioEventLoopGroup
-
多线程模型在Netty中的应用代码如下。
只需要将bossGroup的参数设置为大于1的数,其实就是Reactor多线程模型。
-
主从Reactor多线程模型在Netty中的应用代码如下。
bossGroup为主线程,而workerGroup中的线程数是CPU核数×2,因此对应到Reactor线程模型中,我们知道,这样设置的NioEventLoopGroup其实就是主从Reactor多线程模型。
任务执行者EventLoop
就是干活的
NioEventLoop继承自SingleThreadEventLoop,而SingleThreadEventLoop又继承自SingleThreadEventExecutor。而SingleThreadEventExecutor是Netty对本地线程的抽象,它内部有一个Thread属性,实际上就是存储了一个本地Java线程。因此我们可以简单地认为,一个NioEventLoop对象其实就是和一个特定的线程进行绑定,并且在NioEventLoop生命周期内,其绑定的线程都不会再改变。
通常来说,NioEventLoop负责执行两个任务:
- 第一个任务是作为I/O线程,执行与Channel相关的I/O操作,包括调用Selector等待就绪的I/O事件、读写数据与数据处理等;
- 第二个任务是作为任务队列,执行taskQueue中的任务,例如用户调用eventLoop.schedule提交的定时任务也是由这个线程执行的。
在Netty中,每个Channel都有且仅有一个EventLoop与之关联。当调用AbstractChannel$AbstractUnsafe.register()方法后,就完成了Channel和EventLoop的关联。
EventLoop的启动
NioEventLoop本身就是一个SingleThreadEventExecutor,因此NioEventLoop的启动,其实就是NioEventLoop所绑定的本地Java线程的启动。
ChannelInboundHandler、ChannelOutboundHandler的理解
文档中的图
I/O Request
via Channel or
ChannelHandlerContext
|
+---------------------------------------------------+---------------+
| ChannelPipeline | |
| \|/ |
| +---------------------+ +-----------+----------+ |
| | Inbound Handler N | | Outbound Handler 1 | |
| +----------+----------+ +-----------+----------+ |
| /|\ | |
| | \|/ |
| +----------+----------+ +-----------+----------+ |
| | Inbound Handler N-1 | | Outbound Handler 2 | |
| +----------+----------+ +-----------+----------+ |
| /|\ . |
| . . |
| ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
| [ method call] [method call] |
| . . |
| . \|/ |
| +----------+----------+ +-----------+----------+ |
| | Inbound Handler 2 | | Outbound Handler M-1 | |
| +----------+----------+ +-----------+----------+ |
| /|\ | |
| | \|/ |
| +----------+----------+ +-----------+----------+ |
| | Inbound Handler 1 | | Outbound Handler M | |
| +----------+----------+ +-----------+----------+ |
| /|\ | |
+---------------+-----------------------------------+---------------+
| \|/
+---------------+-----------------------------------+---------------+
| | | |
| [ Socket.read() ] [ Socket.write() ] |
| |
| Netty Internal I/O Threads (Transport Implementation) |
+-------------------------------------------------------------------+
错误的理解
ChannelInboundHandler、ChannelOutboundHandler,这里的inbound和outbound是什么意思呢?inbound对应IO输入,outbound对应IO输出?这是我看到这两个名字时的第一反应,但当我看到ChannelOutboundHandler接口中有read方法时,就开始疑惑了,应该是理解错了,如果outbound对应IO输出,为什么这个接口里会有明显表示IO输入的read方法呢?
正确的理解
直到看到了Stack Overflow上Netty作者Trustin Lee对inbound和outbound的解释,疑团终于解开:
众所周知,Netty是事件驱动的,而事件分为两大类:inboud和outbound,分别由ChannelInboundHandler和ChannelOutboundHandler负责处理。所以,inbound和outbound并非指IO的输入和输出,而是指事件类型。
那么什么样的事件属于inbound,什么样的事件属于outbound呢?也就是说,事件类型的划分依据是什么?
答案是:触发事件的源头。
Inbound
由外部触发的事件是inbound事件。外部是指应用程序之外,因此inbound事件就是并非因为应用程序主动请求做了什么而触发的事件,比如某个socket上有数据读取进来了(注意是“读完了”这个事件,而不是“读取”这个操作),再比如某个socket连接了上来并被注册到了某个EventLoop。
Inbound事件的详细列表:
channelActive / channelInactive
channelRead
channelReadComplete
channelRegistered / channelUnregistered
channelWritabilityChanged
exceptionCaught
userEventTriggered
Outbound
而outbound事件是由应用程序主动请求而触发的事件,可以认为,outbound是指应用程序发起了某个操作。比如向socket写入数据,再比如从socket读取数据(注意是“读取”这个操作请求,而非“读完了”这个事件),这也解释了为什么ChannelOutboundHandler中会有read方法。
Outbound事件列表:
bind
close
connect
deregister
disconnect
flush
read
write
大都是在socket上可以执行的一系列常见操作:绑定地址、建立和关闭连接、IO操作,另外还有Netty定义的一种操作deregister:解除channel与eventloop的绑定关系。
值得注意的时,一旦应用程序发出以上操作请求,ChannelOutboundHandler中对应的方法就会被调用,而不是等到操作完毕之后才被调用,一个handler在处理时甚至可以将请求拦截而不再传递给后续的handler,使得真正的操作并不会被执行。
反射进行堆外内存监控
堆外内存统计字段是 DIRECT_MEMORY_COUNTER,我们可以通过反射拿到这个字段,然后定期 Check 这个值,就可以监控 Netty 堆外内存的增长情况。
项目简介
https://blog.youkuaiyun.com/linuu/category_6362083.html
总结
三个主要模块 注册中心,提供端,消费端
三个主要的类
DefaultRegistryServer
DefaultProvder
DefaultConsumer
里面的逻辑是一样的
把通信部分抽取出来,另外建立了一个nettyremotingbase,且nettyremotingbase分化为nettyremotingserver和nettyremotingclient
各个模块负责自己的部分比较容易,怎样将两者的工作结合.比如register里或provder里都会有nettyremotingserver读数据进入缓冲区,这些数据的处理可能就不是nettyremotingserver做的事了,她只是负责通信部分.
共有的
1.
每个模块都右pocesser,实现了processRequest的这个方法
亮点
异步调用机制
消费端发起调用,等待结果的过程在netty中是异步的,这样会出现问题:没执行完就得到传来的结果了,所以采用添加listerner,在其中触发同步控制channel.writeAndFlush(request).addListener(new ChannelFutureListener() {}
this.responseTable.put(request.getOpaque(), remotingResponse);
//发送请求
channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()){
//如果发送对象成功,则设置成功
remotingResponse.setSendRequestOK(true);
return;
}else{
remotingResponse.setSendRequestOK(false);
}
//如果请求发送直接失败,则默认将其从responseTable这个篮子中移除
responseTable.remove(request.getOpaque());
//失败的异常信息
remotingResponse.setCause(future.cause());
//设置当前请求的返回主体返回体是null(请求失败的情况下,返回的结果肯定是null)
remotingResponse.putResponse(null);
logger.warn("use channel [{}] send msg [{}] failed and failed reason is [{}]",channel,request,future.cause().getMessage());
}
});
接下来等待,阻塞中
RemotingTransporter remotingTransporter = remotingResponse.waitResponse();
监听到调用成功之后,唤醒阻塞
public void putResponse(final RemotingTransporter remotingTransporter){ this.remotingTransporter = remotingTransporter; //接收到对应的消息之后需要countDown this.countDownLatch.countDown(); }
类的设计:
1.比如负责通信的NettyRemotingBase类:里面有最核心的processRemotingResponse方法和processRemotingRequest方法,负责作为client和server接受到远端的数据之后的不同的响应. 由NettyRemotingBase派生出来的NettyRemotingClient和NettyRemotingServer分别作为客户端和服务端.
2.NettyRemotingServer具体业务的线程与传输端解耦,比如在NettyRemotingServer会有registerProcessor()方法,将自己的业务的处理器传给nettyserver和nettyclient,这样两个负责通信的类只负责维持一个处理器和线程池的二元组 和 请求类型的table,收到信息后只需要交给处理器即可,简化,职责清晰.还可以将不同的请求类型号的请求交给对应的处理器,做到了线程隔离.(请求类型好比如SUBCRIBE_RESULT)
怎样在异步的过程之执行同步操作.
netty中的所有操作都是异步的,就会造成问题比如消费端订阅一个服务的时候,从注册中心拿到提供者的地址之后,netty去连接的时候是异步的的,ChannelFuture.channel这边是异步的,也就是说你不知道啥时候能够ChannelFuture.isSuccess()== true,除非在后面增加一个Listener,当operationSuccess的时候才会周知用户,所有的动作初始化完毕了,可以直接调用
在调用DefaultConsumer的subscribeService([服务名])订阅服务的时候
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ddfb8uwY-1597461393001)(http://note.youdao.com/yws/res/48755/WEBRESOURCEead7cc834113685ceb593f42d0090d9b)]
Consumer的设计:为了实现所有功能,实现了三重的继承
Consumer 顶级接口
AbstractDefaultConsumer
重连机制
https://github.com/fengjiachun/Jupiter
1)客户端连接服务端
2)在客户端的的ChannelPipeline中加入一个比较特殊的IdleStateHandler,设置一下客户端的写空闲时间,例如5s
3)当客户端的所有ChannelHandler中4s内没有write事件,则会触发userEventTriggered方法(上文介绍过)
4)我们在客户端的userEventTriggered中对应的触发事件下发送一个心跳包给服务端,检测服务端是否还存活,防止服务端已经宕机,客户端还不知道
5)同样,服务端要对心跳包做出响应,其实给客户端最好的回复就是“不回复”,这样可以服务端的压力,假如有10w个空闲Idle的连接,那么服务端光发送心跳回复,则也是费事的事情,那么怎么才能告诉客户端它还活着呢,其实很简单,因为5s服务端都会收到来自客户端的心跳信息,那么如果10秒内收不到,服务端可以认为客户端挂了,可以close链路
6)加入服务端因为什么因素导致宕机的话,就会关闭所有的链路链接,所以作为客户端要做的事情就是短线重连
IdleStateChecker重写ChannelDuplexHandler
重写一些函数
handlerAdded
channelRegistered
handlerRemoved
channelActive
channelInactive
空闲与心跳
执行一定的逻辑判断是READER_IDLE, WRITER_IDLE, ALL_IDLE;
有空闲,调用ctx.fireUserEventTriggered(evt); 将空闲处理激活,空闲处理handler
netty的服务段的AcceptorIdleStateTrigger在空闲时显示no sign
netty的客户端的ConnectorIdleStateTrigger在空闲时发送心跳包 ctx.writeAndFlush(Heartbeats.heartbeatContent());
重连机制
客户端有重连
ConnectionWatchdog继承了ChannelInboundHandlerAdapter
channelActive中设置重连次数为0
channelInactive判断重连次数是否大于12
调度与定时任务:时间轮调度 hashwheel
timer.newTimeout(任务,定时时间) 任务必须是继承了timeTask并且重写run方法
好处:可以使用一个timer处理所有的定时问题
怎样防止粘包问题
自己写编解码器继承ReplayingDecoder
这个编码器ReplayingDecoder与ByteToMessageDecoder不同,该类可以在接收到所需要长度的字节之后再调用decode方法,而不用一遍又一遍的手动检查流中的字节长度。
自己写编解码器还有一个好处就是可以选择带不带压缩.做了一个粗糙的测试,测试调用时间,带压缩的反而时间长.可能数据太少不划算 压缩使用 Snappy
这个ReplayingDecoder有几个比较关键的方法说明一下
- 构造函数,它的构造函数,传递了一个枚举类型进去,这个是设置state()这个方法的初始值,这样可以进case HEADER_MAGIC的代码分支,首先先校验协议头,是否使我们定义的MAGIC,如果不是的情况下,我们就不进行解码,因为可能此信息与我们规定的不符
- checkpoint的方法作用有两个,一是改变state的值的状态,二是获取到最新的读指针的下标
- 最后一步重新调用checkpoint(State.HEADER_MAGIC),是把state的值重新设置为初始值HEADER_MAGIC,方便下次信息的解析读取
- 在switch case中主要做的事情,就是将byte[]转换成我们需要的RemotingTransporter的对象,且把主体信息的byte[]放到RemotingTransporter的父类ByteHolder中,关于这块的byte[]的序列化则放在具体的业务场景之下
服务端不同服务的管理
因为一个服务消费者的实例和一个服务提供者之间的连接可以有多个,所以放到一个组groups里,添加到组里的任务放到DefaultConsumer
又因为一个服务可能有多个实例,所以放到一个Map<[服务名], [组的list]>里 ConcurrentMap<String, CopyOnWriteArrayList> groups
(CopyOnWriteArrayList特点:写的时候复制一份,效率高,适合并发不大的场合)
一个服务者的实例只有一个地址,对应着一个group.
负载均衡
进行负载均衡的时机见10
默认采用weight,
每个channelgroup都有权重,所以
1.求得权重和
2.随机生成一个0-权重和的数,平均分布
3,从左到右减去权重列表中的元素,小于0就是选择这个channelgroup
限流
- 我们需要在编织服务的时候,用Annotation注明该服务实例的单位时间(分钟)最大的调用次数
- 在服务编织的时候,将其记录好,保存到某个全局变量中去,一个serviceName和一个最大的调用次数是一一对应的
- 当consumer实例端每调用一次,则限流器的次数加1
ServiceFlowControllerManager 负责进行限流控制
持有ServiceFlowController进行具体的分钟轮操作
在服务端调用服务的工作中,每调用一个服务就增加一次当前分钟的调用ProviderRPCController.handlerRPCRequest()中serviceFlowControllerManager.incrementCallCount(serviceName);
注意和服务降级分开,降级是ServiceMeterManager管理的。
持久化
主要三个类:
private String serviceName;
private LoadBalanceStrategy balanceStrategy;
//地址以及是否审核
private List<PersistProviderInfo> providerInfos = new ArrayList<PersistProviderInfo>();
开启定时线程池来处理
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
// 延迟60秒,每隔一段时间将一些服务信息持久化到硬盘上
try {
DefaultRegistry.this.getProviderManager().persistServiceInfo();
} catch (Exception e) {
logger.warn("schedule persist failed [{}]",e.getMessage());
}
}
}, 60, this.registryServerConfig.getPersistTime(), TimeUnit.SECONDS);
}
13 自动降级:每个一分钟查看是否降级了
在服务端处理。不完善的地方。服务端只能判断本实例的服务是否降级。本实例降级了有其他的实例没降级。
负载均衡是在消费端处理的。降级了依然继续调用这个服务,因为对于降级不知情。
注册中心服务变更会主动通知
长连接的实现
设置信道选项option为keep_alive
检测到空闲之后客户端发送心跳包
流程
- 注册中心启动,暴露接口
NettyServerConfig config = new NettyServerConfig();
RegistryServerConfig registryServerConfig = new RegistryServerConfig();
registryServerConfig.setDefaultReviewState(ServiceReviewState.PASS_REVIEW);
//注册中心的端口号
config.setListenPort(18010);
defaultRegistry = new DefaultRegistry(config,registryServerConfig);
defaultRegistry.start();
想发布的服务在打上注解
- 想发布的服务在打上注解
public class HelloSerivceImpl implements HelloSerivce {
@Override
@RPCService(responsibilityName="xiaoy",
serviceName="senRPC.TEST.SAYHELLO",
isVIPService = false,
isSupportDegradeService = true,
degradeServicePath="org.senRPC.example.demo.service.HelloServiceMock",
degradeServiceDesc="默认返回hello")
public String sayHello(String str) {
//真实逻辑可能涉及到查库
return "hello "+ str;
}
}
- 启动发布服务
DefaultProvider defaultProvider = new DefaultProvider(new NettyClientConfig(), new NettyServerConfig());
defaultProvider.registryAddress("127.0.0.1:18010") // 注册中心的地址
.serviceListenPort(8899) // 暴露服务的端口
.publishService(new HelloSerivceImpl(), new ByeServiceImpl()) // 暴露的服务
.start(); // 启动服务
}
-
启动消费服务
-
- 启动
- 获得订阅管理 结果是获得了一个<服务名 : channelgroup的列表>的map,进行负载均衡获得对应的channelgroup
- call
NettyClientConfig registryNettyClientConfig = new NettyClientConfig();
registryNettyClientConfig.setDefaultAddress("127.0.0.1:18010");
NettyClientConfig provideClientConfig = new NettyClientConfig();
ConsumerClient client = new ConsumerClient(registryNettyClientConfig, provideClientConfig, new ConsumerConfig());
client.start();
commonclient = client;
SubscribeManager subscribeManager = client.subscribeService("senRPC.TEST.SAYHELLO");
//响应成功之后才能调用
//是一个内部类subscribeManager
if (!subscribeManager.waitForAvailable(3000l)) {
throw new Exception("no service provider");
}
Object obj = client.call("senRPC.TEST.SAYHELLO", "shine");
if (obj instanceof String) {
System.out.println((String) obj);
}
模块分析
通信端
NettyRemotingBase:负责server和client公共的工作
-
NettyRemotingServer中:添加NettyServerHandler
-
NettyServerHandler里调用父类processMessageReceived的方法
class NettyServerHandler extends SimpleChannelInboundHandler<RemotingTransporter> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, RemotingTransporter msg) throws Exception {
processMessageReceived(ctx, msg);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
processChannelInactive(ctx);
}
}
NettyRemotingBase:processMessageReceived
protected void processMessageReceived(ChannelHandlerContext ctx, RemotingTransporter msg) {
if(logger.isDebugEnabled()){
logger.debug("channel [] received RemotingTransporter is [{}]",ctx.channel(),msg);
}
final RemotingTransporter remotingTransporter = msg;
if (remotingTransporter != null) {
switch (remotingTransporter.getTransporterType()) {
//作为server端 client端的请求的对应的处理
case REQUEST_REMOTING:
processRemotingRequest(ctx, remotingTransporter);
break;
//作为客户端,来自server端的响应的处理
case RESPONSE_REMOTING:
processRemotingResponse(ctx, remotingTransporter);
break;
default:
break;
}
}
}
NettyRemotingBase::processRemotingRequest:
略
这段冗长的代码就是完成的四点需求了,收到服务提供者发布的信息之后就是接受到该信息,从历史记录中恢复该信息的审核记录,负载均衡的策略,然后如果此时这个服务的审核状态是通过的情况下,就请兄弟类ConsumerManager把该服务的信息推送给对应的订阅者,最后发送ack信息给服务提供者,告之它接收信息成功,这段代码整体的流程就是如此
另一种场景就是服务提供者通知发布服务取消的信号的时候,也就是PUBLISH_CANCEL_SERVICE这个信号的时候,要做的事情也是很简单:
1)将记录在java内存中的信息移除掉,其实也就是将全局变量的信息移除掉
2)如果该服务已经是审核通过的,则需要再发送信息给订阅该服务的服务消费者,该服务下线,使其不再调用该服务了
当然还有一种场景就是当服务提供者的实例直接关闭的时候,与注册中心之间保持的netty长连接也会断掉,实例关闭自然就是服务下线,所以我们也要对其作出处理
因为某个服务提供者实例提供的远程服务可能不止一个,所以我们要做的就是将这些服务全部做下线处理,因为我们之前已经将建立的channel上打上了tag,所以我们很容易知道某个服务实例上到底提供了多少个远程服务
服务端
基本构成与流程
public DefaultProvider() {
this.clientConfig = new NettyClientConfig();
this.serverConfig = new NettyServerConfig();
//providerRegistryController 与 defaultProvider(this)互相持有
providerRegistryController = new ProviderRegistryController(this);
providerRPCController = new ProviderRPCController(this);
initialize();
}
public DefaultProvider(NettyClientConfig clientConfig, NettyServerConfig serverConfig) {
this.clientConfig = clientConfig;
this.serverConfig = serverConfig;
providerRegistryController = new ProviderRegistryController(this);
providerRPCController = new ProviderRPCController(this);
initialize();
}
DefaultProvider
handlerRPCRequest
providerRPCController.handlerRPCRequest(request, channel);
具体执行调用的逻辑是在ProviderRPCController.pocess()反射调用
完整流程
1)Provider端与我们传统的Service层一样,有自己的处理的逻辑,有自己的接口,有对这个接口具体的实现,所以一个Provider端最基本的功能就是提供一个稳定的方法调用
2)升级版的服务提供,需要向注册中心提供你提供服务的一些基本信息,所以在提供服务之前,你需要将你的服务进行编织,例如你的服务名是什么,你在哪个端口号提供服务等等,这些信息你可以编织成一个信息类,将这个信息类发送给注册中心,注册中心只要有这些信息就够了,注册中心拿着这些信息给服务订阅者就完成了所有的工作,不要在乎服务提供者是怎么实现的,所以Provider端第二重要的部分是服务的编织**(需要持有一个netty消费者)**
3)当你服务编织好的时候,就需要将信息发送给注册中心,这边网络模块可以实现,我们不深究细节,不过在发送编织信息的同时,Provider需要做的事情就是自己启动一个Netty Server的实例,并在规定好服务端口进行监听,等待服务消费者的链接,进行服务消费,否则Provider发送了注册信息,却没有按照规定在某个端口上监听,就不科学了,你不是在逗人家吗?(需要持有一个netty生产者)
4)最最简单的Provider实现上述3点就完成了基本功能了,不过功能健壮的Provider不仅仅如此,还要实现服务的治理
①提供调用次数的统计,失败次数的统计,方便直接的查看服务的状态
②提供服务降级的功能,假如provider端提供的服务业务逻辑很复杂,数据库的操作比较多,甚至可能也远程调用其他的服务,当业务洪流来的时候,可能会压垮数据库,和其他的系统,所以我们需要提供服务降级的功能,所谓降级,网上的资料很多,简而言之,就是简化提供的功能,举几个简单的例子而言,对于库存系统而言,可以默认返回0,对于推荐系统,我不做大数据的分析了,默认返回几个卖的最好的商品,对于定价系统而言,默认拿取商品成本价*1.01或者某个固定的数值,而不是去数据库查询该产品的定价率,然后再远程调用促销系统,查看该商品是否有促销活动,说白了,就是大大简化逻辑操作,提供默认的返回值,防止大促来的时候,压垮系统
③熔断机制,熔断机制,其实很好理解,就像我们家的保险丝,当电流大于某个值的时候,自动熔断保险丝,拒绝提供某种服务,这种做法看上去很Lower,其实不然,比如说,大促的时候,为了保证一些核心功能,比如下单,详细页面,秒杀页面的正常运行,可以让一些其他的辅佐功能,锦上添花的功能做出牺牲,比如推荐系统,好友系统等,暂时都关闭,或者不进行调用,本地给出默认值,减少本地服务器的压力和流量压力
④限流,限制流量的方式有很多种,具体实现后面详细说明,我们这边只是说限流的重要性,如果不限流,比如秒杀系统,某个时间点,一下剧增的流量会一下冲垮服务器,所以每个服务提供者要有自我保护方法,最简单的方式就是限制一分钟内,最多调用10000次,次数跟你服务器的性能和你服务的复杂性有关(统计每一分钟的调用次数,定时任务)
⑤VIP服务,这个功能还是从RocketMQ源码看到的,其实算有点鸡肋吧,不过也还好,所谓VIP服务,就是该服务在另一个端口监听,提供服务,这样做的好处就是与其他的服务隔离开,不公用同一个网络长连接,提供该服务的稳定性和质量
提供提供者端需要提供以下接口
* 1)需要暴露哪些服务【必要】{@link Provider #publishService(Object…)}
* 2)暴露的服务在哪个端口上提供【必要】{@link Provider #serviceListenAddress(String)}
* 3)设置注册中心的地址【必要】{@link Provider #registryAddress(String)}
* 4)暴露启动服务提供者的方法【必须调用】{@link Provider #start()}
* 5)设置provider端提供的监控地址【非必要】{@link Provider #monitorAddress(String)}
DefaultProvder 详解
1)它需要持有Client去连接注册中心和监控中心,这个有一个NettyClient就可以了,毕竟对这边的性能要求不是很高
2)需要有两个NettyServer,都是等待消息消费者的连接,一个做普通服务用,另一个做VIP服务用,做个约定默认的VIP提供的端口是普通端口-2
- 再说ProviderRegistryController和ProviderRPCController,因为我们知道Provider的主要功能就是分2块,一个是注册服务,一个是提供服务,所以将这两块代码写在单独的两个模块里,方便代码的管理和业务模块化,不至于代码很乱很脏
4)两个ExcutorService线程执行器,可能后面需要优化
5)publishRemotingTransporters这个就是需要发送给注册中心的编织好的服务类
6)// globalPublishService是一个Map,放在这边方便给监控中心和其他功能的使用
7)registryAddress注册中心的地址,需要用户提供(是已知的)
8)exposePort本地暴露的端口
9)// 监控中心的地址,也需要用户提供,当然不是一定需要的
10)要提供的服务obj,这是强依赖的,这太重要了,没他就没得玩了~(服务是)
11)ProvierStateIsHealthy 服务提供者的状态信息,因为假如注册中心宕机重启之后,可能之前注册的信息就丢失了,所以需要重新注册,这时候的服务状态就是不健康的
12)scheduledExecutorService 定时任务执行器,做一些定时校验的活动和操作。比如定时检查监控中心的是否健康,定时发送一些统计的数据给监控中心,定时重发那些发给注册中心失败的注册信息
服务端工作工作可以分为三部分:
服务的调用
服务的编织
服务的限流
远程调用的几个参数:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jX70MX0u-1597461393006)(http://note.youdao.com/yws/res/50255/WEBRESOURCEe5511d2840217d378d97c9e7a80fdd36)]
首先先简单地说明这几个参数
1)invokeId,虽然在我这个RPC中并没有实现多大的价值,但这是我的能力的原因,但这个参数却是不可或缺的,因为有过远程调用使用经历的人,在线上出问题的时候,最最痛苦的就是定位问题了,而一般企业的业务都是链式调用,A系统调用B,B调用C,C调用D,E,往往排查问题的时候,某某接口调用不同的时候,都不知道哪里出问题了,需要一步步地找相关的负责人一一确认,这个过程是很痛苦的,很有可能是跨部门的,不好协调,一个问题一个bug能需要半天的时间才能解决,这就是普通RPC所缺少的链路监控的功能,加入在这种链式调用的场景下,所有的调用日记,能够按照这个invokeId做归类的话,不管是用ElasticSearch还是Hbase,Mysql等等记录方法,加上索引,有个监控系统,这样就可以很简单的找出问题的所在,是哪个环节出了问题了,这样可以大大的加快排查问题的速度,很多S级互联网公司都实现了这个功能,本人暂时没有研究过,不过现在已经有很多开源了,大家可以自主调研,一起学习
2)serviceName,这个很好理解,在上几个小节我们说过自定义Annotation绑定了某个具体的方法,所以一个serviceName是绑定一个方法的,获取到serviceName,我们可以确定唯一的方法,因为远程调用的本质还是调用某个方法
3)args,这个不用多说,调用方法的入参
4)timestamp调用的时间戳,这个时间应该在调用端的时候就形成了,一个远程调用的时间统计应该是从请求发出和接收到响应,这个时间应该算是一个完整的调用流程
服务的编织和注册
首先先进行服务的编织,将一个服务的一些基本信息编织成一个类,发送给注册中心,订阅者在注册中心取到的编织信息就可以调用该方法,这是整体的思路,我们在网络篇说过,所有的数据传输走RemotingTransporter,核心的传输主体要实现CommonCustomBody接口,接下来,我们就定义Provider发送给注册的类:
编织的服务信息基本信息应该有
1)服务的IP地址
2)端口号
3)服务名,这个应该是唯一的
4)是否是VIP服务,如果是VIP服务则当启动NettyServer需要在port-2的端口上监听
5)是否支持降级
6)降级服务的方法路径,这边降级做的比较简单,其实就是一个mock方法
7)降级服务的基本描述(其实并不是必要的,不过却可以用来做统计)
8)服务的权重,这个是很必要的,应该每个系统的线上实例肯定不止一台,而每一台实例的性能也不一样,有些服务器的性能好一点,内存大一点,可以设置大一点,最大100,最小1,这样在服务端调用该实例的时候,默认是使用加权随机负载均衡的算法,去随机访问服务提供端的
9)连接数,该连接数表示的是一个Consumer实例与一个Provider实例之间连接数,一般情况下,一个连接就够用了,特殊情况下,可以设置多个链接
注册中心
注册中心的职责是很明确的,简而言之:
1)服务提供者向其发送它提供的服务的一些基本信息
2)服务消费者来订阅服务
3)服务提供者实例下线的时候,实时通知服务消费者某个服务下线
当然还有一些服务治理的功能,比如:
1)服务审核,这是服务治理最最简单的操作了,因为某个服务提供者上线之后,都是需要审核的,如果不审核,可能会造成很多不必要的麻烦,有可能有些开发小新,不小心把开发环境的服务向线上服务注册,如果不审核,直接通过的话,就会造成线上接口调用线下服务的尴尬局面
2)负载策略的记录,比如默认是随机加权策略,如果管理者希望改成加权轮询的策略,需要通知服务消费者,访问策略的改变
3)手动改变某个服务的访问权重,比如某个服务默认负重是50,(最大100)的时候,但是此时这个服务实例所在的机器压力不大的时候,而其他该服务实例压力很大的时候,可以适当的增加该服务的访问权重,但是又不想让该服务下线,修改它的权重,所以我们可以在注册中心修改它的负重,然后通知服务消费者,这样就可以动态的修改负重了
4)一些持久化的操作,因为注册中心是无状态的,假如某个注册中心实例重启之后,以前的一些审核信息,修改的访问策略信息就会消失,这样就会需要用户重新一一审核,这是很麻烦的,所以需要将这些信息落地,持久化到硬盘,然后每次重启注册中心实例的时候,去读取这些信息
和其他两个模块一样:持有nettyserver,传递自己的处理器,处理不同的业务
@Override
public RemotingTransporter processRequest(ChannelHandlerContext ctx, RemotingTransporter request) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("receive request, {} {} {}",//
request.getCode(), //
ConnectionUtils.parseChannelRemoteAddr(ctx.channel()), //
request);
}
switch (request.getCode()) {
case PUBLISH_SERVICE: // 处理服务提供者provider推送的服务信息
return this.defaultRegistry.getProviderManager().handlerRegister(request, ctx.channel()); // 要保持幂等性,同一个实例重复发布同一个服务的时候对于注册中心来说是无影响的
case PUBLISH_CANCEL_SERVICE: // 处理服务提供者provider推送的服务取消的信息
return this.defaultRegistry.getProviderManager().handlerRegisterCancel(request, ctx.channel());
case SUBSCRIBE_SERVICE: // 处理服务消费者consumer订阅服务的请求
return this.defaultRegistry.getProviderManager().handleSubscribe(request, ctx.channel());
case MANAGER_SERVICE: // 处理管理者发送过来的服务管理服务
return this.defaultRegistry.getProviderManager().handleManager(request, ctx.channel());
}
return null;
}
DefaultRegistryServer
** #######注册中心######*
*** 可以有多个注册中心,所有的注册中心之前不进行通讯,都是无状态的
** 1)provider端与每一个注册中心之间保持长连接,保持重连*
** 2)consumer**随机选择一个注册中心保持长连接,如果断了,不去主动重连,选择其他可用的注册中心*
*** @author senrian
*** @description 默认的注册中心,处理注册端的所有事宜:
** 1)处理consumer**端发送过来的注册信息*
** 2)处理provider**端发送过来的订阅信息*
** 3)当服务下线需要通知对应的consumer**变更后的注册信息*
** 4)*所有的注册订阅信息的储存和健康检查
** 5)*接收管理者的一些信息请求,比如 服务统计 | 某个实例的服务降级 | 通知消费者的访问策略 | 改变某个服务实例的比重
** 6)*将管理者对服务的一些信息 例如审核结果,负载算法等信息持久化到硬盘
这是默认的注册中心,维护了一个NettyServer的客户端,维护了一个服务消费者的管理者ConsumerManager,维护了一个服务提供者的管理者ProviderManager,还有几个线程执行器,每个类各司其职,当然他们之间还有相互协作的过程。从整体上来看,注册中心的整体结构就是这样,下面给出
RegistryConsumerManager
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q5wLXOrp-1597461393008)(http://note.youdao.com/yws/res/48683/WEBRESOURCE3755ae8c54a70b9c7ea3bdf298fb47d0)]
ProviderManager
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aJzl37ZI-1597461393009)(http://note.youdao.com/yws/res/48685/WEBRESOURCE6a39eaa6e08b7285b926cafe00227598)]
handlerRegister()
1)将服务提供者发布的信息记录到注册中心的本地内存中去,相当于把发布的信息记录下来,否则服务消费者来订阅服务的时候,去哪里查找这些记录
2)从历史记录中找到其历史的审核记录,如果历史记录上,也就是说硬盘持久化中记录到的审核记录,如果以前显示是审核通过的情况下,就直接设置该服务审核通过,无需再审核,如果在历史记录中没有找到该服务的信息,说明该服务是第一次注册发布,我们需要给出默认的服务持久化信息
3)如果该服务已经审核通过,我们需要通知通知订阅该服务的消费者新增了一个服务提供者
4)当上述三件事情都搞定了,我们需要发送一个ACK信息给服务提供者,告之它注册中心已经成功接收到它发布的信息了
消费端
1)订阅功能,服务消费者需要发送它需要的服务发送给注册中心,然后注册中心发送服务信息返回给服务消费者
2)当服务消费者从注册中心获取到服务信息的时候,需要拿着这些信息例如IP+HOST等信息,与服务提供者建立长连接
3)当系统开始进行远程调用某个服务的时候,如果提供该服务的不止一个实例,此时就需要根据负载均衡策略,选择一个服务实例去调用
4)调用方式的方式,远程调用的最高境界就是用户不知道是远程调用,面向接口编程,所以我们对接口进行编织封装
订阅问题:Netty建立连接是异步,可能没有连接成功就直接返回
并发考虑:
创建一个client
调用一个服务
整体流程:
- 首先与注册中心建立netty连接 client.start();
- client里面的subcribeService调用的是ConsumerManager里面的subcribeService, 获得服务名字对应的channel,并不直接发送,而是获得连接,获得连接后将连接名字与group放到一个map里,再将servicename和信道组放到另一个的
- 然后NettyRemotingBase调用handlerSubcribeResult,等待返回结果,获得注册元数据,进行服务调用. processRequest等待调用 handlerSubcribeResult
NettyClientConfig registryNettyClientConfig = new NettyClientConfig();
registryNettyClientConfig.setDefaultAddress("127.0.0.1:18010");
NettyClientConfig provideClientConfig = new NettyClientConfig();
ConsumerClient client = new ConsumerClient(registryNettyClientConfig, provideClientConfig, new ConsumerConfig());
client.start();
commonclient = client;
SubscribeManager subscribeManager = client.subscribeService("senRPC.TEST.SAYHELLO");
//响应成功之后才能调用
//是一个内部类subscribeManager,调用下面的方法会阻塞
if (!subscribeManager.waitForAvailable(3000l)) {
throw new Exception("no service provider");
}
Object obj = client.call("senRPC.TEST.SAYHELLO", "shine");
if (obj instanceof String) {
System.out.println((String) obj);
}
consumerManager的属性:
public class ConsumerManager {
//以下服务都是
private static final Logger logger = LoggerFactory.getLogger(ConsumerManager.class);
private DefaultConsumer defaultConsumer; //ConsumerManager的代码手持defaultConsumer好办事
private final ReentrantReadWriteLock registriesLock = new ReentrantReadWriteLock();
//为防止并发问题使用concurenthashmap
private final Map<String, List<RegisterMeta>> registries = new ConcurrentHashMap<String, List<RegisterMeta>>();
//服务名和listerner的map
private ConcurrentHashMap<String, NotifyListener> serviceMatchedNotifyListener = new ConcurrentHashMap<String, NotifyListener>();
private long timeout;
消费端对于服务的信息的维护
public abstract class AbstractDefaultConsumer implements Consumer {
/******key是服务名,Value该服务对应的提供的channel的信息集合************/
private volatile static ConcurrentMap<String, CopyOnWriteArrayList<ChannelGroup>> groups = new ConcurrentHashMap<String, CopyOnWriteArrayList<ChannelGroup>>();
/***********某个服务提供者的地址对应的channelGroup*************/
protected final ConcurrentMap<UnresolvedAddress, ChannelGroup> addressGroups = new ConcurrentHashMap<UnresolvedAddress, ChannelGroup>();
/*********************某个服务对应的负载均衡的策略***************/
protected final ConcurrentHashMap<String, LoadBalanceStrategy> loadConcurrentHashMap = new ConcurrentHashMap<String, LoadBalanceStrategy>();
问题:
每次调用都需要去注册中心拉取吗.服务下线了怎么办?注册中心来通知吗?
解答:
可以试着从getOrUpdateHealthyChannel这个方法中找答案,消费者维持着与注册中心的一个channel,每次得到或更新健康的通道
流程详解
不过虽然消费者很牛,不过它自己要干的事情也是比较复杂的,做的活一点也不轻松,我们现在稍微列举一下服务消费者需要完成的功能
1)订阅功能,服务消费者需要发送它需要的服务发送给注册中心,然后注册中心发送服务信息返回给服务消费者
2)当服务消费者从注册中心获取到服务信息的时候,需要拿着这些信息例如IP+HOST等信息,与服务提供者建立长连接
3)当系统开始进行远程调用某个服务的时候,如果提供该服务的不止一个实例,此时就需要根据负载均衡策略,选择一个服务实例去调用
4)调用方式的方式,远程调用的最高境界就是用户不知道是远程调用,面向接口编程,所以我们对接口进行编织封装
订阅
主要看一下SubscribeManagerImpl的start()方法
先start(),然后获得subscribeManager
ConsumerClient client = new ConsumerClient(registryNettyClientConfig, provideClientConfig, new ConsumerConfig());
//start()主要的工作:将两个nettyclient开启.一个与注册中心相连,一个与服务端相连.
client.start();
commonclient = client;
SubscribeManager subscribeManager = client.subscribeService("senRPC.TEST.SAYHELLO");
subscribeManager的核心工作就是获取的信息之后,建立连接,因为连接是异步的,所以加了通知机制,建立成功之后,将健康的channel放入到group中去,然后就维护了所有的channel
subscribeManager关键的几点
一个服务消费者的实例和一个服务提供者之间的连接可以有多个,所以放到一个组里,又因为一个服务可能有多个实例,所以放到一个list里
一个服务者的实例只有一个地址,对应着一个group.
*2.*NotifyListener
*** @description 当consumer从register注册中心获取到订阅信息之后返回的结果集,这边之所以做的相对复杂的原因就是因为
** 从注册中心拿到提供者的地址之后,netty去连接的时候是异步的的,ChannelFuture.channel这边是异步的,也就是说你不知道啥时候能够*
** ChannelFuture.isSuccess()== true,除非在后面增加一个Listener,当operationSuccess的时候才会周知用户,所有的动作初始化完毕了,可以直接调用*
启动
NettyServerConfig config = new NettyServerConfig();
RegistryServerConfig registryServerConfig = new RegistryServerConfig();
registryServerConfig.setDefaultLoadBalanceStrategy(LoadBalanceStrategy.RANDOM);
registryServerConfig.setDefaultReviewState(ServiceReviewState.PASS_REVIEW);
//注册中心的端口号
config.setListenPort(18010);
defaultRegistryServer = new DefaultRegistryServer(config,registryServerConfig);
defaultRegistryServer.start();
2.DefaultRegistryServer
//创建nettyserver
3.NettyRemotingServer
public NettyRemotingServer(NettyServerConfig nettyServerConfig) {
this.nettyServerConfig = nettyServerConfig;
if(null != nettyServerConfig){
workerNum = nettyServerConfig.getServerWorkerThreads();
writeBufferLowWaterMark = nettyServerConfig.getWriteBufferLowWaterMark();
writeBufferHighWaterMark = nettyServerConfig.getWriteBufferHighWaterMark();
}
//默认公共线程池
this.publicExecutor = Executors.newFixedThreadPool(4, new ThreadFactory() {
private AtomicInteger threadIndex = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "NettyServerPublicExecutor_" + this.threadIndex.incrementAndGet());
}
});
init();
}
init();
高水位和低水位的意思
@Override
public void init() {
ThreadFactory bossFactory = new DefaultThreadFactory("netty.boss");
ThreadFactory workerFactory = new DefaultThreadFactory("netty.worker");
//根据系统创建不同的evenloop
boss = initEventLoopGroup(1, bossFactory);
if(workerNum <= 0){
workerNum = Runtime.getRuntime().availableProcessors() << 1;
}
worker = initEventLoopGroup(workerNum, workerFactory);
serverBootstrap = new ServerBootstrap().group(boss, worker);
allocator = new PooledByteBufAllocator(PlatformDependent.directBufferPreferred());
serverBootstrap.childOption(ChannelOption.ALLOCATOR, allocator)
.childOption(ChannelOption.MESSAGE_SIZE_ESTIMATOR, DefaultMessageSizeEstimator.DEFAULT);
if (boss instanceof EpollEventLoopGroup) {
((EpollEventLoopGroup) boss).setIoRatio(100);
} else if (boss instanceof NioEventLoopGroup) {
((NioEventLoopGroup) boss).setIoRatio(100);
}
if (worker instanceof EpollEventLoopGroup) {
((EpollEventLoopGroup) worker).setIoRatio(100);
} else if (worker instanceof NioEventLoopGroup) {
((NioEventLoopGroup) worker).setIoRatio(100);
}
serverBootstrap.option(ChannelOption.SO_BACKLOG, 32768);
serverBootstrap.option(ChannelOption.SO_REUSEADDR, true);
// child options
serverBootstrap.childOption(ChannelOption.SO_REUSEADDR, true)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.ALLOW_HALF_CLOSURE, false);
if (writeBufferLowWaterMark >= 0 && writeBufferHighWaterMark > 0) {
WriteBufferWaterMark waterMark = new WriteBufferWaterMark(writeBufferLowWaterMark, writeBufferHighWaterMark);
serverBootstrap.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, waterMark);
}
}
注册中心的DefaultRegistryServer和DefaultProvderServer都是类似的
DefaultRegistryServer 有两个定时任务
数据结构
- groups :
ConcurrentHashMap<String, CopyOnWriteArrayList<ChannelGroup>>
因为一个服务消费者的实例和一个服务提供者之间的连接可以有多个,所以放到一个组里,又因为一个服务可能有多个实例,所以放到一个list里
服务名以及提供者的信道组合 - addressGroups:地址以及提供者的信道组合
ConcurrentHashMap<UnresolvedAddress, ChannelGroup>()
private volatile static ConcurrentMap<String, CopyOnWriteArrayList<ChannelGroup>> groups = new ConcurrentHashMap<String, CopyOnWriteArrayList<ChannelGroup>>();
/***********某个服务提供者的地址对应的channelGroup*************/
protected final ConcurrentMap<UnresolvedAddress, ChannelGroup> addressGroups = new ConcurrentHashMap<UnresolvedAddress, ChannelGroup>();
registerMeta:注册的数据
主要代码
注册中心模块
package org.senRPC.base.registry;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.senRPC.base.registry.model.RegistryPersistRecord;
import org.senRPC.common.utils.NamedThreadFactory;
import org.senRPC.common.utils.PersistUtils;
import org.senRPC.registry.RegistryServer;
import org.senRPC.remoting.netty.NettyRemotingServer;
import org.senRPC.remoting.config.NettyServerConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
/**
* #######注册中心######
* @author senrian
* @description 默认的注册中心,处理注册端的所有事宜:
* 1)处理consumer端发送过来的注册信息
* 2)处理provider端发送过来的订阅信息
* 3)当服务下线需要通知对应的consumer变更后的注册信息
* 4)所有的注册订阅信息的储存和健康检查
* 5)接收管理者的一些信息请求,比如 服务统计 | 某个实例的服务降级 | 通知消费者的访问策略 | 改变某个服务实例的比重
* 6)将管理者对服务的一些信息 例如审核结果,负载算法等信息持久化到硬盘
* @time 2020年2月15日
* @modifytime
*/
public class DefaultRegistry implements RegistryServer {
private static final Logger logger = LoggerFactory.getLogger(DefaultRegistry.class);
private final NettyServerConfig nettyServerConfig; //netty Server的一些配置文件
private RegistryServerConfig registryServerConfig; //注册中心的配置文件
private NettyRemotingServer remotingServer; //注册中心的netty server端
private RegistryConsumerManager consumerManager; //注册中心消费侧的管理逻辑控制类
private RegistryProviderManager providerManager; //注册中心服务提供者的管理逻辑控制类
private ExecutorService remotingExecutor; //执行器
private ExecutorService remotingChannelInactiveExecutor; //channel inactive的线程执行器
//定时任务
private final ScheduledExecutorService scheduledExecutorService = Executors
.newSingleThreadScheduledExecutor(new NamedThreadFactory("registry-timer"));
/**
*
* @param nettyServerConfig 注册中心的netty的配置文件 至少需要配置listenPort
* @param nettyClientConfig 注册中心连接Monitor端的netty配置文件,至少需要配置defaultAddress值 这边monitor是单实例,所以address一个就好
*/
public DefaultRegistry(NettyServerConfig nettyServerConfig, RegistryServerConfig registryServerConfig) {
this.nettyServerConfig = nettyServerConfig;
this.registryServerConfig = registryServerConfig;
consumerManager = new RegistryConsumerManager(this);
providerManager = new RegistryProviderManager(this);
//不需要传参的初始化
initialize();
}
private void initialize() {
this.remotingServer = new NettyRemotingServer(this.nettyServerConfig);
this.remotingExecutor =
Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new NamedThreadFactory("RegistryCenterExecutorThread_"));
this.remotingChannelInactiveExecutor =
Executors.newFixedThreadPool(nettyServerConfig.getChannelInactiveHandlerThreads(), new NamedThreadFactory("RegistryCenterChannelInActiveExecutorThread_"));
//注册处理器
this.registerProcessor();
//从硬盘上恢复一些服务的信息
this.recoverServiceInfoFromDisk();
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
// 延迟60秒,每隔60秒开始 定时向consumer发送消费者消费失败的信息
try {
DefaultRegistry.this.getConsumerManager().checkSendFailedMessage();
} catch (Exception e) {
logger.warn("schedule publish failed [{}]",e.getMessage());
}
}
}, 60, 60, TimeUnit.SECONDS);
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
// 延迟60秒,每隔一段时间将一些服务信息持久化到硬盘上
try {
DefaultRegistry.this.getProviderManager().persistServiceInfo();
} catch (Exception e) {
logger.warn("schedule persist failed [{}]",e.getMessage());
}
}
}, 60, this.registryServerConfig.getPersistTime(), TimeUnit.SECONDS);
}
服务端
发布服务
public void publishedAndStartProvider() throws InterruptedException, RemotingException {
List<RemotingTransporter> transporters = defaultProvider.getPublishRemotingTransporters();
if(null == transporters || transporters.isEmpty()){
logger.warn("service is empty please call DefaultProvider #publishService method");
return;
}
//注册中心地址
String address = defaultProvider.getRegistryAddress();
if (address == null) {
logger.warn("registry center address is empty please check your address");
return;
}
String[] addresses = address.split(",");
if (null != addresses && addresses.length > 0 ) {
for (String eachAddress : addresses) {
for (RemotingTransporter msgToPublish : transporters) {
pushPublishServiceToRegistry(msgToPublish,eachAddress);
}
}
}
}
接受调用,执行服务的方法
//现在已经拿到了方法名,入参,接下来就是调用一些反射的API,就可以完成了方法的调用了:
private void process(Pair<CurrentServiceState, ServiceWrapper> pair, final RemotingTransporter request, Channel channel,final String serviceName,final long beginTime) {
Object invokeResult = null;
CurrentServiceState currentServiceState = pair.getKey();
ServiceWrapper serviceWrapper = pair.getValue();
//服务端目标方法。
Object targetCallObj = serviceWrapper.getServiceProvider();
Object[] args = ((RequestCustomBody)request.getCustomHeader()).getArgs();
//判断服务是否已经被设定为自动降级,如果被设置为自动降级且有它自己的mock类的话,则将targetCallObj切换到mock方法上来
if(currentServiceState.getHasDegrade().get() && serviceWrapper.getMockDegradeServiceProvider() != null){
targetCallObj = serviceWrapper.getMockDegradeServiceProvider();
}
//方法名
String methodName = serviceWrapper.getMethodName();
//参数类型
List<Class<?>[]> parameterTypesList = serviceWrapper.getParamters();
Class<?>[] parameterTypes = findMatchingParameterTypes(parameterTypesList, args);
//方法体,
invokeResult = fastInvoke(targetCallObj, methodName, parameterTypes, args);
ResultWrapper result = new ResultWrapper();
result.setResult(invokeResult);
ResponseCustomBody body = new ResponseCustomBody(Status.OK.value(), result);
final RemotingTransporter response = RemotingTransporter.createResponseTransporter(senRPCProtocol.RPC_RESPONSE, body, request.getOpaque());
channel.writeAndFlush(response).addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
long elapsed = SystemClock.millisClock().now() - beginTime;
if (future.isSuccess()) {
//累加某个服务的调用时间
ServiceMeterManager.incrementTotalTime(serviceName, elapsed);
} else {
logger.info("request {} get failed response {}", request, response);
}
}
});
}
消费端
调用的test
public static void main(String[] args) throws Throwable {
NettyClientConfig registryNettyClientConfig = new NettyClientConfig();
registryNettyClientConfig.setDefaultAddress("127.0.0.1:18010");
NettyClientConfig provideClientConfig = new NettyClientConfig();
ConsumerClient client = new ConsumerClient(registryNettyClientConfig, provideClientConfig, new ConsumerConfig());
client.start();
commonclient = client;
SubscribeManager subscribeManager = client.subscribeService("senRPC.TEST.SAYHELLO");
//响应成功之后才能调用
//waitForAvailable是阻塞方法
if (!subscribeManager.waitForAvailable(3000l)) {
throw new Exception("no service provider");
}
Object obj = client.call("senRPC.TEST.SAYHELLO", "shine");
if (obj instanceof String) {
System.out.println((String) obj);
}
SubscribeManager subscribeManager2 = client.subscribeService("senRPC.TEST.SAYBYE");
if (!subscribeManager2.waitForAvailable(3000l)) {
throw new Exception("no service provider");
}
进行订阅
package org.senRPC.client.consumer;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import org.senRPC.common.rpc.RegisterMeta;
import org.senRPC.common.utils.ChannelGroup;
import org.senRPC.common.utils.JUnsafe;
import org.senRPC.common.utils.UnresolvedAddress;
import org.senRPC.remoting.ConnectionUtils;
import org.senRPC.remoting.netty.NettyRemotingClient;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
public class SubscribeManagerImpl implements Consumer.SubscribeManager {
private static final Logger logger = LoggerFactory.getLogger(SubscribeManagerImpl.class);
private final ReentrantLock lock = new ReentrantLock();
private final Condition notifyCondition = lock.newCondition();
private final AtomicBoolean signalNeeded = new AtomicBoolean(false);
private final String service;
private final ConsumerManager consumerManager;
// 持有defaultConsumer对象好办事
private final DefaultConsumer defaultConsumer;
public SubscribeManagerImpl(String service, ConsumerManager consumerManager, DefaultConsumer defaultConsumer) {
this.service = service;
this.consumerManager = consumerManager;
this.defaultConsumer = defaultConsumer;
}
@Override
public void startSubcribe() {
if (service != null) {
// 以下主要工作:将service与对应的Listener结合
// 并建立与生产者的
this.consumerManager.subcribeService(service,new NotifyListener(){
@Override
public void notify(RegisterMeta registerMeta, NotifyEvent event) {
// host
String remoteHost = registerMeta.getAddress().getHost();
// port vip服务 port端口号-2
int remotePort = registerMeta.isVIPService() ? (registerMeta.getAddress().getPort() - 2) : registerMeta.getAddress().getPort();
//因为一个服务消费者的实例和一个服务提供者之间的连接可以有多个,所以放到一个组里,又因为一个服务可能有多个实例,所以放到一个list里
//一个服务者的实例只有一个地址,对应着一个group.
final ChannelGroup group = defaultConsumer.group(new UnresolvedAddress(remoteHost, remotePort));
//增加信道
if (event == NotifyEvent.CHILD_ADDED) {
// 链路复用,如果此host和port对应的链接的channelGroup是已经存在的,则无需建立新的链接,只需要将此group与service建立关系即可
if (!group.isAvailable()) {
// 消费者获得了注册信息元数据,里有连接的个数
int connCount = registerMeta.getConnCount() < 0 ? 1 : registerMeta.getConnCount();
group.setWeight(registerMeta.getWeight());
//把所有服务提供这放到组里
for (int i = 0; i < connCount; i++) {
try {
// 所有的consumer与provider之间的链接不进行短线重连操作
//类名.this一般在内部类中使用
NettyRemotingClient nettyRemotingClient = defaultConsumer.getProviderNettyRemotingClient();
nettyRemotingClient.setreconnect(false);
nettyRemotingClient.getBootstrap()
//connect返回一个ChannelFuture
.connect(ConnectionUtils.string2SocketAddress(remoteHost + ":" + remotePort)).addListener(new ChannelFutureListener() {
// 因为在Netty中操作是异步的,ch.writeAndFlush(message)也是异步的,该方法只是把发
// 送消息加入了任务队列,这时直接关闭连接会导致问题。所以我们需要在消息发送完毕后在去关闭连接。
@Override
//进行连接,为连接的过程,只有成功了将信道的信道加到消费者的
public void operationComplete(ChannelFuture future) throws Exception {
//成功之后将channel加入组中
group.add(future.channel());
//唤醒所有等待的线程 .多个线程只能唤醒一次,保证不重复性.即: 一个channel和注册中心的连接的channel只能唤醒一次.
// getAndSet 以原子方式设置为给定值,并返回以前的值。
onSucceed(signalNeeded.getAndSet(false));
}
});
} catch (Exception e) {
logger.error("connection provider host [{}] and port [{}] occor exception [{}]", remoteHost, remotePort, e.getMessage());
}
}
}else{
//唤醒所有等待的线程,
onSucceed(signalNeeded.getAndSet(false));
}
defaultConsumer.addChannelGroup(service,group);
}else if(event == NotifyEvent.CHILD_REMOVED){
defaultConsumer.removedIfAbsent(service,group);
}
}
});
}
}
@Override
public boolean waitForAvailable(long timeoutMillis) {
if (isServiceAvailable(service)) {
return true;
}
boolean available = false;
long start = System.nanoTime();
final ReentrantLock _look = lock;
_look.lock();
try {
while (!isServiceAvailable(service)) {
signalNeeded.set(true);//需要被唤醒,阻塞在下面
// 这编的不太好,如果超时了依然在阻塞中
notifyCondition.await(timeoutMillis, MILLISECONDS);
available = isServiceAvailable(service);
if (available || (System.nanoTime() - start) > MILLISECONDS.toNanos(timeoutMillis)) {
break;
}
}
} catch (InterruptedException e) {
JUnsafe.throwException(e);
} finally {
_look.unlock();
}
return available;
}
private boolean isServiceAvailable(String service) {
CopyOnWriteArrayList<ChannelGroup> list = defaultConsumer.getChannelGroupByServiceName(service);
if(list == null){
return false;
}else{
for(ChannelGroup channelGroup : list){
//channelGroup长度大于0就可用
if(channelGroup.isAvailable()){
return true;
}
}
}
return false;
}
private void onSucceed(boolean doSignal) {
if (doSignal) {
final ReentrantLock _look = lock;
_look.lock();
try {
notifyCondition.signalAll();
} finally {
_look.unlock();
}
}
}
}
进行服务调用
public Object call(String serviceName, long timeout, Object... args) throws Throwable {
// 查看该服务是否已经可用,第一次调用的时候,需要预热
if (null == serviceName || serviceName.length() == 0) {
throw new NoServiceException("调用的服务名不能为空");
}
ChannelGroup channelGroup = getAllMatchedChannel(serviceName);
if (channelGroup == null || channelGroup.size() == 0) {
throw new NoServiceException("没有第三方提供该服务,请检查服务名");
}
RequestCustomBody body = new RequestCustomBody();
body.setArgs(args);
body.setServiceName(serviceName);
RemotingTransporter request = RemotingTransporter.createRequestTransporter(senRPCProtocol.RPC_REQUEST, body);
RemotingTransporter response = sendRpcRequestToProvider(channelGroup.next(),request,3000l);
ResponseCustomBody customBody = serializerImpl().readObject(response.bytes(), ResponseCustomBody.class);
return customBody.getResultWrapper().getResult();
}
负载均衡
package org.senRPC.client.loadbalance;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import org.senRPC.common.utils.ChannelGroup;
/**
*
* @description 负载均衡算法
*/
public enum LoadBalanceStrategies {
//随机
RANDOMSTRATEGIES(new LoadBalance(){
@Override
public ChannelGroup select(CopyOnWriteArrayList<ChannelGroup> arrayList) {
Random random = new Random();
int randomPos = random.nextInt(arrayList.size());
return arrayList.get(randomPos);
}
}),
//加权随机
WEIGHTRANDOMSTRATEGIES(new LoadBalance(){
@Override
public ChannelGroup select(CopyOnWriteArrayList<ChannelGroup> arrayList) {
int count = arrayList.size();
if (count == 0) {
throw new IllegalArgumentException("empty elements for select");
}
if (count == 1) {
return arrayList.get(0);
}
int totalWeight = 0;
int[] weightSnapshots = new int[count];
for (int i = 0; i < count; i++) {
totalWeight += (weightSnapshots[i] = getWeight(arrayList.get(i)));
}
boolean allSameWeight = true;
for (int i = 1; i < count; i++) {
if (weightSnapshots[0] != weightSnapshots[i]) {
allSameWeight = false;
break;
}
}
ThreadLocalRandom random = ThreadLocalRandom.current();
// 如果权重不相同且总权重大于0, 则按总权重数随机
if (!allSameWeight && totalWeight > 0) {
int offset = random.nextInt(totalWeight);
// 确定随机值落在哪个片
for (int i = 0; i < count; i++) {
offset -= weightSnapshots[i];
if (offset < 0) {
return arrayList.get(i);
}
}
}
return (ChannelGroup) arrayList.get(random.nextInt(count));
}
private int getWeight(ChannelGroup channelGroup) {
return channelGroup.getWeight();
}
}),
ROUNDROBIN(new LoadBalance(){
AtomicInteger position = new AtomicInteger(0);
@Override
public ChannelGroup select(CopyOnWriteArrayList<ChannelGroup> arrayList) {
int count = arrayList.size();
if (count == 0) {
throw new IllegalArgumentException("empty elements for select");
}
if (count == 1) {
return arrayList.get(0);
}
int index = position.getAndIncrement() % count;
ChannelGroup channelGroup = arrayList.get(index);
return channelGroup;
}
});
private final LoadBalance loadBalance;
LoadBalanceStrategies(LoadBalance loadBalance) {
this.loadBalance = loadBalance;
}
public ChannelGroup select(CopyOnWriteArrayList<ChannelGroup> arrayList){
return loadBalance.select(arrayList);
}
interface LoadBalance {
ChannelGroup select(CopyOnWriteArrayList<ChannelGroup> arrayList);
}
}
netty客户端
netty客户端链路的建立
public void start() {
// 每次调用一个start就创建一个EventExecutorGroup.
// DefaultEventExecutorGroup是专门来处理耗时业务的线程池。
this.defaultEventExecutorGroup = new DefaultEventExecutorGroup(
nettyClientConfig.getClientWorkerThreads(),
new ThreadFactory() {
private AtomicInteger threadIndex = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "NettyClientWorkerThread_" + this.threadIndex.incrementAndGet());
}
});
// 不同的操作系统
if (isNativeEt()) {
bootstrap.channel(EpollSocketChannel.class);
} else {
bootstrap.channel(NioSocketChannel.class);
}
// 重连狗,里面聚合所有的handler.
final ConnectionWatchdog watchdog = new ConnectionWatchdog(bootstrap, timer) {
@Override
public ChannelHandler[] handlers() {
return new ChannelHandler[] {
this,
new RemotingTransporterDecoder(), //
new RemotingTransporterEncoder(),
//客户端,出站缓冲中无数据超过WRITER_IDLE_TIME_SECONDS就认为没有要写的了,即空闲了。
//时间轮调度,即将调度事件注册到时间上,这样一个调度器就可以调度多个事件。
//IdleStateChecker是一个hander,完成的工作是
new IdleStateChecker(timer, 0, WRITER_IDLE_TIME_SECONDS, 0),//
idleStateTrigger, new NettyClientHandler() };
}
};
//设定是否需要进行重连
watchdog.setReconnect(isReconnect);
//ChannelInitializer也算一个hander
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(
defaultEventExecutorGroup,
watchdog.handlers());
}
});
}
心跳
@ChannelHandler.Sharable
public class ConnectorIdleStateTrigger extends ChannelInboundHandlerAdapter {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleState state = ((IdleStateEvent) evt).state();
if (state == IdleState.WRITER_IDLE) {
//发送心跳包
ctx.writeAndFlush(Heartbeats.heartbeatContent());
}
} else {
super.userEventTriggered(ctx, evt);
}
}
}
空闲检测,重连
public class IdleStateChecker extends ChannelDuplexHandler {
private static final long MIN_TIMEOUT_MILLIS = 1;
// do not create a new ChannelFutureListener per write operation to reduce GC pressure. 使用一个writeListener
private final ChannelFutureListener writeListener = new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
firstWriterIdleEvent = firstAllIdleEvent = true;
lastWriteTime = SystemClock.millisClock().now(); // make hb for firstWriterIdleEvent and firstAllIdleEvent
}
};
private final HashedWheelTimer timer;
private final long readerIdleTimeMillis;
private final long writerIdleTimeMillis;
private final long allIdleTimeMillis;
private volatile int state; // 0 - none, 1 - initialized, 2 - destroyed
private volatile boolean reading;
private volatile Timeout readerIdleTimeout;
private volatile long lastReadTime;
private boolean firstReaderIdleEvent = true;
private volatile Timeout writerIdleTimeout;
private volatile long lastWriteTime;
private boolean firstWriterIdleEvent = true;
private volatile Timeout allIdleTimeout;
private boolean firstAllIdleEvent = true;
public IdleStateChecker(
HashedWheelTimer timer,
int readerIdleTimeSeconds,
int writerIdleTimeSeconds,
int allIdleTimeSeconds) {
this(timer, readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds, SECONDS);
}
public IdleStateChecker(
HashedWheelTimer timer,
long readerIdleTime,
long writerIdleTime,
long allIdleTime,
TimeUnit unit) {
if (unit == null) {
throw new NullPointerException("unit");
}
this.timer = timer;
if (readerIdleTime <= 0) {
readerIdleTimeMillis = 0;
} else {
readerIdleTimeMillis = Math.max(unit.toMillis(readerIdleTime), MIN_TIMEOUT_MILLIS);
}
if (writerIdleTime <= 0) {
writerIdleTimeMillis = 0;
} else {
writerIdleTimeMillis = Math.max(unit.toMillis(writerIdleTime), MIN_TIMEOUT_MILLIS);
}
if (allIdleTime <= 0) {
allIdleTimeMillis = 0;
} else {
allIdleTimeMillis = Math.max(unit.toMillis(allIdleTime), MIN_TIMEOUT_MILLIS);
}
}
/**
* Return the readerIdleTime that was given when instance this class in milliseconds.
*/
public long getReaderIdleTimeInMillis() {
return readerIdleTimeMillis;
}
/**
* Return the writerIdleTime that was given when instance this class in milliseconds.
*/
public long getWriterIdleTimeInMillis() {
return writerIdleTimeMillis;
}
/**
* Return the allIdleTime that was given when instance this class in milliseconds.
*/
public long getAllIdleTimeInMillis() {
return allIdleTimeMillis;
}
// 这个处理器刚加入就激活的方法
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isActive() && ctx.channel().isRegistered()) {
// channelActive() event has been fired already, which means this.channelActive() will
// not be invoked. We have to initialize here instead.
initialize(ctx);
} else {
// channelActive() event has not been fired yet. this.channelActive() will be invoked
// and initialization will occur there.
}
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
destroy();
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
// Initialize early if channel is active already.
if (ctx.channel().isActive()) {
initialize(ctx);
}
super.channelRegistered(ctx);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// This method will be invoked only if this handler was added
// before channelActive() event is fired. If a user adds this handler
// after the channelActive() event, initialize() will be called by beforeAdd().
initialize(ctx);
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
destroy();
super.channelInactive(ctx);
}
//只是记录上次读数据的时间。透传
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (readerIdleTimeMillis > 0 || allIdleTimeMillis > 0) {
firstReaderIdleEvent = firstAllIdleEvent = true;
reading = true; // make hb for firstReaderIdleEvent and firstAllIdleEvent
}
ctx.fireChannelRead(msg);
}
//read到0个字节或者是read到的字节数小于buffer的容量,满足以上条件就会调用channelReadComplete方法。
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
if (readerIdleTimeMillis > 0 || allIdleTimeMillis > 0) {
lastReadTime = SystemClock.millisClock().now(); // make hb for firstReaderIdleEvent and firstAllIdleEvent
reading = false;
}
//会传给下一个handler
ctx.fireChannelReadComplete();
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (writerIdleTimeMillis > 0 || allIdleTimeMillis > 0) {
if (promise.isVoid()) {
//初次写是空的
firstWriterIdleEvent = firstAllIdleEvent = true;
lastWriteTime = SystemClock.millisClock().now(); // make hb for firstWriterIdleEvent and firstAllIdleEvent
} else {
//写是异步的,所以在监听器记录写完成的时间
promise.addListener(writeListener);
}
}
//会传给下一个handler
ctx.write(msg, promise);
}
private void initialize(ChannelHandlerContext ctx) {
// Avoid the case where destroy() is called before scheduling timeouts.
// See: https://github.com/netty/netty/issues/143
//1和2是初始化完和销毁了
switch (state) {
case 1:
case 2:
return;
}
state = 1;
// 开启定时,定时时长恰好是对应的 xxIdleTimeMillis
lastReadTime = lastWriteTime = SystemClock.millisClock().now();
if (readerIdleTimeMillis > 0) {
readerIdleTimeout = timer.newTimeout(
new ReaderIdleTimeoutTask(ctx),
readerIdleTimeMillis, MILLISECONDS);
}
if (writerIdleTimeMillis > 0) {
writerIdleTimeout = timer.newTimeout(
new WriterIdleTimeoutTask(ctx),
writerIdleTimeMillis, MILLISECONDS);
}
if (allIdleTimeMillis > 0) {
allIdleTimeout = timer.newTimeout(
new AllIdleTimeoutTask(ctx),
allIdleTimeMillis, MILLISECONDS);
}
}
private void destroy() {
state = 2;
if (readerIdleTimeout != null) {
readerIdleTimeout.cancel();
readerIdleTimeout = null;
}
if (writerIdleTimeout != null) {
writerIdleTimeout.cancel();
writerIdleTimeout = null;
}
if (allIdleTimeout != null) {
allIdleTimeout.cancel();
allIdleTimeout = null;
}
}
//判定假死之后,IdleStateHandler类会回调自己的channelIdle()方法。激活了EventTriggered
protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
ctx.fireUserEventTriggered(evt);
}
private final class ReaderIdleTimeoutTask implements TimerTask {
private final ChannelHandlerContext ctx;
ReaderIdleTimeoutTask(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
public void run(Timeout timeout) throws Exception {
if (timeout.isCancelled() || !ctx.channel().isOpen()) {
return;
}
long lastReadTime = IdleStateChecker.this.lastReadTime;
long nextDelay = readerIdleTimeMillis;
if (!reading) {
nextDelay -= SystemClock.millisClock().now() - lastReadTime;
}
if (nextDelay <= 0) {
// Reader is idle - set a new timeout and notify the callback.
readerIdleTimeout = timer.newTimeout(this, readerIdleTimeMillis, MILLISECONDS);
try {
IdleStateEvent event;
if (firstReaderIdleEvent) {
firstReaderIdleEvent = false;
event = IdleStateEvent.FIRST_READER_IDLE_STATE_EVENT;
} else {
event = IdleStateEvent.READER_IDLE_STATE_EVENT;
}
channelIdle(ctx, event);
} catch (Throwable t) {
ctx.fireExceptionCaught(t);
}
} else {
// Read occurred before the timeout - set a new timeout with shorter delay.
readerIdleTimeout = timer.newTimeout(this, nextDelay, MILLISECONDS);
}
}
}
private final class WriterIdleTimeoutTask implements TimerTask {
private final ChannelHandlerContext ctx;
WriterIdleTimeoutTask(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
public void run(Timeout timeout) throws Exception {
if (timeout.isCancelled() || !ctx.channel().isOpen()) {
return;
}
long lastWriteTime = IdleStateChecker.this.lastWriteTime;
long nextDelay = writerIdleTimeMillis - (SystemClock.millisClock().now() - lastWriteTime);
if (nextDelay <= 0) {
// Writer is idle - set a new timeout and notify the callback.
//
writerIdleTimeout = timer.newTimeout(this, writerIdleTimeMillis, MILLISECONDS);
try {
IdleStateEvent event;
if (firstWriterIdleEvent) {
firstWriterIdleEvent = false;
//设置idle状态。
event = IdleStateEvent.FIRST_WRITER_IDLE_STATE_EVENT;
} else {
event = IdleStateEvent.WRITER_IDLE_STATE_EVENT;
}
channelIdle(ctx, event);
} catch (Throwable t) {
ctx.fireExceptionCaught(t);
}
} else {
// Write occurred before the timeout - set a new timeout with shorter delay.
//没到idle阈值,继续定时任务
writerIdleTimeout = timer.newTimeout(this, nextDelay, MILLISECONDS);
}
}
}
private final class AllIdleTimeoutTask implements TimerTask {
private final ChannelHandlerContext ctx;
AllIdleTimeoutTask(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
public void run(Timeout timeout) throws Exception {
if (timeout.isCancelled() || !ctx.channel().isOpen()) {
return;
}
long nextDelay = allIdleTimeMillis;
if (!reading) {
long lastIoTime = Math.max(lastReadTime, lastWriteTime);
nextDelay -= SystemClock.millisClock().now() - lastIoTime;
}
if (nextDelay <= 0) {
// Both reader and writer are idle - set a new timeout and
// notify the callback.
allIdleTimeout = timer.newTimeout(this, allIdleTimeMillis, MILLISECONDS);
try {
IdleStateEvent event;
if (firstAllIdleEvent) {
firstAllIdleEvent = false;
event = IdleStateEvent.FIRST_ALL_IDLE_STATE_EVENT;
} else {
event = IdleStateEvent.ALL_IDLE_STATE_EVENT;
}
channelIdle(ctx, event);
} catch (Throwable t) {
ctx.fireExceptionCaught(t);
}
} else {
// Either read or write occurred before the timeout - set a new
// timeout with shorter delay.
//继续没到idle时间,继续下一轮的
allIdleTimeout = timer.newTimeout(this, nextDelay, MILLISECONDS);
}
}
}
}
链路剔除
//channelhandler可以被多个channel安全地共享。
@ChannelHandler.Sharable
public abstract class ConnectionWatchdog extends ChannelInboundHandlerAdapter implements TimerTask, ChannelHandlerHolder {
private static final Logger logger = LoggerFactory.getLogger(ConnectionWatchdog.class);
private final Bootstrap bootstrap;
private final Timer timer;
private boolean firstConnection = true;
private volatile SocketAddress remoteAddress;
private volatile boolean reconnect = true;
private int attempts;
public ConnectionWatchdog(Bootstrap bootstrap, Timer timer) {
this.bootstrap = bootstrap;
this.timer = timer;
}
public boolean isReconnect() {
return reconnect;
}
public void setReconnect(boolean reconnect) {
this.reconnect = reconnect;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//活跃的时候将重连的次数置为0.
Channel channel = ctx.channel();
attempts = 0;
firstConnection = true;
logger.info("Connects with {}.", channel);
ctx.fireChannelActive();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
logger.info("当前channel inactive 将关闭链接");
boolean doReconnect = reconnect;
if (doReconnect) {
if(firstConnection){
remoteAddress = ctx.channel().remoteAddress();
firstConnection = false;
}
if (attempts < 12) {
attempts++;
}
//重连的间隔时间变长
long timeout = 2 << attempts;
logger.info("因为channel关闭所以讲进行重连~");
timer.newTimeout(this, timeout, MILLISECONDS);
}
logger.warn("Disconnects with {}, address: {}, reconnect: {}.", ctx.channel(), remoteAddress, doReconnect);
ctx.fireChannelInactive(); //剔除
}
@Override
public void run(Timeout timeout) throws Exception {
logger.info("进行重连~");
ChannelFuture future;
//重连需要加上所有的handler
//@ChannelHandler.Sharable 标注一个channel handler可以被多个channel安全地共享。所以需要同步锁。
synchronized (bootstrap) {
bootstrap.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(handlers());
}
});
future = bootstrap.connect(remoteAddress);
}
future.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture f) throws Exception {
boolean succeed = f.isSuccess();
logger.warn("Reconnects with {}, {}.", remoteAddress, succeed ? "succeed" : "failed");
if (!succeed) {
//重连不成功,激活ChannelInactive()
f.channel().pipeline().fireChannelInactive();
}
}
});
}
}
netty服务端
public void start() {
this.defaultEventExecutorGroup = new DefaultEventExecutorGroup(
AVAILABLE_PROCESSORS, new ThreadFactory() {
private AtomicInteger threadIndex = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "NettyServerWorkerThread_" + this.threadIndex.incrementAndGet());
}
});
if (isNativeEt()) {
serverBootstrap.channel(EpollServerSocketChannel.class);
} else {
serverBootstrap.channel(NioServerSocketChannel.class);
}
serverBootstrap.localAddress(new InetSocketAddress(this.nettyServerConfig.getListenPort())).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(
defaultEventExecutorGroup,
new IdleStateChecker(timer, READER_IDLE_TIME_SECONDS, 0, 0),
idleStateTrigger,
new RemotingTransporterDecoder()
,new RemotingTransporterEncoder()
,new NettyServerHandler());
}
});