如何写一个RPC框架(五):服务器端实现
来源:互联网 发布:手机下载在线视频软件 编辑:程序博客网 时间:2024/06/04 16:04
在后续一段时间里, 我会写一系列文章来讲述如何实现一个RPC框架(我已经实现了一个示例框架, 代码在我的github上)。 这是系列第五篇文章, 主要讲述了服务器端的实现。
在前面的几篇文章里, 我们已经实现了客户端创建proxy bean, 并利用它来发送请求、处理返回的全部流程:
1. 扫描package找出需要代理的service
2. 通过服务注册中心和Load Balancer获取service地址
3. 利用Netty与service建立连接, 并且复用所创建的channel
4. 创建request, 用唯一的requestId来标识它, 发送这个请求, 调用future.get()
4. 收到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); } } }
这里几个地方需要说明一下:
1. 这里的ip和port我直接用了配置文件中传入的, 优化的方案应该是获取本地ip以及找到一个可用端口
2. 利用Netty创建server, 在pipeline中加入RPCServerHandler, 这个handler将在下文给出
3. 向服务注册中心注册实现的所有服务
4.RPCServerHandler的实现
@AllArgsConstructorpublic 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。
- 如何写一个RPC框架(五):服务器端实现
- 如何写一个RPC框架(一):关注点与我的实现
- 如何写一个RPC框架(六):负载均衡
- 【远程调用框架】如何实现一个简单的RPC框架(五)优化三:软负载中心设计与实现
- 如何实现一个简单的rpc框架
- 如何实现一个分布式 RPC 框架
- 如何实现一个分布式 RPC 框架
- 如何实现一个分布式RPC框架
- 如何实现一个分布式RPC框架
- 如何实现一个分布式 RPC 框架
- 如何实现一个分布式RPC框架
- 如何写一个RPC框架(二):利用Bean容器和动态代理简化客户端代码
- 如何写一个RPC框架(三):服务注册与服务发现
- 如何写一个RPC框架(四):网络通信之客户端篇
- 【远程调用框架】如何实现一个简单的RPC框架(一)想法与设计
- 如何写一个给自己的框架写一个优雅的Java Config模块(五)
- 一起写RPC框架(五)RPC网络模块的搭建三 序列化
- 一起写RPC框架(七)RPC网络模块的搭建五 Netty模块
- 11月13日笔记
- 策略模式
- inf
- PAT (Basic Level) Practise (中文)1057. 数零壹(20)
- 如何求素数(重点为筛选法)
- 如何写一个RPC框架(五):服务器端实现
- HDU 1201 18岁生日
- 【NOIP2017】滚粗记
- TimeQuest之delay_fall clock_fall傻傻分不清楚
- vagrant init命令及其他一些命令
- 6.4
- 第一次做小游戏
- POJ-1159 Palindrome (区间dp)
- 日志:using the Connector/J connection property 'autoReconnect=true' to avoid this problem