教育网站制作费用,网站建好后,wordpress禁止必应访问,网站侧面的虚浮代码基于 Redis 实现分布式限流 一、 简介二、分布式限流1 数据结构1.1 Redis List1.2 Redis Set1.3 Redis Sorted Set 2 实现分布式限流3 实现原理分析 三、分布式限流算法1. 计数器算法2. 漏斗算法3. 令牌桶算法 四、分布式限流实战1. 单机限流实现2. 基于Redis Clusters的分布式… 基于 Redis 实现分布式限流 一、 简介二、分布式限流1 数据结构1.1 Redis List1.2 Redis Set1.3 Redis Sorted Set 2 实现分布式限流3 实现原理分析 三、分布式限流算法1. 计数器算法2. 漏斗算法3. 令牌桶算法 四、分布式限流实战1. 单机限流实现2. 基于Redis Clusters的分布式限流实现 五、基于Redis分布式限流的优化1. 缓存击穿1.1 问题描述1.2 解决方案1.2.1 使用互斥锁1.2.2 使用预热机制 2. 热点key问题的解决方案2.1 问题描述2.2 解决方案2.2.1 分布式锁2.2.2 分布式缓存 3. 并发竞争优化3.1 问题描述3.2 解决方案3.2.1 使用限流器3.2.2 使用异步线程池 一、 简介
分布式限流是指通过将限流策略嵌入到分布式系统中以控制流量或保护服务保证系统在高并发访问情况下不被过载。
分布式限流可以防止系统因大量请求同时到达导致压力过大而崩溃从而提高系统的稳定性和可靠性。同时它可以使得业务资源能够更好地分配提高系统的效率。
二、分布式限流
1 数据结构
1.1 Redis List
Redis List 是一个可以输入相同元素和非唯一元素的集合且支持在两端进行快速O(1)插入和删除元素。
1.2 Redis Set
Redis Set 是一个无序但不允许重复元素的集合。您可以使用这些命令对Redis集进行常规操作 SADDSREMSISMEMBERSMEMBERS等。
1.3 Redis Sorted Set
Redis Sorted Set 是一个有序的、不重复的元素集合。每个元素都关联着一个浮点数值称为 Score。通过 Score 可以从小到大排序得到一个有序集合。
2 实现分布式限流
在Redis中可以使用令牌桶算法来实现分布式限速。具体方法为
Step 1创建一个列表作为Redis令牌桶
String key rate_limit: userId;
// 模拟用户请求访问
ListString tokens redis.lrange(key, 0, -1);Step 2设定令牌桶的基准参数
int maxTokens 50;
long timeInterval 1 * 1000;
long now System.currentTimeMillis();Step 3计算Redis中令牌数量
long expiredTokens tokens.stream().filter(t - Long.parseLong(t) now - timeInterval).count();
tokens tokens.subList((int) expiredTokens, tokens.size());
long remainTokens maxTokens - tokens.size();Step 4基于令牌数量判断是否超过限制
if (remainTokens 1) {throw new RateLimitException(请求太频繁请稍后再试);
}Step 5如果没有超出限制则更新Redis中令牌数并设置过期时间
Long expiresIn now timeInterval;
redis.multi();
redis.rpush(key, String.valueOf(expiresIn));
redis.pexpire(key, timeInterval);
redis.exec();3 实现原理分析
以上代码所示首先需要建立Redis List用于存储Token其次需要设定令牌桶的基准参数比如最大Token数量和Token过期间隔等。在用户访问请求时需要计算Redis中的令牌数量根据规则对访问量进行限制。如果没有超过限制则需要更新Redis List中令牌数并设置过期时间如果超过了限制则需要返回错误信息并拒绝服务。
整个过程中需要注意并发访问情况下的线程安全问题并确保流量控制配置的公共协商如最大QPSQueries Per Second哪些接口需限制流量等。
三、分布式限流算法
在实际的系统设计中为了防止某一时刻出现大量请求导致系统崩溃我们通常会采用限流策略来控制流量而Redis作为分布式NoSQL数据库在限流中也有着广泛的应用。下面介绍一些Redis分布式限流的经典算法。
1. 计数器算法
计数器算法比较简单直接利用Redis存储每个IP或者用户的请求次数当请求次数超过预设阈值时拒绝服务。代码如下
public boolean isAllowed(String key, int limit, int timeout) {Jedis jedis getJedis();long count jedis.incr(key);if (count 1) {jedis.expire(key, timeout);}boolean allowed count limit;if (!allowed) {jedis.del(key);}jedis.close();return allowed;
}key需要限流的用户标识可根据IP、UserID等进行定义limit阈值即允许的最大请求数timeout过期时间对于计数器算法一定要设置过期时间否则缓存中的请求次数会一直不断累加
2. 漏斗算法
漏斗算法的核心思想是将请求按照恒定的速率转换为水流有效控制请求超出服务处理能力的情况。漏斗算法实现代码如下
public boolean isAllowed(String key, int capacity, double leakRate, int reqCount) {Jedis jedis getJedis();long nowTime System.currentTimeMillis();String luaScript local currentCapacity tonumber(redis.call(hget, KEYS[1], leftCapacity))\n if currentCapacity nil then\n redis.call(hset, KEYS[1], lastTime, ARGV[2])\n redis.call(hset, KEYS[1], leftCapacity, ARGV[1] - 1)\n return 1\n end\n local changeTime tonumber(redis.call(hget, KEYS[1], lastTime))\n local delayMillSeconds nowTime - changeTime\n local currentDelayCount tonumber(delayMillSeconds*ARGV[3])\n local currentCapacity math.min(currentDelayCountcurrentCapacity, ARGV[1])\n if currentCapacity ARGV[4] then\n return 0\n else\n redis.call(hset, KEYS[1], leftCapacity, currentCapacity-1)\n redis.call(hset, KEYS[1], lastTime, nowTime)\n return 1\n end;Object result jedis.eval(luaScript,Collections.singletonList(key),Arrays.asList(String.valueOf(capacity), String.valueOf(nowTime), String.valueOf(leakRate), String.valueOf(reqCount)));boolean allowed (result instanceof Long ? (Long) result : 0L) 1L;jedis.close();return allowed;
}key需要进行限流的用户标识capacity漏斗容量即最大允许请求数量leakRate漏嘴流水速率保证有序的请求到达reqCount预计请求量用于计算漏斗每次流出的数量
3. 令牌桶算法
令牌桶算法的特点是以一个固定的速率不断产生令牌并将令牌放入到桶中访问时若桶为空则表示请求数超限。令牌桶算法实现代码如下
public boolean isAllowed(String key, int capacity, double rate, int reqCount) {long nowTime System.currentTimeMillis();Jedis jedis getJedis();String luaScript local currentLimit tonumber(redis.call(get, KEYS[1]) or 0)\n if currentLimit ARGV[1] tonumber(KEYS[2]) then\n return false\n else\n redis.call(incrby, KEYS[1], ARGV[1])\n redis.call(expire, KEYS[1], ARGV[2])\n return true\n end;Object result jedis.eval(luaScript, 2, key, String.valueOf(capacity), String.valueOf(reqCount), String.valueOf(rate * (nowTime / 1000)));boolean allowed (result instanceof Boolean ? (Boolean) result : false);jedis.close();return allowed;
}key需要进行限流的用户标识capacity桶容量rate令牌发放速率reqCount请求数量
四、分布式限流实战
1. 单机限流实现
假设我们有一个需求需要限制每个IP一分钟内最多只能发送100个请求。可以通过Redis的INCR、EXPIRE等API操作来简单实现单机限流。
public boolean isAllowed(String ip, int limit, int interval) {Jedis jedis getJedis();String key ip: ip;long count jedis.incr(key);if (count 1) {jedis.expire(key, interval);}boolean allowed count limit;if (!allowed) {jedis.del(key);}jedis.close();return allowed;
}2. 基于Redis Clusters的分布式限流实现
当业务规模扩大时单机的限流已经无法满足需求这时候需要考虑使用Redis Clusters实现分布式限流。Clusers扩展了原先Redis的功能不仅支持横向扩展而且提高了整个集群的可用性。限流算法同上只是需要使用把数据分配到Cluser内不同的节点上。
public boolean isAllowed(String ip, int limit, int interval) {JedisCluster jedis getJedisCluster();String key ip: ip;long count jedis.incr(key);if (count 1) {jedis.expire(key, interval);}boolean allowed count limit;if (!allowed) {jedis.del(key);}return allowed;
}五、基于Redis分布式限流的优化
1. 缓存击穿
1.1 问题描述
在高并发场景下如果存在大量的缓存未命中请求将会导致访问底层数据存储系统这种情况被称为缓存击穿。
1.2 解决方案
1.2.1 使用互斥锁
/*** 获取缓存值方法* param key 缓存键值* return 缓存值*/
public String getCacheValue(String key) {String value cache.get(key);if (value null) { //缓存未命中//使用互斥锁Lock lock redisson.getLock(key);if (lock.tryLock()) { //尝试获取锁try {value cache.get(key); //再次尝试获取缓存if (value null) { //如果仍然未命中从数据库中获取value db.get(key);cache.put(key, value); //将查询结果放入缓存}} finally {lock.unlock(); //释放锁}} else {Thread.sleep(100); //自旋一段时间后重试return getCacheValue(key);}}return value;
}1.2.2 使用预热机制
预热机制是指在系统启动的时候提前加载热点数据到缓存中以减少缓存未命中请求。预热的方式可以使用定时任务或者其他方式在系统低峰期进行加载。
2. 热点key问题的解决方案
2.1 问题描述
在高并发场景下如果某个key的请求量过大将会导致这个key成为热点key从而导致缓存雪崩问题。
2.2 解决方案
2.2.1 分布式锁
/*** 获取缓存值方法* param key 缓存键值* return 缓存值*/
public String getCacheValue(String key) {String value cache.get(key);if (value null) { //缓存未命中//使用分布式锁RLock lock redisson.getFairLock(key);if (lock.tryLock()) { //尝试获取锁try {value cache.get(key); //再次尝试获取缓存if (value null) { //如果仍然未命中从数据库中获取value db.get(key);cache.put(key, value); //将查询结果放入缓存}} finally {lock.unlock(); //释放锁}} else {Thread.sleep(100); //自旋一段时间后重试return getCacheValue(key);}}return value;
}2.2.2 分布式缓存
使用分布式缓存将数据均匀地分散到多个节点上从而避免单点瓶颈问题。
3. 并发竞争优化
3.1 问题描述
在高并发场景下对于某些资源的并发访问将会导致性能瓶颈需要进行并发竞争优化。
3.2 解决方案
3.2.1 使用限流器
限流器类似于信号灯用于控制并发请求的数量在高峰期可以采用漏桶算法或令牌桶算法进行限流。
3.2.2 使用异步线程池
对于一些耗时的操作可以使用异步线程池进行处理从而避免阻塞主线程提升系统的并发能力。