##整体模块:
Module 说明
client 客户端
server 服务端
rpc-api RPC框架接口
hello-service-api 接口定义
rpc-netty 基于Netty实现的RPC框架
在 RPC 框架中,最关键的就是理解“桩”的实现原理,桩是 RPC 框架在客户端的服
务代理,它和远程服务具有相同的方法签名,或者说是实现了相同的接口,客户端在调用
RPC 框架提供的服务时,实际调用的就是“桩”提供的方法,在桩的实现方法中,它会发
请求到服务端获取调用结果并返回给调用方。
public interface StubFactory {
<T> T createStub(Transport transport, Class<T> serviceClass);
}
public class DynamicStubFactory implements StubFactory{
private final static String STUB_SOURCE_TEMPLATE =
"package com.github.liyue2008.rpc.client.stubs;\n" +
"import com.github.liyue2008.rpc.serialize.SerializeSupport;\n" +
"\n" +
"public class %s extends AbstractStub implements %s {\n" +
" @Override\n" +
" public String %s(String arg) {\n" +
" return SerializeSupport.parse(\n" +
" invokeRemote(\n" +
" new RpcRequest(\n" +
" \"%s\",\n" +
" \"%s\",\n" +
" SerializeSupport.serialize(arg)\n" +
" )\n" +
" )\n" +
" );\n" +
" }\n" +
"}";
@Override
@SuppressWarnings("unchecked")
public <T> T createStub(Transport transport, Class<T> serviceClass) {
try {
// 填充模板
String stubSimpleName = serviceClass.getSimpleName() + "Stub";
String classFullName = serviceClass.getName();
String stubFullName = "com.github.liyue2008.rpc.client.stubs." + stubSimpleName;
String methodName = serviceClass.getMethods()[0].getName();
String source = String.format(STUB_SOURCE_TEMPLATE, stubSimpleName, classFullName, methodName, classFullName, methodName);
// 编译源代码
JavaStringCompiler compiler = new JavaStringCompiler();
Map<String, byte[]> results = compiler.compile(stubSimpleName + ".java", source);
// 加载编译好的类
Class<?> clazz = compiler.loadClass(stubFullName, results);
// 把 Transport 赋值给桩
ServiceStub stubInstance = (ServiceStub) clazz.newInstance();
stubInstance.setTransport(transport);
// 返回这个桩
return (T) stubInstance;
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
}
先来看这个模板,它唯一的这个方法中,就只有一行代码,把接口的类名、方法名和序列化后的参数封装成一个 RpcRequest 对象,调用父类 AbstractStub 中的 invokeRemote 方法,发送给服务端。invokeRemote 方法的返回值就是序列化的调用结果,我们在模板中把这个结果反序列化之后,直接作为返回值返回给调用方就可以了。
再来看下面的 createStrub 方法,从 serviceClass 这个参数中,可以取到服务接口定义的所有信息,包括接口名、它有哪些方法、每个方法的参数和返回值类型等等。通过这些信息,我们就可以来填充模板,生成桩的源代码。
桩的类名就定义为:“接口名 + Stub”,为了避免类名冲突,我们把这些桩都统一放到固定的包 com.github.liyue2008.rpc.client.stubs 下面。填充好模板生成的源代码存放在 source 变量中,然后经过动态编译、动态加载之后,我们就可以拿到这个桩的类 clazz,利用反射创建一个桩的实例 stubInstance。把用于网络传输的对象 transport 赋值给桩,这样桩才能与服务端进行通信。到这里,我们就实现了动态创建一个桩。
小结: 编程技巧:使用依赖倒置原则解耦调用者和实现
通过定义一个接口来解耦调用方和实现。在设计上这种方法称为“依赖倒置原则(Dependence Inversion Principle)”,它的核心思想是,调用方不应依赖于具体实现,而是为实现定义一个接口,让调用方和实现都依赖于这个接口。这种方法也称为“面向接口编程”。
RPC 框架的服务端主要需要实现下面这两个功能:
首先来看服务端中,使用 Netty 接收所有请求数据的处理类 RequestInvocation 的 channelRead0 方法。
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, Command request) throws Exception {
RequestHandler handler = requestHandlerRegistry.get(request.getHeader().getType());
if(null != handler) {
Command response = handler.handle(request);
if(null != response) {
channelHandlerContext.writeAndFlush(response).addListener((ChannelFutureListener) channelFuture -> {
if (!channelFuture.isSuccess()) {
logger.warn("Write response failed!", channelFuture.cause());
channelHandlerContext.channel().close();
}
});
} else {
logger.warn("Response is null!");
}
} else {
throw new Exception(String.format("No handler for request with type: %d!", request.getHeader().getType()));
}
}
根据请求命令的 Hdader 中的请求类型 type,去 requestHandlerRegistry 中查找对应的请求处理器 RequestHandler,然后调用请求处理器去处理请求,最后把结果发送给客户端。
RPC 框架服务端最核心的部分如下:
@Override
public Command handle(Command requestCommand) {
Header header = requestCommand.getHeader();
// 从 payload 中反序列化 RpcRequest
RpcRequest rpcRequest = SerializeSupport.parse(requestCommand.getPayload());
// 查找所有已注册的服务提供方,寻找 rpcRequest 中需要的服务
Object serviceProvider = serviceProviders.get(rpcRequest.getInterfaceName());
// 找到服务提供者,利用 Java 反射机制调用服务的对应方法
String arg = SerializeSupport.parse(rpcRequest.getSerializedArguments());
Method method = serviceProvider.getClass().getMethod(rpcRequest.getMethodName(), String.class);
String result = (String ) method.invoke(serviceProvider, arg);
// 把结果封装成响应命令并返回
return new Command(new ResponseHeader(type(), header.getVersion(), header.getRequestId()), SerializeSupport.serialize(result));
// ...
}
小结:
在 RPC 框架的服务端处理客户端请求的业务逻辑中,我们分两层做了两次请求分发:
在 RequestInvocation 类中,根据请求命令中的请求类型 (command.getHeader().getType()),分发到对应的请求处理器 RequestHandler 中;
RpcRequestHandler 类中,根据 RPC 请求中的服务名,把 RPC 请求分发到对应的服务实现类的实例中去。
这两次分发采用的设计是差不多的,但你需要注意的是,这并不是一种过度设计。原因是,我们这两次分发分别是在不同的业务抽象分层中,第一次分发是在服务端的网络传输层抽象中,它是网络传输的一部分,而第二次分发是 RPC 框架服务端的业务层,是 RPC 框架服务端的一部分。良好的分层设计,目的也是让系统各部分更加的“松耦合,高内聚”。
_源码链接地址:_GitHub 的simple-rpc-framework项目中
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。