首页 > 数据库 >使用Redis的ZSet集合实现接口限流

使用Redis的ZSet集合实现接口限流

时间:2024-02-18 22:33:07浏览次数:31  
标签:String ZSet -- Redis 接口 window 限流 public

背景

一般在设计后端接口的时候,一般都会预估接口能承受的最大流量是多少。那么如果瞬时流量超过了接口的承载力,我们就需要考虑接口做限流处理了。

限流实际上是指限制系统的输入流量和输出流量已保持系统的稳定性,防止极端条件下系统因为突然的请求激增而造成的崩溃。

思考

我们知道,Spring Cloud Alibaba有一个叫做Ribbon的组件,可以实现限流。在Ribbon中,我们可以通过设置限流策略来限制每个服务的并发访问量,防止服务被过多的请求压垮。一般限流可以通过四个思路去实现,分别是固定窗口算法、滑动窗口算法、漏桶算法、令牌桶算法。使用Redis的ZSet数据接口可以很方便的实现的接口限流。因为redis支持集群化部署,高可用易扩展,天然的支持分布式场景下各种高并发操作。下面给出一个demo实现用Redis的ZSet接口,实现华东窗口的接口限流。

滑动窗口

假如说我们想让某个接口每两秒处理5次的请求,

 而如果2秒以内,第六个请求打到接口中,情况就变成了下面这种

 

代码讲解

demo使用Springboot框架,通过Spring AOP的自定义切面功能,实现在Mapping接口上加一个注解,实现对这个接口的限流。ZSet作为带score的有序集合,可以让接口uri作为ZSet的key(也可以做一个md5加密),value就放唯一的id,score存入当前时间戳(long类型)。代码还使用到了lua脚本,因为对于Redis而言,lua脚本的操作时原子的,这样也就避免了并发场景下的不安全问题。话不多少,先上代码。

Lua脚本:

-- 获取zset的key
local key = KEYS[1]

-- 脚本传入的限流大小
local limit = tonumber(ARGV[1])

-- 脚本传入的限流起始时间戳
local start = tonumber(ARGV[2])

-- 脚本传入的限流当前时间戳
local now = tonumber(ARGV[3])

-- 脚本传入的限流当前时间戳
local uuid = ARGV[4]

-- 获取当前流量总数
local count = tonumber(redis.call('zcount',key, start, now))

--是否超出限流值
if count + 1 >limit then
    return 1
-- 不需要限流
else
    -- 添加当前访问时间戳到zset
    redis.call('zadd', key, now, uuid)
    -- 移除时间区间以外不用的数据,不然会导致zset过大
    redis.call('zremrangeByScore',KEYS[1], 0, ARGV[2])
    return 0
end

 

代码接口截图:

 

 

入口控制器:

@RestController
@RequestMapping("/base")
public class LimitController {

    @RequestMapping(path = "/getUserInfo",method = RequestMethod.GET)
    @RedisLimit(path ="/base/getUserInfo" )
    @ResponseBody
    public String getUserInfo(){
        return "new UserInfo()";
    }
}
View Code

自定义注解:@RedisLimit,其中 limitvalue就是限流的请求次数,window是窗口容量,单位为秒

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RedisLimit {
    //限流请求数
    String limitvalue() default "5";
    //窗口时间
    String window() default "2";

    String path() ;
}

切面逻辑:

实现了所有带RedisLimit注解的接口都作为切入点,在before方法里去执行lua脚本,判断此次请求是否需要被限流,如果需要被限流,则抛出异常DeduplicationException,交由统一异常处理类去处理。(测试代码略显粗糙)

@Aspect
@Component
@Slf4j
public class RedisLimitAspect {

    @Autowired
    private LimitService limitService;

    @Pointcut(value = "@annotation(com.allen.deduplication.RedisLimit)")
    public void pointcut(){
    }

    @Before("pointcut()")
    public void advicBefore(JoinPoint joinPoint) throws Throwable {
        log.info("******开始进入限流判断");
        //目的:获取切入点方法上自定义RequiredLog注解中operation属性值

        //1.1获取目标对象对应的字节码对象
        Class<?> targetCls = joinPoint.getTarget().getClass();


        //1.2获取目标方法对象

        //1.2.1 获取方法签名信息从而获取方法名和参数类型
        Signature signature = joinPoint.getSignature();

        //1.2.1.1将方法签名强转成MethodSignature类型,方便调用
        MethodSignature ms = (MethodSignature) signature;

        //1.2.2通过字节码对象以及方法签名获取目标方法对象
        Method targetMethod = targetCls.getDeclaredMethod(ms.getName(), ms.getParameterTypes());

        com.allen.deduplication.RedisLimit redisLimit = targetMethod.getAnnotation(com.allen.deduplication.RedisLimit.class);
        String path = redisLimit.path();
        Integer limitvalue = Integer.valueOf(redisLimit.limitvalue());
        Integer window = Integer.valueOf(redisLimit.window());
        limitService.limit(path,limitvalue,window);
    }
View Code

 

Redis限流执行类:

RedisLimitServiceImpl.java

@Service
public class RedisLimitServiceImpl implements LimitService {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedisScript<Long> redisluascript;


    @Override
    public boolean limit(String key,Integer limitsize,Integer window) {
        long now = System.currentTimeMillis();
        Long needInterc = redisTemplate.execute(redisluascript, Collections.singletonList(key),String.valueOf(limitsize),String.valueOf(now-1000*window),String.valueOf(now), String.valueOf(IdUtil.getSnowflake().nextIdStr()));

        if(1==needInterc){
            throw new DeduplicationException();
        }
        return 1==needInterc;
    }
}

 

统一异常处理:

@RestControllerAdvice
public class ExceptionController {

    @ExceptionHandler(DeduplicationException.class)
    public String rateLimitExceptionHandler(DeduplicationException e){
        return "Your request has been restricted in flow";
    }

看一下限流的效果:

初次调用,接口正常返回值

 

当两秒点击次数超过5次时,则出现请求被限流的返回值

 这时,Redis里可以看到以下结果

 

标签:String,ZSet,--,Redis,接口,window,限流,public
From: https://www.cnblogs.com/yibao/p/18020073

相关文章

  • 【Azure Webjob + Redis】WebJob一直链接Azure Redis一直报错 Timeout Exception
    问题描述运行在AppService上的Webjob连接AzureRedis出现TimeoutException。 错误截图: 参考AzureRedis对于超时问题的排查建议,在修改MinThread后,问题依旧。流量突增和线程池配置流量激增时,如果 ThreadPool  设置不佳,则可能导致对Redis服务器已发送但尚未在客......
  • redis高频问题--缓存击穿
    缓存击穿互斥锁==分布式锁互斥锁多用于关于钱的业务,保持强一致性性能差一些,因为需要互相等待逻辑过期保证高可用性,注重于用户的体验......
  • redis高频问题
    缓存穿透如果有人知道了你的接口请求地址,用不存在的id或者负数疯狂请求,会导致数据库宕机。所以需要预防布隆过滤器......
  • nginx做白名单和限流
    ​ 在我们生产环境中使用到了地图服务,每个月有免费请求次数,近一个月请求次数突然暴涨,导致直接开启付费模式,一个月上百刀的花销着实难扛,根据实际我们的业务使用情况,远达不到付费标准,故考虑做白名单和限流措施,基于以上情况并遇到春节急需快速处理,所以选择了最简单方便的方式,通过ngin......
  • 分布式缓存应用:Memcache 或 Redis
    为什么要使用分布式缓存高并发环境下,例如典型的淘宝双11秒杀,几分钟内上亿的用户涌入淘宝,这个时候如果访问不加拦截,让大量的读写请求涌向数据库,由于磁盘的处理速度与内存显然不在一个量级,服务器马上就要宕机。缓存可以将经常读取的数据存储在快速的内存中,从而避免了频繁访问慢速......
  • Redis生成无规律不重复的纯数字券码
    需求描述在开发优惠券系统或票务系统的时候,经常要生成纯数字码,券码要求:12位纯数字,无规律,不重复。下面我提供一种思路,利用redis的List数据类型,Lpop+Rpush维护一个1万个码的队列队列数据结构保持1万个券码数量,可以根据项目实际情况自行调整Array([0]=>478439938353[1]=>......
  • Redis使用Lua脚本
    Redis使用Lua脚本Redis使用lua脚本的优点减少网络开销:将原来多次请求的逻辑封装为脚本在服务器上执行,只需1次请求就能完成,减少了网络往返时延;原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入;复用性:客户端发送的脚本会永久保存在Redis中,其他客户端可以复用......
  • 【Redis】【高性能】Redis 批量查询技巧
    1  前言Redis,我们做开发的想必都用过,他是一种缓存,主要用于快速响应结果嘛。比如我们要获取商品的详情,有日销量、月销量、库存数量、评价数量,这些数据都在Redis缓存中,那么我们是要拿四趟?还是一趟呢?当然是一趟最好呀。接下来我们来看看为什么我们要一趟这么做,以及怎么做。2  ......
  • 细扣Redis Cluster原理
    细扣RedisCluster原理RedisCluster是Redis提供的一种分布式数据存储解决方案,它能够将大量数据分布到多个节点上,实现高可用性和水平扩展。以下是RedisCluster的工作原理及其如何保证数据安全、一致性以及避免脏数据问题的详细解释:工作原理数据分片(Slot分配):RedisClu......
  • Redis
    ubuntu安装redis:sudoapt-getupdatesudoapt-getinstallredis-serverredis-server即可启动从打印的信息可以看到端口信息netstat-tulnp也可查看程序的端口号redis-cli连上redissetname"alexli"setage22getnamegetagekeys*查看所有的keysets......