【SpringCloud】这一次终于使用MQ解决了Eureka服务下线延迟感知问题

这篇具有很好参考价值的文章主要介绍了【SpringCloud】这一次终于使用MQ解决了Eureka服务下线延迟感知问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

其实,“通过Redis手动更新Ribbon缓存来解决Eureka微服务架构中服务下线感知的问题”是一种解,但不是最优解

1.痛点

上一篇文章的标题是:
通过Redis手动更新Ribbon缓存来解决Eureka微服务架构中服务下线感知的问题
当时在文章的末尾就指出,使用Redis+AOP的方式有很多漏洞,只有在服务调用方发送调用请求的情况下才会触发切面中更新Ribbon缓存的逻辑。如果每次在发布Eureka新服务的场景下,告警的接口都能准确定位到,那将这些接口方法通过切面去针对性的加上更新Ribbon缓存的前置操作完全是没问题的。但是如果告警接口数量众多,并且无法定位,上述方法就有些不够看了。

2.解决方案

于是,基于此种困境,我想到了用mq的事件驱动模式来推进Ribbon缓存更新(“下线”这一事件驱动,而不是“发送跨服务调用请求”这一事件),具体如下:
【SpringCloud】这一次终于使用MQ解决了Eureka服务下线延迟感知问题,SpringCloud体系,Eureka,eureka,云原生
即,当服务被调用方中调用了下线接口下线了指定服务,会生产消息到MQ里,服务被调用方会监听这个队列去消费消息,并通过消费消息这一事件(消费下线服务端口信息)去驱动更新Ribbon缓存。
说明:
在以前我觉得用MQ不能做下线,压测了很多次也没成功,这本质还是没搞懂Eureka-Server,Eureka-Client,Ribbon三者的关系和之间的动作,其实这个体系里有两个非常关键的点(在配置文件中设置),可以直接影响无感知下线的结果,需要动态调整:那就是要关闭Eureka-server的三级缓存useReadOnlyResponseCache: false,并且缩短Eureka-Client端向Eureka-server端拉取服务列表的时间registry-fetch-interval-seconds: 3。可能这里大家看到去改配置有点鸡肋并且在实际场景中也不太现实,但别急,暂时先往下看,后面我会专门写一篇文章来解决这一问题

3.具体实现

3.1配置RabbitMQ

1.配置RabbitMQ(安装这些大家可以去看看平台比较成熟的文章)这里就不写了,我是直接在服务器上用docker容器化运行的:
【SpringCloud】这一次终于使用MQ解决了Eureka服务下线延迟感知问题,SpringCloud体系,Eureka,eureka,云原生
在调用方与被调用方都配好MQ

3.2生产下线消息

首先声明一个队列:

@Configuration
@EnableRabbit
public class RabbitMqConfig {
    @Bean
    public Queue theQueue() {
        return new Queue("SERVER_LIST");
    }
}

服务下线接口处,生产下线消息到MQ,向这接口/service-down-list发送GET请求,传递指定的下线服务实例信息即可下线服务,即http://localhost:8081/control/service-down-list?portParams=8083就下线了8083服务实例

    @Value("${eureka-server.ipAddress}")
    private String ipAddress;
    @Value("${eureka-server.appName}")
    private String appName;
    @Value("${DIY_QUEUE.VALUE}")
    private String queueName;    
@GetMapping(value = "/service-down-list")
    public String offLine(@RequestParam List<Integer> portParams) {
        List<Integer> successList = new ArrayList<>();
        //得到服务信息
        List<InstanceInfo> instances = eurekaClient.getInstancesByVipAddress(appName, false);
        List<Integer> servicePorts = instances.stream().map(InstanceInfo::getPort).collect(Collectors.toList());

        //去服务列表里挨个下线
        OkHttpClient client = new OkHttpClient();
        log.error("开始时间:{}", System.currentTimeMillis());
        portParams.parallelStream().forEach(temp -> {
            if (servicePorts.contains(temp)) {
                String url = "http://" + ipAddress + ":" + temp + "/control/service-down";
                try {
                    Response response = client.newCall(new Request.Builder().url(url).build()).execute();
                    if (response.code() == 200) {
                        log.debug(temp + "服务下线成功");
                        successList.add(temp);
                    } else {
                        log.debug(temp + "服务下线失败");
                    }
                } catch (IOException e) {
                    log.error(e.toString());
                }
            }
        });
        //todo MQ通知
        HashMap<String, List<Integer>> portInfo = new HashMap<>();
        portInfo.put(appName,successList);
        rabbitTemplate.convertAndSend(queueName,portInfo);
        return successList + "优雅下线成功";
    }

这里向MQ的队列里传递了下线的服务实例端口信息

3.3更新Ribbon缓存

服务调用方通过“下线“这一事件驱动Ribbon缓存更新

/**
 * 消费者
 */
@Slf4j
@Component
public class Consumer {

    @Resource
    SpringClientFactory springClientFactory;
    @Resource
    ClearRibbonCacheBean clearRibbonCacheBean;

    @RabbitListener(queues = "SERVER_LIST")
    public void listenWorkQueue1(HashMap<String, List<Integer>> message) {
        log.debug("消费者1接收到消息——" + message + "时间为:" + LocalTime.now());
        for (String key : message.keySet()) {
            List<Integer> value = message.get(key);
            log.debug("Key: " + key);
            log.debug("Value: " + value);
            if (ObjectUtils.isNotEmpty(value)) {
                clearRibbonCacheBean.clearRibbonCache(springClientFactory, value.toString(), key);
            }
            log.debug("现在的所有服务列表:{}", springClientFactory.getLoadBalancer(key).getAllServers());
        }
    }
}

清理Ribbon缓存的Bean:

/**
 * 手动清除Ribbon缓存
 */
@Configuration
@Slf4j
public class ClearRibbonCacheBean {
    /**
     * 削减
     */
    public static boolean cutDown(List<Integer> ports, Server index) {
        return ports.contains(index.getPort());
    }

    public void clearRibbonCache(SpringClientFactory clientFactory, String portParams,String appName) {
        // 获取指定服务的负载均衡器
        ILoadBalancer loadBalancer = clientFactory.getLoadBalancer(appName);
        //在主动拉取可用列表,而不是走拦截器被动的方式——这里为什么获取可用的之后还要过滤,就是因为所谓的可用不是实时的可用而是缓存中的可用
        List<Server> reachableServers = loadBalancer.getReachableServers();//这里从客户端获取,会等待客户端同步三级缓存
        //过滤掉已经下线的端口,符合条件端口的服务过滤出来
        List<Integer> portList = StringChange.stringToList(portParams);
        List<Server> ableServers = reachableServers.stream().filter(temp -> !cutDown(portList, temp)).collect(Collectors.toList());
        log.debug("可用服务列表:{}", ableServers);
        // 在某个时机需要清除Ribbon缓存
        ((BaseLoadBalancer) loadBalancer).setServersList(ableServers); // 清除Ribbon负载均衡器的缓存
    }

3.4压测

运行项目,调用下线接口并压测来模拟一下线上场景:
此时我们调用下线接口,下线8083服务实例:
【SpringCloud】这一次终于使用MQ解决了Eureka服务下线延迟感知问题,SpringCloud体系,Eureka,eureka,云原生
压测结果,均无异常:【SpringCloud】这一次终于使用MQ解决了Eureka服务下线延迟感知问题,SpringCloud体系,Eureka,eureka,云原生
观察服务实例的日志输出:
未下线的8081,8084
【SpringCloud】这一次终于使用MQ解决了Eureka服务下线延迟感知问题,SpringCloud体系,Eureka,eureka,云原生【SpringCloud】这一次终于使用MQ解决了Eureka服务下线延迟感知问题,SpringCloud体系,Eureka,eureka,云原生
下线的8083
【SpringCloud】这一次终于使用MQ解决了Eureka服务下线延迟感知问题,SpringCloud体系,Eureka,eureka,云原生
这说明,Eureka服务下线感知的延迟已经完全被消除

4.优化

以上的MQ还是采用简单队列的模式,即生产者生产一条消息到队列中,该消息也只能被一个消费者消费到。在微服务架构中,用户微服务肯定不只是被单方面调用,而是会被多方调用。那这就要求我们不能单纯只将消息生产到队列里,应该通过广播的模式进行消息的分发。为了更方便交换机与队列的灵活绑定,以及方便扩展,采用Topic话题的模型进行消息的广播:
【SpringCloud】这一次终于使用MQ解决了Eureka服务下线延迟感知问题,SpringCloud体系,Eureka,eureka,云原生
声明一个新队列:

    @Bean
    public Queue theQueue() {
        return new Queue("USER-QUEUE");
    }

将生产消息的地方改为携带一个routingkey并发送到交换机中:

  //todo MQ通知
  HashMap<String, List<Integer>> portInfo = new HashMap<>();
  portInfo.put(appName,successList);
  rabbitTemplate.convertAndSend(exchangeName,"USER.SERVICE-DOWN",portInfo);// 这个队列以后可能会发USER话题下的很多信息

将消费者端的消息监听器进行改造,变为监听指定话题的消息:文章来源地址https://www.toymoban.com/news/detail-797354.html

 @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "USER-QUEUE"),
            exchange = @Exchange(name = "USER-TOPIC", type = ExchangeTypes.TOPIC),
            key = "USER.SERVICE-DOWN")
    )
    public void listenWorkQueue1(HashMap<String, List<Integer>> message) {
        log.debug("消费者1接收到消息——" + message + "时间为:" + LocalTime.now());
        for (String key : message.keySet()) {
            List<Integer> value = message.get(key);
            log.debug("Key: " + key);
            log.debug("Value: " + value);
            if (ObjectUtils.isNotEmpty(value)) {
                clearRibbonCacheBean.clearRibbonCache(springClientFactory, value.toString(), key);
            }
            log.debug("现在的所有服务列表:{}", springClientFactory.getLoadBalancer(key).getAllServers());
        }
    }

到了这里,关于【SpringCloud】这一次终于使用MQ解决了Eureka服务下线延迟感知问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【SpringCloud微服务--Eureka服务注册中心】

    gitee仓库 内容:SpringCloud + SpringCloud alibaba 技术栈:Java8+maven+git,github+Nginx+RabbitMQ+SpringBoot2.0 微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的 进程 中,服务与服务间采用

    2024年02月09日
    浏览(41)
  • SpringCloud:Eureka服务注册中心

    Eureka是一个服务治理组件,它主要包括服务注册和服务发现,主要用来搭建服务注册中心。 在搭建微服务项目时遇到的三个问题: 服务消费者该如何获取服务提供者的地址信息? 如果有多个服务提供者,消费者该如何选择? 消费者如何得知服务提供者的健康状态? Eureka的作

    2024年01月20日
    浏览(43)
  • SpringCloud之Eureka 服务注册中心

    5 Eureka 服务注册中心 5.1什么是 Eureka Netflix在涉及Eureka时,遵循的就是API原则. Eureka是Netflix的有个子模块,也是核心模块之一。Eureka是基于REST的服务,用于定位服务,以实现云端中间件层服务发现和故障转移,服务注册与发现对于微服务来说是非常重要的,有了服务注册与发

    2024年02月04日
    浏览(49)
  • springcloud Eureka服务注册与发现

    代码上传到 :https://github.com/13thm/study_springcloud/tree/main/days3 什么是服务治理 Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务治理 什么是服务注册与发现 Eureka采用了CS的设计架构,Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使

    2024年01月18日
    浏览(69)
  • Springcloud笔记(2)-Eureka服务注册中心

    Eureka作为一个微服务的治理中心,它是一个服务应用,可以接收其他服务的注册,也可以发现和治理服务实例。 服务治理中心是微服务(分布式)架构中最基础和最核心的功能组件,它主要对各个服务实例进行管理,包括 服务注册和服务发现 等 本文参考:springcloud教程 --

    2024年02月05日
    浏览(46)
  • SpringCloud搭建Eureka服务注册中心(六)

    前面说过eureka是c/s模式的  server服务端就是服务注册中心,其他的都是client客户端,服务端用来管理所有服务,客户端通过注册中心,来调用具体的服务; 我们先来搭建下服务端,也就是服务注册中心; project xmlns=\\\"http://maven.apache.org/POM/4.0.0\\\" xmlns:xsi=\\\"http://www.w3.org/2001/XMLSche

    2024年02月10日
    浏览(63)
  • 【SpringCloud微服务】- Eureka服务注册与服务发现Discovery

    Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。 SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。 Eureka包含两个组件: Eureka Server 和 E

    2024年02月03日
    浏览(53)
  • 什么是SpringCloud Eureka服务注册与发现

    😀前言 本篇博文是关于SpringCloud Eureka 介绍,希望你能够喜欢 🏠个人主页:晨犀主页 🧑个人简介:大家好,我是晨犀,希望我的文章可以帮助到大家,您的满意是我的动力😉😉 💕欢迎大家:这里是CSDN,我总结知识的地方,欢迎来到我的博客,感谢大家的观看🥰 如果文

    2024年02月09日
    浏览(49)
  • SpringCloud Eureka注册服务提供者(七)

    这里我们在原来的服务提供者项目 microservice-student-provider-1001  上面直接修改: dependency     groupIdorg.springframework.cloud/groupId     artifactIdspring-cloud-starter-eureka/artifactId /dependency dependency     groupIdorg.springframework.cloud/groupId     artifactIdspring-cloud-starter-config/artifactId /dependency eurek

    2024年02月09日
    浏览(44)
  • 【微服务 SpringCloud】实用篇 · Eureka注册中心

    微服务(3) 假如我们的服务提供者 user-service部署了多个实例(不同实例,端口号不一致) ,如图: 大家思考几个问题: 我们刚才的编程,是写死在程序里的,ip和端口都是写死的, 这种硬编码的方式,之后改变ip和端口代码不就没法用了? order-service在发起远程调用的时候

    2024年02月08日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包