本节依然分成三次提交。
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;
}
但心跳机制暂时不会奏效,因为设置的客户端完成一个调用后立刻关闭连接,接下来会对此进行调整。
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……