gitee地址
项目基于guide-rpc-framwork 拆解,完整框架请参考guide-rpc-framwork 。
上篇地址:day0
本篇主要内容:
- 解决Channel 复用问题
- 使用kryo 序列化替代JDK 自身的序列化
- 优化远程服务实现方法
先简单介绍下最开始这个RPC的使用方式:
服务端发布服务:
public class HelloServerTest {
public static void main(String[] args) throws Exception {
ServiceProvider.publishService("HelloService", new HelloService());
NettyRpcServer server = new NettyRpcServer();
server.start();
}
}
客户端调用服务:
public class ClientTest {
public static void main(String[] args) {
// HelloService.hello 表示要调用HelloService 这个实例的hello 方法
Object hello = ServiceConsumer.invoke("HelloService.hello", "miemie");
System.out.println(hello);
}
}
先基于这种最简单的实现方式,通过手动填写远程服务名,来发布和调用远程服务,后续会进行改造,模拟dubbo 实现方式,基于注解自动发布和调用服务。
解决Channel 复用问题:
在day0 获取channel 的实现方式是这样的:
private Channel getChannel(InetSocketAddress address) {
CompletableFuture<Channel> completableFuture = new CompletableFuture<>();
bootstrap.connect(address).addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
log.info("The client has connected [{}] successful!", address.toString());
completableFuture.complete(future.channel());
} else {
throw new IllegalStateException();
}
});
return completableFuture.get();
}
这样的实现方式,每调用一次远程服务,都要创建一个channel,严重损耗消费者和生产者的资源。
基于Netty的实现,我们在整个消费者的生命周期中,对于每一个生产者,我们可以保持一个Channel 即可。
//获取Channel,先去容器中判断Channel 存不存在,不存在再建立连接
private Channel getChannel(InetSocketAddress address) {
Channel channel = channelProvider.get(address);
if(channel == null) {
synchronized (this) {
channel = channelProvider.get(address);
if(channel == null) {
//do connect
CompletableFuture<Channel> completableFuture = new CompletableFuture<>();
bootstrap.connect(address).addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
log.info("The client has connected [{}] successful!", address.toString());
completableFuture.complete(future.channel());
} else {
throw new IllegalStateException();
}
});
channel = completableFuture.get();
channelProvider.put(address, channel);
}
}
}
return channel;
}
// Channel 容器
public class ChannelProvider {
private final Map<String, Channel> channelMap = new ConcurrentHashMap<>();
...
}
使用kryo 序列化替代JDK 自身的序列化:
在day0 使用的是Netty 自带的编解码器,底层用的是JDK 自带的序列化方式,性能差,如下:
p.addLast(new ObjectEncoder());
p.addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
这里我们使用自定义的编解码器,底层使用Kryo 序列化方式(当然,也可以改成hessian2,参考SerializerFactory),如下:
p.addLast(new CustomObjectEncoder());
p.addLast(new CustomObjectDecoder());
对此,读者需要了解如何在Netty 中自定义帧的格式,可以查看Netty 粘包解决方案
优化远程服务实现方法
在day0 中,要实现一个远程方法,需要实现RemoteService 接口,接口如下:
public interface RemoteService {
Object invoke(Object ... params);
}
此时一个远程服务类只能提供一个远程方法调用,十分难用,因此本次改造,直接去掉RemoteService.invoke() 方法,每个远程服务类不限制存在多少个远程方法。
/**
* mark interface
* 标记这是一个远程服务,无其他左右,后续结合spring 自动注入可以用注解替代
*/
public interface RemoteService {
}
而此时消费者调用远程服务时,不仅需要提供远程服务实例名,还要提供要调用的方法信息,如下:
Object hello = ServiceConsumer.invoke("HelloService.hello", "miemie");
接下来要优化的点
1、使用zookeeper 作为服务注册中心
2、客户端调用远程服务的时候进行负载均衡