细品服务并发限流+Redis-cell的使用

2023-10-21 01:10

本文主要是介绍细品服务并发限流+Redis-cell的使用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

背景

今天热搜“海底捞的排号系统挂掉了”,也许是今天情人节,各位情侣去海底捞约会,进入排号系统的流量猛增,导致服务支撑不住,直接挂掉,在这里只是猜测(大胆猜测,小心求证)。那我们应该如何防止因为流量突然猛增而导致服务挂掉的问题呢?那就是限流了。
那我们通过redis 来设计限流策略。

服务限流

简介

  • 通过压测我们可以压出我们服务接口可以承受最大的QPS或者TPS,但是我们压测的话只是单压并不知道在生产环境所能承受的最大流量。如果说其他业务接口也在跑,那这就很难把控这个接口在生产环境可以定多大的QPS或TPS。所以预估某个接口的所能承受的QPS和TPS还是很有水平的。我能力有限今天只聊如何限流。

时间窗口限流

什么是时间窗口?
  • TCP/IP为了提高传输效率(提高吞吐量)采用并发进行传输包,由于有ACk机制,如果等并发发出去的包都回来的话,会影响整体的发送效率,所以只要等到他需要等待的数据就进行发起第二次的传输。有人就会问了,TCP包的传输是有序的,如果并发发送的包,顺序是在后面的包先回来了,那怎么搞,那就继续等待先去的包回来再进行下次操作。
    还有就是采用了并发那还得考虑机器的性能,可不能由于发送的包太多导致发送包的服务不可用了。于是就有了滑动时间窗口协议。
  • 具体TCP/IP滑动时间窗口协议详解
  • 滑动时间窗口主要解决的问题就是控制瞬时的量,通过缓存将其分为4个区段,进行滑动处理这4个区段。当这四个区段放满后就会进行等待。想了解得更有深度,可以点击上面链接进行深度学习。下图是TCP滑动窗口示意图
    在这里插入图片描述

时间滑动窗口协议的应用

  1. 现在我们的服务使用的是java语言,现在需要实现一个滑动窗口。
    2.使用ReentrantLock(可重入锁)实现,如下图 这样有个问题就是:粒度太大了,不均匀,针对1秒一下的,没法辨析。
    我们能不能把粒度拆细了,1秒拆成10个100毫秒。每一个100毫秒有一个计数器。了解TCP/IP的应该知道,TCP/IP为了增加传输速度和控制传输速度,有个叫“滑动窗口协议”。就算拆得再细,也无法解决匀速限制速度的问题。而且还有个临界点问题,比如假如,一秒限制10个请求,在第1秒钟,第2秒 之间,第1秒后半段时间10个请求,第2秒前半段10个请求,那第1秒后半段+第2秒前半段时间组成的一秒钟里就有20个请求,没有起到限速的作用。
    在这里插入图片描述

  2. java中的JUC包中的CyclicBarrier。实现一个限流。我们只允许最多执行多少个线程。如果其中一个阻塞了。那这就很尴尬了。会影像服务的正常的吞吐,但是上面的那种方式,当他阻塞了后,随着时间窗口的推进,会将上一次时间串口的请求的技术进行归零。

漏斗限流

漏斗限流是最常用的限流方法之一,顾名思义,这个算法的灵感源于漏斗(funnel)的结
构。
在这里插入图片描述

实现一个简单的漏斗算法
package 漏斗算法;import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;public class FunnelRateLimiter {static class Funnel {//漏斗的容量int capacity;//速率float leakingRate;//剩余容量int leftQuota;long leakingTs;public Funnel(int capacity, float leakingRate) {this.capacity = capacity;this.leakingRate = leakingRate;this.leftQuota = capacity;this.leakingTs = System.currentTimeMillis();}void makeSpace() {long nowTs = System.currentTimeMillis();long deltaTs = nowTs - leakingTs;int deltaQuota = (int) (deltaTs * leakingRate);// 间隔时间太长,整数数字过大溢出if (deltaQuota < 0) {this.leftQuota = capacity;this.leakingTs = nowTs;return;}// 腾出空间太小,最小单位是 1if (deltaQuota < 1) {return;}this.leftQuota += deltaQuota;this.leakingTs = nowTs;if (this.leftQuota > this.capacity) {this.leftQuota = this.capacity;}}boolean watering(int quota) {makeSpace();if (this.leftQuota >= quota) {this.leftQuota -= quota;return true;}return false;}}private Map<String, Funnel> funnels = new ConcurrentHashMap<>();public boolean isActionAllowed(String userId, String actionKey, int capacity, float leakingRate) {String key = String.format("%s:%s", userId, actionKey);Funnel funnel = funnels.get(key);if (funnel == null) {funnel = new Funnel(capacity, leakingRate);funnels.put(key, funnel);}// 需要 1 个 quotareturn funnel.watering(1);}
}
  • 有人就会怀疑自己写的不靠谱,写在服务里面使用内存这更不靠谱,有没有可以使用的中间件,被人造好的轮子。还真有,redis-cell
Redis-cell 的使用
  • Redis 4.0 提供了一个限流 Redis 模块,它叫 redis-cell。该模块也使用了漏斗算法,并
    提供了原子的限流指令。有了这个模块,限流问题就非常简单了。
  • 该模块只有 1 条指令 cl.throttle,它的参数和返回值都略显复杂,接下来让我们来看看这
    个指令具体该如何使用
    在这里插入图片描述

上面这个指令的意思是允许「用户laoqian回复行为」的频率为每 60s 最多 30 次(漏水速
率),漏斗的初始容量为 15,也就是说一开始可以连续回复 15 个帖子,然后才开始受漏水
速率的影响。我们看到这个指令中漏水速率变成了 2 个参数,替代了之前的单个浮点数。用
两个参数相除的结果来表达漏水速率相对单个浮点数要更加直观一些。

> cl.throttle laoqian:reply 15 30 60
1) (integer) 0 # 0 表示允许,1 表示拒绝
2) (integer) 15 # 漏斗容量 capacity
3) (integer) 14 # 漏斗剩余空间 left_quota
4) (integer) -1 # 如果拒绝了,需要多长时间后再试(漏斗有空间了,单位秒)
5) (integer) 2 # 多长时间后,漏斗完全空出来(left_quota==capacity,单位秒)
  • 在执行限流指令时,如果被拒绝了,就需要丢弃或重试。cl.throttle 指令考虑的非常周
    到,连重试时间都帮你算好了,直接取返回结果数组的第四个值进行 sleep 即可,如果不想
    阻塞线程,也可以异步定时任务来重试(放入一个队列进行消费队列)。

总结

  • 两种时间限流方法 时间窗口限流和漏斗限流
  • 简单介绍了时间窗口限流在TCP中的应用,TCP为了达到并发,且安全可靠传输采用时间窗口协议进行并发可靠传输包
  • 时间窗口的限流方式不能达到顺滑,为达到顺滑限流采用漏都限流。使用java简单实现漏斗限流
  • Redis4.0 中cell的使用。完美的且简单的就可以实现限流。

参考

  • 《redis 深度历险》
  • https://juejin.im/entry/6844903695432286215
  • https://www.jianshu.com/p/41781605ed29
  • 其他限流算法(令牌桶https://www.google.com/search?q=%E4%BB%A4%E7%89%8C%E6%A1%B6&oq=%E4%BB%A4%E7%89%8C%E6%A1%B6&aqs=chrome…69i57j69i59j0l4.1915j0j7&sourceid=chrome&ie=UTF-8)

这篇关于细品服务并发限流+Redis-cell的使用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/250923

相关文章

Java中使用Java Mail实现邮件服务功能示例

《Java中使用JavaMail实现邮件服务功能示例》:本文主要介绍Java中使用JavaMail实现邮件服务功能的相关资料,文章还提供了一个发送邮件的示例代码,包括创建参数类、邮件类和执行结... 目录前言一、历史背景二编程、pom依赖三、API说明(一)Session (会话)(二)Message编程客

Redis的Zset类型及相关命令详细讲解

《Redis的Zset类型及相关命令详细讲解》:本文主要介绍Redis的Zset类型及相关命令的相关资料,有序集合Zset是一种Redis数据结构,它类似于集合Set,但每个元素都有一个关联的分数... 目录Zset简介ZADDZCARDZCOUNTZRANGEZREVRANGEZRANGEBYSCOREZ

C++中使用vector存储并遍历数据的基本步骤

《C++中使用vector存储并遍历数据的基本步骤》C++标准模板库(STL)提供了多种容器类型,包括顺序容器、关联容器、无序关联容器和容器适配器,每种容器都有其特定的用途和特性,:本文主要介绍C... 目录(1)容器及简要描述‌php顺序容器‌‌关联容器‌‌无序关联容器‌(基于哈希表):‌容器适配器‌:(

使用Python实现高效的端口扫描器

《使用Python实现高效的端口扫描器》在网络安全领域,端口扫描是一项基本而重要的技能,通过端口扫描,可以发现目标主机上开放的服务和端口,这对于安全评估、渗透测试等有着不可忽视的作用,本文将介绍如何使... 目录1. 端口扫描的基本原理2. 使用python实现端口扫描2.1 安装必要的库2.2 编写端口扫

使用Python实现操作mongodb详解

《使用Python实现操作mongodb详解》这篇文章主要为大家详细介绍了使用Python实现操作mongodb的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、示例二、常用指令三、遇到的问题一、示例from pymongo import MongoClientf

SQL Server使用SELECT INTO实现表备份的代码示例

《SQLServer使用SELECTINTO实现表备份的代码示例》在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误,在SQLServer中,可以使用SELECTINT... 在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误。在 SQL Server 中,可以使用 SE

使用Python合并 Excel单元格指定行列或单元格范围

《使用Python合并Excel单元格指定行列或单元格范围》合并Excel单元格是Excel数据处理和表格设计中的一项常用操作,本文将介绍如何通过Python合并Excel中的指定行列或单... 目录python Excel库安装Python合并Excel 中的指定行Python合并Excel 中的指定列P

浅析Rust多线程中如何安全的使用变量

《浅析Rust多线程中如何安全的使用变量》这篇文章主要为大家详细介绍了Rust如何在线程的闭包中安全的使用变量,包括共享变量和修改变量,文中的示例代码讲解详细,有需要的小伙伴可以参考下... 目录1. 向线程传递变量2. 多线程共享变量引用3. 多线程中修改变量4. 总结在Rust语言中,一个既引人入胜又可

golang1.23版本之前 Timer Reset方法无法正确使用

《golang1.23版本之前TimerReset方法无法正确使用》在Go1.23之前,使用`time.Reset`函数时需要先调用`Stop`并明确从timer的channel中抽取出东西,以避... 目录golang1.23 之前 Reset ​到底有什么问题golang1.23 之前到底应该如何正确的

Redis多种内存淘汰策略及配置技巧分享

《Redis多种内存淘汰策略及配置技巧分享》本文介绍了Redis内存满时的淘汰机制,包括内存淘汰机制的概念,Redis提供的8种淘汰策略(如noeviction、volatile-lru等)及其适用场... 目录前言一、什么是 Redis 的内存淘汰机制?二、Redis 内存淘汰策略1. pythonnoe