关注公众号:离心计划,一起离开地球表面?
【RPC系列合集】
【专栏】RPC系列(理论)-夜的第一章
【专栏】RPC系列(理论)-协议与序列化
【专栏】RPC系列(理论)-动态代理
【专栏】RPC系列(实战)-摸清RPC骨架
【专栏】RPC系列(实战)-优雅的序列化
【专栏】RPC系列(番外)-IO模型与线程模型
【专栏】RPC系列(番外)-“土气”的IO实现
【专栏】RPC系列(实战)-Hello Netty
【专栏】RPC系列(实战)-低配版NameServer
| 前言
? ? 上一小节我们实现了一个低配版的NameServer,那么NameServer对于服务端来说需要完成服务注册,那么对于客户端也就是调用方来说,NameServer也需要提供服务发现的功能,也就是上一小节实现代码中的seekService方法。让我们回到RPC的初衷,客户端像调用本地方法一样调用远程服务,在【专栏】RPC系列(理论)-动态代理中我们已经知道了,替我们负重前行的是代理,所以包括寻找服务、封装调用参数、调用远程服务等操作都是在代理中完成的,这一节我们就回到客户端调用开始剖析动态代理部分的实现。
? ? 这一节的代码在:https://github.com/JAYqq/my-sparrow-rpc/tree/v1.4
| 动态代理
? ? 首先我们新建一个代理工厂类ProxyFactory,两个方法分别表示创建代理类和设置Rpc的传输类,也就是我们之前实现过的NettyTransport
public interface ProxyFactory {
<T> T createProxy(Class<T> clazz, ServiceMetaInfo metaInfo);
void setRpcTransport(RpcTransport rpcTransport);
}
? ? 然后我们实现一个CglibProxyFactory,这里我们使用Cglib代理技术,同样基于JDK代理实现。还需要将该实现类注册到SPI中?com.sparrow.rpc.core.client.ProxyFactory
public class CglibProxyFactory implements ProxyFactory {
RpcTransport rpcTransport;
@Override
public <T> T createProxy(Class<T> clazz, ServiceMetaInfo metaInfo) {
CglibRpcProxy proxy = new CglibRpcProxy(clazz, metaInfo, rpcTransport);
return (T) proxy.getProxy();
}
public void setRpcTransport(RpcTransport rpcTransport) {
this.rpcTransport = rpcTransport;
}
}
? ? CglibRpcProxy正是我们创建代理、发送Rpc请求的重要实现类,它的构造函数接受三个参数,分别是我们代理的目标Class、远程服务元信息(告诉服务端我调的是哪个服务)以及具体Transport类。
public class CglibRpcProxy implements MethodInterceptor
? ? Cglib代理需要实现MethodInterceptor,它创建代理类的方法getProxy是这样的,这样每次调用这个代理类中的方法就会被拦截到MethodInterceptor的intercept方法,重点也就是实现intercept方法,这个和【专栏】RPC系列(理论)-动态代理?中提到的JDK代理invoke是一样的作用
public Object getProxy() {
//设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) {
RpcRequest rpcRequest = new RpcRequest();
//放入调用服务信息
rpcRequest.setNameSpace(metaInfo.getNameSpace());
rpcRequest.setServiceName(metaInfo.getServiceName());
rpcRequest.setMethodName(method.getName());
//指定用jdk序列化方式
rpcRequest.setParameters(SerializeSupport.serialize(objects, SerializerType.TYPE_OBJECT_ARRAY.getType()));
return callRemoteService(rpcRequest);
}
private Object callRemoteService(RpcRequest request) {
RpcCommand rpcCommand = new RpcCommand();
RpcHeader header = new RpcHeader();
header.setType(CommandTypes.RPC_REQUEST.getType());
header.setVersion("v1.0");
header.setTraceId(UUID.randomUUID().toString());
rpcCommand.setHeader(header);
rpcCommand.setData(SerializeSupport.serialize(request));
try {
//todo 这个get与RpcResponseHandler的complete可讲
RpcResponse rpcResponse = rpcTransport.send(rpcCommand).get();
if (RspCode.SUCCESS.getCode() != rpcResponse.getCode()) {
throw new RuntimeException(rpcResponse.getErrorMsg());
}
return rpcResponse.getData();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
? ? intercept的逻辑就是组装Request并通过RpcTransport将请求发送出去,大家可以回顾一下send方法,就是Netty相关的部分。这边我们将调用远程方法的参数指定使用了JDK序列化方式,这也是出于教程目的,至此我们的代码结构是这样
? ? 然后对于客户端来说,创建代理类这个过程应该是透明的,客户端需要做的就是直接调用代理类的某个方法。除此之外,我们的Transport也需要根据服务的地址创建出来,这部分逻辑我们提取到NettyRpcAccessor中去
@Override
public <T> T getRemoteService(ServiceMetaInfo metaInfo, Class<T> clazz) throws InterruptedException, TimeoutException {
//查询服务地址
NameService nameService = SpiSupport.load(NameService.class);
String serviceSign = metaInfo.getServiceSign();
URI uri = nameService.seekService(serviceSign);
//与服务创建Transport
TransportClient transportClient = SpiSupport.load(TransportClient.class);
InetSocketAddress address = new InetSocketAddress(uri.getHost(), uri.getPort());
RpcTransport transport = transportClient.createTransport(address, 3000);
//创建代理类
ProxyFactory proxyFactory = SpiSupport.load(ProxyFactory.class);
proxyFactory.setRpcTransport(transport);
return proxyFactory.createProxy(clazz, metaInfo);
}
? ? 这个方法将NameService、Netty以及动态代理三者联系到了一起,充分理解这个方法的过程很重要。
|?请求测试
? ? 至此,我们完成了Sparrow-Rpc所有的环节,现在我们编写测试类来测试一下我们一步步完成的RPC服务。我们创建一个UserService服务,并实现一个getUserByName方法,在client远程调用这个方法,完整的代码在sparrow-rpc-client和sparrow-rpc-server两个模块下,启动com.sparrow.rpc.server.Server和com.sparrow.rpc.client.Client?就可以看到效果了,通过控制台的信息可以看到通过传入的“Mason”服务端返回了具体的用户信息给我们,另外还有健康检测的打印信息来服务保活。
| 总结
? ? 这一节我们实现了动态代理部分的逻辑,大家应该体会到了动态代理在RPC中的应用是怎样的,它最主要的作用在于代理远程服务,封装了组装调用远程服务的请求并获取结果这部分的网络交互逻辑,对于客户端只需要调用代理类中的目标方法就行了,这也是我们在专栏开头说到,RPC像调用本地方法一样调用远程服务的“魔法”所在。
? ? 另外,这也是实战篇的最后一篇文章,整个Sparrow-Rpc已经可以跑起来了,虽然离一个工业级的RPC有很大差距,但是我们用拙劣的方法摸清了RPC中最重要的功能,并动手实现了代码细节,相信大家对RPC已经有了更加深入的认识了,这里我建议大家可以再去看看开源的RPC框架的设计,知道Sparrow-Rpc和它们还有哪些差距才有优化的方向。
|