原文地址:https://leisure.wang/procedural-framework/framework/704.html
一、学习本文你能学到什么?
- RPC的概念及运作流程
- RPC协议及RPC框架的概念
- Netty的基本使用
- Java序列化及反序列化技术
- Zookeeper的基本使用(注册中心)
- 自定义注解实现特殊业务逻辑
- Java的动态代理
- 自定义Spring Boot Starter
这里只是列出了你能从RPC框架源码中能学到的东西,本文并不会每个知识点都点到,主要讲述如何手写一个RPC框架,更多细节需要读者阅读源码,文章的下方会提供源码链接哦。
二、RPC基础知识
2.1 RPC是什么?
Remote Procedure Call(RPC):远程过程调用。
过程是什么?
过程就是业务处理、计算任务,更直白理解,就是程序。(像调用本地方法一样调用远程的过程。)
RPC采用Client-Server结构,通过Request-Response消息模式实现。
2.2 RPC的流程

- 客户端处理过程中
调用Client stub(就像调用本地方法一样),传递参数; - Client stub将参数
编组为消息,然后通过系统调用向服务端发送消息; - 客户端本地操作系统将消息从客户端机器
发送到服务端机器; - 服务端操作系统将接收到的数据包
传递给Server stub; - Server stub
解组消息为参数; - Server stub
再调用服务端的过程,过程执行结果以反方向的相同步骤响应给客户端。
2.3 RPC流程中需要处理的问题
- Client stub、Server stub的开发;
- 参数如何编组为消息,以及解组消息;
- 消息如何发送;
- 过程结果如何表示、异常情况如何处理;
- 如何实现安全的访问控制。
2.4 RPC协议是什么?
RPC调用过程中需要将参数编组为消息进行发送,接受方需要解组消息为参数,过程处理结果同样需要经编组、解组。消息由哪些部分构成及消息的表示形式就构成了消息协议。
RPC调用过程中采用的消息协议称为RPC协议
RPC协议规定请求、响应消息的格式
在TCP(网络传输控制协议)上可选用或自定义消息协议来完成RPC消息交互
我们可以选用通用的标准协议(如:http、https),也也可根据自身的需要定义自己的消息协议。
2.5 RPC框架是什么?
封装好参数编组、消息解组、底层网络通信的RPC程序开发框架,带来的便捷是可以直接在其基础上只需要专注于过程代码编写。
Java领域:
- 传统的webservice框架:Apache CXF、Apache Axis2、Java自带的JAX-WS等。webservice框架大多基于标准的SOAP协议。
- 新兴的微服务框架:Dubbo、spring cloud、Apache Thrift等。
三、手写RPC
3.1 目标
我们将会写一个简易的RPC框架,暂且叫它leisure-rpc-spring-boot-starter,通过在项目中引入该starter,并简单的配置一下,项目即拥有提供远程服务的能力。
编写自定义注解@Service,被它注解的类将会提供远程服务。
编写自定义注解@InjectService,使用它可注入远程服务。
3.2 项目整体结构

3.3 客户端编写
3.3.1 客户端需要做什么?
客户端想要调用远程服务,必须具备服务发现的能力;在知道有哪些服务过后,还必须有服务代理来执行服务调用;客户端想要与服务端通信,必须要有相同的消息协议;客户端想要调用远程服务,那么必须具备网络请求的能力,即网络层功能。
当然,这是客户端所需的最基本的能力,其实还可以扩展的能力,例如负载均衡。
3.3.2 具体实现
我们先看看客户端的代码结构:

基于面向接口编程的理念,不同角色都实现了定义了相应规范的接口。这里面我们没有发现消息协议相关内容,那是因为服务端也需要消息协议,因此抽离了出来,放在公共层。
3.3.2.1 服务发现者
/**
* 服务发现抽象类,定义服务发现规范
*
* @author 东方雨倾
* @since 1.0.0
*/
public interface ServiceDiscoverer {
List<Service> getServices(String name);
}
/**
* Zookeeper服务发现者,定义以Zookeeper为注册中心的服务发现细则
*
* @author 东方雨倾
* @since 1.0.0
*/
public class ZookeeperServiceDiscoverer implements ServiceDiscoverer {
private ZkClient zkClient;
public ZookeeperServiceDiscoverer(String zkAddress) {
zkClient = new ZkClient(zkAddress);
zkClient.setZkSerializer(new ZookeeperSerializer());
}
/**
* 使用Zookeeper客户端,通过服务名获取服务列表
* 服务名格式:接口全路径
*
* @param name 服务名
* @return 服务列表
*/
@Override
public List<Service> getServices(String name) {
String servicePath = LeisureConstant.ZK_SERVICE_PATH + LeisureConstant.PATH_DELIMITER + name + "/service";
List<String> children = zkClient.getChildren(servicePath);
return Optional.ofNullable(children).orElse(new ArrayList<>()).stream().map(str -> {
String deCh = null;
try {
deCh = URLDecoder.decode(str, LeisureConstant.UTF_8);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return JSON.parseObject(deCh, Service.class);
}).collect(Collectors.toList());
}
}
服务发现者使用Zookeeper来实现,通过ZkClient我们很容易发现已经注册在ZK上的服务。当然我们也可以使用其他组件作为注册中心,例如Redis。
3.3.2.2 网络客户端
/**
* 网络请求客户端,定义网络请求规范
*
* @author 东方雨倾
* @since 1.0.0
*/
public interface NetClient {
byte[] sendRequest(byte[] data, Service service) throws InterruptedException;
}
/**
* Netty网络请求客户端,定义通过Netty实现网络请求的细则。
*
* @author 东方雨倾
* @since 1.0.0
*/
public class NettyNetClient implements NetClient {
private static Logger logger = LoggerFactory.getLogger(NettyNetClient.class);
/**
* 发送请求
*
* @param data 请求数据
* @param service 服务信息
* @return 响应数据
* @throws InterruptedException 异常
*/
@Override
public byte[] sendRequest(byte[] data, Service service) throws InterruptedException {
String[] addInfoArray = service.getAddress().split(":");
String serverAddress = addInfoArray[0];
String serverPort = addInfoArray[1];
SendHandler sendHandler = new SendHandler(data);
byte[] respData;
// 配置客户端
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
p.addLast(sendHandler);
}
});
// 启动客户端连接
b.connect(serverAddress, Integer.parseInt(serverPort)).sync();
respData = (byte[]) sendHandler.rspData();
logger.info("SendRequest get reply: {}", respData);
} finally {
// 释放线程组资源
group.shutdownGracefully();
}
return respData;
}
}
/**
* 发送处理类,定义Netty入站处理细则
*
* @author 东方雨倾
* @since 1.0.0
*/
public class SendHandler extends ChannelInboundHandlerAdapter {
private static Logger logger = LoggerFactory.getLogger(SendHandler.class);
private CountDownLatch cdl;
private Object readMsg = null;
private byte[] data;
public SendHandler(byte[] data) {
cdl = new CountDownLatch(1);
this.data = data;
}
/**
* 当连接服务端成功后,发送请求数据
*
* @param ctx 通道上下文
*/
@Override
public void channelActive(ChannelHandlerContext ctx) {
logger.info("Successful connection to server:{}", ctx);
ByteBuf reqBuf = Unpooled.buffer(data.length);
reqBuf.writeBytes(data);
logger.info("Client sends message:{}", reqBuf);
ctx.writeAndFlush(reqBuf);
}
/**
* 读取数据,数据读取完毕释放CD锁
*
* @param ctx 上下文
* @param msg ByteBuf
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
logger.


2010

被折叠的 条评论
为什么被折叠?



