学习黄勇smart-rpc的笔记
黄勇的码云地址:https://gitee.com/huangyong/rpc
黄勇的oschina博客地址:https://my.oschina.net/huangyong/blog/361751
rpc是客户端把请求对象序列化成二进制通过网络传给服务端,服务端拿到这些二进制后反序列化成本地对象,执行本地方法调用,把生成的结果再用同样的方式传给客户端。如何获取服务端的地址,如何让对象在网络上传输,这两个过程中我们可以做很多自己的操作,比如服务注册,负载均衡,nio等。
1、客户端使用动态代理创建代理对象,smart-rpc使用jdk的动态代理
/**
* 动态代理对接口进行实现
* 代理的好处,不管哪个方法都要进行这样的操作
*
* @param interfaceClass
* @param serviceVersion
* @param <T>
* @return
*/
public <T> T create(final Class<?> interfaceClass, final String serviceVersion) {
// 创建动态代理对象
return (T) Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class<?>[]{interfaceClass},
new InvocationHandler() {
// invoke方法方法调用时执行
// 真正发送请求,接受响应的方法都在这里
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 创建 RPC 请求对象并设置请求属性
RpcRequest request = new RpcRequest();
// uuid做requestId
request.setRequestId(UUID.randomUUID().toString());
// 接口类型
request.setInterfaceName(method.getDeclaringClass().getName());
// 接口版本
request.setServiceVersion(serviceVersion);
// 方法名称
request.setMethodName(method.getName());
// 方法参数
request.setParameterTypes(method.getParameterTypes());
request.setParameters(args);
// 获取 RPC 服务地址
if (serviceDiscovery != null) {
String serviceName = interfaceClass.getName();
if (StringUtil.isNotEmpty(serviceVersion)) {
serviceName += "-" + serviceVersion;
}
// 从注册中心找到服务端的地址(ip+ port)
serviceAddress = serviceDiscovery.discover(serviceName);
LOGGER.debug("discover service: {} => {}", serviceName, serviceAddress);
}
if (StringUtil.isEmpty(serviceAddress)) {
throw new RuntimeException("server address is empty");
}
// 从 RPC 服务地址中解析主机名与端口号
String[] array = StringUtil.split(serviceAddress, ":");
String host = array[0];
int port = Integer.parseInt(array[1]);
// 创建 RPC 客户端对象并发送 RPC 请求
RpcClient client = new RpcClient(host, port);
long time = System.currentTimeMillis();
// 通过网络远程传输请求数据
// 接受返回的响应
RpcResponse response = client.send(request);
LOGGER.debug("time: {}ms", System.currentTimeMillis() - time);
if (response == null) {
throw new RuntimeException("response is null");
}
if (response.hasException()) {
throw response.getException();
} else {
return response.getResult();
}
}
}
);
}
2、客户端将请求对象写入网络中
/**
* 向远程服务器发送请求的过程
* 实际上就是把我们自己封装的请求对象写进输出流中
* @param request
* @return
* @throws Exception
*/
public RpcResponse send(RpcRequest request) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
// 创建并初始化 Netty 客户端 Bootstrap 对象
// 使用netty 传输数据
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
// 设置对传输的数据的处理
// 对RpcRequest对象要编码处理
pipeline.addLast(new RpcEncoder(RpcRequest.class)); // 编码 RPC 请求
// 对RpcResponse对象要解码处理
pipeline.addLast(new RpcDecoder(RpcResponse.class)); // 解码 RPC 响应
// 对通道返回数据的处理,保存到Response对象
pipeline.addLast(RpcClient.this); // 处理 RPC 响应
}
});
bootstrap.option(ChannelOption.TCP_NODELAY, true);
// 连接 RPC 服务器
// 知道服务器地址,真正建立连接
ChannelFuture future = bootstrap.connect(host, port).sync();
// 写入 RPC 请求数据并关闭连接
// 建立通道
Channel channel = future.channel();
// rpc请求对象在这被写入了通道中
channel.writeAndFlush(request).sync();
// 关闭通道
channel.closeFuture().sync();
// 返回 RPC 响应对象
System.out.println("响应要被返回了");
// 响应被保存到了Response对象中
return response;
} finally {
group.shutdownGracefully();
}
}