基于Redis+LUA脚本的令牌桶算法限流策略实现

11 篇文章 0 订阅

    话不多说了,接口限流算法有很多种 基于redis实现的例子,

/**
 * 
 */
package com.matrix.cloud.service.redis.ratelimit;

import java.util.Collections;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;

/**
 * 1 使用redis+lua脚本实现接口限流
 *   基于Redis的令牌桶算法限流策略实现  测试通过
 * @author lmc
 * 2018年12月21日
 */
@Service
public class RateLimitClient {
	
	@Autowired
	StringRedisTemplate  redisTemplate;
	  
	//这里需要到redis配置文件下配置相关bean
	@Qualifier("ratelimitInitLua")
	@Resource
	RedisScript<Long> ratelimitInitLua;
	
	
	 public boolean initToken(String key){
	        boolean token;
	      //此序列化redis会把数字类字符串存为数字类型
		  redisTemplate.setKeySerializer(new StringRedisSerializer());
		  redisTemplate.setValueSerializer(new StringRedisSerializer());
	        Long currMillSecond = redisTemplate.execute(
	                (RedisCallback<Long>) redisConnection -> redisConnection.time()
	        );
	        /** 初始化接收到的参数
	         * redis.pcall("HMSET",KEYS[1],  "last_mill_second",ARGV[1],  "curr_permits",ARGV[2],
	         * "max_burst",ARGV[3], "rate",ARGV[4], "app",ARGV[5])
	         */
	        StringBuffer ratelimitStr= new StringBuffer();
	        ratelimitStr.append(" local result=1 ");
	        ratelimitStr.append(" redis.pcall('HMSET',KEYS[1], ");
	        ratelimitStr.append(" 'last_mill_second',ARGV[1], ");
	        ratelimitStr.append(" 'curr_permits',ARGV[2], ");
	        ratelimitStr.append(" 'max_burst',ARGV[3], ");
	        ratelimitStr.append(" 'rate',ARGV[4], ");
	        ratelimitStr.append(" 'app',ARGV[5]) ");
	        ratelimitStr.append(" return result ");
	     	DefaultRedisScript<Long> ratelimitLua = new DefaultRedisScript<>(ratelimitStr.toString(), Long.class);
	     	
			String last_mill_second =  String.valueOf(currMillSecond);//上一次添加令牌的毫秒数
			String curr_permits = "3";//令牌桶的最少令牌数
			String max_permits = "200";//令牌桶的最大令牌数
			String rate = "100";//向令牌桶中添加令牌的速率  , 令牌消耗速率
			String app = "skynet";//定义标记,比如哪些是被限流的
	        Long accquire = redisTemplate.execute(ratelimitLua,Collections.singletonList("ratelimit:"+key), currMillSecond.toString(), curr_permits, max_permits, rate, app);
	        if (accquire == 1) {
	            token = true;
	        } else if (accquire == 0) {
	            token = true;
	        } else {
	            token = false;
	        }
	        return token;
	    }
	 
		/*
		 *  last_mill_second 最后时间毫秒 
			curr_permits 当前可用的令牌 
			max_burst 令牌桶最大值 
			rate 每秒生成几个令牌 
			app 应用 
			令牌桶内令牌生成借鉴Guava-RateLimiter类的设计 
			每次根据时间戳生成token,不超过最大值
			permits 每次请求令牌数
		 */
	     public boolean accquireToken(String key, Integer permits) {
	        boolean token;
	        redisTemplate.setKeySerializer(new StringRedisSerializer());
			redisTemplate.setValueSerializer(new StringRedisSerializer());
	        Long currMillSecond = redisTemplate.execute(
	                (RedisCallback<Long>) redisConnection -> redisConnection.time()
	        );

	        Long accquire = redisTemplate.execute(ratelimitInitLua,
	                Collections.singletonList("ratelimit:"+key), permits.toString(), currMillSecond.toString());
	        if (accquire == 1) {
	            token = true;
	        } else {
	            token = false;
	        }
	        return token;
	    }
	
}

ratelimitInit.lua   配置文件加载进来

local ratelimit_info=redis.pcall("HMGET",KEYS[1],"last_mill_second","curr_permits","max_burst","rate","app")
local last_mill_second=ratelimit_info[1]
local curr_permits=tonumber(ratelimit_info[2])
local max_burst=tonumber(ratelimit_info[3])
local rate=tonumber(ratelimit_info[4])
local app=tostring(ratelimit_info[5])
if app == nil then
    return 0
end

local local_curr_permits=max_burst;

if(type(last_mill_second) ~='boolean' and last_mill_second ~=nil) then
    local reverse_permits=math.floor((ARGV[2]-last_mill_second)/1000)*rate
    if(reverse_permits>0) then
        redis.pcall("HMSET",KEYS[1],"last_mill_second",ARGV[2])
    end

    local expect_curr_permits=reverse_permits+curr_permits
    local_curr_permits=math.min(expect_curr_permits,max_burst);

else
    redis.pcall("HMSET",KEYS[1],"last_mill_second",ARGV[2])
end

local result=-1
if(local_curr_permits-ARGV[1]>0) then
    result=1
    redis.pcall("HMSET",KEYS[1],"curr_permits",local_curr_permits-ARGV[1])
else
    redis.pcall("HMSET",KEYS[1],"curr_permits",local_curr_permits)
end

return result

调用 方式  方式简单点

	@RequestMapping(value = "/getRatelimitlua", method = RequestMethod.POST)
	@ResponseBody
	public Object RatelimitRedisLua() {//@RequestParam(value="uid") String uid
		
		String key="lmc168";
		
		rateLimitClient.initToken(key);//初如化开始
		
		if (!rateLimitClient.accquireToken(key, 1)) {
	        	System.out.println("触发限流API:调用太忙了,请休息下");
				//throw new Exception();
	    }else{
		  System.out.println("没有触发限流策略");
	    }
		return key;
	}

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
实现分布式限流可以使用 RedisLua 脚本来完成。以下是可能的实现方案: 1. 使用 Redis 的 SETNX 命令来实现基于令牌桶算法限流 令牌桶算法是一种常见的限流算法,它可以通过令牌的放置和消耗来控制流量。在 Redis 中,我们可以使用 SETNX 命令来实现令牌桶算法。 具体实现步骤如下: - 在 Redis 中创建一个有序集合,用于存储令牌桶的令牌数量和时间戳。 - 每当一个请求到达时,我们首先获取当前令牌桶中的令牌数量和时间戳。 - 如果当前时间戳与最后一次请求的时间戳之差大于等于令牌桶中每个令牌的发放时间间隔,则将当前时间戳更新为最后一次请求的时间戳,并且将令牌桶中的令牌数量增加相应的数量,同时不超过最大容量。 - 如果当前令牌桶中的令牌数量大于等于请求需要的令牌数量,则返回 true 表示通过限流,将令牌桶中的令牌数量减去请求需要的令牌数量。 - 如果令牌桶中的令牌数量不足,则返回 false 表示未通过限流。 下面是使用 RedisLua 脚本实现令牌桶算法的示例代码: ```lua -- 限流的 key local key = KEYS[1] -- 令牌桶的容量 local capacity = tonumber(ARGV[1]) -- 令牌的发放速率 local rate = tonumber(ARGV[2]) -- 请求需要的令牌数量 local tokens = tonumber(ARGV[3]) -- 当前时间戳 local now = redis.call('TIME')[1] -- 获取当前令牌桶中的令牌数量和时间戳 local bucket = redis.call('ZREVRANGEBYSCORE', key, now, 0, 'WITHSCORES', 'LIMIT', 0, 1) -- 如果令牌桶为空,则初始化令牌桶 if not bucket[1] then redis.call('ZADD', key, now, capacity - tokens) return 1 end -- 计算当前令牌桶中的令牌数量和时间戳 local last = tonumber(bucket[2]) local tokensInBucket = tonumber(bucket[1]) -- 计算时间间隔和新的令牌数量 local timePassed = now - last local newTokens = math.floor(timePassed * rate) -- 更新令牌桶 if newTokens > 0 then tokensInBucket = math.min(tokensInBucket + newTokens, capacity) redis.call('ZADD', key, now, tokensInBucket) end -- 检查令牌数量是否足够 if tokensInBucket >= tokens then redis.call('ZREM', key, bucket[1]) return 1 else return 0 end ``` 2. 使用 RedisLua 脚本实现基于漏桶算法的限流 漏桶算法是另一种常见的限流算法,它可以通过漏桶的容量和漏水速度来控制流量。在 Redis 中,我们可以使用 Lua 脚本实现漏桶算法。 具体实现步骤如下: - 在 Redis 中创建一个键值对,用于存储漏桶的容量和最后一次请求的时间戳。 - 每当一个请求到达时,我们首先获取当前漏桶的容量和最后一次请求的时间戳。 - 计算漏水速度和漏水的数量,将漏桶中的容量减去漏水的数量。 - 如果漏桶中的容量大于等于请求需要的容量,则返回 true 表示通过限流,将漏桶中的容量减去请求需要的容量。 - 如果漏桶中的容量不足,则返回 false 表示未通过限流。 下面是使用 RedisLua 脚本实现漏桶算法的示例代码: ```lua -- 限流的 key local key = KEYS[1] -- 漏桶的容量 local capacity = tonumber(ARGV[1]) -- 漏水速度 local rate = tonumber(ARGV[2]) -- 请求需要的容量 local size = tonumber(ARGV[3]) -- 当前时间戳 local now = redis.call('TIME')[1] -- 获取漏桶中的容量和最后一次请求的时间戳 local bucket = redis.call('HMGET', key, 'capacity', 'last') -- 如果漏桶为空,则初始化漏桶 if not bucket[1] then redis.call('HMSET', key, 'capacity', capacity, 'last', now) return 1 end -- 计算漏水的数量和漏桶中的容量 local last = tonumber(bucket[2]) local capacityInBucket = tonumber(bucket[1]) local leak = math.floor((now - last) * rate) -- 更新漏桶 capacityInBucket = math.min(capacity, capacityInBucket + leak) redis.call('HSET', key, 'capacity', capacityInBucket) redis.call('HSET', key, 'last', now) -- 检查容量是否足够 if capacityInBucket >= size then return 1 else return 0 end ``` 以上是使用 RedisLua 脚本实现分布式限流的两种方案,可以根据实际需求选择适合的方案。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值