<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Netty on 小盒子的技术分享</title><link>https://xiaobox.github.io/tags/netty/</link><description>Recent content in Netty on 小盒子的技术分享</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Mon, 21 Mar 2022 07:13:56 +0000</lastBuildDate><atom:link href="https://xiaobox.github.io/tags/netty/index.xml" rel="self" type="application/rss+xml"/><item><title>再聊聊Netty</title><link>https://xiaobox.github.io/p/2022-03-21-zai-liao-liao-netty/</link><pubDate>Mon, 21 Mar 2022 07:13:56 +0000</pubDate><guid>https://xiaobox.github.io/p/2022-03-21-zai-liao-liao-netty/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/cover.jpg" alt="Featured image of post 再聊聊Netty" /&gt;&lt;h2 id="为什么还要用-netty"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e8%bf%98%e8%a6%81%e7%94%a8-netty" class="header-anchor"&gt;&lt;/a&gt;为什么还要用 Netty
&lt;/h2&gt;&lt;p&gt;既然 JAVA NIO / JAVA AIO 已经实现了各主流操作系统的底层支持，那么为什么现在主流的 JAVA NIO 技术会是 Netty 和 MINA 呢？答案很简单：因为更好用，这里举几个方面的例子：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;虽然 JAVA NIO 和 JAVA AIO 框架提供了 多路复用 IO/异步 IO 的支持，但是并没有提供上层“信息格式”的良好封装。例如前两者并没有提供针对 Protocol Buffer、JSON 这些信息格式的封装，但是 Netty 框架提供了这些数据格式封装（基于责任链模式的编码和解码功能）&lt;/li&gt;
&lt;li&gt;要编写一个可靠的、易维护的、高性能的（注意它们的排序） NIO/AIO 服务器应用。除了框架本身要兼容实现各类操作系统的实现外。更重要的是它应该还要处理&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="架构"&gt;&lt;a href="#%e6%9e%b6%e6%9e%84" class="header-anchor"&gt;&lt;/a&gt;架构
&lt;/h2&gt;&lt;p&gt;整体架构&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/001-327b8d9b.jpg"&gt;&lt;/p&gt;
&lt;p&gt;逻辑架构&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/002-02d1c42c.jpg"&gt;&lt;/p&gt;
&lt;h2 id="线程模型"&gt;&lt;a href="#%e7%ba%bf%e7%a8%8b%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;线程模型
&lt;/h2&gt;&lt;p&gt;在高性能的 I/O 设计中，有两个著名的模型：Reactor 模型和 Proactor 模型，其中 Reactor 模型用于同步 I/O，而 Proactor 模型运用于异步 I/O 操作。实际上 Netty 线程模型就是 Reactor 模型的一个实现。&lt;/p&gt;
&lt;h3 id="什么是-reactor"&gt;&lt;a href="#%e4%bb%80%e4%b9%88%e6%98%af-reactor" class="header-anchor"&gt;&lt;/a&gt;什么是 Reactor
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers.&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;Reactor 设计模式是一种事件处理模式，用于处理由一个或多个输入同时传递到服务处理程序的服务请求。然后，服务处理程序对传入的请求进行解复用，并将它们同步分派给关联的请求处理程序。&lt;/p&gt;
&lt;p&gt;以上来自 wiki, 我们可以看到以下重点。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;事件驱动（event handling）&lt;/li&gt;
&lt;li&gt;可以处理一个或多个输入源（one or more inputs）&lt;/li&gt;
&lt;li&gt;通过 Service Handler 同步的将输入事件（Event）采用多路复用分发给相应的 Request Handler（多个）处理&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;根据 Doug Lea 在 《Scalable IO in Java 》中的介绍，Reacotr 模型主要分为三个角色：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Reactor：把 IO 事件分配给对应的 handler 处理&lt;/li&gt;
&lt;li&gt;Acceptor：处理客户端连接事件&lt;/li&gt;
&lt;li&gt;Handler：处理非阻塞的任务&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Reactor 处理请求的流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;同步的等待多个事件源到达（采用 select() 实现）&lt;/li&gt;
&lt;li&gt;将事件多路分解以及分配相应的事件服务进行处理，这个分派采用 server 集中处理（dispatch）&lt;/li&gt;
&lt;li&gt;分解的事件以及对应的事件服务应用从分派服务中分离出去（handler）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;为什么使用 Reactor？&lt;/p&gt;
&lt;p&gt;传统阻塞 IO 模型的不足&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个连接都需要独立线程处理，当并发数大时，创建线程数多，占用资源&lt;/li&gt;
&lt;li&gt;采用阻塞 IO 模型，连接建立后，若当前线程没有数据可读，线程会阻塞在读操作上，造成资源浪费&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;针对传统阻塞 IO 模型的两个问题，可以采用如下的方案&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;基于池化思想，避免为每个连接创建线程，连接完成后将业务处理交给线程池处理&lt;/li&gt;
&lt;li&gt;基于 IO 复用模型，多个连接共用同一个阻塞对象，不用等待所有的连接。遍历到有新数据可以处理时，操作系统会通知程序，线程跳出阻塞状态，进行业务逻辑处理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Reactor 线程模型分类&lt;/p&gt;
&lt;p&gt;根据 Reactor 的数量和处理资源的线程数量的不同，分为三类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单 Reactor 单线程模型&lt;/li&gt;
&lt;li&gt;单 Reactor 多线程模型&lt;/li&gt;
&lt;li&gt;多 Reactor 多线程模型&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="单-reactor-单线程模型"&gt;&lt;a href="#%e5%8d%95-reactor-%e5%8d%95%e7%ba%bf%e7%a8%8b%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;单 Reactor 单线程模型
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/003-1d3374b5.jpg"&gt;&lt;/p&gt;
&lt;p&gt;消息处理流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Reactor 对象通过 select 监控连接事件，收到事件后通过 dispatch 进行转发。&lt;/li&gt;
&lt;li&gt;如果是连接建立的事件，则由 acceptor 接受连接，并创建 handler 处理后续事件。&lt;/li&gt;
&lt;li&gt;如果不是建立连接事件，则 Reactor 会分发调用 Handler 来响应。&lt;/li&gt;
&lt;li&gt;handler 会完成 read-&amp;gt;业务处理-&amp;gt;send 的完整业务流程。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;该线程模型的不足&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;仅用一个线程处理请求，对于多核资源机器来说是有点浪费的&lt;/li&gt;
&lt;li&gt;当处理读写任务的线程负载过高后，处理速度下降，事件会堆积，严重的会超时，可能导致客户端重新发送请求，性能越来越差&lt;/li&gt;
&lt;li&gt;单线程也会有可靠性的问题&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Redis 是由 C 语言实现的，它采用的正是「单 Reactor 单进程」的方案，因为 Redis 业务处理主要是在内存中完成，操作的速度是很快的，性能瓶颈不在 CPU 上，所以 Redis 对于命令的处理是单进程的方案。&lt;/p&gt;
&lt;p&gt;对于单线程的 Redis 来说，基于内存，且命令操作时间复杂度低，因此读写速率是非常快的。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/004-03a1ef09.jpg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/005-453f065a.jpg"&gt;&lt;/p&gt;
&lt;p&gt;针对上面的种种不足，就有了下面的线程模型&lt;/p&gt;
&lt;h3 id="单-reactor-多线程模型"&gt;&lt;a href="#%e5%8d%95-reactor-%e5%a4%9a%e7%ba%bf%e7%a8%8b%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;单 Reactor 多线程模型
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/006-3c1765e0.jpg"&gt;消息处理流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Reactor 对象通过 Select 监控客户端请求事件，收到事件后通过 dispatch 进行分发。&lt;/li&gt;
&lt;li&gt;如果是建立连接请求事件，则由 acceptor 通过 accept 处理连接请求，然后创建一个 Handler 对象处理连接完成后续的各种事件。&lt;/li&gt;
&lt;li&gt;如果不是建立连接事件，则 Reactor 会分发调用连接对应的 Handler 来响应。&lt;/li&gt;
&lt;li&gt;Handler 只负责响应事件，不做具体业务处理，通过 Read 读取数据后，会分发给后面的 Worker 线程池进行业务处理。&lt;/li&gt;
&lt;li&gt;Worker 线程池会分配独立的线程完成真正的业务处理，然后将响应结果发给 Handler 进行处理。&lt;/li&gt;
&lt;li&gt;Handler 收到响应结果后通过 send 将响应结果返回给 Client。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;相对于第一种模型来说，在处理业务逻辑，也就是获取到 IO 的读写事件之后，交由线程池来处理，handler 收到响应后通过 send 将响应结果返回给客户端。这样可以降低 Reactor 的性能开销，从而更专注的做事件分发工作了，提升整个应用的吞吐。&lt;/p&gt;
&lt;p&gt;但是这个模型存在的问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;多线程数据共享和访问比较复杂。如果子线程完成业务处理后，把结果传递给主线程 Reactor 进行发送，就会涉及共享数据的互斥和保护机制。&lt;/li&gt;
&lt;li&gt;Reactor 承担所有事件的监听和响应，只在主线程中运行，可能会存在性能问题。例如并发百万客户端连接，或者服务端需要对客户端握手进行安全认证，但是认证本身非常损耗性能。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;为了解决性能问题，产生了第三种主从 Reactor 多线程模型。&lt;/p&gt;
&lt;h3 id="主从-reactor-多线程模型"&gt;&lt;a href="#%e4%b8%bb%e4%bb%8e-reactor-%e5%a4%9a%e7%ba%bf%e7%a8%8b%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;主从 Reactor 多线程模型
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/007-6a178f92.jpg"&gt;&lt;/p&gt;
&lt;p&gt;比起第二种模型，它是将 Reactor 分成两部分：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;mainReactor 负责监听 server socket，用来处理网络 IO 连接建立操作，将建立的 socketChannel 指定注册给 subReactor。&lt;/li&gt;
&lt;li&gt;subReactor 主要做和建立起来的 socket 做数据交互和事件业务处理操作。通常，subReactor 个数上可与 CPU 个数等同。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Nginx、Memcached 和 Netty 都是采用这种实现。&lt;/p&gt;
&lt;p&gt;消息处理流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;从主线程池中随机选择一个 Reactor 线程作为 acceptor 线程，用于绑定监听端口，接收客户端连接&lt;/li&gt;
&lt;li&gt;acceptor 线程接收客户端连接请求之后创建新的 SocketChannel，将其注册到主线程池的其它 Reactor 线程上，由其负责接入认证、IP 黑白名单过滤、握手等操作&lt;/li&gt;
&lt;li&gt;步骤 2 完成之后，业务层的链路正式建立，将 SocketChannel 从主线程池的 Reactor 线程的多路复用器上摘除，重新注册到 Sub 线程池的线程上，并创建一个 Handler 用于处理各种连接事件&lt;/li&gt;
&lt;li&gt;当有新的事件发生时，SubReactor 会调用连接对应的 Handler 进行响应&lt;/li&gt;
&lt;li&gt;Handler 通过 Read 读取数据后，会分发给后面的 Worker 线程池进行业务处理&lt;/li&gt;
&lt;li&gt;Worker 线程池会分配独立的线程完成真正的业务处理，如何将响应结果发给 Handler 进行处理&lt;/li&gt;
&lt;li&gt;Handler 收到响应结果后通过 Send 将响应结果返回给 Client&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Reactor 三种模式形象比喻&lt;/p&gt;
&lt;p&gt;餐厅一般有接待员和服务员，接待员负责在门口接待顾客，服务员负责全程服务顾客&lt;/p&gt;
&lt;p&gt;Reactor 的三种线程模型可以用接待员和服务员类比&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;单 Reactor 单线程模型：接待员和服务员是同一个人，一直为顾客服务。客流量较少适合&lt;/li&gt;
&lt;li&gt;单 Reactor 多线程模型：一个接待员，多个服务员。客流量大，一个人忙不过来，由专门的接待员在门口接待顾客，然后安排好桌子后，由一个服务员一直服务，一般每个服务员负责一片中的几张桌子&lt;/li&gt;
&lt;li&gt;多 Reactor 多线程模型：多个接待员，多个服务员。这种就是客流量太大了，一个接待员忙不过来了&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="什么是-proactor"&gt;&lt;a href="#%e4%bb%80%e4%b9%88%e6%98%af-proactor" class="header-anchor"&gt;&lt;/a&gt;什么是 Proactor
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/008-c0d542c0.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Proactor 正是采用了异步 I/O 技术，所以被称为异步网络模型。&lt;/p&gt;
&lt;p&gt;无论是 Reactor，还是 Proactor，都是一种基于「事件分发」的网络编程模式，区别在于 Reactor 模式是基于「待完成」的 I/O 事件，而 Proactor 模式则是基于「已完成」的 I/O 事件。&lt;/p&gt;
&lt;p&gt;在 Linux 下的异步 I/O 是不完善的， &lt;code&gt;aio&lt;/code&gt; 系列函数是由 POSIX 定义的异步操作接口，不是真正的操作系统级别支持的，而是在用户空间模拟出来的异步，并且仅仅支持基于本地文件的 aio 异步操作，网络编程中的 socket 是不支持的，这也使得基于 Linux 的高性能网络程序都是使用 Reactor 方案。&lt;/p&gt;
&lt;p&gt;而 Windows 里实现了一套完整的支持 socket 的异步编程接口，这套接口就是 &lt;code&gt;IOCP&lt;/code&gt;，是由操作系统级别实现的异步 I/O，真正意义上异步 I/O，因此在 Windows 里实现高性能网络程序可以使用效率更高的 Proactor 方案。&lt;/p&gt;
&lt;h3 id="线程模型的应用"&gt;&lt;a href="#%e7%ba%bf%e7%a8%8b%e6%a8%a1%e5%9e%8b%e7%9a%84%e5%ba%94%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;线程模型的应用
&lt;/h3&gt;&lt;p&gt;Redis&lt;/p&gt;
&lt;p&gt;Redis 是典型的单 Reactor 单线程类型。Redis 主要通过 aeMain、aeProcessEvents，以及 aeCreateFileEvent 三个关键函数来实现 Reactor 模型，对源码 (Linux 系统）的整理如下：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;源码文件&lt;/th&gt;
 &lt;th&gt;函数&lt;/th&gt;
 &lt;th&gt;被调用点&lt;/th&gt;
 &lt;th&gt;主要功能&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;ae.c/ae.h&lt;/td&gt;
 &lt;td&gt;aeMain()&lt;/td&gt;
 &lt;td&gt;server.c 的 main()&lt;/td&gt;
 &lt;td&gt;事件捕获，分发和循环处理&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;ae.c/ae.h&lt;/td&gt;
 &lt;td&gt;aeProcessEvents()&lt;/td&gt;
 &lt;td&gt;ae.c 的 aeMain()&lt;/td&gt;
 &lt;td&gt;根据事件类型进行响应的处理&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;ae.c/ae.h&lt;/td&gt;
 &lt;td&gt;aeApiPoll()&lt;/td&gt;
 &lt;td&gt;ae.c 的 aeProcessEvents()&lt;/td&gt;
 &lt;td&gt;调用操作系统的 IO 多路方法&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux 内核文件&lt;/td&gt;
 &lt;td&gt;epoll_wait()&lt;/td&gt;
 &lt;td&gt;ae.c 的 aeApiPoll()&lt;/td&gt;
 &lt;td&gt;检测并返回内核中网络 IO 事件&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;nginx&lt;/p&gt;
&lt;p&gt;nginx 是多进程模型，master 进程不处理网络 IO，每个 Wroker 进程是一个独立的单 Reacotr 单线程模型。详情参考官方文档：Inside NGINX: Designed for Performance &amp;amp; Scalability&lt;/p&gt;
&lt;p&gt;Netty&lt;/p&gt;
&lt;p&gt;Netty 的线程模型主要是基于 Reactor 模型，但是可以灵活配置，单 reactor 单线程，单 reactor 多线程，和多 reactor 多线程模型。&lt;/p&gt;
&lt;p&gt;kafka&lt;/p&gt;
&lt;p&gt;kafka 采用的是主从 Reactor 多线程模型，因为 Kafka 主要与磁盘 IO 交互，因此真正的读写数据不是从 Reactor 处理的，而是有一个 worker 线程池，专门处理磁盘 IO，从 Reactor 负责网络 IO，然后把任务交给 worker 线程池处理。&lt;/p&gt;
&lt;h2 id="netty-线程模型"&gt;&lt;a href="#netty-%e7%ba%bf%e7%a8%8b%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;Netty 线程模型
&lt;/h2&gt;&lt;p&gt;上文说 Netty 就是采用 Reactor 模型实现的。下面是 Netty 使用中很常见的一段代码&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;throws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EventLoopGroup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bossGroup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NioEventLoopGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EventLoopGroup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;workerGroup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NioEventLoopGroup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ServerBootstrap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ServerBootstrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bossGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;workerGroup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NioServerSocketChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;childOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChannelOption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TCP_NODELAY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;childAttr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AttributeKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;childAttr&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;childAttrValue&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ServerHandler&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;childHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChannelInitializer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SocketChannel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;initChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SocketChannel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChannelFuture&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;8888&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;closeFuture&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bossGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shutdownGracefully&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;workerGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shutdownGracefully&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;boss 线程池作用：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;接收客户端的连接，初始化 Channel 参数。&lt;/li&gt;
&lt;li&gt;将链路状态变更时间通知给 ChannelPipeline。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;worker 线程池作用：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;异步读取通信对端的数据报，发送读事件到 ChannelPipeline。&lt;/li&gt;
&lt;li&gt;异步发送消息到通信对端，调用 ChannelPipeline 的消息发送接口。&lt;/li&gt;
&lt;li&gt;执行系统调用 Task。&lt;/li&gt;
&lt;li&gt;执行定时任务 Task。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通过配置 boss 和 worker 线程池的线程个数以及是否共享线程池等方式，Netty 的线程模型可以在以上三种 Reactor 模型之间进行切换。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/009-88eb9d75.jpg"&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/010-2d671bfc.jpg"&gt;&lt;/p&gt;
&lt;p&gt;netty 通过 Reactor 模型基于多路复用器接收并处理用户请求，内部实现了两个线程池，boss 线程池和 work 线程池：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;其中 boss 线程池的线程负责处理请求的 accept 事件，当接收到 accept 事件的请求时，把对应的 socket 封装到一个 NioSocketChannel 中，并交给 worker 线程池&lt;/li&gt;
&lt;li&gt;其中 worker 线程池负责请求的 read 和 write 事件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/011-bec30573.jpg"&gt;&lt;/p&gt;
&lt;h2 id="零拷贝"&gt;&lt;a href="#%e9%9b%b6%e6%8b%b7%e8%b4%9d" class="header-anchor"&gt;&lt;/a&gt;零拷贝
&lt;/h2&gt;&lt;p&gt;Netty 的接收和发送 ByteBuffer 采用 DIRECT BUFFERS，使用堆外内存直接读写。&lt;/p&gt;
&lt;p&gt;Netty 中的零拷贝和传统 Linux 的零拷贝不太一样。Netty 中的零拷贝技术除了操作系统级别的功能封装，更多的是面向用户态的数据操作优化，主要体现在以下 5 个方面：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;堆外内存，避免 JVM 堆内存到堆外内存的数据拷贝。&lt;/li&gt;
&lt;li&gt;CompositeByteBuf 类，可以组合多个 Buffer 对象合并成一个逻辑上的对象，避免通过传统内存拷贝的方式将几个 Buffer 合并成一个大的 Buffer。&lt;/li&gt;
&lt;li&gt;通过 Unpooled.wrappedBuffer 可以将 byte 数组包装成 ByteBuf 对象，包装过程中不会产生内存拷贝。&lt;/li&gt;
&lt;li&gt;ByteBuf.slice 操作与 Unpooled.wrappedBuffer 相反，slice 操作可以将一个 ByteBuf 对象切分成多个 ByteBuf 对象，切分过程中不会产生内存拷贝，底层共享一个 byte 数组的存储空间。&lt;/li&gt;
&lt;li&gt;Netty 使用 FileRegion 实现文件传输，FileRegion 底层封装了 &lt;code&gt;FileChannel#transferTo()&lt;/code&gt; 方法，可以将文件缓冲区的数据直接传输到目标 Channel，避免内核缓冲区和用户态缓冲区之间的数据拷贝，这属于操作系统级别的零拷贝。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;堆外内存&lt;/p&gt;
&lt;p&gt;如果在 JVM 内部执行 I/O 操作时，必须将数据拷贝到堆外内存，才能执行系统调用。这是所有 VM 语言都会存在的问题。那么为什么操作系统不能直接使用 JVM 堆内存进行 I/O 的读写呢？主要有两点原因：第一，操作系统并不感知 JVM 的堆内存，而且 JVM 的内存布局与操作系统所分配的是不一样的，操作系统并不会按照 JVM 的行为来读写数据。第二，同一个对象的内存地址随着 JVM GC 的执行可能会随时发生变化，例如 JVM GC 的过程中会通过压缩来减少内存碎片，这就涉及对象移动的问题了。&lt;/p&gt;
&lt;p&gt;Netty 在进行 I/O 操作时都是使用的堆外内存，可以避免数据从 JVM 堆内存到堆外内存的拷贝。&lt;/p&gt;
&lt;h2 id="nio-epoll-bug"&gt;&lt;a href="#nio-epoll-bug" class="header-anchor"&gt;&lt;/a&gt;NIO epoll bug
&lt;/h2&gt;&lt;p&gt;JDK 的 NIO 类库有一个 epoll 死循环 bug，它会导致 Selector 空轮询，IO 线程 CPU 达到 100%，严重影响系统运行。&lt;/p&gt;
&lt;p&gt;netty 从 api 使用层面对该 bug 进行了规避解决， 重建 Selector。当发生 epoll bug，则创建一个新的 Selector，将出现 bug 的 Selector 上的 channel 重新注册到新的 Selector 上，关闭 bug 的 Selector，使用新的 Selector 进行替换。&lt;/p&gt;
&lt;p&gt;具体策略：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对 Selector 的 select 操作周期进行统计。&lt;/li&gt;
&lt;li&gt;每完成一次空的 select 操作进行一次计数。&lt;/li&gt;
&lt;li&gt;在某个周期内如果连续 N 次空轮询，则说明触发了 JDK NIO 的 epoll 死循环 bug。&lt;/li&gt;
&lt;li&gt;创建新的 Selector，将出现 bug 的 Selector 上的 channel 重新注册到新的 Selector 上。&lt;/li&gt;
&lt;li&gt;关闭 bug 的 Selector，使用新的 Selector 进行替换。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果发生 epoll 死循环 bug，那么当前 I/O 线程将停下来进行 bug 的修复，然后再继续进行逻辑处理，bug 的修复是非阻塞操作，处理速度非常快；而且 netty 线程池中一般设置有多个 I/O 线程，其中某个 I/O 线程中的 Selector 触发 bug 并不会影响其他 I/O 线程运行，所以 netty 通过这种策略，在几乎不影响性能的情况下从 api 使用层面规避解决了该 bug。&lt;/p&gt;
&lt;h2 id="粘包拆包"&gt;&lt;a href="#%e7%b2%98%e5%8c%85%e6%8b%86%e5%8c%85" class="header-anchor"&gt;&lt;/a&gt;粘包拆包
&lt;/h2&gt;&lt;p&gt;TCP 层可能会出现当次接收到的数据是不完整数据的情况。出现粘包可能的原因有：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;发送方每次写入数据 &amp;lt; 套接字缓冲区大小；&lt;/li&gt;
&lt;li&gt;接收方读取套接字缓冲区数据不够及时。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;出现半包的可能原因有：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;发送方每次写入数据 &amp;gt; 套接字缓冲区大小；&lt;/li&gt;
&lt;li&gt;发送的数据大于协议 MTU，所以必须要拆包。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;解决问题肯定不是在 4 层来做而是在应用层，通过定义通信协议来解决粘包和拆包的问题。发送方 和 接收方约定某个规则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当发生粘包的时候通过某种约定来拆包；&lt;/li&gt;
&lt;li&gt;如果在拆包，通过某种约定来将数据组成一个完整的包处理。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="业界常用解决方案"&gt;&lt;a href="#%e4%b8%9a%e7%95%8c%e5%b8%b8%e7%94%a8%e8%a7%a3%e5%86%b3%e6%96%b9%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;业界常用解决方案
&lt;/h4&gt;&lt;p&gt;定长协议&lt;/p&gt;
&lt;p&gt;指定一个报文具有固定长度。比如约定一个报文的长度是 5 字节，那么：报文：1234，只有 4 字节，但是还差一个怎么办呢，不足部分用空格补齐。就变为：1234 。如果不补齐空格，那么就会读到下一个报文的字节来填充上一个报文直到补齐为止，这样粘包了。定长协议的优点是使用简单，缺点很明显：浪费带宽。&lt;/p&gt;
&lt;p&gt;Netty 中提供了 &lt;code&gt;FixedLengthFrameDecoder&lt;/code&gt; ，支持把固定的长度的字节数当做一个完整的消息进行解码。&lt;/p&gt;
&lt;p&gt;特殊字符分割协议&lt;/p&gt;
&lt;p&gt;很好理解，在每一个你认为是一个完整的包的尾部添加指定的特殊字符，比如：\n，\r 等等。需要注意的是：约定的特殊字符要保证唯一性，不能出现在报文的正文中，否则就将正文一分为二了。&lt;/p&gt;
&lt;p&gt;Netty 中提供了 &lt;code&gt;DelimiterBasedFrameDecoder&lt;/code&gt; 根据特殊字符进行解码，&lt;code&gt;LineBasedFrameDecoder&lt;/code&gt;默认以换行符作为分隔符。&lt;/p&gt;
&lt;p&gt;变长协议&lt;/p&gt;
&lt;p&gt;变长协议的核心就是：将消息分为消息头和消息体，消息头中标识当前完整的消息体长度。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;发送方在发送数据之前先获取数据的二进制字节大小，然后在消息体前面添加消息大小；&lt;/li&gt;
&lt;li&gt;接收方在解析消息时先获取消息大小，之后必须读到该大小的字节数才认为是完整的消息。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Netty 中提供了 &lt;code&gt;LengthFieldBasedFrameDecoder&lt;/code&gt; ，通过&lt;code&gt;LengthFieldPrepender&lt;/code&gt; 来给实际的消息体添加 length 字段。&lt;/p&gt;
&lt;h4 id="netty-提供的能力"&gt;&lt;a href="#netty-%e6%8f%90%e4%be%9b%e7%9a%84%e8%83%bd%e5%8a%9b" class="header-anchor"&gt;&lt;/a&gt;Netty 提供的能力
&lt;/h4&gt;&lt;p&gt;为了解决网络数据流的拆包粘包问题，Netty 为我们内置了如下的解码器：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ByteToMessageDecoder：如果想实现自己的半包解码器，实现该类；&lt;/li&gt;
&lt;li&gt;MessageToMessageDecoder：一般作为二次解码器，当我们在 ByteToMessageDecoder 将一个 bytes 数组转换成一个 java 对象的时候，我们可能还需要将这个对象进行二次解码成其他对象，我们就可以继承这个类；&lt;/li&gt;
&lt;li&gt;LineBasedFrameDecoder：通过在包尾添加回车换行符 &lt;code&gt;\r\n&lt;/code&gt; 来区分整包消息；&lt;/li&gt;
&lt;li&gt;StringDecoder：字符串解码器；&lt;/li&gt;
&lt;li&gt;DelimiterBasedFrameDecoder：特殊字符作为分隔符来区分整包消息；&lt;/li&gt;
&lt;li&gt;FixedLengthFrameDecoder：报文大小固定长度，不够空格补全；&lt;/li&gt;
&lt;li&gt;ProtoBufVarint32FrameDecoder：通过 Protobuf 解码器来区分整包消息；&lt;/li&gt;
&lt;li&gt;ProtobufDecoder：Protobuf 解码器；&lt;/li&gt;
&lt;li&gt;LengthFieldBasedFrameDecoder：指定长度来标识整包消息，通过在包头指定整包长度来约定包长。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Netty 还内置了如下的编码器：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ProtobufEncoder：Protobuf 编码器；&lt;/li&gt;
&lt;li&gt;MessageToByteEncoder：将 Java 对象编码成 ByteBuf；&lt;/li&gt;
&lt;li&gt;MessageToMessageEncoder：如果不想将 Java 对象编码成 ByteBuf，而是自定义类就继承这个；&lt;/li&gt;
&lt;li&gt;LengthFieldPrepender：LengthFieldPrepender 是一个非常实用的工具类，如果我们在发送消息的时候采用的是：消息长度字段+原始消息的形式，那么我们就可以使用 LengthFieldPrepender。这是因为 LengthFieldPrepender 可以将待发送消息的长度（二进制字节长度）写到 ByteBuf 的前两个字节。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/012-35833b92.jpg"&gt;&lt;/p&gt;
&lt;h3 id="udp-是否会发生粘包或拆包的现象呢"&gt;&lt;a href="#udp-%e6%98%af%e5%90%a6%e4%bc%9a%e5%8f%91%e7%94%9f%e7%b2%98%e5%8c%85%e6%88%96%e6%8b%86%e5%8c%85%e7%9a%84%e7%8e%b0%e8%b1%a1%e5%91%a2" class="header-anchor"&gt;&lt;/a&gt;UDP 是否会发生粘包或拆包的现象呢？
&lt;/h3&gt;&lt;p&gt;答案是不会。&lt;/p&gt;
&lt;p&gt;UDP 是基于报文发送的，从 UDP 的帧结构可以看出，在 UDP 首部采用了 16bit 来指示 UDP 数据报文的长度，因此在应用层能很好的将不同的数据报文区分开，从而避免粘包和拆包的问题。&lt;/p&gt;
&lt;p&gt;而TCP 是基于字节流的，虽然应用层和 TCP 传输层之间的数据交互是大小不等的数据块，但是 TCP 把这些数据块仅仅看成一连串无结构的字节流，没有边界；另外从 TCP 的帧结构也可以看出，在 TCP 的首部没有表示数据长度的字段，基于上面两点，在使用 TCP 传输数据时，才有粘包或者拆包现象发生的可能。&lt;/p&gt;
&lt;h2 id="api"&gt;&lt;a href="#api" class="header-anchor"&gt;&lt;/a&gt;API
&lt;/h2&gt;&lt;h3 id="bytebuf"&gt;&lt;a href="#bytebuf" class="header-anchor"&gt;&lt;/a&gt;ByteBuf
&lt;/h3&gt;&lt;p&gt;ByteBuf 是一个字节容器，内部是一个字节数组。从逻辑上来分，字节容器内部，可以分为四个部分：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/013-fb19e073.png"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一个部分是已经丢弃的字节，这部分数据是无效的；&lt;/li&gt;
&lt;li&gt;第二部分是可读字节，这部分数据是 ByteBuf 的主体数据， 从 ByteBuf 里面读取的数据都来自这一部分；&lt;/li&gt;
&lt;li&gt;第三部分的数据是可写字节，所有写到 ByteBuf 的数据都会写到这一段。&lt;/li&gt;
&lt;li&gt;第四部分的字节，表示的是该 ByteBuf 最多还能扩容的大小。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;四个部分的逻辑功能，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/014-baeab11b.png"&gt;&lt;/p&gt;
&lt;p&gt;ByteBuf 通过三个整型的指针（index），有效地区分可读数据和可写数据，使得读写之间相互没有冲突。&lt;/p&gt;
&lt;p&gt;这三个指针，分别是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;readerIndex（读指针）&lt;/li&gt;
&lt;li&gt;writerIndex（写指针）&lt;/li&gt;
&lt;li&gt;maxCapacity（最大容量）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/015-a578c322.png"&gt;&lt;/p&gt;
&lt;p&gt;这三个指针，是三个 int 型的成员属性，定义在 AbstractByteBuf 抽象基类中。三个指针的代码截图，如下：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/016-f2c99b83.png"&gt;&lt;/p&gt;
&lt;p&gt;readerIndex 读指针&lt;/p&gt;
&lt;p&gt;指示读取的起始位置。&lt;/p&gt;
&lt;p&gt;每读取一个字节，readerIndex 自增 1 。一旦 readerIndex 与 writerIndex 相等，ByteBuf 不可读 。&lt;/p&gt;
&lt;p&gt;writerIndex 写指针&lt;/p&gt;
&lt;p&gt;指示写入的起始位置。&lt;/p&gt;
&lt;p&gt;每写一个字节，writerIndex 自增 1。一旦增加到 writerIndex 与 capacity（） 容量相等，表示 ByteBuf 已经不可写了 。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;capacity（）容量不是一个成员属性，是一个成员方法。表示 ByteBuf 内部的总容量。注意，这个不是最大容量。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;maxCapacity 最大容量&lt;/p&gt;
&lt;p&gt;指示可以 ByteBuf 扩容的最大容量。&lt;/p&gt;
&lt;p&gt;当向 ByteBuf 写数据的时候，如果容量不足，可以进行扩容。&lt;/p&gt;
&lt;p&gt;扩容的最大限度，直到 capacity（） 扩容到 maxCapacity 为止，超过 maxCapacity 就会报错。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;capacity() 扩容的操作，是底层自动进行的。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;从三个维度三大系列，介绍 ByteBuf 的常用 API 方法。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/017-f9aef661.png"&gt;&lt;/p&gt;
&lt;p&gt;第一组：容量系列&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;方法 一：capacity()&lt;/p&gt;
&lt;p&gt;表示 ByteBuf 的容量，包括丢弃的字节数、可读字节数、可写字节数。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;方法二：maxCapacity()&lt;/p&gt;
&lt;p&gt;表示 ByteBuf 底层最大能够占用的最大字节数。当向 ByteBuf 中写数据的时候，如果发现容量不足，则进行扩容，直到扩容到 maxCapacity。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;第二组：写入系列&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;方法一：isWritable()&lt;/p&gt;
&lt;p&gt;表示 ByteBuf 是否可写。如果 capacity（） 容量大于 writerIndex 指针的位置 ，则表示可写。否则为不可写。&lt;/p&gt;
&lt;p&gt;isWritable() 的源码，也是很简单的。具体如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;public boolean isWritable() {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; return this.capacity() &amp;gt; this.writerIndex;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;} 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;注意：如果 isWritable() 返回 false，并不代表不能往 ByteBuf 中写数据了。如果 Netty 发现往 ByteBuf 中写数据写不进去的话，会自动扩容 ByteBuf。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;方法二：writableBytes()&lt;/p&gt;
&lt;p&gt;返回表示 ByteBuf 当前可写入的字节数，它的值等于 capacity（）- writerIndex。&lt;/p&gt;
&lt;p&gt;如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/018-9671f438.png"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;方法三：maxWritableBytes()&lt;/p&gt;
&lt;p&gt;返回可写的最大字节数，它的值等于 maxCapacity-writerIndex 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;方法四：writeBytes(byte[] src)&lt;/p&gt;
&lt;p&gt;把字节数组 src 里面的数据全部写到 ByteBuf。&lt;/p&gt;
&lt;p&gt;这个是最为常用的一个方法。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;方法五：writeTYPE(TYPE value） 基础类型写入方法&lt;/p&gt;
&lt;p&gt;基础数据类型的写入，包含了 8 大基础类型的写入。&lt;/p&gt;
&lt;p&gt;具体如下：writeByte()、 writeBoolean()、writeChar()、writeShort()、writeInt()、writeLong()、writeFloat()、writeDouble() ，向 ByteBuf 写入基础类型的数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;方法六：setType(TYPE value）基础类型写入，不改变指针值&lt;/p&gt;
&lt;p&gt;基础数据类型的写入，包含了 8 大基础类型的写入。&lt;/p&gt;
&lt;p&gt;具体如下：setByte()、 setBoolean()、setChar()、setShort()、setInt()、setLong()、setFloat()、setDouble() ，向 ByteBuf 写入基础类型的数据。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;setType 系列与 writeType 系列的不同：&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;setType 系列 不会 改变写指针 writerIndex ；&lt;/p&gt;
&lt;p&gt;writeTYPE 系列 会 改变写指针 writerIndex 的值。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;方法七：markWriterIndex() 与 resetWriterIndex()&lt;/p&gt;
&lt;p&gt;这里两个方法一起介绍。&lt;/p&gt;
&lt;p&gt;前一个方法，表示把当前的写指针 writerIndex 保存在 markedWriterIndex 属性中 后一个方法，表示把当前的写指针 writerIndex 恢复到之前保存的 markedWriterIndex 值 。&lt;/p&gt;
&lt;p&gt;标记 markedWriterIndex 属性， 定义在 AbstractByteBuf 抽象基类中。&lt;/p&gt;
&lt;p&gt;截图如下：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/019-1920a09a.png"&gt;&lt;/p&gt;
&lt;p&gt;第三组：读取系列&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;方法一：isReadable()&lt;/p&gt;
&lt;p&gt;表示 ByteBuf 是否可读。如果 writerIndex 指针的值大于 readerIndex 指针的值 ，则表示可读。否则为不可写。&lt;/p&gt;
&lt;p&gt;isReadable() 的源码，也是很简单的。具体如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;public boolean isReadable() {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; return this.writerIndex &amp;gt; this.readerIndex;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;} 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;方法二：readableBytes()&lt;/p&gt;
&lt;p&gt;返回表示 ByteBuf 当前可读取的字节数，它的值等于 writerIndex - readerIndex 。&lt;/p&gt;
&lt;p&gt;如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/020-43b20a10.png"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;方法三：readBytes(byte[] dst)&lt;/p&gt;
&lt;p&gt;把 ByteBuf 里面的数据全部读取到 dst 字节数组中，这里 dst 字节数组的大小通常等于 readableBytes() 。这个方法，也是最为常用的一个方法。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;方法四：readType(） 基础类型读取&lt;/p&gt;
&lt;p&gt;基础数据类型的读取，可以读取 8 大基础类型。&lt;/p&gt;
&lt;p&gt;具体如下：readByte()、readBoolean()、readChar()、readShort()、readInt()、readLong()、readFloat()、readDouble() ，从 ByteBuf 读取对应的基础类型的数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;方法五：getTYPE(TYPE value）基础类型读取，不改变指针值&lt;/p&gt;
&lt;p&gt;基础数据类型的读取，可以读取 8 大基础类型。&lt;/p&gt;
&lt;p&gt;具体如下：getByte()、 getBoolean()、getChar()、getShort()、getInt()、getLong()、getFloat()、getDouble() ，从 ByteBuf 读取对应的基础类型的数据。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;getType 系列与 readTYPE 系列的不同：&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;getType 系列 不会 改变读指针 readerIndex ；&lt;/p&gt;
&lt;p&gt;readTYPE 系列 会 改变读指针 readerIndex 的值。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;方法六：markReaderIndex() 与 resetReaderIndex()&lt;/p&gt;
&lt;p&gt;前一个方法，表示把当前的读指针 ReaderIndex 保存在 markedReaderIndex 属性中。&lt;/p&gt;
&lt;p&gt;后一个方法，表示把当前的读指针 ReaderIndex 恢复到之前保存的 markedReaderIndex 值 。&lt;/p&gt;
&lt;p&gt;标记 markedReaderIndex 属性， 定义在 AbstractByteBuf 抽象基类中。&lt;/p&gt;
&lt;p&gt;截图如下：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/021-21dea289.png"&gt;&lt;/p&gt;
&lt;h3 id="pipeline"&gt;&lt;a href="#pipeline" class="header-anchor"&gt;&lt;/a&gt;pipeline
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;ChannelPipeline 和 ChannelHandler 机制类似于 servlet 和 Filter 过滤器，这类拦截器实际上是职责链模式的一种变形，主要为了方便事件的拦截和用户业务逻辑的定制。&lt;/li&gt;
&lt;li&gt;ChannelPipeline 实际上是一个 ChannelHandler 的容器，内部维护了一个 ChnnelHandler 的链表和迭代器，可以方便地实现 ChannelHandler 查找、添加、替换和删除。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-03-21-zai-liao-liao-netty/022-b7014433.jpg"&gt;&lt;/p&gt;
&lt;h2 id="参考"&gt;&lt;a href="#%e5%8f%82%e8%80%83" class="header-anchor"&gt;&lt;/a&gt;参考
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://pdai.tech/md/java/io/java-io-aio.html" target="_blank" rel="noopener"
 &gt;https://pdai.tech/md/java/io/java-io-aio.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://mp.weixin.qq.com/s?__biz=MzI3Njk5ODg4OQ==&amp;amp;mid=2247484273&amp;amp;idx=1&amp;amp;sn=a63b992284766a920e0c15ed821e5b02&amp;amp;chksm=eb6dbcf7dc1a35e176706dc3b54f3e4e6fbd53c552868e0162fe8c693ac9b42255d591c5d38a&amp;amp;token=586356569&amp;amp;lang=zh_CN&amp;amp;scene=21#wechat_redirect" target="_blank" rel="noopener"
 &gt;https://mp.weixin.qq.com/s?__biz=MzI3Njk5ODg4OQ==&amp;amp;mid=2247484273&amp;amp;idx=1&amp;amp;sn=a63b992284766a920e0c15ed821e5b02&amp;amp;chksm=eb6dbcf7dc1a35e176706dc3b54f3e4e6fbd53c552868e0162fe8c693ac9b42255d591c5d38a&amp;amp;token=586356569&amp;amp;lang=zh_CN#rd&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="http://yuanjava.cn/archives/-zhong-bang-tui-jian--yi-wen-pou-xi-reactormo-xing" target="_blank" rel="noopener"
 &gt;http://yuanjava.cn/archives/-zhong-bang-tui-jian--yi-wen-pou-xi-reactormo-xing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.nginx.com/blog/inside-nginx-how-we-designed-for-performance-scale/" target="_blank" rel="noopener"
 &gt;https://www.nginx.com/blog/inside-nginx-how-we-designed-for-performance-scale/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.artima.com/articles/comparing-two-high-performance-io-design-patterns" target="_blank" rel="noopener"
 &gt;https://www.artima.com/articles/comparing-two-high-performance-io-design-patterns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="http://lse.sourceforge.net/io/aio.html" target="_blank" rel="noopener"
 &gt;http://lse.sourceforge.net/io/aio.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="http://blogxin.cn/2017/03/20/Netty-epollbug/" target="_blank" rel="noopener"
 &gt;http://blogxin.cn/2017/03/20/Netty-epollbug/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://edgar615.github.io/netty-architecture.html" target="_blank" rel="noopener"
 &gt;https://edgar615.github.io/netty-architecture.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://cloud.tencent.com/developer/article/1754078" target="_blank" rel="noopener"
 &gt;https://cloud.tencent.com/developer/article/1754078&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.cnblogs.com/rickiyang/p/12904552.html" target="_blank" rel="noopener"
 &gt;https://www.cnblogs.com/rickiyang/p/12904552.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.cnblogs.com/crazymakercircle/p/9979897.html" target="_blank" rel="noopener"
 &gt;https://www.cnblogs.com/crazymakercircle/p/9979897.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://learn.lianglianglee.com/" target="_blank" rel="noopener"
 &gt;https://learn.lianglianglee.com/&lt;/a&gt;专栏/Netty 核心原理剖析与 RPC 实践-完/16 IO 加速：与众不同的 Netty 零拷贝技术.md&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>自顶向下学习 RocketMQ（一）: QuickStart</title><link>https://xiaobox.github.io/p/2021-11-30-zi-ding-xiang-xia-xue-xi-rocketmq-yi-quickstart/</link><pubDate>Tue, 30 Nov 2021 06:42:12 +0000</pubDate><guid>https://xiaobox.github.io/p/2021-11-30-zi-ding-xiang-xia-xue-xi-rocketmq-yi-quickstart/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-30-zi-ding-xiang-xia-xue-xi-rocketmq-yi-quickstart/cover.jpg" alt="Featured image of post 自顶向下学习 RocketMQ（一）: QuickStart" /&gt;&lt;h2 id="安装部署-rocketmq"&gt;&lt;a href="#%e5%ae%89%e8%a3%85%e9%83%a8%e7%bd%b2-rocketmq" class="header-anchor"&gt;&lt;/a&gt;安装部署 RocketMQ
&lt;/h2&gt;&lt;p&gt;采用源码编译安装，注意请提前将 maven 安装调试好。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;操作系统 macOS&lt;/li&gt;
&lt;li&gt;RocketMQ 版本：4.9.2&lt;/li&gt;
&lt;li&gt;Maven 版本 3.3.9&lt;/li&gt;
&lt;li&gt;JDK 版本 1.8.0_181&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以下安装是单 Master 模式，非集群模式，仅适用于本地开发和测试环境。&lt;/p&gt;
&lt;h3 id="下载源码包"&gt;&lt;a href="#%e4%b8%8b%e8%bd%bd%e6%ba%90%e7%a0%81%e5%8c%85" class="header-anchor"&gt;&lt;/a&gt;下载源码包
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;wget https://dlcdn.apache.org/rocketmq/4.9.2/rocketmq-all-4.9.2-source-release.zip
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="解压源码并进行编译"&gt;&lt;a href="#%e8%a7%a3%e5%8e%8b%e6%ba%90%e7%a0%81%e5%b9%b6%e8%bf%9b%e8%a1%8c%e7%bc%96%e8%af%91" class="header-anchor"&gt;&lt;/a&gt;解压源码并进行编译
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&amp;gt; unzip rocketmq-all-4.9.2-source-release.zip
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&amp;gt; cd rocketmq-all-4.9.2/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&amp;gt; mvn -Prelease-all -DskipTests clean install -U
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;&amp;gt; cd distribution/target/rocketmq-4.9.2/rocketmq-4.9.2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="启动-name-server"&gt;&lt;a href="#%e5%90%af%e5%8a%a8-name-server" class="header-anchor"&gt;&lt;/a&gt;启动 Name Server
&lt;/h3&gt;&lt;p&gt;在上一步的目录下，执行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&amp;gt; nohup sh bin/mqnamesrv &amp;amp;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&amp;gt; tail -f ~/logs/rocketmqlogs/namesrv.log
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;021-11-24 16:50:14 INFO main - tls.client.keyPassword = null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;2021-11-24 16:50:14 INFO main - tls.client.certPath = null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;2021-11-24 16:50:14 INFO main - tls.client.authServer = false
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;2021-11-24 16:50:14 INFO main - tls.client.trustCertPath = null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;2021-11-24 16:50:14 INFO main - Using JDK SSL provider
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;2021-11-24 16:50:15 INFO main - SSLContext created for server
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;2021-11-24 16:50:15 INFO NettyEventExecutor - NettyEventExecutor service started
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;2021-11-24 16:50:15 INFO main - Try to start service thread:FileWatchService started:false lastThread:null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;2021-11-24 16:50:15 INFO FileWatchService - FileWatchService service started
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;2021-11-24 16:50:15 INFO main - The Name Server boot success. serializeType=JSON
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="启动-broker"&gt;&lt;a href="#%e5%90%af%e5%8a%a8-broker" class="header-anchor"&gt;&lt;/a&gt;启动 Broker
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&amp;gt; nohup sh bin/mqbroker -n localhost:9876 &amp;amp;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&amp;gt; tail -f ~/logs/rocketmqlogs/broker.log 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;2021-11-24 16:52:22 INFO main - The broker[localhost, 10.3.10.244:10911] boot success. serializeType=JSON and name server is localhost:9876
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;2021-11-24 16:52:32 INFO BrokerControllerScheduledThread1 - dispatch behind commit log 0 bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt;2021-11-24 16:52:32 INFO BrokerControllerScheduledThread1 - Slave fall behind master: 202890 bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;7&lt;/span&gt;&lt;span class="cl"&gt;2021-11-24 16:52:32 INFO brokerOutApi_thread_2 - register broker[0]to name server localhost:9876 OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;8&lt;/span&gt;&lt;span class="cl"&gt;2021-11-24 16:53:02 INFO brokerOutApi_thread_3 - register broker[0]to name server localhost:9876 OK
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="安装-rocketmq-dashboard"&gt;&lt;a href="#%e5%ae%89%e8%a3%85-rocketmq-dashboard" class="header-anchor"&gt;&lt;/a&gt;安装 RocketMQ-Dashboard
&lt;/h2&gt;&lt;p&gt;可视化工具，可以在浏览器中查看消息队列的状态。&lt;/p&gt;
&lt;p&gt;同样用源码安装，当然也可以用 docker 安装。&lt;/p&gt;
&lt;h3 id="下载源码包-1"&gt;&lt;a href="#%e4%b8%8b%e8%bd%bd%e6%ba%90%e7%a0%81%e5%8c%85-1" class="header-anchor"&gt;&lt;/a&gt;下载源码包
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt; wget https://github.com/apache/rocketmq-dashboard/archive/refs/tags/rocketmq-dashboard-1.0.0.zip
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="编译运行"&gt;&lt;a href="#%e7%bc%96%e8%af%91%e8%bf%90%e8%a1%8c" class="header-anchor"&gt;&lt;/a&gt;编译运行
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&amp;gt; unzip rocketmq-dashboard-1.0.0.zip
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&amp;gt; cd rocketmq-dashboard-rocketmq-dashboard-1.0.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;&amp;gt; mvn spring-boot:run
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这时候会出现如下错误：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Caused&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rocketmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remoting&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RemotingConnectException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;connect&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rocketmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remoting&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;netty&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NettyRemotingClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invokeSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NettyRemotingClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;394&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rocketmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;impl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MQClientAPIImpl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getBrokerClusterInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MQClientAPIImpl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1333&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rocketmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultMQAdminExtImpl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;examineBrokerClusterInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DefaultMQAdminExtImpl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;306&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rocketmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultMQAdminExt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;examineBrokerClusterInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DefaultMQAdminExt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;257&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rocketmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MQAdminExtImpl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;examineBrokerClusterInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MQAdminExtImpl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rocketmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MQAdminExtImpl&lt;/span&gt;&lt;span class="o"&gt;$$&lt;/span&gt;&lt;span class="n"&gt;FastClassBySpringCGLIB&lt;/span&gt;&lt;span class="o"&gt;$$&lt;/span&gt;&lt;span class="n"&gt;a15c4ca6&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;generated&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;springframework&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cglib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodProxy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MethodProxy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;218&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;springframework&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aop&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;framework&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CglibAopProxy&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;CglibMethodInvocation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invokeJoinpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CglibAopProxy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;769&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;springframework&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aop&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;framework&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReflectiveMethodInvocation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;proceed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ReflectiveMethodInvocation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;163&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;springframework&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aop&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;framework&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CglibAopProxy&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;CglibMethodInvocation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;proceed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CglibAopProxy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;747&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;springframework&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aop&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aspectj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodInvocationProceedingJoinPoint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;proceed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MethodInvocationProceedingJoinPoint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rocketmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aspect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MQAdminAspect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aroundMQAdminMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MQAdminAspect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;sun&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NativeMethodAccessorImpl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoke0&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Native&lt;/span&gt; &lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;sun&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NativeMethodAccessorImpl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NativeMethodAccessorImpl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;sun&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DelegatingMethodAccessorImpl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DelegatingMethodAccessorImpl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;43&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;498&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;明显是连不上服务器，需要修改配置文件：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&amp;gt; cd src/main/resources/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;打开 application.properties 文件&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;#
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;# Licensed to the Apache Software Foundation (ASF) under one or more
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;# contributor license agreements. See the NOTICE file distributed with
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;# this work for additional information regarding copyright ownership.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;# The ASF licenses this file to You under the Apache License, Version 2.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;# (the &amp;#34;License&amp;#34;); you may not use this file except in compliance with
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;# the License. You may obtain a copy of the License at
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;#
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;# http://www.apache.org/licenses/LICENSE-2.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;#
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;# Unless required by applicable law or agreed to in writing, software
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;# distributed under the License is distributed on an &amp;#34;AS IS&amp;#34; BASIS,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;# See the License for the specific language governing permissions and
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;# limitations under the License.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;#
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;server.address=0.0.0.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;server.port=8080
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt;### SSL setting
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt;#server.ssl.key-store=classpath:rmqcngkeystore.jks
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt;#server.ssl.key-store-password=rocketmq
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt;#server.ssl.keyStoreType=PKCS12
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt;#server.ssl.keyAlias=rmqcngkey
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;26&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;27&lt;/span&gt;&lt;span class="cl"&gt;#spring.application.index=true
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;28&lt;/span&gt;&lt;span class="cl"&gt;spring.application.name=rocketmq-dashboard
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;29&lt;/span&gt;&lt;span class="cl"&gt;spring.http.encoding.charset=UTF-8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;30&lt;/span&gt;&lt;span class="cl"&gt;spring.http.encoding.enabled=true
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;31&lt;/span&gt;&lt;span class="cl"&gt;spring.http.encoding.force=true
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;32&lt;/span&gt;&lt;span class="cl"&gt;logging.level.root=INFO
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;33&lt;/span&gt;&lt;span class="cl"&gt;logging.config=classpath:logback.xml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;34&lt;/span&gt;&lt;span class="cl"&gt;#if this value is empty,use env value rocketmq.config.namesrvAddr NAMESRV_ADDR | now, you can set it in ops page.default localhost:9876
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;35&lt;/span&gt;&lt;span class="cl"&gt;rocketmq.config.namesrvAddr=
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;36&lt;/span&gt;&lt;span class="cl"&gt;#if you use rocketmq version &amp;lt; 3.5.8, rocketmq.config.isVIPChannel should be false.default true
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;37&lt;/span&gt;&lt;span class="cl"&gt;rocketmq.config.isVIPChannel=
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;38&lt;/span&gt;&lt;span class="cl"&gt;#timeout for mqadminExt, default 5000ms
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;39&lt;/span&gt;&lt;span class="cl"&gt;rocketmq.config.timeoutMillis=
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;40&lt;/span&gt;&lt;span class="cl"&gt;#rocketmq-console&amp;#39;s data path:dashboard/monitor
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;41&lt;/span&gt;&lt;span class="cl"&gt;rocketmq.config.dataPath=/tmp/rocketmq-console/data
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;42&lt;/span&gt;&lt;span class="cl"&gt;#set it false if you don&amp;#39;t want use dashboard.default true
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;43&lt;/span&gt;&lt;span class="cl"&gt;rocketmq.config.enableDashBoardCollect=true
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;44&lt;/span&gt;&lt;span class="cl"&gt;#set the message track trace topic if you don&amp;#39;t want use the default one
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;45&lt;/span&gt;&lt;span class="cl"&gt;rocketmq.config.msgTrackTopicName=
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;46&lt;/span&gt;&lt;span class="cl"&gt;rocketmq.config.ticketKey=ticket
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;47&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;48&lt;/span&gt;&lt;span class="cl"&gt;#Must create userInfo file: ${rocketmq.config.dataPath}/users.properties if the login is required
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;49&lt;/span&gt;&lt;span class="cl"&gt;rocketmq.config.loginRequired=false
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;50&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;51&lt;/span&gt;&lt;span class="cl"&gt;#set the accessKey and secretKey if you used acl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;52&lt;/span&gt;&lt;span class="cl"&gt;#rocketmq.config.accessKey=
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;53&lt;/span&gt;&lt;span class="cl"&gt;#rocketmq.config.secretKey=
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;54&lt;/span&gt;&lt;span class="cl"&gt;rocketmq.config.useTLS=false
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;根据注释我们得知，需要将 rocketmq.config.namesrvAddr 设置为：localhost:9876, 再次启动成功。&lt;/p&gt;
&lt;p&gt;访问：http://localhost:8080/ ，当然你也可以修改地址或者更改端口号。同样是修改 application.properties 文件，修改成类似这样的配置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;server.port=8083
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;server.servlet.context-path=/rocketmq-dashboard
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-30-zi-ding-xiang-xia-xue-xi-rocketmq-yi-quickstart/001-d57063f5.jpg"&gt;&lt;/p&gt;
&lt;h2 id="测试消息的发送和接收"&gt;&lt;a href="#%e6%b5%8b%e8%af%95%e6%b6%88%e6%81%af%e7%9a%84%e5%8f%91%e9%80%81%e5%92%8c%e6%8e%a5%e6%94%b6" class="header-anchor"&gt;&lt;/a&gt;测试消息的发送和接收
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="n"&gt;NAMESRV_ADDR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9876&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rocketmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quickstart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Producer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;SendResult&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sendStatus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SEND_OK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msgId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rocketmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quickstart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Consumer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ConsumeMessageThread_&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="n"&gt;Receive&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt; &lt;span class="n"&gt;Messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MessageExt&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="关闭-nameserver-和-broker"&gt;&lt;a href="#%e5%85%b3%e9%97%ad-nameserver-%e5%92%8c-broker" class="header-anchor"&gt;&lt;/a&gt;关闭 nameServer 和 broker
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&amp;gt; sh bin/mqshutdown broker
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;The mqbroker(36695) is running...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;Send shutdown request to mqbroker(36695) OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;&amp;gt; sh bin/mqshutdown namesrv
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt;The mqnamesrv(36664) is running...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;7&lt;/span&gt;&lt;span class="cl"&gt;Send shutdown request to mqnamesrv(36664) OK
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="其他高可用部署方式"&gt;&lt;a href="#%e5%85%b6%e4%bb%96%e9%ab%98%e5%8f%af%e7%94%a8%e9%83%a8%e7%bd%b2%e6%96%b9%e5%bc%8f" class="header-anchor"&gt;&lt;/a&gt;其他高可用部署方式
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;多 Master 模式&lt;/li&gt;
&lt;li&gt;多 Master 多 Slave 模式-异步复制&lt;/li&gt;
&lt;li&gt;多 Master 多 Slave 模式-同步双写&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;具体部署方式可参考：文档&lt;/p&gt;
&lt;p&gt;这里贴一个比较完整的生产 K8S 部署方案：&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://cloud.tencent.com/developer/article/1785729" target="_blank" rel="noopener"
 &gt;https://cloud.tencent.com/developer/article/1785729&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;上面这个方案是一个双主双从的配置，每个 statefulset 只有一个副本，每个 Pod 拥有自己的配置文件，根据 RocketMQ 文档的说明来配置，没有什么问题。但当我们要扩展多 Master, 多 Slave 的时候，就会比较麻烦，要再建 Master Slave 对，增加 statefulset。&lt;/p&gt;
&lt;p&gt;下面有一个部署技巧可以解决这个的问题。&lt;/p&gt;
&lt;h3 id="通过-statefulset-副本扩展集群规模"&gt;&lt;a href="#%e9%80%9a%e8%bf%87-statefulset-%e5%89%af%e6%9c%ac%e6%89%a9%e5%b1%95%e9%9b%86%e7%be%a4%e8%a7%84%e6%a8%a1" class="header-anchor"&gt;&lt;/a&gt;通过 statefulset 副本扩展集群规模
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;以上 Broker 与 Slave 配对是通过指定相同的 BrokerName 参数来配对，Master 的 BrokerId 必须是 0，Slave 的 BrokerId 必须是大于 0 的数。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;根据文档得知 Master 和 Slave 是根据 BrokerName 来配对的，所以上面的部署配置中也对同一对的主从设置了相同的 BrokerName。&lt;/p&gt;
&lt;p&gt;如果没有配置 BrokerName 有默认值吗？&lt;/p&gt;
&lt;p&gt;我们来看下 RocketMQ 的源码：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BrokerConfig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;InternalLogger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;InternalLoggerFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LoggerName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;COMMON_LOGGER_NAME&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rocketmqHome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MixAll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ROCKETMQ_HOME_PROPERTY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MixAll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ROCKETMQ_HOME_ENV&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@ImportantField&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;namesrvAddr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MixAll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NAMESRV_ADDR_PROPERTY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MixAll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NAMESRV_ADDR_ENV&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@ImportantField&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;brokerIP1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RemotingUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLocalAddress&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;brokerIP2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RemotingUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLocalAddress&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@ImportantField&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;brokerName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;localHostName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@ImportantField&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;brokerClusterName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;DefaultCluster&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@ImportantField&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aclEnable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;storeReplyMessageEnable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;autoDeleteUnusedStats&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;localHostName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;26&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;InetAddress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLocalHost&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getHostName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;27&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UnknownHostException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;28&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Failed to obtain the host name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;29&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;30&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;31&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;DEFAULT_BROKER&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;32&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可见，brokerName 有默认值 ，即如果没有主动 set brokerName，那么获取的是 localhostName。&lt;/p&gt;
&lt;p&gt;pod 的 hostname 即为 pod 的名字，如果我们的 pod 名字是 broker-0，那么它的 hostname 就是 broker-0。联系上面 RocketMQ 的源码，brokerName 的值如果不设置拿到的就是 hostname, 即 pod Name。&lt;/p&gt;
&lt;p&gt;由于我们部署 RocketMQ 时用的是有状态服务 (statefulset)，statefulset 的 Pod 的命名是有规则的：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;对于一个拥有 N 个副本的 StatefulSet，Pod 被部署时是按照 {0 …… N-1} 的序号顺序创建的。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;于是我们就可以利用 statefulset 的命名规则进行集群的扩展了，例如我要设置一个双主双从的集群，那么只需要将 master 和 slave 的 statefulset 的副本数设置为 2 就可以了，结果类似这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;rocketmq-broker-master-0 1/1 Running 1 462d
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;rocketmq-broker-master-1 1/1 Running 0 462d
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;rocketmq-broker-slave-0 1/1 Running 1 462d
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;rocketmq-broker-slave-1 1/1 Running 0 462d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可见，根据命名规则 RocketMQ 的 Master 和 Slave 可以根据 brokerName（即 pod name）配对正确。那么如果我想要扩展集群，那么我只需要把 master 和 slave 的 statefulset 的副本数设置为 2、3、4、5&amp;hellip; 就可以了。&lt;/p&gt;
&lt;p&gt;以上就完成了只需要改两个 statefulset 的副本数就可以扩展集群的操作。一个小技巧分享给大家。&lt;/p&gt;
&lt;h2 id="参考"&gt;&lt;a href="#%e5%8f%82%e8%80%83" class="header-anchor"&gt;&lt;/a&gt;参考
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/apache/rocketmq/tree/master/docs/cn" target="_blank" rel="noopener"
 &gt;https://github.com/apache/rocketmq/tree/master/docs/cn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://rocketmq.apache.org/docs/quick-start/" target="_blank" rel="noopener"
 &gt;https://rocketmq.apache.org/docs/quick-start/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/apache/rocketmq-dashboard" target="_blank" rel="noopener"
 &gt;https://github.com/apache/rocketmq-dashboard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://cloud.tencent.com/developer/article/1785729" target="_blank" rel="noopener"
 &gt;https://cloud.tencent.com/developer/article/1785729&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>填坑Reactor模型和Netty线程模型</title><link>https://xiaobox.github.io/p/2020-04-03-tian-keng-reactor-mo-xing-he-netty-xian-cheng-mo-xing/</link><pubDate>Fri, 03 Apr 2020 10:14:57 +0000</pubDate><guid>https://xiaobox.github.io/p/2020-04-03-tian-keng-reactor-mo-xing-he-netty-xian-cheng-mo-xing/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-04-03-tian-keng-reactor-mo-xing-he-netty-xian-cheng-mo-xing/cover.jpg" alt="Featured image of post 填坑Reactor模型和Netty线程模型" /&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-04-03-tian-keng-reactor-mo-xing-he-netty-xian-cheng-mo-xing/001-545043fe.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="http://mp.weixin.qq.com/s?__biz=MzI3Njk5ODg4OQ==&amp;amp;mid=2247483680&amp;amp;idx=1&amp;amp;sn=fd933abbd4f044d8997390b056a3285c&amp;amp;chksm=eb6dbea6dc1a37b0e37d5662811c97e32f7a3571c9efe421340f890b0ade2881008f54f05e65&amp;amp;scene=21#wechat_redirect" target="_blank" rel="noopener"
 &gt;Java 的I/O、NIO ,Java IO 模型，Unix 网络 IO 模型等相关概念的解析&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;上面这篇幅文章我们讨论了IO相关的问题，文末留了个坑说要说下Netty的线程模型，今天来填坑。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在高性能的I/O设计中，有两个著名的模型：Reactor模型和Proactor模型，其中Reactor模型用于同步I/O，而Proactor模型运用于异步I/O操作。实际上Netty线程模型就是Reactor模型的一个实现。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="reactor模型"&gt;&lt;a href="#reactor%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;Reactor模型
&lt;/h2&gt;
 &lt;blockquote&gt;
 &lt;p&gt;The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;以上来自wiki,&lt;strong&gt;我们可以看到以下重点&lt;/strong&gt;。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;事件驱动（event handling）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可以处理一个或多个输入源（one or more inputs）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;通过Service Handler同步的将输入事件（Event）采用多路复用分发给相应的Request Handler（多个）处理&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;根据大神Doug Lea 在 《Scalable IO in Java 》中的介绍，&lt;strong&gt;Reacotr模型主要分为三个角色&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reactor&lt;/strong&gt;：把IO事件分配给对应的handler处理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Acceptor&lt;/strong&gt;：处理客户端连接事件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Handler&lt;/strong&gt;：处理非阻塞的任务&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Reactor处理请求的流程&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;同步的等待多个事件源到达（采用select()实现）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将事件多路分解以及分配相应的事件服务进行处理，这个分派采用server集中处理（dispatch）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分解的事件以及对应的事件服务应用从分派服务中分离出去（handler）&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="为什么使用reactor"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e4%bd%bf%e7%94%a8reactor" class="header-anchor"&gt;&lt;/a&gt;为什么使用Reactor？
&lt;/h2&gt;&lt;p&gt;传统阻塞IO模型的不足&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;每个连接都需要独立线程处理，当并发数大时，创建线程数多，占用资源&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;采用阻塞IO模型，连接建立后，若当前线程没有数据可读，线程会阻塞在读操作上，造成资源浪费&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;针对传统阻塞IO模型的两个问题，可以采用如下的方案&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;基于池化思想，避免为每个连接创建线程，连接完成后将业务处理交给线程池处理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;基于IO复用模型，多个连接共用同一个阻塞对象，不用等待所有的连接。遍历到有新数据可以处理时，操作系统会通知程序，线程跳出阻塞状态，进行业务逻辑处理&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="reactor线程模型分类"&gt;&lt;a href="#reactor%e7%ba%bf%e7%a8%8b%e6%a8%a1%e5%9e%8b%e5%88%86%e7%b1%bb" class="header-anchor"&gt;&lt;/a&gt;Reactor线程模型分类
&lt;/h2&gt;&lt;p&gt;根据Reactor的数量和处理资源的线程数量的不同，分为三类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;单Reactor单线程模型&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;单Reactor多线程模型&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;多Reactor多线程模型&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="单reactor单线程模型"&gt;&lt;a href="#%e5%8d%95reactor%e5%8d%95%e7%ba%bf%e7%a8%8b%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;单Reactor单线程模型
&lt;/h2&gt;&lt;p&gt;&lt;img alt="Image" data-title-escaped="image.png" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" title="image.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;消息处理流程：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Reactor对象通过select监控连接事件，收到事件后通过dispatch进行转发。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果是连接建立的事件，则由acceptor接受连接，并创建handler处理后续事件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果不是建立连接事件，则Reactor会分发调用Handler来响应。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;handler会完成read-&amp;gt;业务处理-&amp;gt;send的完整业务流程。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;该线程模型的不足&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;仅用一个线程处理请求，对于多核资源机器来说是有点浪费的&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当处理读写任务的线程负载过高后，处理速度下降，事件会堆积，严重的会超时，可能导致客户端重新发送请求，性能越来越差&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;单线程也会有可靠性的问题&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;针对上面的种种不足，就有了下面的线程模型&lt;/p&gt;
&lt;h2 id="单reactor多线程模型"&gt;&lt;a href="#%e5%8d%95reactor%e5%a4%9a%e7%ba%bf%e7%a8%8b%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;单Reactor多线程模型
&lt;/h2&gt;&lt;p&gt;&lt;img alt="Image" data-title-escaped="image.png" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" title="image.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;消息处理流程：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Reactor对象通过Select监控客户端请求事件，收到事件后通过dispatch进行分发。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果是建立连接请求事件，则由acceptor通过accept处理连接请求，然后创建一个Handler对象处理连接完成后续的各种事件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果不是建立连接事件，则Reactor会分发调用连接对应的Handler来响应。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Handler只负责响应事件，不做具体业务处理，通过Read读取数据后，会分发给后面的Worker线程池进行业务处理。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Worker线程池会分配独立的线程完成真正的业务处理，如何将响应结果发给Handler进行处理。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Handler收到响应结果后通过send将响应结果返回给Client。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;相对于第一种模型来说，在处理业务逻辑，也就是获取到IO的读写事件之后，交由线程池来处理，handler收到响应后通过send将响应结果返回给客户端。这样可以降低Reactor的性能开销，从而更专注的做事件分发工作了，提升整个应用的吞吐。&lt;/p&gt;
&lt;p&gt;但是这个模型存在的问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;多线程数据共享和访问比较复杂。如果子线程完成业务处理后，把结果传递给主线程Reactor进行发送，就会涉及共享数据的互斥和保护机制。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reactor承担所有事件的监听和响应，只在主线程中运行，可能会存在性能问题。例如并发百万客户端连接，或者服务端需要对客户端握手进行安全认证，但是认证本身非常损耗性能。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;为了解决性能问题，产生了第三种主从Reactor多线程模型。&lt;/p&gt;
&lt;h2 id="主从reactor多线程模型"&gt;&lt;a href="#%e4%b8%bb%e4%bb%8ereactor%e5%a4%9a%e7%ba%bf%e7%a8%8b%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;主从Reactor多线程模型
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;&lt;img alt="Image" data-title-escaped="image.png" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" title="image.png"&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;比起第二种模型，它是将Reactor分成两部分：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;mainReactor负责监听server socket，用来处理网络IO连接建立操作，将建立的socketChannel指定注册给subReactor。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;subReactor主要做和建立起来的socket做数据交互和事件业务处理操作。通常，subReactor个数上可与CPU个数等同。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Nginx、Memcached和Netty都是采用这种实现。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;消息处理流程：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;从主线程池中随机选择一个Reactor线程作为acceptor线程，用于绑定监听端口，接收客户端连接&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;acceptor线程接收客户端连接请求之后创建新的SocketChannel，将其注册到主线程池的其它Reactor线程上，由其负责接入认证、IP黑白名单过滤、握手等操作&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;步骤2完成之后，业务层的链路正式建立，将SocketChannel从主线程池的Reactor线程的多路复用器上摘除，重新注册到Sub线程池的线程上，并创建一个Handler用于处理各种连接事件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当有新的事件发生时，SubReactor会调用连接对应的Handler进行响应&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Handler通过Read读取数据后，会分发给后面的Worker线程池进行业务处理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Worker线程池会分配独立的线程完成真正的业务处理，如何将响应结果发给Handler进行处理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Handler收到响应结果后通过Send将响应结果返回给Client&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Reactor三种模式形象比喻&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;餐厅一般有接待员和服务员，接待员负责在门口接待顾客，服务员负责全程服务顾客&lt;/p&gt;
&lt;p&gt;Reactor的三种线程模型可以用接待员和服务员类比&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;单Reactor单线程模型：接待员和服务员是同一个人，一直为顾客服务。客流量较少适合&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;单Reactor多线程模型：一个接待员，多个服务员。客流量大，一个人忙不过来，由专门的接待员在门口接待顾客，然后安排好桌子后，由一个服务员一直服务，一般每个服务员负责一片中的几张桌子&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;多Reactor多线程模型：多个接待员，多个服务员。这种就是客流量太大了，一个接待员忙不过来了&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="netty线程模型"&gt;&lt;a href="#netty%e7%ba%bf%e7%a8%8b%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;Netty线程模型
&lt;/h2&gt;&lt;p&gt;上文说Netty就是采用Reactor模型实现的。下面是Netty使用中很常见的一段代码&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;throws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EventLoopGroup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bossGroup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NioEventLoopGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EventLoopGroup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;workerGroup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NioEventLoopGroup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ServerBootstrap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ServerBootstrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bossGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;workerGroup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NioServerSocketChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;childOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChannelOption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TCP_NODELAY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;childAttr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AttributeKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;childAttr&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;childAttrValue&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ServerHandler&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;childHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChannelInitializer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SocketChannel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;initChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SocketChannel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChannelFuture&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;8888&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;closeFuture&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bossGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shutdownGracefully&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;workerGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shutdownGracefully&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;boss线程池作用：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;接收客户端的连接，初始化Channel参数。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将链路状态变更时间通知给ChannelPipeline。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;worker线程池作用：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;异步读取通信对端的数据报，发送读事件到ChannelPipeline。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;异步发送消息到通信对端，调用ChannelPipeline的消息发送接口。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;执行系统调用Task。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;执行定时任务Task。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;通过配置boss和worker线程池的线程个数以及是否共享线程池等方式，Netty的线程模型可以在以上三种Reactor模型之间进行切换&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" data-title-escaped="image.png" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" title="image.png"&gt;&lt;/p&gt;
&lt;p&gt;netty通过Reactor模型基于多路复用器接收并处理用户请求，内部实现了两个线程池，boss线程池和work线程池，其中boss线程池的线程负责处理请求的accept事件，当接收到accept事件的请求时，把对应的socket封装到一个NioSocketChannel中，并交给work线程池，其中work线程池负责请求的read和write事件&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" data-title-escaped="image.png" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" title="image.png"&gt;&lt;/p&gt;
&lt;h2 id="tomcat的线程模型"&gt;&lt;a href="#tomcat%e7%9a%84%e7%ba%bf%e7%a8%8b%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;tomcat的线程模型
&lt;/h2&gt;&lt;p&gt;Tomcat支持四种接收请求的处理方式：BIO、NIO、APR和AIO&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;NIO
同步非阻塞，比传统BIO能更好的支持大并发，tomcat 8.0 后默认采用该模型。
使用方法(配置server.xml)：&lt;Connector port="8080" protocol="HTTP/1.1"/&gt; 改为 protocol=&amp;ldquo;org.apache.coyote.http11.Http11NioProtocol&amp;rdquo;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;BIO
阻塞式IO，tomcat7之前默认，采用传统的java IO进行操作，该模型下每个请求都会创建一个线程，适用于并发量小的场景。
使用方法(配置server.xml)：protocol =&amp;quot; org.apache.coyote.http11.Http11Protocol&amp;quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;APR
tomcat 以JNI形式调用http服务器的核心动态链接库来处理文件读取或网络传输操作，需要编译安装APR库。
使用方法(配置server.xml)：protocol =&amp;ldquo;org.apache.coyote.http11.Http11AprProtocol&amp;rdquo;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;AIO
异步非阻塞 (NIO2)，tomcat8.0后支持。多用于连接数目多且连接比较长（重操作）的架构，比如相册服务器，充分调用OS参与并发操作，编程比较复杂，JDK7开始支持。
使用方法(配置server.xml)：protocol =&amp;ldquo;org.apache.coyote.http11.Http11Nio2Protocol&amp;rdquo;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;参考：&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://zhuanlan.zhihu.com/p/69341619" target="_blank" rel="noopener"
 &gt;https://zhuanlan.zhihu.com/p/69341619&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://cloud.tencent.com/developer/article/1488120" target="_blank" rel="noopener"
 &gt;https://cloud.tencent.com/developer/article/1488120&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-04-03-tian-keng-reactor-mo-xing-he-netty-xian-cheng-mo-xing/007-04ecede2.jpg"&gt;&lt;/p&gt;
&lt;p&gt;关注公众号 获取更多精彩内容&lt;/p&gt;</description></item><item><title>什么是零拷贝</title><link>https://xiaobox.github.io/p/2020-03-05-shen-me-shi-ling-kao-bei/</link><pubDate>Thu, 05 Mar 2020 10:58:37 +0000</pubDate><guid>https://xiaobox.github.io/p/2020-03-05-shen-me-shi-ling-kao-bei/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-03-05-shen-me-shi-ling-kao-bei/cover.jpg" alt="Featured image of post 什么是零拷贝" /&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-03-05-shen-me-shi-ling-kao-bei/001-e7aa8dc3.png"&gt;预计阅读时间6分钟&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-03-05-shen-me-shi-ling-kao-bei/002-375cd3dd.png"&gt;&lt;/p&gt;
&lt;p&gt;学习Netty时 看到Netty高性能的原因之一是使用零拷贝技术&lt;/p&gt;
&lt;p&gt;学习Kafka时 看到其高性能的原因之一也使用了零拷贝技术&lt;/p&gt;
&lt;p&gt;那么到底什么是零拷贝？本文简单做个描述。&lt;/p&gt;
&lt;h3 id="先解释几个概念"&gt;&lt;a href="#%e5%85%88%e8%a7%a3%e9%87%8a%e5%87%a0%e4%b8%aa%e6%a6%82%e5%bf%b5" class="header-anchor"&gt;&lt;/a&gt;先解释几个概念
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;用户态：只能受限地访问内存，不允许访问外围设备。占用 CPU 的能力被剥夺，CPU资源可以被其他程序获取。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;内核态：CPU可以访问内存中所有的数据，包括外围设备，例如硬盘、网卡，CPU也可以将自己从一个程序切换到另一个程序。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DMA (direct memory access):直接内存访问，是一种不经过CPU而直接从内存存取数据的数据交换模式。在DMA模式下，CPU只须向DMA控制器下达指令，让DMA控制器来处理数据的传送，数据传送完毕再把信息反馈给CPU，这样就很大程度上减轻了CPU资源占有率，可以大大节省系统资源&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="传统的数据拷贝方法"&gt;&lt;a href="#%e4%bc%a0%e7%bb%9f%e7%9a%84%e6%95%b0%e6%8d%ae%e6%8b%b7%e8%b4%9d%e6%96%b9%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;传统的数据拷贝方法
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt; 下图为传统的数据拷贝方法：
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-03-05-shen-me-shi-ling-kao-bei/003-7d3200be.jpg"&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 上图分别对应传统 I/O 操作的数据读写流程，整个过程涉及 4 次 拷贝，
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;1 .数据从磁盘读取到内核的read buffer&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;2. 数据从内核缓冲区拷贝到用户缓冲区&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;3. 数据从用户缓冲区拷贝到内核的socket buffer&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;4. 数据从内核的socket buffer拷贝到网卡接口的缓冲区&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;并且在用户态和内核态中间进行了2次切换，无疑也加重了CPU负担。

在此过程中，我们没有对文件内容做任何修改，那么在内核空间和用户空间来回拷贝数据无疑就是一种浪费，而零拷贝主要就是为了解决这种低效性。
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="解决方案"&gt;&lt;a href="#%e8%a7%a3%e5%86%b3%e6%96%b9%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;解决方案
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt; **一个很明显的着力点就是减少数据在内核空间和用户空间来回拷贝。**

 **Linux能够做到在数据传输的过程中，避免数据在操作系统内核态buffer和用户态buffer之间进行复制。****Linux中提供类似的系统调用函数主要有mmap()、sendfile()及splice()。下面介绍其中两种。**
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;**mmap
**&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 我们减少拷贝次数的一种方法是调用mmap()来代替read调用：
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-makefile" data-lang="makefile"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;write(sockfd,&lt;/span&gt; &lt;span class="err"&gt;buf,&lt;/span&gt; &lt;span class="err"&gt;len);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt; 应用程序调用mmap()，磁盘上的数据会通过DMA被拷贝的内核缓冲区，接着操作系统会把这段内核缓冲区与应用程序共享，这样就不需要把内核缓冲区的内容往用户空间拷贝。应用程序再调用write(),操作系统直接将内核缓冲区的内容拷贝到socket缓冲区中，这一切都发生在内核态，最后，socket缓冲区再把数据发到网卡去。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-03-05-shen-me-shi-ling-kao-bei/004-95800f3c.jpg"&gt;
使用mmap替代read很明显减少了一次拷贝，当拷贝数据量很大时，无疑提升了效率。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h5 id="sendfile"&gt;&lt;a href="#sendfile" class="header-anchor"&gt;&lt;/a&gt;sendfile
&lt;/h5&gt;&lt;h5 id="从21版内核开始linux引入了sendfile来简化操作"&gt;&lt;a href="#%e4%bb%8e21%e7%89%88%e5%86%85%e6%a0%b8%e5%bc%80%e5%a7%8blinux%e5%bc%95%e5%85%a5%e4%ba%86sendfile%e6%9d%a5%e7%ae%80%e5%8c%96%e6%93%8d%e4%bd%9c" class="header-anchor"&gt;&lt;/a&gt;从2.1版内核开始，Linux引入了sendfile来简化操作。
&lt;/h5&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#include&amp;lt;sys/sendfile.h&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ssize_t&lt;/span&gt; &lt;span class="nf"&gt;sendfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;out_fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;in_fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;off_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;系统调用sendfile()在代表输入文件的描述符in\_fd和代表输出文件的描述符out\_fd之间传送文件内容（字节）。描述符out\_fd必须指向一个套接字，而in\_fd指向的文件必须是可以mmap的。这些局限限制了sendfile的使用，使sendfile只能将数据从文件传递到套接字上，反之则不行。
使用sendfile不仅减少了数据拷贝的次数，还减少了上下文切换，数据传送始终只发生在kernel space。
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-03-05-shen-me-shi-ling-kao-bei/005-5502487c.jpg"&gt;&lt;/p&gt;
&lt;h5 id=""&gt;&lt;a href="#" class="header-anchor"&gt;&lt;/a&gt;
&lt;/h5&gt;&lt;h3 id="方案对比"&gt;&lt;a href="#%e6%96%b9%e6%a1%88%e5%af%b9%e6%af%94" class="header-anchor"&gt;&lt;/a&gt;方案对比
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt; 限于篇幅原因并没有把所有的零拷贝方式都介绍完整，以下是Linux中各方式的对比
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;拷贝方式&lt;/th&gt;
 &lt;th&gt;CPU拷贝&lt;/th&gt;
 &lt;th&gt;DMA拷贝&lt;/th&gt;
 &lt;th&gt;系统调用&lt;/th&gt;
 &lt;th&gt;上下文切换&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;传统方式（read + write）&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;read / write&lt;/td&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;内存映射（mmap + write）&lt;/td&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;mmap / write&lt;/td&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;sendfile&lt;/td&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;sendfile&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;sendfile + DMA gather copy&lt;/td&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;sendfile&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;splice&lt;/td&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;splice&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h5 id="kafka采用的是linux系统的函数sendfile允许操作系统将数据从page-cache直接发送到网络以此来避免数据复制"&gt;&lt;a href="#kafka%e9%87%87%e7%94%a8%e7%9a%84%e6%98%aflinux%e7%b3%bb%e7%bb%9f%e7%9a%84%e5%87%bd%e6%95%b0sendfile%e5%85%81%e8%ae%b8%e6%93%8d%e4%bd%9c%e7%b3%bb%e7%bb%9f%e5%b0%86%e6%95%b0%e6%8d%ae%e4%bb%8epage-cache%e7%9b%b4%e6%8e%a5%e5%8f%91%e9%80%81%e5%88%b0%e7%bd%91%e7%bb%9c%e4%bb%a5%e6%ad%a4%e6%9d%a5%e9%81%bf%e5%85%8d%e6%95%b0%e6%8d%ae%e5%a4%8d%e5%88%b6" class="header-anchor"&gt;&lt;/a&gt;&lt;strong&gt;Kafka&lt;/strong&gt;采用的是Linux系统的函数sendfile()，允许操作系统将数据从Page Cache直接发送到网络，以此来避免数据复制。
&lt;/h5&gt;&lt;pre&gt;&lt;code&gt; **Netty** 中的零拷贝和上面提到的操作系统层面上的零拷贝不太一样, 我们所说的 Netty 零拷贝完全是基于（Java 层面）用户态的，它的更多的是偏向于数据操作优化这样的概念，具体表现在以下几个方面：
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Netty 通过 DefaultFileRegion 类对 java.nio.channels.FileChannel 的 tranferTo() 方法进行包装，在文件传输时可以将文件缓冲区的数据直接发送到目的通道（Channel）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ByteBuf 可以通过 wrap 操作把字节数组、ByteBuf、ByteBuffer 包装成一个 ByteBuf 对象, 进而避免了拷贝操作&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ByteBuf 支持 slice 操作, 因此可以将 ByteBuf 分解为多个共享同一个存储区域的 ByteBuf，避免了内存的拷贝&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Netty 提供了 CompositeByteBuf 类，它可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf，避免了各个 ByteBuf 之间的拷贝&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-03-05-shen-me-shi-ling-kao-bei/006-ce2ba975.png"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;参考 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://www.jianshu.com/p/fad3339e3448" target="_blank" rel="noopener"
 &gt;https://www.jianshu.com/p/fad3339e3448&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://segmentfault.com/a/1190000007560884" target="_blank" rel="noopener"
 &gt;https://segmentfault.com/a/1190000007560884&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://juejin.im/post/5d84bd1f6fb9a06b2d780df7" target="_blank" rel="noopener"
 &gt;https://juejin.im/post/5d84bd1f6fb9a06b2d780df7&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a class="link" href="https://www.infoq.cn/article/netty-high-performance/" target="_blank" rel="noopener"
 &gt;https://www.infoq.cn/article/netty-high-performance/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-03-05-shen-me-shi-ling-kao-bei/007-04ecede2.jpg"&gt;&lt;/p&gt;
&lt;p&gt;关注公众号 获取更多精彩内容&lt;/p&gt;</description></item></channel></rss>