当前位置: 首页 > news >正文

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 服务上下线无缝切换

相关文章:

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

前言 不知道各位在使用 SpringCloud Gateway Nacos的时候有没有遇到过服务刚上线偶尔会出现一段时间的503 Service Unavailable&#xff0c;或者服务下线后&#xff0c;下线服务仍然被调用的问题。而以上问题都是由于Ribbon或者LoadBalancer的默认处理策略有关&#xff0c;其…...

c/c++里 对 共用体 union 的内存分配

对union 的内存分配&#xff0c;是按照最大的那个成员分配的。 谢谢...

博途SCL区间搜索指令(判断某个数属于某个区间)

S型速度曲线行车位置控制,停靠位置搜索功能会用到区间搜索指令,下面我们详细介绍区间搜索指令的相关应用。 S型加减速行车位置控制(支持点动和停车位置搜索)-CSDN博客S型加减速位置控制详细算法和应用场景介绍,请查看下面文章博客。本篇文章不再赘述,这里主要介绍点动动和…...

(三)激光线扫描-中心线提取

光条纹中心提取算法是决定线结构光三维重建精度以及光条纹轮廓定位准确性的重要因素。 1. 光条的高斯分布 激光线条和打手电筒一样,中间最亮,越像周围延申,光强越弱,这个规则符合高斯分布,如下图。 2. 传统光条纹中心提取算法 传统的光条纹中心提取算法有 灰度重心法、…...

递归与分治算法(1)--经典递归、分治问题

目录 一、递归问题 1、斐波那契数列 2、汉诺塔问题 3、全排列问题 4、整数划分问题 二、递归式求解 1、代入法 2、递归树法 3、主定理法 三、 分治问题 1、二分搜索 2、大整数乘法 一、递归问题 1、斐波那契数列 斐波那契数列不用过多介绍&#xff0c;斐波那契提出…...

Java之SpringCloud Alibaba【六】【Alibaba微服务分布式事务组件—Seata】

一、事务简介 事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。 在关系数据库中&#xff0c;一个事务由一组SQL语句组成。 事务应该具有4个属性: 原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。 原子性(atomicity) ∶个事务…...

Android逆向学习(五)app进行动态调试

Android逆向学习&#xff08;五&#xff09;app进行动态调试 一、写在前面 非常抱歉鸽了那么久&#xff0c;前一段时间一直在忙&#xff0c;现在终于结束了&#xff0c;可以继续更新android逆向系列的&#xff0c;这个系列我会尽力做下去&#xff0c;然后如果可以的话我看看能…...

音频编辑软件Steinberg SpectraLayers Pro mac中文软件介绍

Steinberg SpectraLayers Pro mac是一款专业的音频编辑软件&#xff0c;旨在帮助音频专业人士进行精细的音频编辑和声音处理。它提供了强大的频谱编辑功能&#xff0c;可以对音频文件进行深入的频谱分析和编辑。 Steinberg SpectraLayers Pro mac软件特点 1. 频谱编辑&#xff…...

基于.Net Core实现自定义皮肤WidForm窗口

前言 今天一起来实现基于.Net Core、Windows Form实现自定义窗口皮肤&#xff0c;并实现窗口移动功能。 素材 准备素材&#xff1a;边框、标题栏、关闭按钮图标。 窗体设计 1、创建Window窗体项目 2、窗体设计 拖拉4个Panel控件&#xff0c;分别用于&#xff1a;标题栏、关…...

【Rust】操作日期与时间

目录 介绍 一、计算耗时 二、时间加减法 三、时区转换 四、年月日时分秒 五、时间格式化 介绍 Rust的时间操作主要用到chrono库&#xff0c;接下来我将简单选一些常用的操作进行介绍&#xff0c;如果想了解更多细节&#xff0c;请查看官方文档。 官方文档&#xff1a;chr…...

blender快捷键

1&#xff0c; shift a 添加物体 2&#xff0c;ctrl alt q 切换四格视图 3, ~ 展示物体的各个视图按钮&#xff0c;&#xff08;~ 就是tab键上面的键&#xff09; 4&#xff0c;a 全选&#xff0c;全选后&#xff0c;点 ctrl 鼠标框选 减去已经选择的&#xff1b…...

java Spring Boot 自动启动热部署 (别再改点东西就要重启啦)

上文 java Spring Boot 手动启动热部署 我们实现了一个手动热部署的代码 但其实很多人会觉得 这叫说明热开发呀 这么捞 写完还要手动去点一下 很不友好 其实我们开发人员肯定是希望重启这种事不需要自己手动去做 那么 当然可以 我们就让它自己去做 Build Project 这个操作 我们…...

TouchGFX之后端通信

在大多数应用中&#xff0c;UI需以某种方式连接到系统的其余部分&#xff0c;并发送和接收数据。 它可能会与硬件外设&#xff08;传感器数据、模数转换和串行通信等&#xff09;或其他软件模块进行交互通讯。 Model类​ 所有TouchGFX应用都有Model类&#xff0c;Model类除了存…...

cesium gltf控制

gltf格式详解 glTF格式本质上是一个JSON文件。这一文件描述了整个3D场景的内容。它包含了对场景结构进行描述的场景图。场景中的3D对象通过场景结点引用网格进行定义。材质定义了3D对象的外观,动画定义了3D对象的变换操作(比如选择、平移操作)。蒙皮定义了3D对象如何进行骨骼…...

Spring的依赖注入(DI)以及优缺点

Spring的依赖注入&#xff08;DI&#xff09;&#xff1a;解释和优点 依赖注入&#xff08;Dependency Injection&#xff0c;简称DI&#xff09;是Spring框架的核心概念之一&#xff0c;也是现代Java应用程序开发的重要组成部分。本文将深入探讨DI是什么&#xff0c;以及它的…...

【强化学习】05 —— 基于无模型的强化学习(Prediction)

文章目录 简介蒙特卡洛算法时序差分方法Example1 MC和TD的对比偏差&#xff08;Bias&#xff09;/方差&#xff08;Variance&#xff09;的权衡Example2 Random WalkExample3 AB 反向传播(backup)Monte-Carlo BackupTemporal-Difference BackupDynamic Programming Backup Boot…...

【计算机组成原理】考研真题攻克与重点知识点剖析 - 第 1 篇:计算机系统概述

前言 本文基础知识部分来自于b站&#xff1a;分享笔记的好人儿的思维导图&#xff0c;感谢大佬的开源精神&#xff0c;习题来自老师划的重点以及考研真题。此前我尝试了完全使用Python或是结合大语言模型对考研真题进行数据清洗与可视化分析&#xff0c;本人技术有限&#xff…...

【Java-LangChain:面向开发者的提示工程-8】聊天机器人

第八章 聊天机器人 使用一个大型语言模型的一个令人兴奋的事情是&#xff0c;我们可以用它来构建一个定制的聊天机器人 (Chatbot) &#xff0c;只需要很少的工作量。在这一节中&#xff0c;我们将探索如何利用聊天的方式&#xff0c;与个性化&#xff08;或专门针对特定任务或…...

利用t.ppft.interval分别计算T分布置信区间[实例]

scipy.stats.t.interval用于计算t分布的置信区间&#xff0c;即给定置信水平时&#xff0c;计算对应的置信区间的下限和上限。 scipy.stats.t.ppf用于计算t分布的百分位点&#xff0c;即给定百分位数&#xff08;概率&#xff09;时&#xff0c;该函数返回给定百分位数对应的t…...

软件工程第三周

可行性研究 续 表达工作量的方式 LOC估算&#xff1a;Line of Code 估算公式S(Sopt4SmSpess)/6 FP&#xff1a;功能点 1. LOC (Line of Code) 估算 定义&#xff1a;LOC是指一个软件项目中的代码行数。 2. FP (Function Points) 估算 定义&#xff1a;FP是基于软件的功能性和…...

变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析

一、变量声明设计&#xff1a;let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性&#xff0c;这种设计体现了语言的核心哲学。以下是深度解析&#xff1a; 1.1 设计理念剖析 安全优先原则&#xff1a;默认不可变强制开发者明确声明意图 let x 5; …...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)

概述 在 Swift 开发语言中&#xff0c;各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过&#xff0c;在涉及到多个子类派生于基类进行多态模拟的场景下&#xff0c;…...

Linux简单的操作

ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

初学 pytest 记录

安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...

莫兰迪高级灰总结计划简约商务通用PPT模版

莫兰迪高级灰总结计划简约商务通用PPT模版&#xff0c;莫兰迪调色板清新简约工作汇报PPT模版&#xff0c;莫兰迪时尚风极简设计PPT模版&#xff0c;大学生毕业论文答辩PPT模版&#xff0c;莫兰迪配色总结计划简约商务通用PPT模版&#xff0c;莫兰迪商务汇报PPT模版&#xff0c;…...

C#学习第29天:表达式树(Expression Trees)

目录 什么是表达式树&#xff1f; 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持&#xff1a; 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

WPF八大法则:告别模态窗口卡顿

⚙️ 核心问题&#xff1a;阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程&#xff0c;导致后续逻辑无法执行&#xff1a; var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题&#xff1a…...

加密通信 + 行为分析:运营商行业安全防御体系重构

在数字经济蓬勃发展的时代&#xff0c;运营商作为信息通信网络的核心枢纽&#xff0c;承载着海量用户数据与关键业务传输&#xff0c;其安全防御体系的可靠性直接关乎国家安全、社会稳定与企业发展。随着网络攻击手段的不断升级&#xff0c;传统安全防护体系逐渐暴露出局限性&a…...

Qt Quick Controls模块功能及架构

Qt Quick Controls是Qt Quick的一个附加模块&#xff0c;提供了一套用于构建完整用户界面的UI控件。在Qt 6.0中&#xff0c;这个模块经历了重大重构和改进。 一、主要功能和特点 1. 架构重构 完全重写了底层架构&#xff0c;与Qt Quick更紧密集成 移除了对Qt Widgets的依赖&…...