在後續一段時間裏, 我會寫一系列文章來講述如何實現一個RPC框架(我已經實現了一個示例框架, 代碼在我的github上)。 這是系列第五篇文章, 主要講述了服務器端的實現。
在前面的幾篇文章裏, 我們已經實現了客戶端創建proxy bean, 並利用它來發送請求、處理返回的全部流程:
- 掃描package找出需要代理的service
- 通過服務註冊中心和Load Balancer獲取service地址
- 利用Netty與service建立連接, 並且複用所創建的channel
- 創建request, 用唯一的requestId來標識它, 發送這個請求, 調用future.get()
- 收到response,利用response中附帶的requestId找到對應future,讓future變成done的狀態
這篇文章, 我們會介紹server端的實現。
1.獲取server端所實現的接口
private List<Class<?>> getServiceInterfaces(ApplicationContext ctx) {
Class<? extends Annotation> clazz = RPCService.class;
return ctx.getBeansWithAnnotation(clazz)
.values().stream()
.map(AopUtils::getTargetClass)
.map(cls -> Arrays.asList(cls.getInterfaces()))
.flatMap(List::stream)
.filter(cls -> Objects.nonNull(cls.getAnnotation(clazz)))
.collect(Collectors.toList());
}
利用Spring的ApplicationContext, 獲取bean容器中帶有RPCService註解的bean。
2.bean與對應的接口名一一對應並保存在map中
private Map<String, Object> handlerMap = new HashMap<>();
getServiceInterfaces(ctx)
.stream()
.forEach(interfaceClazz -> {
String serviceName = interfaceClazz.getAnnotation(RPCService.class).value().getName();
Object serviceBean = ctx.getBean(interfaceClazz);
handlerMap.put(serviceName, serviceBean);
log.debug("Put handler: {}, {}", serviceName, serviceBean);
});
handlerMap的作用是, 收到請求時, 可以通過這個map找到該請求所對應的處理對象。
3.啓動服務器並註冊所實現的service
private void startServer() {
// Get ip and port
String[] addressArray = StringUtils.split(serviceAddress, ":");
String ip = addressArray[0];
int port = Integer.parseInt(addressArray[1]);
log.debug("Starting server on port: {}", port);
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new RPCDecoder(RPCRequest.class, new ProtobufSerializer()));
pipeline.addLast(new RPCEncoder(RPCResponse.class, new ProtobufSerializer()));
pipeline.addLast(new RPCServerHandler(handlerMap));
}
});
bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = bootstrap.bind(ip, port).sync();
log.info("Server started");
registerServices();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
throw new RuntimeException("Server shutdown!", e);
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
private void registerServices() {
if (serviceRegistry != null && serviceAddress != null) {
for (String interfaceName : handlerMap.keySet()) {
serviceRegistry.register(interfaceName, serviceAddress.toString());
log.info("Registering service: {} with address: {}", interfaceName, serviceAddress);
}
}
}
這裏幾個地方需要說明一下:
- 這裏的ip和port我直接用了配置文件中傳入的, 優化的方案應該是獲取本地ip以及找到一個可用端口
- 利用Netty創建server, 在pipeline中加入RPCServerHandler, 這個handler將在下文給出
- 向服務註冊中心註冊實現的所有服務
4.RPCServerHandler的實現
@AllArgsConstructor
public class RPCServerHandler extends SimpleChannelInboundHandler<RPCRequest> {
private Map<String, Object> handlerMap;
@Override
public void channelRead0(final ChannelHandlerContext ctx, RPCRequest request) throws Exception {
log.debug("Get request: {}", request);
RPCResponse response = new RPCResponse();
response.setRequestId(request.getRequestId());
try {
Object result = handleRequest(request);
response.setResult(result);
} catch (Exception e) {
log.warn("Get exception when hanlding request, exception: {}", e);
response.setException(e);
}
ctx.writeAndFlush(response).addListener(
(ChannelFutureListener) channelFuture -> {
log.debug("Sent response for request: {}", request.getRequestId());
});
}
private Object handleRequest(RPCRequest request) throws Exception {
// Get service bean
String serviceName = request.getInterfaceName();
Object serviceBean = handlerMap.get(serviceName);
if (serviceBean == null) {
throw new RuntimeException(String.format("No service bean available: %s", serviceName));
}
// Invoke by reflect
Class<?> serviceClass = serviceBean.getClass();
String methodName = request.getMethodName();
Class<?>[] parameterTypes = request.getParameterTypes();
Object[] parameters = request.getParameters();
Method method = serviceClass.getMethod(methodName, parameterTypes);
method.setAccessible(true);
return method.invoke(serviceBean, parameters);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
log.error("server caught exception", cause);
ctx.close();
}
}
這個實現相當簡單,收到請求之後, 根據servicename,找到對應的handler,再利用反射進行方法調用。
就這樣, 一個簡單的RPCServer就實現了。 完整代碼請看我的github。