Netty RPC Demo 实现
简介
一个 RPC 框架 Demo 的简单实现(业务场景中还没用过RPC,但Dome应该还是有那个意思的)
完整的项目工程地址:RPC-Demo
工程运行说明
- 服务端启动:rpcfx-demo-server:ServerApplication
- 客户端启动:rpcfx-demo-client:ClientApplication
工程结构说明
RPC-DEMO
├─rpcfx-core: 框架核心部分,client、server的代理
|
├─rpcfx-demo-api: 接口定义部分,类似于web后端给前端写的接口文档
|
├─rpcfx-demo-client: 客户端,调用接口发送请求
|
└─rpcfx-demo-server: 服务端,接收请求,进行CURD之类的
关于 RPC 的一些思考
秦老师说过一句话,可以道破其本质:RPC 就是为了 OOP。下面以HTTP的相似场景举例:
上面是一个比较简略的使用postman或者浏览器访问的数据流程,因为HTTP协议本质传输的也是一个问题。在自己接触的写前端请求后端接口,传输的基本是字符串
下面是 RPC 的:
RPC表示不想这么玩,咱后端就要有后端的样子,要像平时使用类,调用方法那样才用的爽。
类比平时Web的开发,客户端就像Vue前端,服务端就是使用Spring boot web写的那些业务CURD服务,RPC框架就是Spring Boot Web框架
RPC框架想达到的目的有如下几个:
- 1.客户端:封装屏蔽从客户端发送请求到服务端,并将结果序列号成对象的过程,让客户端感觉像在本地调用类方法一样
- 2.服务端:封装屏蔽服务端根据请求路由到基本的处理类和函数的查找,让服务端只专心写好CRUD即可
代码实现
根据上面的描述,客户端只需要调用接口获取结果进行处理,服务端只需要实现好接口CRUD就行了,其它的全由 RPC 框架帮你搞定
代码编写的大致流程如下:
- 1.定义接口:类似于后端给前端编写的 API 文档
- 2.客户端调用:客户端调用接口,获取结果,并反序列化成对象
- 3.服务端接收处理:服务端接收到请求,匹配相应的实现类,调用相应的方法,得到结果返回返回
1.定义接口:类似于后端给前端编写的 API 文档
类似于接口文档,描述了能提供哪些功能、能返回什么、需要传什么参数。即本工程的 API 模块
public interface UserService {
/**
* find by id
* @param id id
* @return user
*/
User findById(Integer id);
}
2.客户端调用:客户端调用接口,获取结果,并反序列成对象
客户端只需要简单的使用RPC生成的代理,进行调用即可
2.1 客户端业务代码
public class ClientApplication {
public static void main(String[] args) {
RpcClient jdk = new RpcClientJdk();
UserService userService = jdk.create(UserService.class, "http://localhost:8080/");
User user = userService.findById(1);
if (user == null) {
log.info("Clint service invoke Error");
return;
}
System.out.println("find user id=1 from server: " + user.getName());
}
}
2.2 RPC 框架客户端:代理类生成、netty请求发送和响应处理
下面的发送请求和读取结果序列号返回都是 RPC 框架的事情了,大致代码如下:
生成代理类:
public class RpcClientJdk extends RpcProxy implements RpcClient {
@Override
public <T> T create(Class<T> serviceClass, String url) {
// 查询是否之前生成过,存储的直接返回
if (!isExit(serviceClass.getName())) {
add(serviceClass.getName(), newProxy(serviceClass, url));
}
return (T) getProxy(serviceClass.getName());
}
private <T> T newProxy(Class<T> serviceClass, String url) {
ClassLoader loader = RpcClientJdk.class.getClassLoader();
Class[] classes = new Class[]{
serviceClass};
return (T) Proxy.newProxyInstance(loader, classes, new RpcInvocationHandler(serviceClass, url));
}
}
请求发送与返回处理具体实现
/**
* 用于jdk、cglib、buddy
*
* @author lw1243925457
*/
@Slf4j
public class RpcInvocationHandler implements InvocationHandler, MethodInterceptor {
private final Class<?> serviceClass;
private final String url;
<T> RpcInvocationHandler(Class<T> serviceClass, String url) {
this.serviceClass = serviceClass;
this.url = url;
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
return process(serviceClass, method, args, url);
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) {
return process(serviceClass, method, args, url);
}
/**
* 发送请求到服务端
* 获取结果后序列号成对象,返回
* @param service service name
* @param method service method
* @param params method params
* @param url server host
* @return object
*/