gitee地址
项目基于guide-rpc-framwork 拆解,完整框架请参考guide-rpc-framwork 。
先简单介绍下最开始这个RPC的使用方式:
服务端发布服务:
public static void main(String[] args) throws Exception {
// 提供一个名字叫做 hello 的远程服务,服务实例是 HelloService
ServiceProvider.publishService("hello", new HelloService());
//使用netty 启动RPC 服务
NettyRpcServer server = new NettyRpcServer();
server.start();
}
客户端调用服务:
public static void main(String[] args) {
// 调用服务名为 hello 的远程服务,参数是 miemie
Object hello = ServiceConsumer.invoke("hello", "miemie");
System.out.println(hello);
}
先基于这种最简单的实现方式,通过手动填写远程服务名,来发布和调用远程服务,后续会进行改造,模拟dubbo 实现方式,基于注解自动发布和调用服务。
简单的原理说明
基本流程:
- 服务生产者向注册中心提供以下信息: 服务的名称、服务地址和服务端口,这些信息将保存在注册中心。
- 服务消费者拿着服务名称,向注册中心询问,这个远程服务有哪些机器在提供,注册中心向消费者返回服务地址和服务端口。
- 消费者这下知道了要向哪台生产者建立连接,连接建立完成之后,向生产者发送一条数据:服务名和参数,表示要调用该远程服务。
- 生产者收到服务名和参数,通过服务名查找本地存在的服务实例,然后进行调用,并向消费者返回结果。
介绍用到的组件
consume
public class ServiceConsumer {
private static ServiceRegister serviceRegister = new LocalFileServiceRegister();
private static NettyRpcClient nettyRpcClient = NettyRpcClient.getNettyRpcClient();
public static Object invoke(String serviceName, Object ...params) {
InetSocketAddress address = serviceRegister.lookup(serviceName);
//发送参数,调用服务
CompletableFuture<RpcResponse> result = nettyRpcClient.sendRequest(address, serviceName, params);
try {
return result.get(15, TimeUnit.SECONDS).getData();
} catch (InterruptedException e) {
e.printStackTrace();
return null;
} catch (TimeoutException e) {
e.printStackTrace();
return null;
} catch (ExecutionException e) {
e.printStackTrace();
return null;
}
}
}
在基本流程第2 步中说到:服务消费者拿着服务名称,向注册中心询问,这个远程服务有哪些机器在提供,注册中心向消费者返回服务地址和服务端口。
因此,自然而然可以让 ServiceConsumer 持有一个 注册中心的引用 serviceRegister;
又如基本流程第3 步中说到:消费者这下向生产者建立连接,并发送服务名和参数,表示要调用该远程服务。
在这里我们用 Netty 替我们完成这项工作。
provider
public class ServiceProvider {
private static final ConcurrentHashMap<String, RemoteService> servicesMap = new ConcurrentHashMap<String, RemoteService>();
private static final ServiceRegister serviceRegister = new LocalFileServiceRegister();
//发布服务的方法
public static void publishService(String serviceName, RemoteService remoteService) {
servicesMap.put(serviceName, remoteService);
serviceRegister.regist(serviceName);
}
//查找服务的方法
public static RemoteService find(String serviceName) {
return servicesMap.get(serviceName);
}
}
在基本流程第1 步中说到,生产者向注册中心提供信息,由注册中心保存,因此生产者 **ServiceProvider ** 可以持有一个注册中心的引用。
在基本流程第4 步中说到,生产者通过服务名查找本地服务实例,这里用HashMap 来实现,key 是服务名称,value 是服务实例。
regist
public interface ServiceRegister {
void regist(String serviceName);
InetSocketAddress lookup(String serviceName);
}
/**
* 本地文件实现方式
*/
public class LocalFileServiceRegister implements ServiceRegister{
private static final String PATH = "register/dir";
private static final File dir = new File(PATH);
@Override
public void regist(String serviceName) {
if(!dir.exists()) {
dir.mkdirs();
}
File service = new File(PATH + "/" + serviceName);
service.deleteOnExit();
try {
service.createNewFile();
FileWriter fw = new FileWriter(service);
fw.write(InetAddress.getLocalHost().getHostAddress() + "@@" + NettyRpcServer.PORT);
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public InetSocketAddress lookup(String serviceName) {
File service = new File(PATH + "/" + serviceName);
if(!service.exists()) {
return null;
}
try {
BufferedReader br = new BufferedReader(new FileReader(service));
String ipPort = br.readLine();
String[] split = ipPort.split("@@");
return new InetSocketAddress(split[0], Integer.parseInt(split[1]));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
通过上面的基本流程,我们知道,注册中心至少要提供两个功能:服务注册和服务发现。
这里为了实现方便,先使用本地文件作为注册中心,后续改为zookeeper。
transport
传输层,我觉得是RPC 的核心,这里我使用Netty 作为生产者和消费者通信的桥梁。
在这里,简单实现,我们使用Java 序列化,后续这一块作为性能优化点,可以使用别的序列化方式。
我们将请求和响应封装成:
//rpc 请求
public class RpcRequest implements Serializable {
private static final long serialVersionUID = 1905122041950251207L;
private String requestId;
private String serviceName;
private Object[] parameters;
private Class<?>[] paramTypes;
}
// rpc 响应
public class RpcResponse<T> implements Serializable {
private static final long serialVersionUID = 715745410605631233L;
private String requestId;
/**
* response code
*/
private Integer code;
/**
* response message
*/
private String message;
/**
* response body
*/
private T data;
public static <T> RpcResponse<T> success(T data, String requestId) {
RpcResponse<T> response = new RpcResponse<>();
response.setCode(200);
response.setMessage("remote success");
response.setRequestId(requestId);
if (null != data) {
response.setData(data);
}
return response;
}
public static <T> RpcResponse<T> fail(int code) {
RpcResponse<T> response = new RpcResponse<>();
response.setCode(code);
response.setMessage("fail");
return response;
}
我们先看看客户端调用流程:
ServiceConsumer.invoke(…) 中,调用 NettyRpcClient 的 sendRequest(…) 方法, 这里用到了 CompletableFuture 接收返回结果,具体使用参考:CompletableFuture 使用
public CompletableFuture sendRequest(InetSocketAddress address, String serviceName, Object[] params) {
Class[] paramTypes = null;
if(params != null && params.length > 0) {
paramTypes = new Class[params.length];
for (int i = 0; i < params.length; i++) {
Object param = params[i];
paramTypes[i] = param.getClass();
}
}
// build return value
CompletableFuture<RpcResponse> resultFuture = new CompletableFuture<>();
//获取连接
Channel channel = getChannel(address);
RpcRequest rpcRequest = RpcRequest.builder()
.requestId(UUID.randomUUID().toString())
.parameters(params)
.serviceName(serviceName)
.paramTypes(paramTypes)
.build();
unprocessedRequests.put(rpcRequest.getRequestId(), resultFuture);
channel.writeAndFlush(rpcRequest).addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
log.info("client send message: [{}]", rpcRequest);
} else {
future.channel().close();
resultFuture.completeExceptionally(future.cause());
log.error("Send failed:", future.cause());
}
});
return resultFuture;
}
这里传入向注册中心获取的生产者地址建立连接通道(这里有个优化点,channel 没有复用,每次调用都新建连接);
Channel channel = getChannel(address);
@SneakyThrows
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();
}
然后构建RpcRequest,发送给生产者
channel.writeAndFlush(rpcRequest).addListener(....)
当生产者返回数据时,会触发NettyRpcClientHandler 的channelRead(…) 方法,到此就完成的远程服务的调用。
public class NettyRpcClientHandler extends ChannelInboundHandlerAdapter {
...
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
log.info("client receive msg: [{}]", msg);
if (msg instanceof RpcResponse) {
RpcResponse rpcResponse = (RpcResponse) msg;
unprocessedRequests.complete(rpcResponse);
}
} finally {
ReferenceCountUtil.release(msg);
}
}
...
}
接下来看看服务端的执行流程:
当生产者收到消费者的请求时,会触发NettyRpcServerHandler 的channelRead(…) 方法:
public class NettyRpcServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
if (msg instanceof RpcRequest) {
log.info("server receive msg: [{}] ", msg);
RpcRequest rpcRequest = (RpcRequest) msg;
// Execute the target method (the method the client needs to execute) and return the method result
Object result = rpcRequestHandler.handle(rpcRequest);
log.info(String.format("server get result: %s", result.toString()));
RpcResponse rpcResponse = null;
if (ctx.channel().isActive() && ctx.channel().isWritable() && ! (result instanceof Exception)) {
rpcResponse = RpcResponse.success(result, rpcRequest.getRequestId());
} else {
rpcResponse = RpcResponse.fail(500);
log.error("not writable now, message dropped");
}
ctx.writeAndFlush(rpcResponse).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}
} finally {
//Ensure that ByteBuf is released, otherwise there may be memory leaks
ReferenceCountUtil.release(msg);
}
}
}
然后查找指定的服务实例,执行调用:
public class RpcRequestHandler {
/**
* Processing rpcRequest: call the corresponding method, and then return the method
*/
public Object handle(RpcRequest rpcRequest) {
RemoteService service = ServiceProvider.find(rpcRequest.getServiceName());
if(service == null) {
return new RemoteException("not find service");
}
return invokeTargetMethod(rpcRequest, service);
}
/**
* get method execution results
*
* @param rpcRequest client request
* @param service service object
* @return the result of the target method execution
*/
private Object invokeTargetMethod(RpcRequest rpcRequest, RemoteService service) {
return service.invoke(rpcRequest.getParameters());
}
}
接下来要优化的点
1、解决channel 复用问题
2、目前服务实例必须实现 RemoteService 接口,不够灵活
3、不适用java 自身的序列化