手把手实现RPC框架--简易版Dubbo构造(十三)Netty心跳机制、实现服务负载均衡

本节依然分成三次提交。

Netty心跳机制

commit地址:35b95d8

什么是心跳机制:https://blog.csdn.net/u013967175/article/details/78591810

Netty对心跳机制有两个层面实现,第一个是TCP层面,之前给通道初始化设置的值 .option(ChannelOption.SO_KEEPALIVE, true) 就是TCP的心跳机制,第二个层面是通过通道中的处理IdleStateHandler来实现,可以自定义心跳检测间隔时长,以及具体检测的逻辑实现。

首先是客户端的心跳检测实现,在ChannelProvider类中向管道注册Handler,.addLast(new IdleStateHandler(0, 5, 0, TimeUnit.SECONDS)),即设定每5秒进行一次写检测,如果5秒内write()方法未被调用则触发一次userEventTrigger()方法,该方法在NettyClientHandler类中实现:

        @Override
 26	    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
 27	        if(evt instanceof IdleStateEvent){
 28	            IdleState state = ((IdleStateEvent) evt).state();
 29	            if(state == IdleState.WRITER_IDLE){
 30	                logger.info("发送心跳包[{}]", ctx.channel().remoteAddress());
 31	                Channel channel = ChannelProvider.get((InetSocketAddress) ctx.channel().remoteAddress(),
 32	                        CommonSerializer.getByCode(CommonSerializer.DEFAULT_SERIALIZER));
 33	                RpcRequest rpcRequest = new RpcRequest();
 34	                rpcRequest.setHeartBeat(true);
 35	                //设置一个Listener监测服务端是否接收到心跳包,如果接收到就代表对方在线,不用关闭Channel
 36	                channel.writeAndFlush(rpcRequest).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
 37	            }
 38	        }else {
 39	            super.userEventTriggered(ctx, evt);
 40	        }
 41	    }

然后是服务端的实现,在NettyServer类中向管道注册Handler,.addLast(new IdleStateHandler(30, 0, 0, TimeUnit.SECONDS)),即设定IdleStateHandler心跳检测每30秒进行一次读检测,如果30秒内ChannelRead()方法未被调用则触发一次userEventTrigger()方法:

        @Override
 30	    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
 31	        if(evt instanceof IdleStateEvent){
 32	            IdleState state = ((IdleStateEvent) evt).state();
 33	            if(state == IdleState.READER_IDLE){
 34	                logger.info("长时间未收到心跳包,断开连接……");
 35	                ctx.close();
 36	            }
 37	        }else {
 38	            super.userEventTriggered(ctx, evt);
 39	        }
 32	    }

对于客户端心跳包发来心跳包在channelRead0()中判断,只要服务端收到心跳包,客户端就能监听到,因此不需要额外处理:

if(msg.getHeartBeat()){
    logger.info("接收到客户端心跳包……");
    return;
}

但心跳机制暂时不会奏效,因为设置的客户端完成一个调用后立刻关闭连接,接下来会对此进行调整。

统一管理Netty客户端请求

commit地址:7858cec

我们利用CompletableFuture来异步获取Netty请求的响应结果,将每个请求对应的CompletableFuture实例都保存在一个Map中,key为请求ID,value为创建的CompletableFuture实例,核心代码如下:

    public class UnprocessedRequests {
 14	
 15	    private static ConcurrentHashMap> unprocessedRequests = new ConcurrentHashMap<>();
 16	
 17	    public void put(String requestId, CompletableFuture future) {
 18	        unprocessedRequests.put(requestId, future);
 19	    }
 20	
 21	    public void remove(String requestId) {
 22	        unprocessedRequests.remove(requestId);
 23	    }
 24	
 25	    public void complete(RpcResponse rpcResponse){
 26	        //请求完成了,把请求从未完成的请求中移除
 27	        CompletableFuture future = unprocessedRequests.remove(rpcResponse.getRequestId());
 28	        if(null != future){
 29	            //把响应对象放入future中
 30	            future.complete(rpcResponse);
 31	        }else {
 32	            throw new IllegalStateException();
 33	        }
 34	    }
 35	}

这次启动Netty服务端和客户端就会发现,调用执行结束后,控制台会打印出心跳检测的记录信息。

服务负载均衡

commit地址:2f03391

之前提到过客户端在lookupService()方法中,从Nacos获取到的是所有提供这个服务的服务端信息列表,而我们需要从中选择一个,这便涉及到客户端侧的负载均衡策略。首先新建一个负载均衡接口:

public interface LoadBalancer {
    /**
     *从一系列Instance中选择一个
    */
    Instance select(List instances);
}

实现随机算法:

public class RandomLoadBalancer implements LoadBalancer {
    @Override
    public Instance select(List instances) {
        return instances.get(new Random().nextInt(instances.size()));
    }
}

实现轮询算法,按照顺序依次选择第一个、第二个、第三个……

public class RoundRobinLoadBalancer implements LoadBalancer{

    /**
     * index表示当前选到了第几个服务器,并且每次选择后都会自增一
     */
    private int index = 0;

    @Override
    public Instance select(List instances) {
        if(index >= instances.size()){
            index %= instances.size();
        }
        return instances.get(index++);
    }
}

最后在NacosServiceRegistry中调用就可以了,这里选择外部传入的方式使用LoadBalancer,看客户端传入哪个算法就使用哪个:

public class NacosServiceDiscovery implements ServiceDiscovery {
    private final LoadBalancer loadBalancer;

    public NacosServiceDiscovery(LoadBalancer loadBalancer) {
        if(loadBalancer == null) this.loadBalancer = new RandomLoadBalancer();
        else this.loadBalancer = loadBalancer;
    }
    
    public InetSocketAddress lookupService(String serviceName) {
        try {
            List instances = NacosUtil.getAllInstance(serviceName);
            Instance instance = loadBalancer.select(instances);
            return new InetSocketAddress(instance.getIp(), instance.getPort());
        } catch (NacosException e) {
            logger.error("获取服务时有错误发生:", e);
        }
        return null;
    }
}

可以同时启动Socket和Netty两个服务端,对负载均衡进行测试(Netty和Socket之前已经通过统一序列化端序实现互通了)

本节over……

你可能感兴趣的:(JAVA,Netty,java,netty,rpc)