基于Redis实现高并发计数器(lua脚本版)

1、业务需求背景

一个手机号一天限制发送5条短信、一个接口一分钟限制多少请求、一个接口一天限制调用多少次等等。

2、代码实现

2.1、RedisConfig.java

package com.demo.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        // key采用String的序列化方式
        template.setKeySerializer(new StringRedisSerializer());
        // value序列化方式采用jackson
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }

}

2.2、RedisController.java

package com.demo.limit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;

@RestController
@RequestMapping("/redis")
public class RedisController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @RequestMapping(value = "/redisIncr/{key}/{maxCount}/{expire}")
    public Long redisIncr(@PathVariable("key") String key, @PathVariable("maxCount") Integer maxCount, @PathVariable("expire") Integer expire) {
        Long result = null;
        try {
            //调用lua脚本并执行
            DefaultRedisScript<Long> limitRedisScript = new DefaultRedisScript<>();
            limitRedisScript.setResultType(Long.class);//返回类型是Long
            //redis_incr.lua文件存放在resources目录下的redis文件夹内
            limitRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/redis_incr.lua")));
            result = redisTemplate.execute(limitRedisScript, Arrays.asList(key), maxCount, expire);
            System.out.println("==" + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

}

2.3、redis_incr.lua

-- 自增1
local times = redis.call("incr", KEYS[1])
-- 数值为1的时候设置KEY的超时时间
if times == 1 then
    redis.call('expire',KEYS[1],ARGV[2])
end
-- 判断是否超过设置的最大次数,如果是返回1
if times > tonumber(ARGV[1]) then
    return 1
end
-- 默认返回0表示没有超次数
return 0

在这里插入图片描述

3、测试效果

浏览器中连续敲以下链接6次,查看窗口和控制台打印的值,以下只截取部分过程图片
/key123/5/3000 表示 key123的计数器,超时时间为3000秒,计数超过5则返回1

http://127.0.0.1:8080/redis/redisIncr/key123/5/3000

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

4、总结

  • 如果先incr命令自增次数,在设置expire失效时间时由于网络等原因没有将命令提交成功,就产生了一个永不过期的计数器KEY;
  • 使用lua脚本使得set命令和expire命令一同到达Redis被执行且不会被干扰,在很大程度上保证了原子操作;
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值