基于netty的RPC实现

6 篇文章 0 订阅
4 篇文章 0 订阅

背景

RPC长用户客户端、服务端 以及 服务端和服务端之前传输数据。
这里的实现基于长连接,适合两端频繁通信的场景。连接数有限制的场景也可以考虑。

1. 客户端 服务端框架图示

无论客户端采用单链接 或是单链接,服务端都是并发进行处理.
1).单链接情况,客户端并发通过一个通道发送消息
在这里插入图片描述
2).多链接情况,这里只要修改下ClientHelper,每次发送时候,实例化一个实例,即可达到目的
在这里插入图片描述

2. 这里解决了三个问题

  1. 协议定义,解决 粘包/拆包 问题
  2. 单客户端并发发送/消息维护问题
  3. 服务端并发提供服务问题

3. 三个问题的具体实现如下

协议定义:

完整数据块包含数据 开始标识头,数据长度,真实数据三部分,如下图.
在这里插入图片描述
客户端,具体发送代码实现如下:

 public class RpcEncoder extends MessageToByteEncoder {
    @Override
    protected void encode(ChannelHandlerContext ctx, Object requestBoday, ByteBuf out) throws Exception {
        //序列化传输对象. 也可只是传输字符串,服务端解析,但是局限不较大,无法应对多样的调用函数,对应参数,已经类型
        byte[] data = SerializationUtil.serialize(requestBoday);
        //先写入 开始标识
        out.writeBytes(Constants.SERVIE_HEARD.getBytes());
        //再写入数据长度
        out.writeInt(data.length);
        //再写入真实数据
        out.writeBytes(data);
    }
}

服务端,具体接收解析代码实现如下:

public class RpcDecoder extends ByteToMessageDecoder {
     .............
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        //hadReadHeard避免多次判断头信息
        if (!hadReadHeard) {
            while (true) {
                //这里保证至少读到一个头信息,也可以读到一个头和数据长度在做处理
                if (in.readableBytes() < 4) {
                    return;
                }
                in.markReaderIndex();
                in.readBytes(dataHeardBuffer);
                System.out.println(Constants.SERVIE_HEARD.getBytes().length);
                String s = new String(dataHeardBuffer);
                //读到头标识信息,准备读取数据长度和数据
                if (s.equals(Constants.SERVIE_HEARD)) {
                    hadReadHeard = true;
                    break;
                } else {
                    in.resetReaderIndex();
                    //为读取到 头标识,则过滤一个字节,继续判断是否收到头标识
                    in.readByte();
                }
            }
        }

        in.markReaderIndex();
        int dataLength = in.readInt();
        if (in.readableBytes() < dataLength) {
            in.resetReaderIndex();
            return;
        }
        hadReadHeard = false;
        byte[] data = new byte[dataLength];
        in.readBytes(data);
        out.add(SerializationUtil.deserialize(data, requestResponseRpc));
    }
}
单客户端并发发送/消息维护问题:

发送消息的维护:
1)消息通过唯一id来区分
2)所有"发送的消息" 都记录到hashmap中维护记录.
3)发送消息后,会阻塞等待结果返回
4)所有接收的消息,都借助唯一ID匹配到"发送的消息",并唤醒(notify)阻塞的发送线程处理返回数据

public class ProxyHelperTool {
    ...........
    public <T> T create(final Class<?> interfaceClass) {
        return (T) Proxy.newProxyInstance(
                interfaceClass.getClassLoader(),
                new Class<?>[]{interfaceClass},
                new InvocationHandler() {
                    //@Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if (method.getDeclaringClass().getAnnotation(ServiceName.class) == null) {
                            throw new RuntimeException("Annotation(ServiceName) is null.");
                        }
                        //构造请求消息,并获取请求服务,方法,参数,参数类型
                        RequestRpc requestRpc = new RequestRpc();
                        requestRpc.setMethodName(method.getName());
                        requestRpc.setServiceName(method.getDeclaringClass().getAnnotation(ServiceName.class).name());
                        requestRpc.setParameters(args);
                        requestRpc.setParameterTypes(method.getParameterTypes());
                        //设置唯一id,确保消息的唯一性(1).
                        requestRpc.setRequestId(StringUtil.getUiid());
                        //将发送的消息 送入列表维护起来(2).
                        ClientHandler.waitingRPC.put(requestRpc.getRequestId(),requestRpc);
                        ProxyHelperTool.client.send(requestRpc);
                        //进入阻塞等待,直到服务返回消息 唤醒.To do:这里缺过时处理(3).
                        synchronized(requestRpc){
                            requestRpc.wait();
                        }
                        Object object = requestRpc.getResult();
                       ClientHandler.waitingRPC.remove(requestRpc.getRequestId());
                        return object;
                    }
                }
        );
    }
}

public class ClientHandler extends SimpleChannelInboundHandler<ResponseRpc> {
    public static ConcurrentHashMap<String, RequestRpc> waitingRPC = new ConcurrentHashMap<>();
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ResponseRpc msg) throws Exception {
        if(null != msg.getException()){
            throw new RuntimeException("server run error:"+msg.getException());
        } else {
            RequestRpc requestRpc = waitingRPC.get(msg.getRequestId());
            //赋值的同时,触发notify(4).
            requestRpc.setResult(msg.getResult());
        }
    }
    .....
}
服务端并发服务:
public class ServerHandler extends ChannelInboundHandlerAdapter {
    .............
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //將服务方静线程里执行,避免阻塞
        ServerService.submit(new Runnable() {
            @Override
            public void run() {
                RequestRpc requestRpc = (RequestRpc)msg;
                ResponseRpc responseRpc = handle(requestRpc);
                responseRpc.setRequestId(requestRpc.getRequestId());
                ctx.writeAndFlush(responseRpc).addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture channelFuture) throws Exception {
                        System.out.println("Server operationComplete");
                    }
                });
            }
        });

        /*.addListener(ChannelFutureListener.CLOSE)*/
    }
    //真实处理服务的地方,依据对方传递的 调用服务和参数通过反射调用获取结果返回
    private ResponseRpc handle(RequestRpc requestRpc){
        ResponseRpc responseRpc = new ResponseRpc();
        Object object = ServerService.getService(requestRpc.getServiceName());
        if(object == null){
            responseRpc.setException(new RuntimeException("Not service:"+requestRpc.
                    getServiceName()));
            return responseRpc;
        }

        try {
            Class<?> serviceClass = object.getClass();
            Method method = serviceClass.getMethod(requestRpc.getMethodName(),
                    requestRpc.getParameterTypes());
            method.setAccessible(true);
            Object[] parameters = requestRpc.getParameters();
            responseRpc.setResult(method.invoke(object, parameters));
        } catch (Exception e){
            responseRpc.setResult(e);
        }
        return responseRpc;
    }
  ........
}

4. 测试方式,以及结果

客戶端 测试模拟 调用远程服务

这里, 客户端建立单链接,并发发送消息的方式 向服务端发起服务调用

public class TestClient {
    public static ProxyHelperTool proxyHelperTool = new ProxyHelperTool();
    public static void main(String[] args) throws Exception {
        int threadNumber = 15;
        CountDownLatch countDownLatch = new CountDownLatch(threadNumber);
        //开始15个线程发送 服务调用消息
        for(int i=0;i<threadNumber;i++){
            new Thread(){
                @Override
                public void run() {
                    //客户端,通过传递当前线程的名称(Thread.currentThread().getName)给服务端;
                    //服务端,组合收到的字符 再次发回来。
                    //通过对比 "线程名",可见各个线程收到的是否是自己发送的。
                    MsgService msgService = proxyHelperTool.create(MsgService.class);
                    String reslut = msgService.send(Thread.currentThread().getName());
                    System.out.println("Client("+Thread.currentThread().getName()+") get mag:" + "\n" + "..." + reslut);
                    countDownLatch.countDown();
                }
            }.start();
        }
        countDownLatch.await();
        ClientHelper.getClientHelper().close();
    }

}
客戶端 测试模拟 收到的结果

可见对应的调用线程,都收到了自己发出去的消息. 服务端返回的和自身的thread-name 匹配的上,说明消息未串,流程正常。
在这里插入图片描述

6.未处理问题

消息过时问题,可以加个线程循环检测

6. github代码地址

https://github.com/liubin192837/rpcNetty1812

7. 参考

https://my.oschina.net/huangyong/blog/361751?fromerr=NpC3phqY
https://github.com/apache/hadoop

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值