结合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;
    }
}

  

posted on 2019-12-18 22:39  溪水静幽  阅读(241)  评论(0)    收藏  举报