我在《透彻理解Java网络编程(二一)——Netty实战:服务发布与订阅》中给出了服务消费方的部分实现,服务消费方在调用服务时,应该只需要调用接口方法,然后就能拿到返回结果,不需要关心底层的网络通信、服务发现、负载均衡等具体细节。那么,服务消费方在底层究竟是如何实现的呢?这其实就是RPC框架最核心的功能之一——动态代理。代理类可以看作是对被代理对象的包装,对目标方法的调用是通过代理类来完成的。所以,通过动态代理机制可以有效地将服务提供者和服务消费者进行解耦,隐藏了RPC调用的具体细节:本章,我就来完成服务消费者的动态代理机制的编码。我先来带大家回顾下Java中两种常见的动态代理机制:JDK动态
在分布式系统中,服务消费者和服务提供者都存在多个节点,所以RPC框架需要实现合理的负载均衡算法,控制流量能够均匀地分摊到每个服务提供者,避免单点故障导致的服务调用异常。所以,本章我就来实现RPC框架的负载均衡机制。一、注册中心服务消费者在发起RPC调用之前,需要知道服务提供者有哪些节点是可用的。由于服务提供者节点会存在上/下线的情况,所以服务消费者需要感知服务提供者节点的动态变化。RPC框架一般采用注册中心来实现服务的注册和发现,主流的注册中心有ZooKeeper、Eureka、Etcd、Consul、Nacos等。在本工程中,我使用Zookeeper,并设计一个通用的注册中心接口,各类注册中
本章,我先对RPC请求调用和结果响应两个过程分别进行详细分析,然后基于Netty完成整个RPC框架的通信协议设计和开发。RPC请求的过程对于服务消费者来说是出站操作,对于服务提供者来说是入站操作:服务消费者将RPC请求信息封装成Protocol对象,然后通过编码器Encoder进行二进制编码,最后直接发送至远端即可;服务提供者收到请求后,将二进制数据交给解码器Decoder,解码后再次生成Protocol对象,然后传递给Handler执行真正的请求执行。RPC响应的过程对于服务消费者来说是入站操作,对于服务提供者来说是出站操作:服务提供者将响应结果封装成Protocol对象,然后通过Encod
从本章开始,我将正式进入RPC框架的原型开发阶段。我会从服务发布与订阅、通信协议设计、负载均衡机制、动态代理四个方面详细地讲解一个通用RPC框架的实现过程。本章,我主要完成整个工程的搭建,以及服务发布与订阅功能的源码实现。工程源码存放在Gitee,需要的童鞋请自行下载。一、工程搭建整个工程使用SpringBoot2.1.12.RELEASE+JDK1.8.0_221+Netty4.1.65.Final的技术栈,并使用Zookeeper3.4.14作为注册中心。Zookeeper的搭建我不再赘述,参考官网或我的专栏《分布式系统从理论到实战》。1.1项目结构整个工程划分为七个模块:rpc-prov
前面章节,我对Java网络编程的核心原理以及Netty这个主流的开源网络通信框架的原理和底层源码进行了详尽的分析。从本章开始,我将完成一个相对完整的RPC框架原型,帮助大家加深对Netty的理解,同时让大家看看实际的项目中到底是如何运用Netty进行开发的。RPC又称远程过程调用(RemoteProcedureCall),用于解决分布式系统中服务之间的调用问题,目前主流的RPC框架有Dubbo、Thrift、gRPC等。RPC框架包含三个最重要的组件,分别是客户端、服务端和注册中心:在一次RPC调用流程中,这三个组件是这样交互的:服务端在启动后,将自己的服务信息注册到注册中心,客户端向注册中心
Netty中大量使用了一种名为MpscQueue的无锁队列。比如,上一章我在分析HashedWheelTimer时间轮时,就提到过它内部就是通过MpscQueue保存待执行的任务。为什么Netty不使用JDK原生并发包的队列呢?MpscQueue有什么特点?适用于什么样的场景?本章,我就对MpscQueue的底层原理进行剖析。一、并发队列我曾经在《透彻理解Java并发编程》专栏中,对J.U.C包中的所有并发队列进行过源码分析,本节我先带大家回顾下,作为后续讲解MpscQueue的铺垫。1.1JDK并发队列J.U.C包中的并发队列按照阻塞方式归类可以分为阻塞队列和非阻塞队列两种类型:阻塞队列阻塞
本章,我将对Netty中的HashedWheelTimer这个延迟任务处理器进行讲解。我曾经在《透彻理解Kafka》系列中介绍过Kafka的时间轮算法,为了实现高性能的定时任务调度,Netty也引入了时间轮算法驱动定时任务的执行。为什么Netty要用时间轮来处理定时任务?JDK原生的实现方案不能满足要求吗?本章我将一步步深入剖析时间轮的原理以及Netty中是如何实现时间轮算法的。一、JDK定时器定时器一般有三种表现形式:按固定周期定时执行、延迟一定时间后执行、指定某个时刻执行。一般定时器都需要通过轮询的方式来实现,即每隔一个时间片去检查任务是否到期。JDK提供了三种常用的定时器,分别为Time
本章,我将对Netty中的FastThreadLocal这个线程本地工具类进行讲解。我曾经在《透彻理解Java并发编程》系列中介绍过JDK中的ThreadLocal,Netty官方表示FastThreadLocal是比JDK的ThreadLocal性能更高。那么,FastThreadLocal到底比ThreadLocal快在哪里呢?一、ThreadLocal我先来带大家回顾下JDK中的ThreadLocal。ThreadLocal可以理解为线程本地变量,它是Java并发编程中非常重要的一个工具类。ThreadLocal为变量在每个线程中都创建了一个副本,该副本只能被当前线程访问,多线程之间是隔
本章,我将对Netty中的Recycler对象池进行讲解。所谓对象池,顾名思义,就是程序对象(Java对象)的一个缓存池。与内存池类似,对象池的目的也是为了提升Netty的并发处理能力,避免频繁创建和销毁对象所带来的性能损耗。那么,Netty是如何实现对象池的?我们在实践中又该如何运用对象池呢?带着这两个问题,我们来看Netty中Recycler对象池的设计与实现。一、Recycler简介Recycler是Netty实现的轻量级对象回收站,借助Recycler可以完成对象的获取和回收。1.1基本使用我们通过一个例子直观感受下Recycler如何使用。假设我们有一个User类,需要实现User对
了解了ByteBuf的结构和基本使用,我们来看Netty是如何进行内存管理的。Netty借鉴了jemalloc内存分配器,实现了一套高性能的内存管理机制:在单线程和多线程的场景下,高效地进行内存分配和回收;减少内存碎片,有效提高内存利用率。jemalloc是由JasonEvans在FreeBSD项目中引入的新一代内存分配器。它是一个通用的malloc实现,侧重于减少内存碎片和提升高并发场景下内存的分配效率,其目标是能够替代malloc。jemalloc的思想在很多场景都非常适用,在Redis、Netty等知名的高性能组件中都有它的原型,你会发现它们的实现思路都是类似的。所以,我本章将先对Lin
Netty中大量使用了堆外内存,以提升性能。我们知道,对于Java程序而言,堆外内存不受JVM管理,直接由操作系统管理,所以性能上往往比堆内内存更好,如果你想实现高效的I/O操作、缓存常用的对象、降低JVMGC压力,堆外内存是一个非常不错的选择。我们来看下堆外内存的具体优点:堆内内存由JVMGC自动回收,GC需要”StoptheWorld“,而堆外内存由于不受JVM管理,所以一定程度上可以降低GC对应用运行时带来的影响;当进行I/O操作时,堆内内存都需要转换为堆外内存,然后再与底层硬件设备交互,所以直接使用堆外内存可以减少一次内存拷贝;堆外内存可以实现进程之间、JVM多实例之间的数据共享。当然
Netty既然是一个网络通信框架,就必然涉及编解码技术:Netty从底层Java通道读到二进制数据后,传入Netty通道的流水线,随后开始入站处理,在入站处理过程中,需要将二进制类型数据解码成JavaPOJO对象。这个解码过程,可以通过Netty的Decoder解码器去完成;出站处理过程中,业务处理后的结果(出站数据),需要从某个JavaPOJO对象,编码为最终的二进制数据,然后通过底层Java通道发送到对端。这个编码过程,可以通过Netty的Encoder编码器去完成。本章,我就对Netty中的Encoder编码器和Decoder解码器进行讲解。一、粘包/拆包在讲解编码/解码之前,我需要先讲
EventLoop虽然是Netty的调度中心,负责监听各类事件:I/O事件、信号事件、定时事件等,但与我们实际开发最息息相关的却是ChannelPipeline和ChannelHandler。这一节,我就对Netty中的这两个组件进行深入分析。一、ChannelPipelineNetty的流水线ChannelPipeline用以实现网络事件的动态编排和有序传播,基于责任链设计模式(ChainofResponsibility)设计,内部是一个双向链表结构,支持动态地添加和删除ChannelHandler业务处理器。1.1内部结构Channel、ChannelPipeline、ChannelHan
Netty服务端通过ServerBootstrap启动的时候,会创建两个EventLoopGroup,它们本质是两个Reactor线程池:一个用于与客户端建立TCP连接,另一个用于处理IO相关的读写操作。下图是ServerBootstrap启动时,EventLoopGroup的核心工作流程图:EventLoopGroup属于Netty最核心的接口之一,Netty默认提供了NioEventLoopGroup、OioEventLoopGroup等多种实现。而EventLoop则是EventLoopGroup内部的事件处理器,每个EventLoop内部都有一个线程、一个JavaNIOSelector
通过上一章的讲解,大家应该对Netty的基本使用以及整体架构有了初步了解。从本章开始,我将正式分析Netty的底层原理并对部分源码进行讲解。我们在使用Netty时,首先就需要使用Bootstrap(客户端)和ServerBootstrap(服务端)来对Netty中的各类核心组件进行装配。本章,我就对Bootstrap和ServerBootstrap的底层原理进行讲解。一、Bootstrap启动流程我们先来回顾一下Netty中的AbstractBootstrap类,它是Bootstrap和ServerBootstrap的抽象父类,封装了一些公有方法:事实上,即时我们不使用Bootstrap,也可
从本章开始,我将对Netty这个基于JavaNIO实现的开源网络通信框架进行讲解。Netty在高性能网络编程中有着大量的应用,比如很多公司会基于Netty来实现OA、消息网关、云盘等涉及大量客户端并发访问的应用,一些开源中间件也将Netty作为底层的网络通讯组件,比如RocketMQ、ElasticSearch、Dubbo、Hadoop等。那么,既然Netty是基于JavaNIO实现的,为什么我们不直接使用JavaNIO呢?比如Kafka、Zookeeper就是直接使用JavaNIO来封装网络通信模块。我在上一章已经给出了一个基于JavaNIO实现Reactor模式的示例,从这个示例大家应该也
异步回调并非Java网络编程这块的内容,事实上,我曾在《透彻理解Java并发编程》专栏中详细剖析过Future模式,Java中的异步回调实际上就是基于Future模式。考虑到后续章节,我会剖析Netty源代码,而Netty源码中大量使用了异步回调技术,并且基于Java的异步回调,设计了自己的一整套异步回调接口和实现。所以,本章我先从JavaFuture异步回调技术入手,然后介绍比较常用的第三方异步回调技术——谷歌公司的GuavaFuture相关技术,最后介绍一下Netty的异步回调技术,为后续Netty源码剖析作铺垫。一、Future模式Java在1.5版本之后提供了一种新的多线程的创建方式—
上一章,我对JavaNIO进行了介绍。我们使用的很多开源中间件底层均使用了JavaNIO,比如Kafka、Zookeeper就基于JavaNIO构建了自己的网络通信组件。在Java网络编程中,如果使用JavaNIO,通常是和Reactor模式结合在一起,构建通信模块。本章,我就对Reactor模式进行介绍,并给出使用JavaNIO实现Reactor模式的代码示例。一、Reactor模式Reactor模式本质是一种事件驱动模型,DougLea曾在《ScalableIOinJava》中对Reactor模式进行定义,Reactor模式由Reactor反应器线程、Handlers处理器两大角色组成:R
前面的章节,我对Linux的IO模型和零拷贝机制进行了全面讲解。在Java语言中,从JDK1.4开始引入了NIO,JavaNIO对应LinuxIO模型中同步非阻塞模型,同时底层又采用了IO多路复用(IOMultiplexing)技术。在JDK1.4之前,只有BIO一种模式,开发过程相对简单,新来一个连接就会创建一个新的线程处理。随着请求并发度的提升,BIO很快遇到了性能瓶颈。JDK1.4以后开始引入了NIO技术,支持select和poll;JDK1.5支持了epoll;JDK1.7发布了NIO2,支持AIO模型。JavaNIO的核心思想在于:通过Selector线程不断的轮询所有SocketC
LinuxIO模型本质是在讲应用程序在进行IO操作时,线程如何等待数据在硬件设备、内核空间、用户空间之间的交换过程。既然到涉及数据的交换复制,有一个概念就必须要提了,那就是零拷贝(zero-copy)。所谓零拷贝(zero-copy),是指在计算机执行IO操作时,CPU不需要先将数据从一个内存区域复制到另一个内存区域。具体来讲,就是数据从网络设备到用户程序空间传递的过程中,减少数据拷贝次数,减少系统调用,实现CPU的零参与,彻底消除CPU在这方面的负载。实现零拷贝的最主要技术是DMA数据传输技术和内存区域映射技术,零拷贝具有以下优点:减少数据在内核缓冲区和用户进程缓冲区之间反复的I/O拷贝操作
我们在进行网络编程时,必然涉及网络I/O,比较常见的两种网络I/O编程模式就是同步阻塞I/O和同步非阻塞I/O。比如,传统的基于Socket的InputStrteam/OutputStream读写属于同步阻塞I/O。事实上,Linux操作系统一共包含五种I/O模型,分别是:同步阻塞I/O(BlockingIO):即传统的IO模型;同步非阻塞I/O(NonblockingIO):注意这里所说的NIO并非Java中的NIO库;I/O多路复用(IOMultiplexing):JavaNIO和Linux中的epoll/select/poll都是这种模型;信号驱动I/O(SignalDrivenIO):
HTTP协议,即HyperTextTransferProtocol(超文本传输协议),属于TCP/IP协议模型中的应用层协议,也是最常见的协议。比如,我们通过浏览器访问网页,本质就是通过HTTP协议获取网页内容。HTTP协议是一种无状态的请求/响应协议,它的底层还是通过TCP协议来建立连接的。本章,我将会对HTTP协议的报文结构、通信原理进行讲解。一、报文结构客户端发送一个HTTP请求到服务器,请求报文的格式如下,一共由四个部分组成:请求行(requestline);请求头部(header);空行;请求体数据。服务端的响应报文也由四个部分组成:状态行;响应头部(header);空行;响应体数据
TCP协议,属于TCP/IP协议模型中传输层协议,也是最核心的一个协议。比如,我们在进行Java网络编程时使用的Socket,本质就是一套针对TCP协议实现的SDK。本章,我将会对TCP协议的底层原理进行讲解,包含TCP连接建立、连接断开、滑动窗口、TCP长连接等等。一、TCP连接TCP协议用于应用程序之间的通信,基于端口寻址。当客户端程序希望通过TCP与服务端应用程序通信时,会发送一个TCP连接请求。这个请求必须指定明确的IP和端口。在双方“握手”之后,TCP将在两个应用程序之间建立一个全双工(full-duplex)的通信信道。这个全双工的通信信道将占用两个计算机之间的通信线路,直到它被一
在计算机网络领域,有两个非常重要的网络模型——ISO/OSI参考模型和TCP/IP协议模型。ISO/OSI参考模型,是一个具有7层协议结构的开放式系统互联模型,并不基于某个特定的协议集而设计,而是一套普遍适用的规范集合,更偏向于理论层面,是其它各类协议模型的理论基础。与OSI模型相关的协议实际应用很少,但该模型本身具有较高的参考价值。TCP/IP协议模型,主要基于实践而来,先有协议实现再有模型,模型只是对现有协议的描述,因此模型与协议非常吻合,在实际中也运用最为广泛。一、网络模型1.1OSI参考模型OSI参考模型一共七层,下层为上层提供服务:从上图可以看出,数据流动是从上层往下层流动:数据在传