Nacos 实现服务平滑上下线(Ribbon 和 LB)

前言

不知道各位在使用 SpringCloud Gateway + Nacos的时候有没有遇到过服务刚上线偶尔会出现一段时间的503 Service Unavailable,或者服务下线后,下线服务仍然被调用的问题。而以上问题都是由于Ribbon或者LoadBalancer的默认处理策略有关,其中Ribbon默认是 30s 更新一次服务信息,LoadBalancer则是默认 35s 更新一次缓存。接下来本文讲解则如何通过监听Nacos 的服务变更事件来实时进行相关服务的更新,以实现服务的平滑上下线。

监听 Nacos 服务变更实现

首先我们要知道的是,在前言中提到的服务上线未被及时感知是由于使用Ribbon或者LoadBalancer组件的默认处理策略所导致的,Nacos是可以及时感知并触发服务上下线的事件,因为我们要做的就是监听Nacos的这个事件,然后在事件处理中,自己手动去调用相关的更新操作以实现需求。而com.alibaba.nacos.client.naming.event.InstancesChangeEvent这个事件则正是符合我们需求的事件,然后我们就可以参考com.alibaba.nacos.client.naming.event.InstancesChangeNotifier这个类来实现一个我们自己的订阅类:

import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.client.naming.event.InstancesChangeEvent;
import com.alibaba.nacos.common.notify.NotifyCenter;
import com.alibaba.nacos.common.notify.listener.Subscriber;
import com.alibaba.nacos.common.notify.Event;
import com.netflix.loadbalancer.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;/*** 服务变更监听** @author butterfly* @date 2023-09-24*/
@Slf4j
@Component
public class ServiceChangeNotifier extends Subscriber<InstancesChangeEvent> {@PostConstructpublic void init() {// 注册当前自定义的订阅者以获取通知NotifyCenter.registerSubscriber(this);}@Overridepublic void onEvent(InstancesChangeEvent event) {String serviceName = event.getServiceName();// 使用 dubbo 时包含 rpc 服务类会注册以 providers: 或者 consumers: 开头的服务// 由于不是正式的服务, 这里需要进行排除, 如果未使用 dubbo 则不需要该处理if (serviceName.contains(":")) {return;}// serviceName 格式为 groupName@@nameString split = Constants.SERVICE_INFO_SPLITER;if (serviceName.contains(split)) {serviceName = serviceName.substring(serviceName.indexOf(split) + split.length());}log.info("服务上下线: {}", serviceName);// 针对服务进行后续更新操作}@Overridepublic Class<? extends Event> subscribeType() {return InstancesChangeEvent.class;}}

nacos-client 为 2.1.1 时会出现订阅失效的 bug,需要重写以下方法:

@Override
public boolean scopeMatches(InstancesChangeEvent event) {return true;
}

具体原因参考issue。

基于 Ribbon 的实现

Ribbon默认情况下是 30s 刷新一次服务列表,详情可看com.netflix.loadbalancer.PollingServerListUpdater,其中部分代码如下:

public class PollingServerListUpdater implements ServerListUpdater {private static int LISTOFSERVERS_CACHE_REPEAT_INTERVAL = 30 * 1000; // msecs;private static long getRefreshIntervalMs(IClientConfig clientConfig) {return clientConfig.get(CommonClientConfigKey.ServerListRefreshInterval,LISTOFSERVERS_CACHE_REPEAT_INTERVAL);}
}

这里的时间间隔可以通过ribbon.ServerListRefreshInterval=xxx进行配置,其中xxx对应自定义的毫秒时间间隔,而通过监听Nacos的服务变更事件,则不必调整时间间隔即可实现服务的平滑上下线,具体代码如下:

import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.client.naming.event.InstancesChangeEvent;
import com.alibaba.nacos.common.notify.NotifyCenter;
import com.alibaba.nacos.common.notify.listener.Subscriber;
import com.alibaba.nacos.common.notify.Event;
import com.netflix.loadbalancer.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Optional;/*** 服务变更监听** @author butterfly* @date 2023-09-24*/
@Slf4j
@Component
public class ServiceChangeNotifier extends Subscriber<InstancesChangeEvent> {@Resourceprivate SpringClientFactory springClientFactory;@PostConstructpublic void init() {// 注册当前自定义的订阅者以获取通知NotifyCenter.registerSubscriber(this);}@Overridepublic void onEvent(InstancesChangeEvent event) {String serviceName = event.getServiceName();// 使用 dubbo 时包含 rpc 服务类会注册以 providers: 或者 consumers: 开头的服务// 由于不是正式的服务, 这里需要进行排除, 如果未使用 dubbo 则不需要该处理if (serviceName.contains(":")) {return;}// serviceName 格式为 groupName@@nameString split = Constants.SERVICE_INFO_SPLITER;if (serviceName.contains(split)) {serviceName = serviceName.substring(serviceName.indexOf(split) + split.length());}log.info("服务上下线: {}", serviceName);// 手动更新服务列表// 如果自定义负载均衡方式则将默认的 ZoneAwareLoadBalancer 替换为自己的实现即可Optional.ofNullable(springClientFactory.getLoadBalancer(serviceName)).ifPresent(loadBalancer -> ((ZoneAwareLoadBalancer<?>) loadBalancer).updateListOfServers());}@Overridepublic Class<? extends Event> subscribeType() {return InstancesChangeEvent.class;}}

基于 LoadBalancer 的实现

默认情况下LoadBalancer的缓存时间是 35s,可通过spring.cloud.loadbalancer.cache.ttl=35s进行设置,在org.springframework.cloud.loadbalancer.cache.DefaultLoadBalancerCacheManager类中可以看到,下面是部分代码:

public class DefaultLoadBalancerCacheManager implements LoadBalancerCacheManager {private Set<DefaultLoadBalancerCache> createCaches(String[] cacheNames,LoadBalancerCacheProperties loadBalancerCacheProperties) {// loadBalancerCacheProperties.getTtl().toMillis() 则是进行缓存的设置return Arrays.stream(cacheNames).distinct().map(name -> new DefaultLoadBalancerCache(name,new ConcurrentHashMapWithTimedEviction<>(loadBalancerCacheProperties.getCapacity(),new DelayedTaskEvictionScheduler<>()),loadBalancerCacheProperties.getTtl().toMillis(), false)).collect(Collectors.toSet());}}

同样的,通过监听Nacos的事件,我们可以在服务上下线时使相应的缓存失效即可:

import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.client.naming.event.InstancesChangeEvent;
import com.alibaba.nacos.common.notify.NotifyCenter;
import com.alibaba.nacos.common.notify.listener.Subscriber;
import com.alibaba.nacos.common.notify.Event;
import com.netflix.loadbalancer.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;/*** 服务变更监听** @author butterfly* @date 2023-09-24*/
@Slf4j
@Component
public class ServiceChangeNotifier extends Subscriber<InstancesChangeEvent> {/*** 由于会有多个类型的 CacheManager bean, 这里的 defaultLoadBalancerCacheManager 名称不可修改*/@Resourceprivate CacheManager defaultLoadBalancerCacheManager;@PostConstructpublic void init() {// 注册当前自定义的订阅者以获取通知NotifyCenter.registerSubscriber(this);}@Overridepublic void onEvent(InstancesChangeEvent event) {String serviceName = event.getServiceName();// 使用 dubbo 时包含 rpc 服务类会注册以 providers: 或者 consumers: 开头的服务// 由于不是正式的服务, 这里需要进行排除, 如果未使用 dubbo 则不需要该处理if (serviceName.contains(":")) {return;}// serviceName 格式为 groupName@@nameString split = Constants.SERVICE_INFO_SPLITER;if (serviceName.contains(split)) {serviceName = serviceName.substring(serviceName.indexOf(split) + split.length());}log.info("服务上下线: {}", serviceName);// 手动更新服务列表Cache cache = defaultLoadBalancerCacheManager.getCache(CachingServiceInstanceListSupplier.SERVICE_INSTANCE_CACHE_NAME);if (cache != null) {cache.evictIfPresent(serviceName);}}@Overridepublic Class<? extends Event> subscribeType() {return InstancesChangeEvent.class;}}

参考资料

  • Nacos 自定义服务变化订阅

  • Spring Cloud之负载均衡组件Ribbon原理分析

  • SpringBoot + Nacos + k8s 优雅停机

  • 微服务网关实战二SCG + Nacos 服务上下线无缝切换

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

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

相关文章

虚拟机数据恢复:Stellar Data Recovery for Virtual Machine

虚拟机数据恢复-----Crack Version Stellar Data Recovery for Virtual Machine 软件可从 VMware (.vmdk)、ORACLE (.vdi) 和 Microsoft (.vhd) 虚拟映像文件中恢复丢失和删除的数据。 从 VMDK、VDI 和 VHD 虚拟映像文件中恢复数据提供原始恢复选项来恢复数据从已删除或无法识…

原型模式 创建型模式之二

一、原型&#xff08;Prototype&#xff09;模式概述 DP书上的定义为&#xff1a;用原型实例指定创建对象的种类&#xff0c;并且通过拷贝这些原型创建新的对象。其中有一个词很重要&#xff0c;那就是拷贝(既clone)。可以说&#xff0c;拷贝(clone)是原型模式的精髓所在。其中…

ACM MM 2023 | 基于去中心化表征的人体姿态估计方法

01. 前言 北京邮电大学与EVOL创新团队共同提出人体姿态估计方法DecenterNet&#xff0c;用于在提高在拥挤场景下人体姿态估计的准确度。该方法引入了一种去中心化的姿势表征方法&#xff0c;使得网络在纠缠区域/拥挤区域中将更加稳健地表达人体姿态。该方法还提出了一个解耦的…

2023-9-28 JZ7 重建二叉树

题目链接&#xff1a;重建二叉树 import java.util.*;/** public class TreeNode {* int val 0;* TreeNode left null;* TreeNode right null;* public TreeNode(int val) {* this.val val;* }* }*/public class Solution {/*** 代码中的类名、方法名、参数…

使用ElementUI完成登入注册的跨域请求提高开发效率

&#x1f3ac; 艳艳耶✌️&#xff1a;个人主页 &#x1f525; 个人专栏 &#xff1a;《Spring与Mybatis集成整合》​​​​​​​ ⛺️ 生活的理想&#xff0c;为了不断更新自己 ! 目录 ​编辑 1、前言 1.1.什么是ELementUI 2、完成登陆注册前端页面 2.1环境搭建 运行…

Java高级特性-泛型继承与子类型

Java高级特性-泛型的继承与子类型 在Java中,泛型继承关系是通过使用类型参数来实现的。 类型参数是一种在编译时指定类型的机制,它使得我们可以在编写代码时使用一般化的类型,而不必关注具体的类型。泛型继承关系的基本语法为: public class MyClass<T> extends M…

跨境电商如何利用海外代理IP,提高竞争力?

随着经济全球化的深度发展&#xff0c;跨境电商已经成为外贸发展的主要赛道&#xff0c;跨境电商王者般的新业态&#xff0c;近年来&#xff0c;我国跨境电商发展得到政府的大力扶持。而代理IP也逐渐成为助力跨境业务的强大工具之一。可以为我们跨境电商种出现的如地域限制、安…

CentOS 7系统安装与配置、常用100条操作命令

CentOS 7 是一个广泛使用的开源 Linux 操作系统&#xff0c;它是 Red Hat Enterprise Linux (RHEL) 的一个免费重建版本&#xff0c;以稳定性和安全性而著称。在 CentOS 7 上安装虚拟机通常使用虚拟化技术&#xff0c;如 VirtualBox 或 VMware 等。以下是 CentOS 7 的简要介绍以…

Zilliz@阿里云:大模型时代下Milvus Cloud向量数据库处理非结构化数据的最佳实践

大模型时代下的数据存储与分析该如何处理?有没有已经落地的应用实践? 为探讨这些问题,近日,阿里云联合 Zilliz 和 Doris 举办了一场以《大模型时代下的数据存储与分析》为主题的技术沙龙,其中,阿里云对象存储 OSS 上拥有海量的非结构化数据,Milvus(Zilliz)作为全球最有…

C语言-控制语句

C语言中有三大结构&#xff0c;分别是顺序结构、选择结构和循环结构&#xff08;分支结构&#xff09;。 一、C 判断语句 判断结构要求程序员指定一个或多个要评估或测试的条件&#xff0c;以及条件为真时要执行的语句&#xff08;必需的&#xff09;和条件为假时要执行的语句…

解决使用flex布局引起的变形问题

只需在变形的样式中加以下代码&#xff0c;禁止拉伸就ok了 flex-shrink: 0;

ipaguard界面概览

ipaguard界面概览 ipaguard界面分左右2块&#xff1a;左边菜单导航栏&#xff0c;右边的功能区 左侧菜单&#xff1a;按模块分成启动界面&#xff0c;代码模块&#xff0c;文件模块&#xff0c;重签名与测试模块 右侧主功能区会随着功能变化&#xff0c;但是整体分3块&#xf…

此芯科技加入百度飞桨硬件生态共创计划,加速端侧AI生态布局

近日&#xff0c;此芯科技&#xff08;上海&#xff09;有限公司&#xff08;以下简称“此芯科技”&#xff09;与百度签署硬件生态共创计划合作协议&#xff0c;正式加入由百度发起的硬件生态共创计划。双方将共同推动端侧AI和大模型在个人计算、车载计算以及元宇宙计算等领域…

读取txt文档并解析数据输出

读取如下内容&#xff1a; [INFO] [2023927 13:23:04.303] [estimator.cpp:1894]: XYZI: 1.70492 -1.33857 1.99379 134 [INFO] [2023927 13:23:04.303] [estimator.cpp:1894]: XYZI: 1.70944 -1.32323 1.99325 125 [INFO] [2023927 13:23:04.303] [estimator.cpp:1892]: 1695…

【发版】java项目一键式发版,一键式发版脚本,如何实现一键式发版

0. 手动打包&#xff0c;jar包生产后 jenkins打包。 1. Dockerfile 文件&#xff1a; FROM openjdk:8MAINTAINER "维护者 <email>" RUN echo "Asia/Shanghai" > /etc/timezoneLABEL description"descr ... " ADD xxx-1.0.0-SNAPSHOT.…

BiMPM实战文本匹配【下】

引言 这是BiMPM实战文本匹配的第二篇文章。 注意力匹配 如上图所示&#xff0c;首先计算每个正向(或反向)上下文嵌入 h i p → \overset{\rightarrow}{\pmb h_i^p} hip​→​(或 h i p ← \overset{\leftarrow}{\pmb h_i^p} hip​←​)与另一句的每个正向(或反向)上下文嵌入 …

python爬虫:JavaScript 混淆、逆向技术

Python爬虫在面对JavaScript混淆和逆向技术时可能会遇到一些挑战&#xff0c;因为JavaScript混淆技术和逆向技术可以有效地阻止爬虫对网站内容的正常抓取。以下是一些应对这些挑战的方法&#xff1a; 分析网页源代码&#xff1a;首先&#xff0c;尝试分析网页的源代码&#xf…

redis实现布隆过滤器

1 概述 布隆过滤器是一种基于概率的数据结构&#xff0c;用于判断一个元素是否存在于一个集合中。相比于传统的数据结构&#xff0c;布隆过滤器具有占用空间少、查询速度快的特点&#xff0c;常被用于缓存、爬虫去重等场景。Redis 作为一款流行的 NoSQL 数据库&#xff0c;也提…

计算机图像处理-直方图均衡化

直方图均衡化 直方图均衡化是图像灰度变换中有一个非常有用的方法。图像的直方图是对图像对比度效果上的一种处理&#xff0c;旨在使得图像整体效果均匀&#xff0c;黑与白之间的各个像素级之间的点分布更均匀一点。通过这种方法&#xff0c;亮度可以更好地在直方图上分布。 …

2009-2018年31省份旅游收入(入境、国内、总收入;第三产值;GDP)

2009&#xff0d;2018年31省份旅游收入&#xff08;入境、国内、总收入&#xff1b;第三产值&#xff1b;GDP&#xff09; 1、时间&#xff1a;2009-2018年 2、指标&#xff1a; 汇率、入境旅游收入&#xff08;万美元&#xff09;、国内旅游收入&#xff08;亿元&#xff0…