RPC全称为Remote Procedure Call,翻译过来为“远程过程调用”
背景:现如今微服务甚是火热,针对单体服务按功能模块各种拆分、拆出了订单中心、商品中心、商户中心等等。一个中心下边又有多个springboot包、多个包 多个进程之间的数据交互就不可避免,跨进程之间的调用就是rpc。
市场上流行的框架是dubbo,那么自己实现一个rpc 该如何下手 ?
1、基于netty实现
2、请求实体 序列化成字节流—>netty传输—>反序列化接收
消费端实现
1.1 基于 netty 获取通道 Channel、同时设置序列化方式 new ObjectEncoder()
public class NettyClient {
public Channel doOpen(String host, int port) {
final EventLoopGroup group = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
// 使用NioSocketChannel来作为连接用的channel类
b.group(group).channel(NioSocketChannel.class)
// 绑定连接初始化器
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ChannelPipeline ph = ch.pipeline();
ph.addLast(new ObjectDecoder(1024, ClassResolvers.cacheDisabled(this.getClass().getClassLoader())));
ph.addLast(new ObjectEncoder());
//客户端处理类
ph.addLast(new DubboChannelHandler());
}
});
//发起异步连接请求,绑定连接端口和host信息
ChannelFuture future = null;
try {
future = b.connect(host, port).sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (future.isSuccess()) {
System.out.println("连接服务器成功");
} else {
System.out.println("连接服务器失败");
future.cause().printStackTrace();
group.shutdownGracefully(); //关闭线程组
}
return future.channel();
}
}
1.2 利用接口代理发送请求 this.channel.writeAndFlush(request);
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
//代理对象-通道写数据
DubboRequest request=new DubboRequest();
request.setId(Math.round(100));
request.setClassName(clazz.getName());
request.setMethod(method.getName());
request.setArgs(args);
this.channel.writeAndFlush(request);
//事件监听线程-
return new DubboFuture(request.getId()).get().getData();
}
1.3 SimpleChannelInboundHandler 通道事件监听 channelRead0 接收响应
public class DubboChannelHandler extends SimpleChannelInboundHandler<DubboResult> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, DubboResult result) {
//监听接收请求- DubboResult(id=100, data=Hello World你好 呀 我已经进来了 哈哈哈)
DubboFuture.received(result);
}
// ChannelHandlerContext(DubboChannelHandler#0, [id: 0x3f770713, L:/127.0.0.1:58188 - R:/127.0.0.1:8088])
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//获取通道
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
}
}
生产端实现
2.1 ServerBootstrap 绑事件_EventLoopGroup 监听通道 socketChannel
public class NettyServer {
public void bind(int port) {
EventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(worker)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 50000)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) {
ChannelPipeline ph = socketChannel.pipeline();
ph.addLast(new ObjectDecoder(1024*1024, ClassResolvers.weakCachingConcurrentResolver(this.getClass().getClassLoader())));
ph.addLast(new ObjectEncoder());
//添加NettyServerHandler,用来处理Server端接收和处理消息的逻辑
ph.addLast(new DubboChannelHandler());
}
});
// 服务器绑定端口监听
ChannelFuture f = b.bind(port).sync();
if (f.isSuccess()) {
System.err.println("启动Netty服务成功,端口号:" + port);
}
// 监听服务器关闭监听
f.channel().closeFuture().sync();
} catch (Exception e) {
System.err.println("启动Netty服务异常,异常信息:" + e.getMessage());
e.printStackTrace();
} finally {
worker.shutdownGracefully();
}
}
}
2.2 ChannelInboundHandlerAdapter 的 channelRead方法接收客户端 _序列化后 ObjectEncoder() 值
public class DubboChannelHandler extends ChannelInboundHandlerAdapter {
/**
* 通道读取数据事件,监听
* ChannelHandlerContext(DubboChannelHandler#0, [id: 0x99314047, L:/127.0.0.1:8088 - R:/127.0.0.1:57663])
* DubboRequest(id=100, className=com.nettyRpc.api.IHelloService, method=hello, args=[Hello World])
*
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
DubboRequest request = (DubboRequest) msg;
//根据接口-反射实现,拿到结果
Object result = new InvokeSupport().invoke(request);
DubboResult response = new DubboResult();
response.setId(request.getId());
response.setData(result);
System.err.println("服务器回复消息:" + JSON.toJSONString(response));
ctx.writeAndFlush(response);
//写回信息
}
/**
* 数据读取完毕事件
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
//完毕
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 通道发生异常事件
super.exceptionCaught(ctx, cause);
}
}
2.3 此刻已经拿到请求 DubboRequest(id=100, className=com.nettyRpc.api.IHelloService, method=hello, args=[Hello World])、根据 className反射找到实现 invoke即可
2.4 ctx.writeAndFlush(response) 通道响应数据即可。。。。
至此,一个基于netty的简单rpc已经成功。其中涉及了序列化、反序列化、netty 发送数据、监听接收数据等
查看代码前往 基于netty的rpc
ps:dubbo也是基于netty、但是比这复杂很多。默认使用 Hessian2 序列化
#dubbo.protocol.serialization=hessian2
public class Hessian2Serialization implements Serialization {}
org.apache.dubbo.common.serialize.hessian2.Hessian2Serialization
public ObjectOutput serialize(URL url, OutputStream out) throws IOException {
return new Hessian2ObjectOutput(out);
}
# org.apache.dubbo.remoting.exchange.codec.ExchangeCodec
protected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException {
Serialization serialization = this.getSerialization(channel);
byte[] header = new byte[16];
Bytes.short2bytes((short)-9541, header);
header[2] = (byte)(-128 | serialization.getContentTypeId());
if (req.isTwoWay()) {
header[2] = (byte)(header[2] | 64);
}
if (req.isEvent()) {
header[2] = (byte)(header[2] | 32);
}
Bytes.long2bytes(req.getId(), header, 4);
int savedWriteIndex = buffer.writerIndex();
buffer.writerIndex(savedWriteIndex + 16);
ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
// 序列化
ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
if (req.isEvent()) {
this.encodeEventData(channel, out, req.getData());
} else {
this.encodeRequestData(channel, out, req.getData(), req.getVersion());
}
out.flushBuffer();
if (out instanceof Cleanable) {
((Cleanable)out).cleanup();
}
bos.flush();
bos.close();
int len = bos.writtenBytes();
checkPayload(channel, (long)len);
Bytes.int2bytes(len, header, 12);
buffer.writerIndex(savedWriteIndex);
buffer.writeBytes(header);
buffer.writerIndex(savedWriteIndex + 16 + len);
}