降级、熔断、限流学习笔记

news2024/11/26 6:22:21

1. 面对高流量出现故障的原因

  • 由于依赖的资源或者服务不可用,最终导致整体服务宕机。在电商系统中就可能由于数据库访问缓慢,导致整体服务不可用。

  • 乐观地预估了可能到来的流量,当有超过系统承载能力的流量到来时,系统不堪重负,从而出现拒绝服务的情况。

2. 雪崩效应

  • 系统运行需要消耗资源,包括 CPU、内存等系统资源,也包括执行业务逻辑需要的线程资源。比如说Tomcat 定义了线程池来处理 HTTP 请求。这些线程池中的线程资源是有限的,如果这些线程资源被耗尽,服务无法处理新请求,服务提供方也就宕机了。

    【举例】

    • A 调用 B,B 调用 C 和 D。其中ABD 服务是系统的核心服务(像电商系统中的订单服务、支付服务),C 是非核心服务(像反垃圾服务、审核服务)。

    • 一旦作为入口的 A 流量增加,你可能会考虑把 ABD 服务扩容,忽略 C。那么 C 就有可能因为无法承担这么大的流量,导致请求处理缓慢,进一步会让 B 在调用 C 的时候,B 中的请求被阻塞,等待 C 返回响应结果。这样一来,B 服务中被占用的线程资源就不能释放。

    • 久而久之,B 就会因为线程资源被占满,无法处理后续的请求。那么从 A 发往 B 的请求,就会被放入 B 服务线程池的队列中,然后 A 调用 B 响应时间变长,进而拖垮 A 服务。你看,仅仅因为非核心服务 C 的响应时间变长,就可以导致整体服务宕机,这就是我们经常遇到的一种服务雪崩情况。

       ==》在分布式环境下,系统最怕的反而不是某一个服务或者组件宕机(影响部分功能),而是最怕它响应缓慢(雪崩拖垮整个系统)

    解决思路:检测到某一个服务的响应时间出现异常时,切断调用它的服务与它之间的联系,让服务的调用快速返回失败,从而释放这次请求持有的资源  ==》熔断

3. 熔断机制

  • 类似电路中的保险丝保护机制,服务调用失败次数达到阈值时,停止调用并返回错误。

  • 三种状态:关闭(正常调用)、半打开(尝试调用)、打开(返回错误)。

不仅仅微服务之间调用需要熔断的机制,我们在调用 Redis、Memcached 等资源的时候也可以引入这套机制

实现:使用定时器定期检测服务是否恢复。

  1. 当熔断器处于 Open 状态时,定期地检测 Redis 组件是否可用

  2. 在通过 Redis 客户端操作 Redis 中的数据时,在其中加入熔断器的逻辑。比如,当节点处于熔断状态时,直接返回空值以及熔断器三种状态之间的转换

  3. 这样当某一个 Redis 节点出现问题,Redis 客户端中的熔断器就会实时监测到,并且不再请求有问题的 Redis 节点,避免单个节点的故障导致整体系统的雪崩

4. 降级机制

将有限的资源效益最大化:通过开关控制非核心服务,保证核心服务可用。

常用策略:

  • 牺牲时效性

    • 返回降级数据:数据库压力大时只考虑读取缓存数据,非核心接口出现问题直接返回服务繁忙或固定的降级数据

    • 降频:对于轮询查询数据场景,增加轮询间隔

    • 同步写转异步写:通过牺牲数据一致性来保证系统可用性

  • 牺牲功能完整性

    • 关闭风控等功能

    • 取消条件判断

  • 牺牲用户体验

    • 为了减少对「冷数据」的获取,禁用列表的翻页功能。

    • 为了放缓流量进入的速率,增加验证码机制。

    • 为了减少“大查询”浪费过多的资源,提高筛选条件要求(禁用模糊查询、部分条件必选等)。

    • 用通用的静态化数据代替「千人千面」的动态数据。

    • 甚至更简单粗暴的,直接挂一个页面显示「XX 功能在 XX 时间内暂时关闭」。

5. 限流机制

定义:限流是通过限制并发请求数量,保证系统能够正常响应部分请求,对于超过限制的流量则拒绝服务。限流策略通常部署在服务的入口层,如API网关。

常见限流算法:

  • 固定窗口算法:统计固定时间窗口内的请求数量,超过限制则触发限流。缺点是无法应对短时间内的突发流量。

  • 滑动窗口算法(TCP协议):将时间窗口划分为多个小窗口,统计滑动时间窗口内的请求数量,解决了固定窗口算法的缺陷,但还是无法限制短时间之内的集中流量,也就是说无法控制流量让它们更加平滑

  • 漏桶算法:通过漏桶机制平滑流量,对突发流量进行缓冲处理,常用于消息队列。【缺点是流量缓存在漏桶中,响应时间增长】

  • 【最推荐】令牌桶算法:在桶中按固定速率(1/限制访问次数)加入令牌,请求需要消耗令牌才能被处理。适用于应对突发流量的情况,如Guava库提供了RateLimiter类。【缺点是要存储并获取令牌数量,在分布式中用redis存储,每次请求redis都会有延迟,解决办法是使用Lua脚本每次获取一批令牌而不是一个,减少请求redis次数】

限流代码 —— 令牌桶限流

        实现令牌桶限流算法,需要反复调用 Redis 查询与计算,一次限流判断需要多次请求较为耗时。因此我们采用编写 Lua 脚本运行的方式,将运算过程放在 Redis 端,使得对 Redis 进行一次请求就能完成限流的判断。

        令牌桶算法需要在 Redis 中存储桶的大小、当前令牌数量,并且实现每隔一段时间添加新的令牌。最简单的办法当然是每隔一段时间请求一次 Redis,将存储的令牌数量递增。

        但实际上我们可以通过对限流两次请求之间的时间和令牌添加速度来计算得出上次请求之后到本次请求时,令牌桶应添加的令牌数量。因此我们在 Redis 中只需要存储上次请求的时间和令牌桶中的令牌数量,而桶的大小和令牌的添加速度可以通过参数传入实现动态修改。

        由于第一次运行脚本时默认令牌桶是满的,因此可以将数据的过期时间设置为令牌桶恢复到满所需的时间,及时释放资源。

编写Lua脚本如下:

local ratelimit_info = redis.pcall('HMGET',KEYS[1],'last_time','current_token')
local last_time = ratelimit_info[1]
local current_token = tonumber(ratelimit_info[2])
local max_token = tonumber(ARGV[1])
local token_rate = tonumber(ARGV[2])
local current_time = tonumber(ARGV[3])
local reverse_time = 1000/token_rate
if current_token == nil then
  current_token = max_token
  last_time = current_time
else
  local past_time = current_time-last_time
  local reverse_token = math.floor(past_time/reverse_time)
  current_token = current_token+reverse_token
  last_time = reverse_time*reverse_token+last_time
  if current_token>max_token then
    current_token = max_token
  end
end
local result = 0
if(current_token>0) then
  result = 1
  current_token = current_token-1
end
redis.call('HMSET',KEYS[1],'last_time',last_time,'current_token',current_token)
redis.call('pexpire',KEYS[1],math.ceil(reverse_time*(max_token-current_token)+(current_time-last_time)))
return result

使用 SpringDataRedis 来进行 Redis 脚本的调用,执行限流

public class RedisReteLimitScript implements RedisScript<String> {
   private static final String SCRIPT =".Lua";

  @Override   public String getSha1() {
    return DigestUtils.sha1Hex(SCRIPT);
  }

  @Override   public Class<String> getResultType() {     
      return String.class;
  }

  @Override   public String getScriptAsString() {     
      return SCRIPT;
  }
}

// 执行脚本
public boolean rateLimit(String key, int max, int rate) {
    List<String> keyList = new ArrayList<>(1);
    keyList.add(key);
    return "1".equals(stringRedisTemplate.execute(new RedisReteLimitScript(), 
        keyList, Integer.toString(max), Integer.toString(rate),
        Long.toString(System.currentTimeMillis())));
  }

编写测试类:rateLimit 方法传入的 key 为限流接口的 ID,max 为令牌桶的最大大小,rate 为每秒钟恢复的令牌数量,返回的 boolean 即为此次请求是否通过了限流。为了测试 Redis 脚本限流是否可以正常工作,我们编写一个单元测试进行测试看看

@Autowired
private RedisManager redisManager;

@Test
public void rateLimitTest() throws InterruptedException {
    String key = "test_rateLimit_key";
    int max = 10;  //令牌桶大小
    int rate = 10; //令牌每秒恢复速度
    AtomicInteger successCount = new AtomicInteger(0);
    Executor executor = Executors.newFixedThreadPool(10);
    CountDownLatch countDownLatch = new CountDownLatch(30);
    for (int i = 0; i < 30; i++) {
      executor.execute(() -> {
        boolean isAllow = redisManager.rateLimit(key, max, rate);
        if (isAllow) {
          successCount.addAndGet(1);
        }
        log.info(Boolean.toString(isAllow));
        countDownLatch.countDown();
      });
    }
    countDownLatch.await();
    log.info("请求成功{}次", successCount.get());
}

日志输出:

[19:12:50,283]true
[19:12:50,284]true
[19:12:50,284]true
[19:12:50,291]true
[19:12:50,291]true
[19:12:50,291]true
[19:12:50,297]true
[19:12:50,297]true
[19:12:50,298]true
[19:12:50,305]true
[19:12:50,305]false
[19:12:50,305]true
[19:12:50,312]false
[19:12:50,312]false
[19:12:50,312]false
[19:12:50,319]false
[19:12:50,319]false
[19:12:50,319]false
[19:12:50,325]false
[19:12:50,325]false
[19:12:50,326]false
[19:12:50,380]false
[19:12:50,380]false
[19:12:50,380]false
[19:12:50,387]false
[19:12:50,387]false
[19:12:50,387]false
[19:12:50,392]false
[19:12:50,392]false
[19:12:50,392]false
[19:12:50,393]请求成功11次

熔断代码 —— Redis 开关

当熔断器处于 Open 状态时,定期地检测 Redis 组件是否可用

new Timer("RedisPort-Recover", true).scheduleAtFixedRate(new TimerTask() {
    @Override
    public void run() {
        if (breaker.isOpen()) {
            Jedis jedis = null;
            try {
                jedis = connPool.getResource();
                jedis.ping(); // 验证 redis 是否可用
                successCount.set(0); // 重置连续成功的计数
                breaker.setHalfOpen(); // 设置为半打开态
            } catch (Exception ignored) {
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
        }
    }
}, 0, recoverInterval); // 初始化定时器定期检测 redis 是否可用

在通过 Redis 客户端操作 Redis 中的数据时,我们会在其中加入熔断器的逻辑。比如,当节点处于熔断状态时,直接返回空值以及熔断器三种状态之间的转换。

if (breaker.isOpen()) { 
    return null;  // 断路器打开则直接返回空值
}
K value = null;
Jedis jedis = null;
try {
     jedis = connPool.getResource();
     value = callback.call(jedis);
     if(breaker.isHalfOpen()) { // 如果是半打开状态
          if(successCount.incrementAndGet() >= SUCCESS_THRESHOLD) {// 成功次数超过阈值
                failCount.set(0);  // 清空失败数
                breaker.setClose(); // 设置为关闭态
          }
     }
     return value;
} catch (JedisException je) {
     if(breaker.isClose()){  // 如果是关闭态
         if(failCount.incrementAndGet() >= FAILS_THRESHOLD){ // 失败次数超过阈值
            breaker.setOpen();  // 设置为打开态
         }
     } else if(breaker.isHalfOpen()) {  // 如果是半打开态
         breaker.setOpen();    // 直接设置为打开态
     }
     throw  je;
} finally {
     if (jedis != null) {
           jedis.close();
     }
}

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

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

相关文章

Vue.js 3.x 必修课|008|计算属性:提高代码服用性和可维护性

欢迎关注公众号:CodeFit。 创作不易,如果你觉得这篇文章对您有帮助,请不要忘了 点赞、分享 和 关注,为我的 持续创作 提供 动力! 欢迎订阅《Vue 3.x 必修课| 2024》:http://t.csdnimg.cn/hHRrM 精品内容,物超所值,一杯咖啡的价格(9.9 元)只为持续创作提供动力。 在 …

【AI】人工智能时代,程序员如何保持核心竞争力?

目录 程序员在AI时代的应对策略1. 引言2. AI在编程领域的影响2.1 AI辅助编程工具的现状2.2 AI对编程工作的影响2.3 程序员的机遇与挑战 3. 深耕细作&#xff1a;专注领域的深度学习3.1 专注领域的重要性3.2 深度学习的策略3.2.1 选择合适的领域3.2.2 持续学习和研究3.2.3 实践与…

【PXE+kickstart】linux网络服务之自动装机

PXE&#xff1a; 简介&#xff1a;PXE(Preboot execute environment 是一种能够让计算机通过网络启动的引导方式&#xff0c;只要网卡支持PXE协议即可使用Kickstart 是一种无人值守的安装方式&#xff0c;工作原理就是预先把原本需要运维人员手工填写的参数保存成一个 ks.cfg 文…

centos7安装 ES集群 elasticsearch

这里写自定义目录标题 编写启动脚本 elasticsearch.sh启动可能报错&#xff1a;elasticsearch 7.10启动报错 bootstrap checks failed解决方法问题原因&#xff1a;注意 退出xshell&#xff0c;重新登录&#xff1a; 上面两个配置项改完后&#xff0c;ES启动用户(es 或root) **…

Debian | 更换 Gnome 至 Xfce4

Debian | 更换 Gnome 至 Xfce4 更新源 sudo apt update && sudo apt upgrade安装 xfce4 sudo apt install xfce4我选择 lightdm&#xff0c;回车 切换桌面 sudo update-alternatives --config x-session-manager输入 xfce 所在序号&#xff0c;我这里是 3 卸载 …

洛谷 P1560 [USACO5.2]蜗牛的旅行Snail Trails(c++)

describe 蜗牛在制定今天的旅游计划&#xff0c;有 n 个景点可选&#xff0c;它已经把这些景点按照顺路游览的顺序排 成一排了&#xff0c;每个地方有相应的景观&#xff0c;这里用一个整数表示。 蜗牛希望选取连续的一段景点&#xff0c;还要选出来的每一个景点的景观都不同…

ASP.NET Core基础 - 简介

目录 一. 简介 A、跨平台性 B、高性能 C、开源性 D、模块化与可扩展性 E、集成现代前端技术 二. ASP.NET 4.x 和 ASP.NET Core 比较 A、架构与平台支持 B、性能 C、开发体验 D、社区支持与生态系统 三. NET 与 .NET Framework 比较 A、概念范围 B、跨平台能力 C…

文献综述如何有助于识别研究中的关键变量和概念

VersaBot文献综述助手 进行良好的文献综述对于从多个方面确定研究的关键变量和概念起着至关重要的作用&#xff1b; 1.揭示相关领域和理论&#xff1a; 通过沉浸在现有的学术研究中&#xff0c;你会遇到围绕你的主题的各种理论和概念。这些可以作为识别与您的研究问题相关的潜…

天和环保业绩波动性明显,应收账款逾期率和回款率欠佳

《港湾商业观察》施子夫 7月17日&#xff0c;北交所网站更新唐山天和环保科技股份有限公司&#xff08;以下简称&#xff0c;天和环保&#xff09;及保荐机构江海证券关于第三轮审核问询函的回复。 公开信息显示&#xff0c;2023年6月&#xff0c;天和环保的IPO申请获受理。今…

Linux IPC解析:匿名命名管道与共享内存

目录 一.IPC机制介绍二.匿名与命名管道1.匿名管道2.命名管道3.日志 三.共享内存三.System V 标准1.System V简介2.IPC在内核的数据结构设计3.信号量 一.IPC机制介绍 IPC&#xff08;Inter-Process Communication&#xff0c;进程间通信&#xff09;是计算机系统中不同进程之间交…

还没用过OBS Studio?快来提升你的技术分享效率!

前言 在浩瀚的数字海洋中&#xff0c;有这么一款神器&#xff0c;它低调却光芒四射&#xff0c;默默改变着无数内容创作者的命运&#xff1b;嘿&#xff0c;你猜怎么着&#xff1f;它既不是天价的专业设备&#xff0c;也不是遥不可及的神秘黑科技&#xff0c;而是开源世界的瑰宝…

低功耗工业控制器用于风电场绿色可持续能源行业

全球对清洁能源的需求不断增长&#xff0c;风电场作为一种可再生能源的重要来源&#xff0c;正经历着快速发展。然而&#xff0c;传统的风电场管理和运营方式存在着效率低下、维护成本高等问题。为了提高风电场的运行效率和可靠性&#xff0c;实现绿色能源的可持续发展&#xf…

c语言-链表1

10 链表 一、链表是什么&#xff1f; -- 数据的一种存储方式 -- 链式存储 &#xff08;1&#xff09;线性存储 -- 地址连续 -- 自动开辟&#xff0c;自动释放 -- 默认是线性存储 &#xff08;2&#xff09;链式存储 -- 地址不连续…

【Git】git 从入门到实战系列(二)—— Git 介绍以及安装方法

文章目录 一、前言二、git 是什么三、版本控制系统是什么四、本地 vs 集中式 vs 分布式本地版本控制系统集中式版本控制系统分布式版本控制系统 五、安装 git 一、前言 本系列上一篇文章【Git】git 从入门到实战系列&#xff08;一&#xff09;—— Git 的诞生&#xff0c;Lin…

S硅谷-AI大模型实战训练

课程概述 AI大模型实战训练课程是一门专为有志于深入学习人工智能领域的学员设计的高级课程。本课程以当前人工智能领域的前沿技术——大模型为核心&#xff0c;通过理论与实践相结合的教学方式&#xff0c;培养学员在AI领域的实战能力。 课程目标 理解大模型的基本原理和架构。…

python爬虫之用scrapy下载中间件爬取网易新闻

python爬虫之用scrapy下载中间件爬取网易新闻 相关资源如下&#xff1a; 采用scrapy下载中间件爬取网易新闻国内、国际、数读、军事、航空五大板块新闻标题和内容 程序wangyi.py主代码&#xff1a; import scrapy from selenium import webdriver from selenium.webdriver.e…

PDF——分割pdf的10个工具

PDF分割器是一种可用于将PDF文档分割成更小的文档甚至单个页面的工具。分割 PDF 文档的主要原因是为了更容易共享。 但该过程的成功取决于您用于拆分 PDF 的工具。较简单的工具仅提供几个选项&#xff0c;可能并不适合所有类型的文档。我们将在本文中列出的 10 个最佳 PDF 分割…

gemini api 应用

安装 gemini Prerequisites To complete this quickstart locally, ensure that your development environment meets the following requirements: Python 3.9 An installation of jupyter to run the notebook. Install the Gemini API SDK The Python SDK for the Gemin…

手机在网时长查询接口如何对接?(一)

一、什么是手机在网时长查询接口&#xff1f; 传入手机号码&#xff0c;查询该手机号的在网时长&#xff0c;返回时间区间&#xff0c;支持携号转网号码查询。 二、手机在网时长查询接口适用于哪些场景&#xff1f; 例如&#xff1a;客户画像与精准营销 &#xff08;1&…

2个一键生成PPT目录的AI软件,轻松搞定PPT目录制作!

在各种信息都在努力争夺每个人的注意力的当下&#xff0c;一份精心制作的PPT可能成为决定成败的关键因素。而PPT目录&#xff0c;作为整个PPT演示文稿的门面和导航&#xff0c;其重要性往往被低估。 每个人的时间都是有限的&#xff0c;如果PPT目录没有让潜在观众Get到重点&am…