点一下关注吧!!!非常感谢!!持续更新!!!
🚀 AI篇持续更新中!(长期更新)
AI炼丹日志-29 - 字节跳动 DeerFlow 深度研究框斜体样式架 私有部署 测试上手 架构研究,持续打造实用AI工具指南!📐🤖
💻 Java篇正式开启!(300篇)
目前2025年07月07日更新到:
Java-65 深入浅出 分布式服务 网络通信 BIO NIO AIO 详解 附代码
MyBatis 已完结,Spring 已完结,Nginx已完结,Tomcat已完结,分布式服务正在更新!深入浅出助你打牢基础!
📊 大数据板块已完成多项干货更新(300篇):
包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈!
大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT案例 详解
Netty
Netty 是一个 面向 JVM 的异步事件驱动网络开发框架,目标是让高并发、高吞吐的网络程序开发像写普通业务代码一样简单。它在内部采用 Reactor/Proactor + 线程池 组合,将 I/O 多路复用、缓冲区管理、协议编解码等复杂细节彻底封装,从而让开发者只需关注 ChannelHandler 里的业务逻辑。
结构速览
- Channel:套接字抽象,支持 NIO、Epoll、KQueue、io_uring、UDT、QUIC 等多种传输
- EventLoop/Group:事件循环线程,单线程串行化 + 任务队列,天然避免锁竞争
- ChannelPipeline:责任链,入站/出站双向流,动态编解码、聚合、加密
- ByteBuf / Buffer:内存模型,直接内存池、零拷贝、引用计数,5.0 起改为更安全的 Buffer API
基于Netty自定义RPC
基本介绍
RPC(Remote Procedure Call)又称为远程过程调用,是分布式系统中服务间通信的重要方式。远程调用主要分为两种类型,现代微服务架构中服务间通信基本以这两种方式为主:
-
基于HTTP的RESTful形式的广义远程调用
- 代表框架:Spring Cloud的Feign和RestTemplate
- 通信协议:HTTP/HTTPS协议
- 特点:
- 遵循RESTful设计风格
- 一般采用短连接方式
- 协议参数和响应序列化以JSON格式为主,也支持XML格式
- 典型的应用场景包括Web服务、跨语言系统集成等
-
基于TCP协议的RPC远程调用
- 代表框架:阿里的Dubbo、gRPC等
- 通信协议:自定义二进制协议
- 特点:
- 主要通过Netty实现4层网络协议
- 采用NIO进行异步高效传输
- 支持多种序列化方式:JSON、hessian2、protobuf、Java自带序列化等
- 可配置性强,性能更高
- 典型的应用场景包括高并发微服务、内部服务调用等
实现目标
我们将模仿Dubbo框架实现一个简单的RPC调用演示,包含以下核心功能:
- 消费者和生产者约定统一的接口和协议
- 消费者远程调用生产者的方法
3.生产者返回一个字符串响应 - 消费者接收并打印返回数据
详细实现步骤
第一步:创建公共接口项目
- 新建Maven项目
rpc-api
- 定义接口规范:
public interface DemoService {
String sayHello(String name);
}
- 打包发布到本地仓库或私服,供生产者和消费者项目依赖
第二步:创建生产者服务
- 新建Maven项目
rpc-provider
,依赖rpc-api
- 实现接口:
public class DemoServiceImpl implements DemoService {
@Override
public String sayHello(String name) {
return "Hello " + name + "!";
}
}
- 配置Netty服务端:
- 监听指定端口(如8080)
- 使用自定义协议编解码器
- 实现请求处理逻辑
- 启动时注册服务到注册中心(可选)
第三步:创建消费者服务
- 新建Maven项目
rpc-consumer
,依赖rpc-api
- 实现透明调用:
- 创建动态代理类
- 代理类内部使用Netty客户端发起远程调用
- 配置Netty客户端:
- 连接生产者服务端
- 序列化请求参数
- 处理响应结果
- 测试调用:
public class ConsumerApp {
public static void main(String[] args) {
DemoService service = ProxyFactory.create(DemoService.class);
String result = service.sayHello("World");
System.out.println("Received: " + result);
}
}
技术实现要点
- 序列化方案选择:建议使用hessian2或protobuf以提高性能
- 连接管理:需要实现连接池机制
- 超时处理:设置合理的调用超时时间
- 负载均衡:多服务提供者时需实现负载均衡策略
- 容错机制:失败重试、熔断降级等
通过以上步骤,可以完成一个基本的RPC调用演示,展现了RPC框架的核心工作原理。在实际生产环境中,还需要考虑服务发现、监控、链路追踪等更多功能。
公共模块
首先,在公共模块中添加 Netty 的依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.16.Final</version>
</dependency>
生产者和消费者都需要依赖这个模块,这样提供者来实现接口并提供网络调用,消费者直接根据接口来进行 TCP 通信及一定协议定制获取提供者的实现返回值。
接口定义
package icu.wzk.nettyrpc.service;
public interface WzkUserService {
String sayHello(String str);
}
生产者
package icu.wzk.nettyrpc.service.impl;
import icu.wzk.nettyrpc.service.WzkUserService;
import icu.wzk.nettyrpc.hanlder.WzkUserServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
public class WzkUserServiceImpl implements WzkUserService {
@Override
public String sayHello(String str) {
System.out.println("调用函数: " + str);
return "调用成功: " + str;
}
public static void startServer(String hostName, int port) throws Exception {
NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap
.group(eventLoopGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline
.addLast(new StringDecoder())
.addLast(new StringEncoder())
.addLast(new WzkUserServerHandler());
}
});
bootstrap.bind(hostName, port);
}
}
在实现中加入了 Netty 的服务启动程序,上面的代码添加了 String 类型的 Handler:
package icu.wzk.nettyrpc.hanlder;
import icu.wzk.nettyrpc.service.impl.WzkUserServiceImpl;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class WzkUserServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg.toString().startsWith("WzkUserService")) {
String result = new WzkUserServiceImpl()
.sayHello(msg.toString().substring(msg.toString().lastIndexOf("#") + 1));
ctx.writeAndFlush(result);
}
}
}
我们通过这样的方式直接启动:
package icu.wzk.nettyrpc;
import icu.wzk.nettyrpc.service.impl.WzkUserServiceImpl;
public class WzkNettyRPCServer {
public static void main(String[] args) throws Exception {
WzkUserServiceImpl.startServer("localhost", 9999);
}
}
消费者
消费者有一个需要注意的地方,就是调用需要透明,也就是说,框架使用者不用关心底层的网络实现。这里可以使用JDK的动态带来实现。
思路:客户端调用代理方法,返回一个实现了 HelloSerivce 接口的代理对象,调用代理对象的方法,返回结果。
我们需要在代理中做手脚,当调用代理方法的时候,我们需要初始化 Netty 客户端,还需要向服务端请求数据,并返回数据。
首先创建代理相关的类:
package icu.wzk.nettyrpc;
import icu.wzk.nettyrpc.hanlder.WzkUserClientHandler;
import icu.wzk.nettyrpc.service.WzkUserService;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.lang.reflect.Proxy;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class WzkNettyRPCClient {
private static ExecutorService executor =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
private static WzkUserClientHandler client;
public Object createProxy(final Class<?> serviceClass, final String providerName) {
return Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class<?>[]{serviceClass}, (proxy, method, args) -> {
if (null == client) {
initClient();
}
client.setParam(providerName + args[0]);
return executor.submit(client).get();
}
);
}
private static void initClient() throws Exception {
client = new WzkUserClientHandler();
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap
.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline
.addLast(new StringDecoder())
.addLast(new StringEncoder())
.addLast(client);
}
});
bootstrap.connect("localhost", 9999).sync();
}
public static void main(String[] args) throws Exception {
String providerName = "WzkUserService#sayHello#";
WzkNettyRPCClient wzkNettyRPCClient = new WzkNettyRPCClient();
WzkUserService wzkUserService =
(WzkUserService) wzkNettyRPCClient.createProxy(WzkUserService.class, providerName);
while (true) {
Thread.sleep(1000);
System.out.println(wzkUserService.sayHello("hello???"));
}
}
}
创建代理的逻辑:使用 JDK 的动态代理技术,代理对象中的 invoke 方法实现如下:如果 Client 没有初始化,则初始化 Client。将参数设置到 Client,使用线程池调用 Client 的 call 方法并阻塞等待数据返回。
初始化客户端逻辑:创建一个 Netty 的客户端,并连接提供者,并设置一个自定义 Handler,和一些 String 类型的序列化方式。
Handler
package icu.wzk.nettyrpc.hanlder;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.concurrent.Callable;
public class WzkUserClientHandler extends ChannelInboundHandlerAdapter implements Callable {
private ChannelHandlerContext context;
private String result;
private String param;
public void setParam(String param) {
this.param = param;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
context = ctx;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
synchronized (this) {
result = msg.toString();
notify();
}
}
@Override
public Object call() throws Exception {
synchronized (this) {
context.writeAndFlush(param);
wait();
return result;
}
}
}
该类缓存了 ChannelHandlerContext,用于下次使用,有两个属性:返回结果和请求参数。
当成功连接后,缓存 ChannelHandlerContext,当调用 call 方法的时候,将请求参数发送到服务端。当服务端收到并返回数据后,调用 ChannelRead 方法,将返回值赋值给 result,并唤醒等待在 call 方法上的线程。
测试结果
我们启动两个程序,Server 端结果如下所示:
客户端如下所示: