2018.7.4 la-rpc心跳与断线重连部分记录

心跳与断线重连主要解决客户端连接时出现的三种问题:

(1)client初次连接失败,需要重连

(2)server进程关闭,client检测tcp断开重连

(3)server断电,client通过心跳检测重连

(注:所有情况都默认是在单一server的情况下实现的,即暂时不考虑多服务器多channel的负载均衡实现)

一、client初次连接失败,需要重连

使用ConnectionListener

b.connect().addListener(new ConnectionListener());

覆盖operationComplete方法

public void operationComplete(ChannelFuture channelFuture) throws Exception {
        if (!channelFuture.isSuccess()) {
            System.out.println(Thread.currentThread());
            System.out.println("Reconnect");
            RpcClient.client.times++;
            if(RpcClient.client.times==5) {
                RpcClient.client.flag=false;
                return;
            }
            final EventLoop loop = channelFuture.channel().eventLoop();
            loop.schedule(new Runnable() {
                @Override
                public void run() {
                        RpcClient.client.connect(loop);
                }
            }, 1L, TimeUnit.SECONDS);
        }
    }

如果连接不成功,在1s后在channel所在eventloop执行重新连接

并且设置当前times数量加一,如果已失败四次,则不考虑重连,停止进行“递归”调用。同时设置flag为false,通知consumer线程出现异常

public class RpcClient:

public static volatile boolean flag=true;
    public void send(byte[] bytes) throws Exception {
        while(channel==null){
            if(!flag){
                throw new Exception("Can't connect to server");
            }
        }
        ByteBuf firstMessage=Unpooled.directBuffer();
        firstMessage.writeInt(bytes.length);
        firstMessage.writeBytes(bytes);
        channel.writeAndFlush(firstMessage);
    }

consumer线程轮询channel与flag

二、server进程关闭,client检测tcp断开重连

server进程关闭时,操作系统会释放掉进程的资源,tcp进行挥手操作断开连接,ChannelInboundHandlerAdapter会检测到连接断开,并调用覆盖的channelInactive方法。

    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        final EventLoop eventLoop = ctx.channel().eventLoop();
        eventLoop.schedule(new Runnable() {
            @Override
            public void run() {
                    RpcClient.client.connect(eventLoop);
            }
        }, 1L, TimeUnit.SECONDS);
        super.channelInactive(ctx);
    }

channelInactive调用connect(),进入初次连接的流程

如果重新连接成功,则重新发送requestCache中的请求

        int cacheSize;
        if((cacheSize=rpcClient.requestCache.size())>0){
            for(Map.Entry entry:rpcClient.requestCache.entrySet()){
                try {
                    rpcClient.send(entry.getValue());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

三、server断电,client通过心跳检测重连

事实上,心跳检测分为两种:

(1)client向server发送心跳检测,channel短时间没有收到server的消息,就会向server发送心跳包。这是发生比较频繁的情况,防止server断电。

(2)server对client的存活检测,如果超过client心跳检测间隔时间,却没有收到心跳信息,则默认为client断电关闭,关闭对应channel。

下面看下client端具体实现:

client加入netty自带的IdleStateHandler,检测channel读超时,主要关注读超时参数,这里笔者设置成了240s,是RFC规范MSL(max segment lifetime)——2min的2倍。

ch.pipeline().addLast(new IdleStateHandler(240, 1000, 1000, TimeUnit.SECONDS));

若发生读超时的事件,则回调userEventTriggered方法

    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        super.userEventTriggered(ctx, evt);
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state().equals(IdleState.READER_IDLE)) {
                System.out.println("No message from server for 5s");
                RpcClient.client.times++;
                if(RpcClient.client.times==3){
                    RpcClient.client.times=0;
                    ctx.channel().close();
                    channelInactive(ctx);
                }
                ByteBuf heartbeat=Unpooled.directBuffer();
                heartbeat.writeBytes(this.heartbeat);
                ctx.channel().writeAndFlush(heartbeat);
            } else if (event.state().equals(IdleState.WRITER_IDLE)) {
            } else if (event.state().equals(IdleState.ALL_IDLE)) {
            }
        }
    }

若已经发送三次心跳包,则调用channelInactive()进行重连,并关闭原有channel

heartbeat实现如下:

    private byte[] heartbeat;
        ByteBuf heartbeat=Unpooled.directBuffer();
        byte ping=0;
        heartbeat.writeInt(1);
        heartbeat.writeByte(ping);
        this.heartbeat=new byte[heartbeat.readableBytes()];
        heartbeat.readBytes(this.heartbeat);

heartbeat是只有1byte的信息,由option(ChannelOption.TCP_NODELAY,true)保证发送

在server接收时

        if(request.length==1){
            ByteBuf heartbeat=Unpooled.directBuffer();
            heartbeat.writeBytes(this.heartbeat);
            ctx.channel().writeAndFlush(heartbeat);
            System.out.println("ping");
        }

判断request长度,如果只为1byte则为收到心跳包

下面看下server端具体实现:

同样加入IdleStateHandler

ch.pipeline().addLast(new IdleStateHandler(360, 1000, 1000, TimeUnit.SECONDS));
            if (event.state().equals(IdleState.READER_IDLE)) {
                ctx.channel().close();

如果发生读超时,则默认client关闭,关闭对应channel

另:

==写了一大段connect的分析被吞了,不想再写了,就附上两个非常好的netty解析教程吧

beautyboss.farbox.com/post/netty/nettylian-jie-chao-shi-fen-xi

https://www.jianshu.com/p/f1227ee26088

多写一句,ConnectListener不会提前运行的关键在于,connect()过程成功||超时||发生异常,才会调用ChannelFuture的trySuccess或tryFailure,唤醒ChannelFuture绑定的listeners

la-rpc项目地址:

https://github.com/bysoul/la-rpc

你可能感兴趣的:(RPC)