springboot RedisTemplate+lua接口限流 超过限流次数后返回null问题

lua脚本内容:


local c = redis.call('get',KEYS[1]) or '0' 
if tonumber(c) > tonumber(ARGV[1]) then 
return c end
c = redis.call('incr',KEYS[1])
if tonumber(c) == 1 then 
redis.call('expire',KEYS[1],ARGV[2]) end
return c

接口限流方法:


/**
	 * 接口限流
	 *
	 * @param lockKey 锁key
	 * @param count   限制访问次数
	 * @param period  多少时间内,单位秒
	 */
public static Long limit(String lockKey, int count, int period) {
		// 执行lua脚本
		Object result = staticRedisTemplate.execute(
				new RedisScript<Long>() {
					@Override
					public String getSha1() {
						return SCRIPT_LIMIT_SHA1;
					}

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

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

				}, Collections.singletonList(lockKey), count, period);
		return Long.valueOf(StrUtil.utf8Str(result));
	}
	

自定义限流注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Limit {

	// 资源名称,用于描述接口功能
	String name() default "";

	// 资源 key
	String key() default "";

	// key prefix
	String prefix() default "";

	// 时间的,单位秒
	int period();

	// 限制访问次数
	int count();

	// 限制类型
	LimitType limitType() default LimitType.CUSTOMER;
}

切面:

@Around("pointcut(limit)")
	public Object around(ProceedingJoinPoint joinPoint, Limit limit) throws Throwable {
		HttpServletRequest request = ContextUtil.getRequest();
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		Method signatureMethod = signature.getMethod();
		LimitType limitType = limit.limitType();
		String key = limit.key();
		if (StrUtil.isBlank(key)) {
			if (limitType == LimitType.IP) {
				key = IPUtil.getIp(request);
			} else {
				key = signatureMethod.getName();
			}
		}
		// 拼接key
		String lockKey = StrUtil.join(limit.prefix(), "_", key, "_", request.getRequestURI().replaceAll("/", "_"));
		// redis操作
		Number count = RedisLockUtil.limit(lockKey, limit.count(), limit.period());
		if (null != count && count.intValue() <= limit.count()) {
			log.info("第{}次访问key为 {},描述为 [{}] 的接口", count.intValue(), lockKey, limit.name());
			return joinPoint.proceed();
		} else {
			throw new BizException("访问次数受限制");
		}
	}

接口:

在这里插入图片描述

错误信息:

在这里插入图片描述
在这里插入图片描述
本该是返回6,却变成null了,奇了个怪了

解决方法:

修改lua脚本代码,返回值转换一下类型


local c = redis.call('get',KEYS[1]) or '0' 
if tonumber(c) > tonumber(ARGV[1]) then 
return tonumber(c) end
c = redis.call('incr',KEYS[1])
if tonumber(c) == 1 then 
redis.call('expire',KEYS[1],ARGV[2]) end
return tonumber(c)

重新测试:
在这里插入图片描述
在这里插入图片描述
就此问题解决!!!

### Spring Boot 中实现请求限流的方法及工具 在 Spring Boot 应用程序中,可以通过多种方法和工具来实现请求限流。以下是几种常见的实现方式及其特点: #### 1. 使用 Guava 的 RateLimiter Guava 提供了一个名为 `RateLimiter` 的类,可以用来控制应用程序的调用速率。它支持基于令牌桶算法的限流机制。 ```java package cn.juwatech.limiter; import com.google.common.util.concurrent.RateLimiter; public class GuavaRateLimiterExample { private static final RateLimiter rateLimiter = RateLimiter.create(10); // 每秒最多处理10个请求 public void processRequest() { if (rateLimiter.tryAcquire()) { // 尝试获取一个许可 System.out.println("处理请求"); } else { System.out.println("请求被限流"); } } } ``` 这种方式适用于单机环境下的限流场景[^2]。 --- #### 2. 基于 RedisLua 脚本的分布式限流 对于分布式系统,使用 Redis 结合 Lua 脚本来实现限流是一种常见的方式。这种方法通过原子操作确保多个节点之间的限流一致性。 以下是一个简单的 Lua 脚本示例,用于实现滑动窗口限流: ```lua local key = KEYS[1] local limit = tonumber(ARGV[1]) local interval = tonumber(ARGV[2]) -- 时间间隔(毫秒) local now = redis.call('TIME')[1] * 1000 -- 当前时间戳(毫秒) -- 删除过期记录 redis.call('ZREMRANGEBYSCORE', key, '-inf', tostring(now - interval)) -- 获取当前计数 local count = tonumber(redis.call('ZCOUNT', key, '-inf', '+inf')) if count < limit then redis.call('ZADD', key, now, now) return 1 -- 允许访问 else return 0 -- 阻止访问 end ``` Java 代码调用上述 Lua 脚本: ```java String script = "..." // 上述 Lua 脚本内容 DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class); Long result = redisTemplate.execute( redisScript, Collections.singletonList("limiter_key"), Arrays.asList(maxRequests.toString(), timeWindowMs.toString()) ); if (result != null && result == 1) { System.out.println("允许请求"); } else { System.out.println("拒绝请求"); } ``` 这种方案适合高并发、分布式的场景,并能有效防止热点数据引发的服务雪崩问题[^4]。 --- #### 3. 利用 Resilience4j 实现限流 Resilience4j 是一款轻量级的容错库,提供了丰富的功能模块,其中包括限流器组件。它可以轻松集成到 Spring Boot 项目中。 配置文件示例: ```yaml resilience4j.ratelimiter: instances: myRateLimiter: limitForPeriod: 10 # 单位时间内最大请求数 limitRefreshPeriod: 1s # 时间周期 ``` 代码示例: ```java @RateLimiter(name = "myRateLimiter", fallbackMethod = "fallback") public String handleRequest(String input) { return "成功处理:" + input; } public String fallback(String input, Throwable throwable) { return "请求被限流"; } ``` 此方法具有良好的扩展性和灵活性,特别适合微服务架构中的限流需求[^3]。 --- #### 4. 使用 Spring Cloud Gateway 进行网关层限流 如果希望在 API 网关层面统一管理限流规则,则可以选择 Spring Cloud Gateway。其内置了对限流的支持,可通过配置 YAML 文件快速启用。 配置示例: ```yaml spring: cloud: gateway: routes: - id: example_route uri: http://example.com predicates: - Path=/api/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20 ``` 该方法主要针对外部流量进行管控,减少了下游服务的压力。 --- ### 总结 不同的限流技术各有优劣,在实际开发过程中应根据具体的应用场景选择合适的解决方案。例如,单体应用可优先考虑 **Guava RateLimiter**;而分布式环境下则更适合采用 **Redis 或 Resilience4j** 来保障全局的一致性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值