Netty、Redis、Zookeeper高并发实战
元数据
- 书名: Netty、Redis、Zookeeper高并发实战
- 作者: 尼恩
- 简介: 本书从操作系统底层的IO原理入手,同时提供高性能开发的实战案例,是一本高并发Java编程应用基础图书。 本书共分为12章。第1~5章为高并发基础,浅显易懂地剖析高并发IO的底层原理,细致地讲解Reactor高性能模式,图文并茂地介绍Java异步回调模式。这些原理方面的基础知识非常重要,会为读者打下坚实的基础,也是日常开发Java后台应用时解决实际问题的金钥匙。第6~9章为Netty原理和实战,是本书的重中之重,主要介绍高性能通信框架Netty、Netty的重要组件、单体IM的实战设计和模块实现。第10~12章对ZooKeeper、Curator API、Redis、Jedis API的使用进行详尽的说明,以提升读者设计和开发高并发、可扩展系统的能力。本书兼具基础知识和实战案例,既可作为对Java NIO、高性能IO、高并发编程感兴趣的大专院校学生和初、中级Java工程师的学习参考书,也可作为在生产项目中需要用到Netty、Redis、ZooKeeper三大框架的架构师或项目人员的使用参考书。
- 出版时间 2019-08-01 00:00:00
- ISBN: 9787111632900
- 分类: 计算机-编程设计
- 出版社: 机械工业出版社
这里的内容仅为读书笔记,如果您需要阅读原版书籍,请购买正版以支持原创。感谢您的理解和支持。
高亮划线
1.1 Netty为何这么火
-
📌 Netty是JBOSS提供的一个Java开源框架,是基于NIO的客户端/服务器编程框架,它既能快速开发高并发、高可用、高可靠性的网络服务器程序,也能开发高可用、高可靠的客户端程序。
- ⏱ 2023-03-23 00:11:32
-
📌 NOI是指非阻塞输入输出(Non-Blocking IO),也称非阻塞IO。
- ⏱ 2023-03-23 00:12:04
1.2 高并发利器Redis
-
📌 支持管道Redis管道是指客户端可以将多个命令一次性发送到服务器,然后由服务器一次性返回所有结果。管道技术的优点是:在批量执行命令的应用场景中,可以大大减少网络传输的开销,提高性能。
- ⏱ 2023-03-23 12:46:33
1.3 分布式利器ZooKeeper
-
📌 ZooKeeper的核心优势是,实现了分布式环境的数据一致性,简单地说:每时每刻我们访问ZooKeeper的树结构时,不同的节点返回的数据都是一致的。也就是说,对ZooKeeper进行数据访问时,无论是什么时间,都不会引起脏读、重复读。注:脏读是指在数据库存取中无效数据的读出。
- ⏱ 2023-03-23 12:48:46
2.1 IO读写的基础原理
-
📌 用户程序进行IO的读写,依赖于底层的IO读写,基本上会用到底层的read&write两大系统调用。
- ⏱ 2023-03-23 14:34:53
-
📌 调用操作系统的read,是把数据从内核缓冲区复制到进程缓冲区;而write系统调用,是把数据从进程缓冲区复制到内核缓冲区。
- ⏱ 2023-03-23 14:35:55
-
📌 read&write两大系统调用,都不负责数据在内核缓冲区和物理设备(如磁盘)之间的交换,这项底层的读写交换,是由操作系统内核(Kernel)来完成的
- ⏱ 2023-03-23 14:37:38
-
📌 缓冲区的目的,是为了减少频繁地与设备之间的物理交换
- ⏱ 2023-03-23 14:41:15
-
📌 发生系统中断时,需要保存之前的进程数据和状态等信息,而结束中断之后,还需要恢复之前的进程数据和状态等信息。为了减少这种底层系统的时间损耗、性能损耗,于是出现了内存缓冲区。
- ⏱ 2023-03-23 14:43:40
-
📌 底层操作会对内核缓冲区进行监控,等待缓冲区达到一定数量的时候,再进行IO设备的中断处理
- ⏱ 2023-03-23 14:44:57
-
📌 至于什么时候中断(读中断、写中断),由操作系统的内核来决定,用户程序则不需要关心。
- ⏱ 2023-03-23 14:45:06
-
📌 的IO读写程序,在大多数情况下,并没有进行实际的IO操作,而是在进程缓冲区和内核缓冲区之间直接进行数据的交换。
- ⏱ 2023-03-23 14:47:53
-
📌 等待数据从网络中到达网卡。当所等待的分组到达时,它被复制到内核中的某个缓冲区。这个工作由操作系统自动完成,用户程序无感知。
- ⏱ 2023-03-23 14:51:23
2.2 四种主要的IO模型
-
📌 阻塞IO,指的是需要内核IO操作彻底完成后,才返回到用户空间执行用户的操作。阻塞指的是用户空间程序的执行状态。传统的IO模型都是同步阻塞IO。在Java中,默认创建的socket都是阻塞的。
- ⏱ 2023-03-23 15:03:01
-
📌 同步IO,是一种用户空间与内核空间的IO发起方式。同步IO是指用户空间的线程是主动发起IO请求的一方,内核空间是被动接受方。异步IO则反过来,是指系统内核是主动发起IO请求的一方,用户空间的线程是被动接受方。
- ⏱ 2023-03-23 15:04:34
-
📌 非阻塞IO,指的是用户空间的程序不需要等待内核IO操作彻底完成,可以立即返回用户空间执行用户的操作,即处于非阻塞的状态,与此同时内核会立即返回给用户一个状态值
- ⏱ 2023-03-23 15:06:44
-
📌 阻塞是指用户空间(调用线程)一直在等待,而不能干别的事情;非阻塞是指用户空间(调用线程)拿到内核返回的状态值就返回自己的空间,IO操作可以干就干,不可以干,就去干别的事情。
- ⏱ 2023-03-23 15:08:00
-
📌 用户空间的线程向内核空间注册了各种IO事件的回调函数,由内核去主动调用。
- ⏱ 2023-03-23 15:15:16
-
📌 在阻塞式IO模型中,Java应用程序从IO系统调用开始,直到系统调用返回,在这段时间内,Java进程是阻塞的。返回成功后,应用进程开始处理用户空间的缓存区数据。
- ⏱ 2023-03-23 15:15:58
-
📌 [插图]
- ⏱ 2023-03-23 15:16:13
-
📌 高并发的应用场景下,需要大量的线程来维护大量的网络连接,内存、线程切换开销会非常巨大。因此,基本上阻塞IO模型在高并发应用场景下是不可用的。
- ⏱ 2023-03-23 15:19:08
-
📌 [插图]
- ⏱ 2023-03-23 15:21:57
-
📌 同步非阻塞IO的特点:应用程序的线程需要不断地进行IO系统调用,轮询数据是否已经准备好,如果没有准备好,就继续轮询,直到完成IO系统调用为止。
- ⏱ 2023-03-23 15:24:31
-
📌 每次发起的IO系统调用,在内核等待数据过程中可以立即返回
- ⏱ 2023-03-23 15:25:11
-
📌 同步非阻塞IO的缺点:不断地轮询内核,这将占用大量的CPU时间,效率低下
- ⏱ 2023-03-23 15:26:53
-
📌 通过该系统调用,一个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是内核缓冲区可读/可写),内核能够将就绪的状态返回给应用程序。随后,应用程序根据就绪的状态,进行相应的IO系统调用。
- ⏱ 2023-03-23 15:34:11
-
📌 用户线程获得了就绪状态的列表后,根据其中的socket连接,发起read系统调用,用户线程阻塞。内核开始复制数据,将数据从内核缓冲区复制到用户缓冲区。
- ⏱ 2023-03-23 15:42:19
-
📌 [插图]图2-4 IO多路复用模型的流程
- ⏱ 2023-03-23 15:43:32
-
📌 和NIO模型相似,多路复用IO也需要轮询。负责select/epoll状态查询调用的线程,需要不断地进行select/epoll轮询,查找出达到IO操作就绪的socket连接。
- ⏱ 2023-03-23 16:01:37
-
📌 IO多路复用模型与同步非阻塞IO模型是有密切关系的。对于注册在选择器上的每一个可以查询的socket连接,一般都设置成为同步非阻塞模型。仅是这一点,对于用户程序而言是无感知的。
- ⏱ 2023-03-23 16:00:49
-
📌 IO多路复用模型的缺点:本质上,select/epoll系统调用是阻塞式的,属于同步IO。都需要在读写事件就绪后,由系统调用本身负责进行读写,也就是说这个读写过程是阻塞的。
- ⏱ 2023-03-23 16:07:48
-
📌 AIO的基本流程是:用户线程通过系统调用,向内核注册某个IO操作。内核在整个IO操作(包括数据准备、数据复制)完成后,通知用户程序,用户执行后续的业务操作。
- ⏱ 2023-03-23 16:08:54
-
📌 异步IO模型的特点:在内核等待数据和复制数据的两个阶段,用户线程都不是阻塞的。用户线程需要接收内核的IO操作完成的事件,或者用户线程需要注册一个IO操作完成的回调函数。正因为如此,异步IO有的时候也被称为信号驱动IO。
- ⏱ 2023-03-23 16:14:51
2.3 通过合理配置来支持百万级并发连接
-
📌 文件句柄,也叫文件描述符。在Linux系统中,文件可分为:普通文件、目录文件、链接文件和设备文件。文件描述符(File Descriptor)是内核为了高效管理已被打开的文件所创建的索引,它是一个非负整数(通常是小整数),用于指代被打开的文件。所有的IO系统调用,包括socket的读写调用,都是通过文件描述符完成的。
- ⏱ 2023-03-23 16:18:34
-
📌 在服务器运行Netty时,也需要去解除文件句柄数量的限制,修改/etc/security/limits.conf文件即可。
- ⏱ 2023-03-23 16:22:06
3.1 Java NIO简介
-
📌 Java NIO由以下三个核心组件组成:· Channel(通道)· Buffer(缓冲区)· Selector(选择器)
- ⏱ 2023-03-23 23:19:28
-
📌 在NIO中,同一个网络连接使用一个通道表示,所有的NIO的IO操作都是从通道开始的。一个通道类似于OIO中的两个流的结合体,既可以从通道读取,也可以向通道写入。
- ⏱ 2023-03-23 23:33:13
-
📌 通道的读取,就是将数据从通道读取到缓冲区中;通道的写入,就是将数据从缓冲区中写入到通道中。
- ⏱ 2023-03-23 23:38:13
3.2 详解NIO Buffer类及其属性
-
📌 3.2 详解NIO Buffer类及其属性NIO的Buffer(缓冲区)本质上是一个内存块,既可以写入数据,也可以从中读取数据。NIO的Buffer类,是一个抽象类,位于java.nio包中,其内部是一个内存块(数组)。NIO的Buffer与普通的内存块(Java数组)不同的是:NIO Buffer对象,提供了一组更加有效的方法,用来进行写入和读取的交替访问。需要强调的是:Buffer类是一个非线程安全类。3.2.1 Buffer类
- ⏱ 2023-03-24 02:06:27
-
📌 MappedByteBuffer是专门用于内存映射的一种ByteBuffer类型
- ⏱ 2023-03-25 01:32:40
-
📌 Buffer类在其内部,有一个byte[]数组内存块,作为内存缓冲区。为了记录读写的状态和位置,Buffer类提供了一些重要的属性。其中,有三个重要的成员属性:capacity(容量)、position(读写位置)、limit(读写的限制)。
- ⏱ 2023-03-24 21:55:14
-
📌 标记属性:mark(标记),可以将当前的position临时存入mark中;需要的时候,可以再从mark标记恢复到position位置。
- ⏱ 2023-03-24 21:54:56
-
📌 Buffer类的capacity属性,表示内部容量的大小。一旦写入的对象数量超过了capacity容量,缓冲区就满了,不能再写入了。Buffer类的capacity属性一旦初始化,就不能再改变。原因是什么呢?Buffer类的对象在初始化时,会按照capacity分配内部的内存。在内存分配好之后,它的大小当然就不能改变了。再强调一下,capacity容量不是指内存块byte[]数组的字节的数量。capacity容量指的是写入的数据对象的数量。前面讲到,Buffer类是一个抽象类,Java不能直接用来新建对象。
- ⏱ 2023-03-25 01:13:26
-
📌 Buffer类的capacity属性一旦初始化,就不能再改变。
- ⏱ 2023-03-24 21:55:33
-
📌 Buffer类的对象在初始化时,会按照capacity分配内部的内存。在内存分配好之后,它的大小当然就不能改变了。
- ⏱ 2023-03-24 21:55:43
-
📌 capacity容量不是指内存块byte[]数组的字节的数量。capacity容量指的是写入的数据对象的数量。
- ⏱ 2023-03-24 21:56:35
-
📌 Buffer类的position属性,表示当前的位置。position属性与缓冲区的读写模式有关。在不同的模式下,position属性的值是不同的。当缓冲区进行读写的模式改变时,position会进行调整。
- ⏱ 2023-03-24 21:59:51
-
📌 在写入模式下,position的值变化规则如下:(1)在刚进入到写模式时,position值为0,表示当前的写入位置为从头开始。(2)每当一个数据写到缓冲区之后,position会向后移动到下一个可写的位置。(3)初始的position值为0,最大可写值position为limit-1。当position值达到limit时,缓冲区就已经无空间可写了。
- ⏱ 2023-03-24 22:00:15
-
📌 在读模式下,position的值变化规则如下:(1)当缓冲区刚开始进入到读模式时,position会被重置为0。(2)当从缓冲区读取时,也是从position位置开始读。读取数据后,position向前移动到下一个可读的位置。(3)position最大的值为最大可读上限limit,当position达到limit时,表明缓冲区已经无数据可读。
- ⏱ 2023-03-24 22:00:21
-
📌 模式的切换,可以使用(即调用)flip翻转方法,将缓冲区变成读取模式。
- ⏱ 2023-03-24 22:00:50
-
📌 在这个flip翻转过程中,position会进行非常巨大的调整,具体的规则是:position由原来的写入位置,变成新的可读位置,也就是0,表示可以从头开始读。
- ⏱ 2023-03-24 22:01:05
-
📌 Buffer类的limit属性,表示读写的最大上限。limit属性,也与缓冲区的读写模式有关。在不同的模式下,limit的值的含义是不同的。
- ⏱ 2023-03-25 11:59:53
-
📌 在写模式下,limit属性值的含义为可以写入的数据最大上限。
- ⏱ 2023-03-25 12:00:24
-
📌 在读模式下,limit的值含义为最多能从缓冲区中读取到多少数据。
- ⏱ 2023-03-25 12:00:42
-
📌 一般来说,是先写入再读取。当缓冲区写入完成后,就可以开始从Buffer读取数据,可以使用flip翻转方法,这时,limit的值也会进行非常大的调整。
- ⏱ 2023-03-25 12:00:50
-
📌 将写模式下的position值,设置成读模式下的limit值,也就是说,将之前写入的最大数量,作为可以读取的上限值。
- ⏱ 2023-03-25 12:01:13
3.3 详解NIO Buffer类的重要方法
-
📌 flip()翻转方法是Buffer类提供的一个模式转变的重要方法,它的作用就是将写入模式翻转成读取模式
- ⏱ 2023-03-24 08:48:03
-
📌 新的问题来了,在读取完成后,如何再一次将缓冲区切换成写入模式呢?可以调用Buffer.clear()清空或者Buffer.compact()压缩方法,它们可以将缓冲区转换为写模式。
- ⏱ 2023-03-24 09:00:37
-
📌 在读完之后,是否可以立即进行写入模式呢?不能。现在还处于读取模式,我们必须调用Buffer.clear()或Buffer.compact(),即清空或者压缩缓冲区,才能变成写入模式,让其重新可写。
- ⏱ 2023-03-24 09:07:39
-
📌 缓冲区是不是可以重复读呢?答案是可以的。
- ⏱ 2023-03-24 09:12:07
-
📌 已经读完的数据,如果需要再读一遍,可以调用rewind()方法。rewind()也叫倒带,就像播放磁带一样倒回去,再重新播放。
- ⏱ 2023-03-24 09:12:38
-
📌 Buffer.mark()方法的作用是将当前position的值保存起来,放在mark属性中,让mark属性记住这个临时位置;之后,可以调用Buffer.reset()方法将mark的值恢复到position中。
- ⏱ 2023-03-24 09:18:41
-
📌 Buffer.mark()和Buffer.reset()方法是配套使用的
- ⏱ 2023-03-24 09:19:21
-
📌 在读取模式下,调用clear()方法将缓冲区切换为写入模式。此方法会将position清零,limit设置为capacity最大容量值,可以一直写入,直到缓冲区写满。
- ⏱ 2023-03-24 09:21:35
-
📌 总体来说,使用Java NIO Buffer类的基本步骤如下:(1)使用创建子类实例对象的allocate()方法,创建一个Buffer类的实例对象。(2)调用put方法,将数据写入到缓冲区中。(3)写入完成后,在开始读取数据前,调用Buffer.flip()方法,将缓冲区转换为读模式。(4)调用get方法,从缓冲区中读取数据。(5)读取完成后,调用Buffer.clear() 或Buffer.compact()方法,将缓冲区转换为写入模式。
- ⏱ 2023-03-24 09:26:08
3.4 详解NIO Channel(通道)类
-
📌 一个通道可以表示一个底层的文件描述符,例如硬件设备、文件、网络连接等
- ⏱ 2023-03-24 09:28:29
-
📌 (1)FileChannel文件通道,用于文件的数据读写。(2)SocketChannel套接字通道,用于Socket套接字TCP连接的数据读写。(3)ServerSocketChannel服务器嵌套字通道(或服务器监听通道),允许我们监听TCP连接请求,为每个监听到的请求,创建一个SocketChannel套接字通道。(4)DatagramChannel数据报通道,用于UDP协议的数据读写。
- ⏱ 2023-03-24 09:30:04
-
📌 FileChannel为阻塞模式,不能设置为非阻塞模式。
- ⏱ 2023-03-24 09:31:14
-
📌 在大部分应用场景,从通道读取数据都会调用通道的int read(ByteBufferbuf)方法,它从通道读取到数据写入到ByteBuffer缓冲区,并且返回读取到的数据量
- ⏱ 2023-03-26 16:23:24
-
📌 虽然对于通道来说是读取数据,但是对于ByteBuffer缓冲区来说是写入数据,这时,ByteBuffer缓冲区处于写入模式。
- ⏱ 2023-03-24 10:38:04
-
📌 在将缓冲区写入通道时,出于性能原因,操作系统不可能每次都实时将数据写入磁盘。如果需要保证写入通道的缓冲数据,最终都真正地写入磁盘,可以调用FileChannel的force()方法。[插图]
- ⏱ 2023-03-24 10:46:57
-
📌 更高效的文件复制,可以调用文件通道的transferFrom方法
- ⏱ 2023-03-26 23:26:32
-
📌 ServerSocketChannel应用于服务器端,而SocketChannel同时处于服务器端和客户端。换句话说,对应于一个连接,两端都有一个负责传输的SocketChannel传输通道。
- ⏱ 2023-03-24 11:51:47
-
📌 无论是ServerSocketChannel,还是SocketChannel,都支持阻塞和非阻塞两种模式
- ⏱ 2023-03-24 11:53:06
-
📌 (1)socketChannel.configureBlocking(false)设置为非阻塞模式。(2)socketChannel.configureBlocking(true)设置为阻塞模式。
- ⏱ 2023-03-24 11:52:22
-
📌 在非阻塞模式下,通道的操作是异步、高效率的,这也是相对于传统的OIO的优势所在
- ⏱ 2023-03-26 23:32:42
-
📌 在客户端,先通过SocketChannel静态方法open()获得一个套接字传输通道;然后,将socket套接字设置为非阻塞模式;最后,通过connect()实例方法,对服务器的IP和端口发起连接。
- ⏱ 2023-03-24 12:03:03
-
📌 并且通过调用服务器端ServerSocketChannel监听套接字的accept()方法,来获取新连接的套接字通道
- ⏱ 2023-03-24 13:23:26
-
📌 在非阻塞模式下,如何知道通道何时是可读的呢?这就需要用到NIO的新组件——Selector通道选择器
- ⏱ 2023-03-24 13:24:41
-
📌 在关闭SocketChannel传输通道前,如果传输通道用来写入数据,则建议调用一次shutdownOutput()终止输出方法,向对方发送一个输出的结束标志(-1)。然后调用socketChannel.close()方法,关闭套接字连接。
- ⏱ 2023-03-24 13:33:09
-
📌 和Socket套接字的TCP传输协议不同,UDP协议不是面向连接的协议。使用UDP协议时,只要知道服务器的IP和端口,就可以直接向对方发送数据。
- ⏱ 2023-03-24 13:35:53
-
📌 和前面的SocketChannel的读取方式不同,不是调用read方法,而是调用receive(ByteBufferbuf)方法将数据从DatagramChannel读入,再写入到ByteBuffer缓冲区中
- ⏱ 2023-03-27 03:57:59
-
📌 向DatagramChannel发送数据,和向SocketChannel通道发送数据的方法也是不同的。这里不是调用write方法,而是调用send方法
- ⏱ 2023-03-27 03:59:31
-
📌 由于UDP是面向非连接的协议,因此,在调用send方法发送数据的时候,需要指定接收方的地址(IP和端口)。
- ⏱ 2023-03-27 04:00:05
-
📌 当数据到来后,调用了receive方法,从datagramChannel数据包通道接收数据,再写入到ByteBuffer缓冲区中
- ⏱ 2023-03-27 04:03:06
3.5 详解NIO Selector选择器
-
📌 Java NIO的三大核心组件:Channel(通道)、Buffer(缓冲区)、Selector(选择器)。其中通道和缓冲区,二者的联系也比较密切:数据总是从通道读到缓冲区内,或者从缓冲区写入到通道中。
- ⏱ 2023-03-24 20:55:31
-
📌 选择器的使命是完成IO的多路复用。一个通道代表一条连接通路,通过选择器可以同时监控多个通道的IO(输入输出)状况。选择器和通道的关系,是监控和被监控的关系。
- ⏱ 2023-03-25 10:42:47
-
📌 调用通道的Channel.register(Selector sel, int ops)方法,可以将通道实例注册到一个选择器中。
- ⏱ 2023-03-25 10:44:34
-
📌 可供选择器监控的通道IO事件类型,包括以下四种:(1)可读:SelectionKey.OP_READ(2)可写:SelectionKey.OP_WRITE(3)连接:SelectionKey.OP_CONNECT(4)接收:SelectionKey.OP_ACCEPT
- ⏱ 2023-03-24 20:56:35
-
📌 判断一个通道能否被选择器监控或选择,有一个前提:判断它是否继承了抽象类SelectableChannel(可选择通道)。如果继承了SelectableChannel,则可以被选择,否则不能。
- ⏱ 2023-03-25 10:48:42
-
📌 而FileChannel文件通道,并没有继承SelectableChannel,因此不是可选择通道。
- ⏱ 2023-03-25 10:50:19
-
📌 一旦在通道中发生了某些IO事件(就绪状态达成),并且是在选择器中注册过的IO事件,就会被选择器选中,并放入SelectionKey选择键的集合中。
- ⏱ 2023-03-27 04:08:03
-
📌 SelectionKey选择键就是那些被选择器选中的IO事件
- ⏱ 2023-03-27 04:08:36
-
📌 一个IO事件发生(就绪状态达成)后,如果之前在选择器中注册过,就会被选择器选中,并放入SelectionKey选择键集合中;如果之前没有注册过,即使发生了IO事件,也不会被选择器选中。SelectionKey选择键和IO的关系,可以简单地理解为:选择键,就是被选中了的IO事件。
- ⏱ 2023-03-27 04:09:10
-
📌 (1)获取选择器实例;(2)将通道注册到选择器中;(3)轮询感兴趣的IO就绪事件(选择键集合)。
- ⏱ 2023-03-27 04:09:42
-
📌 Java通过SPI的方式,提供选择器的默认实现版本
- ⏱ 2023-03-27 04:10:35
-
📌 注册到选择器的通道,必须处于非阻塞模式下,否则将抛出IllegalBlockingModeException异常。
- ⏱ 2023-03-27 04:13:14
-
📌 通过Selector选择器的select()方法,选出已经注册的、已经就绪的IO事件,保存到SelectionKey选择键集合中
- ⏱ 2023-03-27 04:14:27
-
📌 select()方法返回的数量,指的是通道数,而不是IO事件数,准确地说,是指发生了选择器感兴趣的IO事件的通道数。
- ⏱ 2023-03-27 04:18:02
第4章 鼎鼎大名的Reactor反应器模式
-
📌 Reactor反应器模式是高性能网络编程在设计和架构层面的基础模式
- ⏱ 2023-03-27 04:25:13
4.1 Reactor反应器模式为何如此重要
-
📌 反应器模式由Reactor反应器线程、Handlers处理器两大角色组成:(1)Reactor反应器线程的职责:负责响应IO事件,并且分发到Handlers处理器。(2)Handlers处理器的职责:非阻塞的执行业务处理逻辑。
- ⏱ 2023-03-27 04:26:39
4.2 单线程Reactor反应器模式
-
📌 反应器模式中的反应器角色,类似于事件驱动模式中的dispatcher事件分发器角色
- ⏱ 2023-03-27 04:32:01
-
📌 Reactor反应器:负责查询IO事件,当检测到一个IO事件,将其发送给相应的Handler处理器去处理。这里的IO事件,就是NIO中选择器监控的通道IO事件。
- ⏱ 2023-03-27 04:32:26
-
📌 Handler处理器:与IO事件(或者选择键)绑定,负责IO事件的处理。完成真正的连接建立、通道的读取、处理业务逻辑、负责将结果写出到通道等。
- ⏱ 2023-03-27 04:32:46
-
📌 什么是单线程版本的Reactor反应器模式呢?简单地说,Reactor反应器和Handers处理器处于一个线程中执行。
- ⏱ 2023-03-27 11:13:00
-
📌 作为附件添加到SelectionKey实例。
- ⏱ 2023-03-27 11:14:46
-
📌 在选择键注册完成之后,调用attach方法,将Handler处理器绑定到选择键;当事件发生时,调用attachment方法,可以从选择键取出Handler处理器,将事件分发到Handler处理器中,完成业务处理。
- ⏱ 2023-03-27 11:15:54
-
📌 AcceptorHandler处理器的两大职责:一是接受新连接,二是在为新连接创建一个输入输出的Handler处理器,称之为IOHandler。
- ⏱ 2023-03-27 16:13:27
-
📌 IOHandler的构造器中,有两点比较重要:(1)将新的SocketChannel传输通道,注册到了反应器Reactor类的同一个选择器中。这样保证了Reactor类和Handler类在同一个线程中执行。(2)Channel传输通道注册完成后,将IOHandler自身作为附件,attach到了选择键中。这样,在Reactor类分发事件(选择键)时,能执行到IOHandler的run方法。
- ⏱ 2023-03-27 16:16:13
-
📌 在单线程反应器模式中,Reactor反应器和Handler处理器,都执行在同一条线程上。这样,带来了一个问题:当其中某个Handler阻塞时,会导致其他所有的Handler都得不到执行
- ⏱ 2023-03-28 11:58:09
-
📌 一旦AcceptorHandler处理器阻塞,会导致整个服务不能接收新的连接,使得服务器变得不可用。因为这个缺陷,因此单线程反应器模型用得比较少。
- ⏱ 2023-03-28 11:58:40
4.3 多线程的Reactor反应器模式
-
📌 将负责输入输出处理的IOHandler处理器的执行,放入独立的线程池中。这样,业务处理线程与负责服务监听和IO事件查询的反应器线程相隔离,避免服务器的连接监听受到阻塞。
- ⏱ 2023-03-28 15:28:00
-
📌 如果服务器为多核的CPU,可以将反应器线程拆分为多个子反应器(SubReactor)线程;同时,引入多个选择器,每一个SubReactor子线程负责一个选择器。这样,充分释放了系统资源的能力;也提高了反应器管理大量连接,提升选择大量通道的能力。
- ⏱ 2023-03-28 15:28:55
-
📌 建议SubReactor的数量和选择器的数量一致。避免多个线程负责一个选择器,导致需要进行线程同步,引起的效率降低
- ⏱ 2023-03-28 15:34:06
-
📌 int state = RECIEVING;
//引入线程池
static ExecutorService pool = Executors.newFixedThreadPool(4);
MultiThreadEchoHandler(Selector selector, SocketChannel c) throws
IOException {
channel = c;
c.configureBlocking(false);
//取得选择键,、再设置感兴趣的IO事件
sk = channel.register(selector, 0);
//将本Handler作为sk选择键的附件,方便事件分发(dispatch)
sk.attach(this);
//向sk选择键注册Read就绪事件
sk.interestOps(SelectionKey.OP_READ);
selector.wakeup();- ⏱ 2023-03-28 16:38:36
4.4 Reactor反应器模式小结
-
📌 1.反应器模式和生产者消费者模式对比相似之处:在一定程度上,反应器模式有点类似生产者消费者模式。在生产者消费者模式中,一个或多个生产者将事件加入到一个队列中,一个或多个消费者主动地从这个队列中提取(Pull)事件来处理。不同之处在于:反应器模式是基于查询的,没有专门的队列去缓冲存储IO事件,查询到IO事件之后,反应器会根据不同IO选择键(事件)将其分发给对应的Handler处理器来处理。
- ⏱ 2023-03-30 17:06:00
5.2 join异步阻塞
-
📌 join操作的原理是:阻塞当前的线程,直到准备合并的目标线程的执行完成。
- ⏱ 2023-03-30 17:46:30
-
📌 假设线程A调用了线程B的B.join方法,合并B线程。那么,线程A进入阻塞状态,直到B线程执行完成。
- ⏱ 2023-03-30 17:47:18
5.3 FutureTask异步回调之重武器
-
📌 Runnable有一个重要的问题,它的run方法是没有返回值的。正因为如此,Runnable不能用于需要有返回值的应用场景。
- ⏱ 2023-04-22 14:14:58
-
📌 Callable接口是一个泛型接口,也声明为了“函数式接口”。其唯一的抽象方法call有返回值,返回值的类型为泛型形参的实际类型。call抽象方法还有一个Exception的异常声明,容许方法内部的异常不经过捕获。
- ⏱ 2023-04-22 14:15:30
-
📌 如果Callable实例需要异步执行,就要想办法赋值给Thread的target成员,一个Runnable类型的成员。为此,Java提供了在Callable实例和Thread的target成员之间一个搭桥的类——FutureTask类。
- ⏱ 2023-04-22 15:15:30
6.1 第一个Netty的实践案例DiscardServer
-
📌 也就是说,如果要实现自己的入站处理器Handler,只要继承ChannelInboundHandlerAdapter入站处理器,再写入自己的入站处理的业务逻辑。
- ⏱ 2023-03-31 17:34:14
6.2 解密Netty中的Reactor反应器模式
-
📌 对于每一种通信连接协议,Netty都实现了自己的通道
- ⏱ 2023-04-02 22:23:50
-
📌 Netty中的每一种协议的通道,都有NIO(异步IO)和OIO(阻塞式IO)两个版本
- ⏱ 2023-04-02 22:24:03
-
📌 对应于不同的协议,Netty中常见的通道类型如下:· NioSocketChannel:异步非阻塞TCP Socket传输通道。· NioServerSocketChannel:异步非阻塞TCP Socket服务器端监听通道。· NioDatagramChannel:异步非阻塞的UDP传输通道。· NioSctpChannel:异步非阻塞Sctp传输通道。· NioSctpServerChannel:异步非阻塞Sctp服务器端监听通道。· OioSocketChannel:同步阻塞式TCP Socket传输通道。· OioServerSocketChannel:同步阻塞式TCP Socket服务器端监听通道。· OioDatagramChannel:同步阻塞式UDP传输通道。· OioSctpChannel:同步阻塞式Sctp传输通道。· OioSctpServerChannel:同步阻塞式Sctp服务器端监听通道。
- ⏱ 2023-04-02 22:24:53
-
📌 一个EventLoopNetty反应器和NettyChannel通道是一对多的关系:一个反应器可以注册成千上万的通道。
- ⏱ 2023-04-02 22:30:21
-
📌 ChannelPipeline(通道流水线),它像一条管道,将绑定到一个通道的多个Handler处理器实例,串在一起,形成一条流水线
- ⏱ 2023-04-03 11:39:58
-
📌 每一个来自通道的IO事件,都会进入一次ChannelPipeline通道流水线。
- ⏱ 2023-04-03 11:38:56
-
📌 Netty是这样规定的:入站处理器Handler的执行次序,是从前到后;出站处理器Handler的执行次序,是从后到前
- ⏱ 2023-04-03 11:40:48
-
📌 入站的IO操作只会且只能从Inbound入站处理器类型的Handler流过;出站的IO操作只会且只能从Outbound出站处理器类型的Handler流过。
- ⏱ 2023-04-03 11:43:06
6.3 详解Bootstrap启动器类
-
📌 Bootstrap类是Netty提供的一个便利的工厂类,可以通过它来完成Netty的客户端或服务器端的Netty组件的组装,以及Netty程序的初始化。
- ⏱ 2023-04-03 11:43:53
-
📌 操作系统底层的socket描述符分为两类:· 连接监听类型。连接监听类型的socket描述符,放在服务器端,它负责接收客户端的套接字连接;在服务器端,一个“连接监听类型”的socket描述符可以接受(Accept)成千上万的传输类的socket描述符。· 传输数据类型。数据传输类的socket描述符负责传输数据。同一条TCP的Socket传输链路,在服务器和客户端,都分别会有一个与之相对应的数据传输类型的socket描述符。
- ⏱ 2023-04-03 12:02:52
-
📌 在Netty中,将有接收关系的NioServerSocketChannel和NioSocketChannel,叫作父子通道。其中,NioServerSocketChannel负责服务器连接监听和接收,也叫父通道(Parent Channel)。对应于每一个接收到的NioSocketChannel传输类通道,也叫子通道(Child Channel)。
- ⏱ 2023-04-03 12:03:35
-
📌 Netty的EventLoopGroup线程组就是一个多线程版本的反应器。而其中的单个EventLoop线程对应于一个子反应器(SubReactor)。
- ⏱ 2023-04-03 12:09:48
-
📌 默认的EventLoopGroup内部线程数为最大可用的CPU处理器数量的2倍。
- ⏱ 2023-04-03 12:21:12
-
📌 对应到Netty服务器程序中,则是设置两个EventLoopGroup线程组,一个EventLoopGroup负责新连接的监听和接受,一个EventLoopGroup负责IO事件处理。
- ⏱ 2023-04-03 12:25:19
6.4 详解Channel通道
-
📌 在Netty中,通道是其中的核心概念之一,代表着网络连接
- ⏱ 2023-04-23 15:42:29
-
📌 AbstractChannel内部有一个pipeline属性,表示处理器的流水线。Netty在对通道进行初始化的时候,将pipeline属性初始化为DefaultChannelPipeline的实例。这段代码也表明,每个通道拥有一条ChannelPipeline处理器流水线。
- ⏱ 2023-04-23 21:14:58
-
📌 方法1. ChannelFuture connect(SocketAddress address)此方法的作用为:连接远程服务器。方法的参数为远程服务器的地址,调用后会立即返回,返回值为负责连接操作的异步任务ChannelFuture。此方法在客户端的传输通道使用。
- ⏱ 2023-04-23 21:43:22
-
📌 方法2. ChannelFuture bind(SocketAddress address)此方法的作用为:绑定监听地址,开始监听新的客户端连接。此方法在服务器的新连接监听和接收通道使用。
- ⏱ 2023-04-23 21:52:00
10.1 ZooKeeper伪集群安装和配置
-
📌 ZooKeeper集群节点数必须是奇数。
- ⏱ 2023-04-16 13:50:38
-
📌 在ZooKeeper集群中,需要一个主节点,也称为Leader节点。主节点是集群通过选举的规则从所有节点中选举出来的。在选举的规则中很重要的一条是:要求可用节点数量>总节点数量/2。如果是偶数个节点,则可能会出现不满足这个规则的情况。
- ⏱ 2023-04-16 13:50:48
12.1 如何支撑亿级流量的高并发IM架构的理论基础
-
📌 Protobuf是最高效的二进制序列化协议,用于长连接
- ⏱ 2023-04-22 10:10:30
读书笔记
1.2 高并发利器Redis
划线评论
-
📌 丰富的数据结构 除了string之外,还有list、hash、set、sortedset,一共五种类型。
- 💭 常用有这五种类型,现在一共有九种类型
我们都知道 Redis 提供了丰富的数据类型,常见的有五种:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合) 。
随着 Redis 版本的更新,后面又支持了四种数据类型: BitMap(2.2 版新增)、HyperLogLog(2.8 版新增)、GEO(3.2 版新增)、Stream(5.0 版新增) 。
- 💭 常用有这五种类型,现在一共有九种类型
作者:wefashe
链接:https://juejin.cn/post/7108920755592626207
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- ⏱ 2023-03-23 12:45:03
2.1 IO读写的基础原理
划线评论
-
📌 如果是在Java服务器端,完成一次socket请求和响应,完整的流程
- 💭 简单来说就是系统先通过网卡从客户端获取数据刷入系统内核缓冲区->Java程序通过read调用操作将数据刷入应用缓冲区->Java处理数据->通过write操作将数据从应用缓冲区刷入内核缓冲区
- ⏱ 2023-03-23 15:45:34
2.2 四种主要的IO模型
划线评论
-
📌 IO多路复用模型的优点:与一个线程维护一个连接的阻塞IO模式相比,使用select/epoll的最大优势在于,一个选择器查询线程可以同时处理成千上万个连接(Connection)。系统不必创建大量的线程,也不必维护这些线程,从而大大减小了系统的开销。
- 💭 IO多路复用模型的优点:不需要创建和维护大量的线程去查询和处理连接状态,只需要去维护Select查询线程就可以同时处理成千上万的连接,减小了系统开销
- ⏱ 2023-03-23 16:06:53
划线评论
-
📌 当用户进程调用了select查询方法,那么整个线程会被阻塞掉。
- 💭 是用户调用select查询方法后已经就绪的socket数据从内核缓冲区刷入用户缓冲区导致线程阻塞吗?
- ⏱ 2023-03-23 15:41:41
划线评论
-
📌 在IO多路复用模型中通过select/epoll系统调用,单个应用程序的线程,可以不断地轮询成百上千的socket连接,当某个或者某些socket网络连接有IO就绪的状态,就返回对应的可以执行的读写操作。
- 💭 有误,epoll应该为poll
- ⏱ 2023-03-23 15:36:40
划线评论
-
📌 总体来说,在高并发应用场景下,同步非阻塞IO也是不可用的。
- 💭 同步非阻塞IO在内核缓冲区准备阶段实现了异步,在这个阶段用户发起的请求都会立刻获得反馈,但是在内核缓冲区到应用缓冲区的过程中还是阻塞,同时应用还需要不断轮询获取状态,占用大量cpu时间。在高并发场景下这种模式并不可用
- ⏱ 2023-03-23 15:30:55
划线评论
-
📌 3.IO多路复用(IO Multiplexing)
即经典的Reactor反应器设计模式,有时也称为异步阻塞IO, Java中的Selector选择器和Linux中的epoll都是这种模型。- 💭 存疑,需要查证准确性
- ⏱ 2023-03-23 15:14:14
2.3 通过合理配置来支持百万级并发连接
划线评论
-
📌 Linux的系统默认值为1024,也就是说,一个进程最多可以接受1024个socket连接
- 💭 在生产环境中1024个句柄是远远不够的,需要解除句柄数的限制
- ⏱ 2023-03-23 16:17:22
3.1 Java NIO简介
划线评论
-
📌 应用程序与通道(Channel)主要的交互操作,就是进行数据的read读取和write写入。
- 💭 应用程序与channel的主要操作就是进行数据的read&write。Nio使用buffer缓冲区进行数据交互,channel的读取就是将channel的数据读到buffer中,而channel的写入则是将数据从buffer中读到channel中
- ⏱ 2023-03-23 23:42:30
划线评论
-
📌 Selector选择器
- 💭 基于操作系统的select等实现
- ⏱ 2023-03-23 23:36:25
划线评论
-
📌 NIO和OIO的对比
- 💭 nio与oio的区别
1.oio是面向流的,严格要求顺序读取;nio是面向通道和缓存区的,可以乱序读取后组合成完整数据
2.oio是阻塞的,而nio采用多路复用模型极大的降低了阻塞的影响
3.nio中有selector调查器 - ⏱ 2023-03-23 23:31:07
- 💭 nio与oio的区别
划线评论
-
📌 NIO如何做到非阻塞的呢?
- 💭 nio也是阻塞的,但是nio使用了多路复用模型,极大的降低了阻塞带来的影响
- ⏱ 2023-03-23 23:26:47
划线评论
-
📌 NIO中引入了Channel(通道)和Buffer(缓冲区)的概念。读取和写入,只需要从通道中读取数据到缓冲区中,或将数据从缓冲区中写入到通道中。NIO不像OIO那样是顺序操作,可以随意地读取Buffer中任意位置的数据。
- 💭 从流中读取数据需要严格要求顺序,而从channel和buffer中可以使用数据分块,乱序传输再组装成完整的数据
- ⏱ 2023-03-23 23:23:48
3.3 详解NIO Buffer类的重要方法
划线评论
-
📌 读取操作会改变可读位置position的值,而limit值不会改变
- 💭 如果position=limit则代表所有元素已经读完了,此时position所指向的位置已经没有值了,如果再读就会报错
- ⏱ 2023-03-24 09:11:20
划线评论
-
📌 如何将缓冲区切换成读取模式
- 💭 缓冲区有两种模式,分别为读模式和写模式,只有缓冲区处于对应模式下才能读或者写,而flip方法则是用来反转模式的。为什么需要反转模式呢?因为在读模式和写模式下缓冲区下同一个元素的状态和值是不太一样的,需要反转才能不使数据混乱。这是目前的理解还需要验证
- ⏱ 2023-03-24 08:58:38
划线评论
-
📌 最后,清除之前的mark标记,因为mark保存的是写模式下的临时位置。在读模式下,如果继续使用旧的mark标记,会造成位置混乱。
- 💭 这个mark标记是指的写入元素的位置吗
- ⏱ 2023-03-24 08:51:41
3.4 详解NIO Channel(通道)类
划线评论
-
📌 在读取时,因为是异步的,因此我们必须检查read的返回值,以便判断当前是否读取到了数据。read()方法的返回值,是读取的字节数。如果返回-1,那么表示读取到对方的输出结束标志,对方已经输出结束,准备关闭连接。
- 💭 这个读取时是异步的,应该为主线程循环查询调用read的线程状态
- ⏱ 2023-03-24 13:26:09
划线评论
-
📌 此时的ByteBuffer缓冲区要求是可读的,处于读模式下。
- 💭 写入Channel需要buffer处于可读模式,读取Channel需要buffer处于可写模式。因为Channel的写入是从buffer中读取,而读取相反
- ⏱ 2023-03-24 10:44:16
划线评论
-
📌 FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel。
- 💭 其中常用的channel
- ⏱ 2023-03-24 09:29:20
本书评论
书评 No.1 对于io部分讲的不错,但是其他的内容就像是博客文章拼凑在一起,总得来说前小部分推荐阅读
⏱ 2023-03-30 22:29:52