结合zk实现简易的RPC框架
一个RPC框架需要重点关注哪几个点:
- 代理 (动态代理)
- 通讯协议
- 序列化
- 网络传输

基于Netty实现RPC
1、需要确定通信双方的协议格式,请求对象和响应对象,请求对象的ID是客户端用来验证服务器请求和响应是否匹配
@Data @ToString public class RpcRequest { /** * 请求对象的Id */ private String requestId; /** * 类名 */ private String className; /** * 方法名 */ private String methodName; /** * 参数类型 */ private Class<?>[] parameterTypes; /** * 入参 */ private Object[] parameters; }
响应对象
@Data public class RpcResponse { /** * 响应Id */ private String requestId; /** * 错误信息 */ private String error; /** * 结果 */ private Object result; }
序列化
选用JSON作为序列化协议,使用fastjson作为JSON框架,序列化接口:
public interface Serializer { /** * java对象序列化二进制 * * @param object * @return * @throws IOException */ byte[] serialize(Object object) throws IOException; /** * 二进制转化为java对象 * * @param clazz * @param bytes * @param <T> * @return * @throws IOException */ <T> T deserialize(Class<T> clazz, byte[] bytes) throws IOException; }
JSON序列化方式
public class JSONSerializer implements Serializer { @Override public byte[] serialize(Object object) throws IOException { return JSON.toJSONBytes(object); } @Override public <T> T deserialize(Class<T> clazz, byte[] bytes) throws IOException { return JSON.parseObject(bytes, clazz); } }
编码器将请求对象转换为适合于传输的格式(一般来说是字节流),而对应的解码器是将网络字节流转换回应用程序的消息格式,netty 支持自定义 coder 。所以只需要实现 ByteToMessageDecoder 和 MessageToByteEncoder 两个接口。
public class RpcEncoder extends MessageToByteEncoder { private Class<?> clazz; private Serializer serializer; public RpcEncoder(Class<?> clazz, Serializer serializer) { this.clazz = clazz; this.serializer = serializer; } @Override protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception { if (clazz != null && clazz.isInstance(msg)) { byte[] bytes = serializer.serialize(msg); out.writeInt(bytes.length); out.writeBytes(bytes); } } }
解码器实现:
public class RpcDecoder extends ByteToMessageDecoder {
private Class<?> clazz;
private Serializer serializer;
public RpcDecoder(Class<?> clazz, Serializer serializer) {
this.clazz = clazz;
this.serializer = serializer;
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> list) throws Exception {
if (byteBuf.readableBytes() < 4) {
return;
}
int dataLength = byteBuf.readInt();
if (byteBuf.readableBytes() < dataLength) {
byteBuf.resetReaderIndex();
return;
}
byte[] data = new byte[dataLength];
byteBuf.readBytes(data);
Object obj = serializer.deserialize(clazz, data);
list.add(obj);
}
}
netty客户端
需要注意以下几点:
- 编写启动方法,指定传输使用Channel
- 指定ChannelHandler,对网络传输中的数据进行读写处理
- 添加编解码器
- 添加失败重试机制
- 添加发送请求消息的方法
前端调用方法
@Slf4j public class RpcClientDynamicProxy<T> implements InvocationHandler { private Class<T> clazz; public RpcClientDynamicProxy(Class<T> clazz) throws Exception { this.clazz = clazz; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { RpcRequest rpcRequest = new RpcRequest(); String requestId = UUID.randomUUID().toString(); String className = method.getDeclaringClass().getName(); String methodName = method.getName(); Class<?>[] parameterTypes = method.getParameterTypes(); rpcRequest.setRequestId(requestId); rpcRequest.setClassName(className); rpcRequest.setMethodName(methodName); rpcRequest.setParameterTypes(parameterTypes); rpcRequest.setParameters(args); String[] hostAndPort = ZkService.discover("/" + className).split(":"); log.info("请求的内容:{},连接服务的地址为:{}", rpcRequest, hostAndPort); NettyClient nettyClient = new NettyClient(hostAndPort[0], Integer.parseInt(hostAndPort[1])); RpcResponse response = nettyClient.connectAndSend(rpcRequest); log.info("请求返回的结果:{}", response.getResult()); return response.getResult(); } }
nettyClient实现
@Slf4j public class NettyClient { private String host; private Integer port; private static final int MAX_RETRY = 5; public NettyClient(String host, Integer port) { this.host = host; this.port = port; } public RpcResponse connectAndSend(RpcRequest request) { NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup(); ClientHandler clientHandler = new ClientHandler(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(eventLoopGroup) .channel(NioSocketChannel.class) .remoteAddress(new InetSocketAddress(host, port)) .option(ChannelOption.SO_KEEPALIVE, true) .option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4)); pipeline.addLast(new RpcEncoder(RpcRequest.class, new JSONSerializer())); pipeline.addLast(new RpcDecoder(RpcResponse.class, new JSONSerializer())); pipeline.addLast(clientHandler); } }); ChannelFuture future = bootstrap.connect().sync(); Channel channel = future.channel(); channel.writeAndFlush(request).await(); RpcResponse rpcResponse = clientHandler.getRpcResponse(request.getRequestId()); return rpcResponse; } catch (InterruptedException e) { e.printStackTrace(); } finally { eventLoopGroup.shutdownGracefully(); } return null; } private void connect(Bootstrap bootstrap, String host, int port, int retry) { ChannelFuture channelFuture = bootstrap.connect(host, port).addListener(future -> { if (future.isSuccess()) { log.info("连接服务端成功"); } else if (retry == 0) { log.error("重试次数已达上限"); } else { //重连次数 int order = (MAX_RETRY - retry) + 1; //本次重连的间隔 int delay = 1 << order; log.error("{}:连接失败,第{}次重连....", new Date(), order); bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retry - 1), delay, TimeUnit.SECONDS); } }); // channel = channelFuture.channel(); } }
ClientHandler类继承了ChannelDuplexHandler类,可以对出站和入站的数据进行处理
public class ClientHandler extends ChannelDuplexHandler { /** * 使用Map维护请求对象ID与响应结果Future的映射关系 */ private final Map<String, DefaultFuture> futureMap = new ConcurrentHashMap<>(); public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof RpcResponse) { RpcResponse response = (RpcResponse) msg; DefaultFuture defaultFuture = futureMap.get(response.getRequestId()); defaultFuture.setResponse(response); } super.channelRead(ctx, msg); } @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { if (msg instanceof RpcRequest) { RpcRequest request = (RpcRequest) msg; //发送请求对象之前,先把请求ID保存下来,并构建一个与响应Future的映射关系 futureMap.putIfAbsent(request.getRequestId(), new DefaultFuture()); } super.write(ctx, msg, promise); } public RpcResponse getRpcResponse(String requestId){ try{ DefaultFuture defaultFuture = futureMap.get(requestId); return defaultFuture.getRpcResponse(5000); }finally { //获取成功以后,从map中移除 futureMap.remove(requestId); } } }
定义一个Map,维护请求ID与响应结果的映射关系,目的是为了客户端用来验证服务端响应是否与请求相匹配,因为Netty的channel可能被多个线程使用,当结果返回时,不知道是从哪个线程返回的,所以需要一个映射关系。
结果是封装在DefaultFuture中的,因为Netty是异步框架,所有的返回都是基于Future和Callback机制的,这里自定义Future来实现客户端”异步调用”
public class DefaultFuture { private RpcResponse rpcResponse; private volatile boolean isSuccess = true; private final Object object = new Object(); public RpcResponse getRpcResponse(int timeout) { synchronized (object) { while (!isSuccess) { try { object.wait(timeout); } catch (InterruptedException e) { e.printStackTrace(); } } return rpcResponse; } } public void setResponse(RpcResponse response) { if (isSuccess) { return; } synchronized (object) { this.rpcResponse = response; this.isSuccess = true; object.notify(); } } }
用了wait和notify机制,同时使用一个boolean变量做辅助
netty服务端
当对请求进行解码过后,需要通过代理的方式调用本地函数。下面是Server端代码:
/** * netty server服务 * * @author 12780 */ @Slf4j @Component public class NettyServer implements CommandLineRunner { @Autowired private ServerHandler serverHandler; private final String ipAddress = "127.0.0.1"; private final int port = 8888; @Value("${zk.server}") private String zkServer; @Value("${zk.port}") private int zkPort; public void registerAndStart() throws Exception { Map<String, Object> rpcServer = new HashMap<>(); Collection<Object> services = SpringContextHolder.getBeansWithAnnotation(RpcServer.class).values(); for (Object obj : services) { rpcServer.put("/" + obj.getClass().getAnnotation(RpcServer.class).cls().getName(), obj); } try { start(rpcServer); } catch (Exception e) { log.error("启动服务异常:{}", e.getMessage()); } } public void start(Map<String, Object> rpcServer) throws Exception { EventLoopGroup boss = new NioEventLoopGroup(); EventLoopGroup worker = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(boss, worker) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1024) .option(ChannelOption.SO_KEEPALIVE, true) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new LengthFieldBasedFrameDecoder(65535, 0, 4)); pipeline.addLast(new RpcEncoder(RpcResponse.class, new JSONSerializer())); pipeline.addLast(new RpcDecoder(RpcRequest.class, new JSONSerializer())); pipeline.addLast(serverHandler); } }); ChannelFuture future = serverBootstrap.bind(ipAddress, port).sync(); if (future.isSuccess()) { rpcServer.keySet().forEach(key -> ZkService.registry(key, ipAddress + ":" + port)); } future.channel().closeFuture().sync(); } finally { boss.shutdownGracefully().sync(); worker.shutdownGracefully().sync(); } } @Override public void run(String... args) throws Exception { registerAndStart(); } }
ServerHandler实现类
@Slf4j @Component @ChannelHandler.Sharable public class ServerHandler extends SimpleChannelInboundHandler<RpcRequest> implements ApplicationContextAware { private ApplicationContext applicationContext; @Override protected void channelRead0(ChannelHandlerContext ctx, RpcRequest msg) throws Exception { RpcResponse rpcResponse = new RpcResponse(); rpcResponse.setRequestId(msg.getRequestId()); try { Object handler=handler(msg); log.info("获取返回结果:{}",handler); rpcResponse.setResult(handler); } catch (Throwable throwable) { rpcResponse.setError(throwable.toString()); throwable.printStackTrace(); } ctx.writeAndFlush(rpcResponse); } /** * 服务端使用代理处理请求 * * @param request * @return */ private Object handler(RpcRequest request) throws ClassNotFoundException, InvocationTargetException { Class<?> clazz = Class.forName(request.getClassName()); Object serviceBean = applicationContext.getBean(clazz); log.info("serviceBean: {}", serviceBean); Class<?> serviceBeanClass = serviceBean.getClass(); log.info("serverClass:{}", serviceBeanClass); String methodName = request.getMethodName(); Class<?>[] parameterTypes = request.getParameterTypes(); Object[] parameters = request.getParameters(); //使用CGLIB FastClass fastClass = FastClass.create(serviceBeanClass); FastMethod fastMethod = fastClass.getMethod(methodName, parameterTypes); log.info("调用CGLIB动态代理执行服务端方法..."); return fastMethod.invoke(serviceBean, parameters); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
立志如山 静心求实
浙公网安备 33010602011771号