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

news2025/2/24 20:27:14

前言

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

1.痛点

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

2.解决方案

于是,基于此种困境,我想到了用mq的事件驱动模式来推进Ribbon缓存更新(“下线”这一事件驱动,而不是“发送跨服务调用请求”这一事件),具体如下:
在这里插入图片描述
即,当服务被调用方中调用了下线接口下线了指定服务,会生产消息到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容器化运行的:
在这里插入图片描述
在调用方与被调用方都配好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服务实例:
在这里插入图片描述
压测结果,均无异常:在这里插入图片描述
观察服务实例的日志输出:
未下线的8081,8084
在这里插入图片描述在这里插入图片描述
下线的8083
在这里插入图片描述
这说明,Eureka服务下线感知的延迟已经完全被消除

4.优化

以上的MQ还是采用简单队列的模式,即生产者生产一条消息到队列中,该消息也只能被一个消费者消费到。在微服务架构中,用户微服务肯定不只是被单方面调用,而是会被多方调用。那这就要求我们不能单纯只将消息生产到队列里,应该通过广播的模式进行消息的分发。为了更方便交换机与队列的灵活绑定,以及方便扩展,采用Topic话题的模型进行消息的广播:
在这里插入图片描述
声明一个新队列:

    @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话题下的很多信息

将消费者端的消息监听器进行改造,变为监听指定话题的消息:

 @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());
        }
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1391483.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Flutter 开发实战】Dart 基础篇:List 详解

嗨&#xff0c;各位朋友们&#xff0c;欢迎来到这篇博客&#xff01;今天我们将一起踏入 Dart 语言的神奇世界&#xff0c;深入了解 Dart 中的 List 类型。不用担心&#xff0c;我会尽可能用最通俗易懂的语言&#xff0c;让你对 List 有一个更深刻的理解。 Dart 中的 List Li…

重学Java 7 数组Arr.1

我欲与君相知&#xff0c;长命无绝衰 ——24.1.16 一、数组的定义 1.概述&#xff1a;数组是一个容器&#xff0c;数组本身属于引用数据类型 2.作用&#xff1a;一次存储多个数据 3.特点&#xff1a; ①既可以存储基本类型的数据&#xff0c;也可以存储引用类型的数据 ②定长&a…

Redis 消息队列和发布订阅

文章目录 基本模式生产者消费者原理&模型redis实现java实现 发布者订阅者原理&模型redis实现java实现 stream模式原理&模型工作原理redis实现Java实现 选型外传 基本模式 采用redis 三种方案&#xff1a; ● 生产者消费者&#xff1a;一个消息只能有一个消费者 ●…

计算机毕业设计 基于SSM的历史/博物馆藏系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

一天吃透计算机网络面试八股文

目录&#xff1a; 网络分层结构三次握手两次握手可以吗&#xff1f;四次挥手第四次挥手为什么要等待2MSL&#xff1f;为什么是四次挥手&#xff1f;TCP有哪些特点&#xff1f;说说TCP报文首部有哪些字段&#xff0c;其作用又分别是什么&#xff1f;TCP和UDP的区别&#xff1f;…

DWM1000 MAC层

DWM1000 MAC层 MAC层 概述 MAC层&#xff0c;即媒体访问控制层&#xff0c;是数据通信协议栈中的一个重要部分&#xff0c;位于链路层的下半部分&#xff0c;紧邻物理层。在OSI模型中&#xff0c;它属于第二层&#xff0c;即数据链路层的一部分。MAC层的主要职责是控制如何在…

去中心化社交:Web3如何重新定义社交媒体

随着区块链技术的快速发展&#xff0c;Web3正在崭露头角&#xff0c;成为社交媒体领域的一股新潮流。去中心化社交的理念与技术创新正在重新定义用户的社交体验&#xff0c;颠覆传统社交媒体的中心化模式。本文将深入探讨Web3对社交媒体的影响&#xff0c;以及去中心化社交是如…

Ubuntu使用QtCreator + CMake 开发C/C++程序

平台 OS: Ubuntu 20.04 cmake: 3.16.3 IDE: Qt Creator 4.11.1 Based on Qt 5.14.1 (GCC 5.3.1 20160406 (Red Hat 5.3.1-6), 64 bit) Built on Feb 5 2020 12:48:30 From revision b2ddeacfb5 Copyright 2008-2019 The Qt Company Ltd. All rights reserved. The program …

DC电源模块在新能源领域的应用前景

BOSHIDA DC电源模块在新能源领域的应用前景 DC电源模块在新能源领域有着广阔的应用前景。随着可再生能源技术的发展和普及&#xff0c;如太阳能和风能等的应用逐渐增多&#xff0c;DC电源模块在这些领域的应用越来越重要。 首先&#xff0c;DC电源模块可以用于太阳能发电系统…

transfomer中正余弦位置编码的源码实现

简介 Transformer模型抛弃了RNN、CNN作为序列学习的基本模型。循环神经网络本身就是一种顺序结构&#xff0c;天生就包含了词在序列中的位置信息。当抛弃循环神经网络结构&#xff0c;完全采用Attention取而代之&#xff0c;这些词序信息就会丢失&#xff0c;模型就没有办法知…

子域名的介绍及收集

1、子域名作用编辑 收集子域名可以扩大测试范围&#xff0c;同一域名下的二级域名都属于目标范围。 2、 常用方式编辑 子域名中的常见资产类型一般包括办公系统&#xff0c;邮箱系统&#xff0c;论坛&#xff0c;商城&#xff0c;其他管理系统&#xff0c;网站管理后台也有可…

获得店铺的所有商品 API、店铺列表api

taobao.item_search_shop 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff09;[item_search,item_get,item_search_shop等]cacheStrin…

transfomer的位置编码

什么是位置编码 在transformer的encoder和decoder的输入层中&#xff0c;使用了Positional Encoding&#xff0c;使得最终的输入满足&#xff1a; input_embeddingpositional_encoding 这里&#xff0c;input_embedding的shape为[n,b,embed_dim],positional_encoding和input_…

推荐一个页面引导库 driver.js

页面引导功能是 web 开发中常见的一个功能。通过页面引导功能&#xff0c;你可以让用户第一时间熟悉你的页面功能。今天给大家推荐一个页面引导库 driver.js。 简介 driver.js 是一款用原生 js 实现的页面引导库&#xff0c;上手非常简单&#xff0c;体积在 gzip 压缩下仅仅 5…

《手把手教你》系列技巧篇(十)-java+ selenium自动化测试-元素定位大法之By class name(详细教程)

1.简介 按宏哥计划&#xff0c;本文继续介绍WebDriver关于元素定位大法&#xff0c;这篇介绍By ClassName。看到ID&#xff0c;NAME这些方法的讲解&#xff0c;小伙伴们和童鞋们应该知道&#xff0c;要做好Web自动化测试&#xff0c;最好是需要了解一些前端的基本知识。有了前端…

DDOS攻击,一篇文章给你讲清!

1、互联网安全现状 随着网络世界的高速发展&#xff0c;各行业数字化转型也在如火如荼的进行。但由于TCP/IP网络底层的安全性缺陷&#xff0c;钓鱼网站、木马程序、DDoS攻击等层出不穷的恶意攻击和高危漏洞正随时入侵企业的网络&#xff0c;如何保障网络安全成为网络建设中的刚…

如何实现扫码填报信息,并且可以做统计和导出excel

日常工作中经常遇到需要收集信息的情况&#xff0c;如果能实现扫一下二维码&#xff0c;就可以直接填写信息&#xff0c;不用登录&#xff0c;不用开账号&#xff0c;填写完直接可以生成excel&#xff0c;那就非常好了。 我试用了很多平台&#xff0c;有的收费的&#xff0c;也…

VL53L5CX距离传感器

I2C接口的飞行时间多区测距传感器 意法半导体VL53L5CX是一款先进的飞行时间 (ToF) 多区域测距传感器 VL53L5CX 采用意法半导体最新一代的直接 ToF 技术&#xff0c;无论目标颜色和反射率如何&#xff0c;都可以进行绝对距离测量。它提供高达 400 cm的精确测距&#xff0c;并且…

transbigdata笔记:栅格参数优化

在transbigdata中&#xff0c;栅格参数有如下几个 params(lonStart,latStart,deltaLon,deltaLat,theta) 如何选择合适的栅格参数是很重要的事情&#xff0c;这会对最终的分析结果产生很大的影响。 怎么选择参数&#xff0c;和数据以及分析的目的息息相关&#xff0c;transbi…

C语言爬虫程序编写的爬取APP通用模板

互联网的飞快发展&#xff0c;尤其是手机终端业务的发展&#xff0c;让越来越多的事情都能通过手机来完成&#xff0c;电脑大部分的功能也都能通过手机实现&#xff0c;今天我就用C语言写一个手机APP类爬虫教程&#xff0c;方便后期拓展APP爬虫业务。而且这个模板是通用的适合各…