手写RPC框架--9.心跳动态上下线

RPC框架-Gitee代码(麻烦点个Starred, 支持一下吧)
RPC框架-GitHub代码(麻烦点个Starred, 支持一下吧)

9.心跳动态上下线

a.实现心跳动态下线

在core模块下com.dcyrpc.loadbalancer.impl包下

修改MinResponseTimeLoadBalancer类的静态内部类MyTimerTaskrun()方法

  • 添加重试连接的次数
  • 通过while循环,当发生问题,需要优先重试,重试3次后,将失效的地址移除服务列表
@Override
public void run() {

    // 每次启动将响应时长的map清空
    DcyRpcBootstrap.ANSWER_TIME_CHANNEL_CACHE.clear();

    // 遍历所有的channel
    Map<InetSocketAddress, Channel> channelCache = DcyRpcBootstrap.CHANNEL_CACHE;
    for (Map.Entry<InetSocketAddress, Channel> entry : channelCache.entrySet()) {
        // 重试的次数
        int tryTimes = 3;
        while(tryTimes > 0) {

            Channel channel = entry.getValue();

            long start = System.currentTimeMillis();
            // 构建心跳请求
            DcyRpcRequest dcyRpcRequest = DcyRpcRequest.builder()
                    .requestId(DcyRpcBootstrap.ID_GENERATOR.getId())
                    .compressType(CompressorFactory.getCompressor(DcyRpcBootstrap.COMPRESS_TYPE).getCode())
                    .serializeType(SerializerFactory.getSerializer(DcyRpcBootstrap.SERIALIZE_TYPE).getCode())
                    .requestType(RequestType.HEART_BEAT.getId())
                    .timeStamp(start)
                    .build();

            CompletableFuture<Object> completableFuture = new CompletableFuture<>();
            DcyRpcBootstrap.PENDING_REQUEST.put(dcyRpcRequest.getRequestId(), completableFuture);
            channel.writeAndFlush(dcyRpcRequest).addListener((ChannelFutureListener) promise -> {
                if (!promise.isSuccess()) {
                    completableFuture.completeExceptionally(promise.cause());
                }
            });

            long endTime = 0L;
            try {
                completableFuture.get(1, TimeUnit.SECONDS);
                endTime = System.currentTimeMillis();
            } catch (InterruptedException | ExecutionException | TimeoutException e) {
                // 一旦发生问题,需要优先重试
                tryTimes--;
                log.error("和地址为【{}】的主机发生异常.正在进行【{}】次重试........", channel.remoteAddress(), 3 - tryTimes);

                // 重试3次后,将失效的地址移除服务列表
                if (tryTimes == 0) {
                    DcyRpcBootstrap.CHANNEL_CACHE.remove(entry.getKey());
                }

                // 尝试等待一段时间后重试
                try {
                    Thread.sleep(10*(new Random().nextInt(5)));
                } catch (InterruptedException ex) {
                    throw new RuntimeException(ex);
                }
                continue;
            }

            Long time = endTime - start;

            // 使用TreeMap进行缓存
            DcyRpcBootstrap.ANSWER_TIME_CHANNEL_CACHE.put(time, channel);

            log.info("和【{}】服务器的响应时间是【{}】", entry.getKey() ,time);
            break;
        }
    }

}

b.动态感知服务上线

通过Zookeeper的Watcher机制来进行动态上线:

  • 1.调用方拉取服务列表时,注册一个watcher关注该服务节点的变化
  • 2.当服务提供方上线或线下时会触发watcher机制(节点发生了变化)
  • 3.通知调用方,执行动态上下线的操作。

在core模块下的com.dcyrpc包下,创建watcher

在该包下创建UpAndDownWatcher类:

  • 实现 Watcher 接口
  • 处理新增的节点:发现有节点不在缓存里(证明是新增节点),先建立连接,在缓存
/**
 * 动态上下线的Watcher
 */
@Slf4j
public class UpAndDownWatcher implements Watcher {
    @Override
    public void process(WatchedEvent event) {
        if (event.getType() == Event.EventType.NodeChildrenChanged) {
            log.info("检测到服务【{}】有节点上/下线,将重新拉取服务列表..", event.getPath());

            String serviceName = getServiceName(event.getPath());
            // 重新拉取服务列表
            Registry registry = DcyRpcBootstrap.getInstance().getRegistry();
            List<InetSocketAddress> addressList = registry.lookup(serviceName);

            // 处理新增的节点
            // 新增的节点,会在addressList,不在CHANNEL_CACHE中
            for (InetSocketAddress address : addressList) {
                if (!DcyRpcBootstrap.CHANNEL_CACHE.containsKey(address)) {
                    // 根据地址建立连接,并缓存
                    Channel channel = null;
                    try {
                        channel = NettyBootstrapInitializer.getBootstrap().connect(address).sync().channel();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    DcyRpcBootstrap.CHANNEL_CACHE.put(address, channel);
                }
            }
        }
    }

    private String getServiceName(String path) {
        String[] split = path.split("/");
        return split[split.length - 1];
    }
}

在core模块下的com.dcyrpc.discovery.impl包中,修改ZookeeperRegistry类的lookup()方法

  • 增加Watcher机制:只有子节点有改变,就重新拉取一下服务的子节点
// 略...
// 2.从zk中获取它的子节点,
List<String> children = ZookeeperUtils.getChildren(zooKeeper, serviceNode, new UpAndDownWatcher());
// 略...

c.动态感知服务下线

在core模块下的com.dcyrpc包下,修改UpAndDownWatcher类的process()方法:新增代码

  • 处理下线的节点:一定不在addressList,可能会在CHANNEL_CACHE中
// 略...
// 处理下线的节点
// 下线的节点,一定不在addressList,可能会在CHANNEL_CACHE中
for (Map.Entry<InetSocketAddress, Channel> entry : DcyRpcBootstrap.CHANNEL_CACHE.entrySet()) {
    if (!addressList.contains(entry.getKey())) {
        DcyRpcBootstrap.CHANNEL_CACHE.remove(entry.getKey());
    }
}

d.实现重新的负载均衡reLoadBalance

当有节点上下线时,要对节点进行重新的负载均衡

  • 在core模块下的com.dcyrpc包下,修改UpAndDownWatcher类的process()方法:新增代码

    • 重新的负载均衡:获得负载均衡器,进行重新的loadBalance
// 略...
// 获得负载均衡器,进行重新的loadBalance
LoadBalancer loadBalancer = DcyRpcBootstrap.LOAD_BALANCER;
loadBalancer.reLoadBalance(serviceName, addressList);

把原来在LoadBalancer接口中定义的reBalance()方法全部去掉

LoadBalancer接口中,创建reLoadBalance()接口

/**
 * 当感知节点发生了动态上下线,我们需要重新进行负载均衡
 * @param serviceName 服务名称
 */
void reLoadBalance(String serviceName, List<InetSocketAddress> addressList);

AbstractLoadBalancer抽象类中,实现该方法

@Override
public synchronized void reLoadBalance(String serviceName, List<InetSocketAddress> addressList) {
    // 根据新的服务列表生成新的selector
    cache.put(serviceName, getSelector(addressList));
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值