RPC框架(6 - 实现自动注销和负载均衡策略)

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
EMR Serverless StarRocks,5000CU*H 48000GB*H
应用型负载均衡 ALB,每月750个小时 15LCU
简介: RPC框架(6 - 实现自动注销和负载均衡策略)

5.6实现自动注销和负载均衡策略



5.6.1服务自动注销


上一节我们实现了服务的自动注册和发现,但是有些细心的同学就可能会发现,如果你启动完成服务端后把服务端给关闭了,并不会自动地注销 Nacos 中对应的服务信息,这样就导致了当客户端再次向 Nacos 请求服务时,会获取到已经关闭的服务端信息,最终就有可能因为连接不到服务器而调用失败。


那么我们就需要一种办法,在服务端关闭之前自动向 Nacos 注销服务。但是有一个问题,我们不知道什么时候服务器会关闭,也就不知道这个方法调用的时机,就没有办法手工去调用。这时,我们就需要钩子。


钩子是什么呢?是在某些事件发生后自动去调用的方法。那么我们只需要把注销服务的方法写到关闭系统的钩子方法里就行了。


首先先写向 Nacos 注销所有服务的方法,这部分被放在了 NacosUtils 中作为一个静态方法,NacosUtils 是一个 Nacos 相关的工具类:

public static void clearRegistry() {
    if(!serviceNames.isEmpty() && address != null) {
        String host = address.getHostName();
        int port = address.getPort();
        Iterator<String> iterator = serviceNames.iterator();
        while(iterator.hasNext()) {
            String serviceName = iterator.next();
            try {
                namingService.deregisterInstance(serviceName, host, port);
            } catch (NacosException e) {
                logger.error("注销服务 {} 失败", serviceName, e);
            }
        }
    }
}


所有的服务名称都被存储在 NacosUtils 类中的 serviceNames 中,在注销时只需要用迭代器迭代所有服务名,调用 deregisterInstance 注销服务。


接着就是钩子了,新建一个类,ShutdownHook:

public class ShutdownHook {
    private static final Logger logger = LoggerFactory.getLogger(ShutdownHook.class);
    private final ExecutorService threadPool = ThreadPoolFactory.createDefaultThreadPool("shutdown-hook");
    private static final ShutdownHook shutdownHook = new ShutdownHook();
    public static ShutdownHook getShutdownHook() {
        return shutdownHook;
    }
    public void addClearAllHook() {
        logger.info("关闭后将自动注销所有服务");
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            NacosUtil.clearRegistry();
            threadPool.shutdown();
        }));
    }
}


使用了单例模式创建其对象,在 addClearAllHook 中,Runtime 对象是 JVM 虚拟机的运行时环境,调用其 addShutdownHook 方法增加一个钩子函数,创建一个新线程调用 clearRegistry 方法完成注销工作。这个钩子函数会在 JVM 关闭之前被调用。


这样在 RpcServer 启动之前,只需要调用 addClearAllHook,就可以注册这个钩子了。例如在 NettyServer 中:

ChannelFuture future = serverBootstrap.bind(host, port).sync();
+            ShutdownHook.getShutdownHook().addClearAllHook();
             future.channel().closeFuture().sync();


启动服务端后再关闭,就会发现 Nacos 中的注册信息都被注销了。


5.6.2负载均衡策略


负载均衡大家应该都熟悉,在上一节中客户端在 lookupService 方法中,从 Nacos 获取到的是所有提供这个服务的服务端信息列表,我们就需要从中选择一个,这便涉及到客户端侧的负载均衡策略。我们新建一个接口:LoadBalancer

public interface LoadBalancer {
    Instance select(List<Instance> instances);
}


接口中的 select 方法用于从一系列 Instance 中选择一个。这里我就实现两个比较经典的算法:随机和转轮。


随机算法顾名思义,就是随机选一个,毫无技术含量:

public class RandomLoadBalancer implements LoadBalancer {
    @Override
    public Instance select(List<Instance> instances) {
        // nextInt():生成一个介于[0, instances.size())的int型随机值
        return instances.get(new Random().nextInt(instances.size()));
    }
}


而转轮算法大家也应该了解,按照顺序依次选择第一个、第二个、第三个……这里就需要一个变量来表示当前选到了第几个:

public class RoundRobinLoadBalancer implements LoadBalancer {
    private int index = 0;
    @Override
    public Instance select(List<Instance> instances) {
        if(index >= instances.size()) {
            index %= instances.size();
        }
        return instances.get(index++);
    }
}


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<Instance> instances = NacosUtil.getAllInstance(serviceName);
            Instance instance = loadBalancer.select(instances);
            return new InetSocketAddress(instance.getIp(), instance.getPort());
        } catch (NacosException e) {
            logger.error("获取服务时有错误发生:", e);
        }
        return null;
    }
}


而这个负载均衡策略,也可以在创建客户端时指定,例如无参构造 NettyClient 时就用默认的策略,也可以有参构造传入策略,具体的实现留给大家。

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
7天前
|
自然语言处理 负载均衡 API
gRPC 一种现代、开源、高性能的远程过程调用 (RPC) 可以在任何地方运行的框架
gRPC 是一种现代开源高性能远程过程调用(RPC)框架,支持多种编程语言,可在任何环境中运行。它通过高效的连接方式,支持负载平衡、跟踪、健康检查和身份验证,适用于微服务架构、移动设备和浏览器客户端连接后端服务等场景。gRPC 使用 Protocol Buffers 作为接口定义语言,支持四种服务方法:一元 RPC、服务器流式处理、客户端流式处理和双向流式处理。
|
24天前
|
负载均衡 应用服务中间件 nginx
Nginx的6大负载均衡策略及权重轮询手写配置
【10月更文挑战第9天】 Nginx是一款高性能的HTTP服务器和反向代理服务器,它在处理大量并发请求时表现出色。Nginx的负载均衡功能可以将请求分发到多个服务器,提高网站的吞吐量和可靠性。以下是Nginx支持的6大负载均衡策略:
108 7
|
22天前
|
负载均衡 算法 Java
腾讯面试:说说6大Nginx负载均衡?手写一下权重轮询策略?
尼恩,一位资深架构师,分享了关于负载均衡及其策略的深入解析,特别是基于权重的负载均衡策略。文章不仅介绍了Nginx的五大负载均衡策略,如轮询、加权轮询、IP哈希、最少连接数等,还提供了手写加权轮询算法的Java实现示例。通过这些内容,尼恩帮助读者系统化理解负载均衡技术,提升面试竞争力,实现技术上的“肌肉展示”。此外,他还提供了丰富的技术资料和面试指导,助力求职者在大厂面试中脱颖而出。
腾讯面试:说说6大Nginx负载均衡?手写一下权重轮询策略?
|
2月前
|
负载均衡 Java 对象存储
负载均衡策略:Spring Cloud与Netflix OSS的最佳实践
负载均衡策略:Spring Cloud与Netflix OSS的最佳实践
46 2
|
2月前
|
负载均衡 Java 开发者
Ribbon框架实现客户端负载均衡的方法与技巧
Ribbon框架为微服务架构中的客户端负载均衡提供了强大的支持。通过简单的配置和集成,开发者可以轻松地在应用中实现服务的发现、选择和负载均衡。适当地使用Ribbon,配合其他Spring Cloud组件,可以有效提升微服务架构的可用性和性能。
31 0
|
2月前
|
XML 负载均衡 监控
分布式-dubbo-简易版的RPC框架
分布式-dubbo-简易版的RPC框架
|
3月前
|
负载均衡 应用服务中间件 Linux
在Linux中,Nginx如何实现负载均衡分发策略?
在Linux中,Nginx如何实现负载均衡分发策略?
|
3月前
|
缓存 负载均衡 算法
在Linux中, LVS负载均衡有哪些策略?
在Linux中, LVS负载均衡有哪些策略?
|
5月前
|
缓存 负载均衡 算法
解读 Nginx:构建高效反向代理和负载均衡的秘密
解读 Nginx:构建高效反向代理和负载均衡的秘密
116 2
|
4月前
|
负载均衡 算法 应用服务中间件
nginx自定义负载均衡及根据cpu运行自定义负载均衡
nginx自定义负载均衡及根据cpu运行自定义负载均衡
52 1