我的编程空间,编程开发者的网络收藏夹
学习永远不晚

如何使用Redis+Lua脚本实现计数器接口防刷功能

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

如何使用Redis+Lua脚本实现计数器接口防刷功能

这篇文章主要介绍如何使用Redis+Lua脚本实现计数器接口防刷功能,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!

    【实现过程】

    一、问题分析

     如果set命令设置上,但是在设置失效时间时由于网络抖动等原因导致没有设置成功,这时就会出现死计数器(类似死锁);

    二、解决方案

     Redis+Lua是一个很好的解决方案,使用脚本使得set命令和expire命令一同达到Redis被执行且不会被干扰,在很大程度上保证了原子操作;

    为什么说是很大程度上保证原子操作而不是完全保证?因为在Redis内部执行的时候出问题也有可能出现问题不过概率非常小;即使针对小概率事件也有相应的解决方案,比如解决死锁一个思路值得参考:防止死锁会将锁的值存成一个时间戳,即使发生没有将失效时间设置上在判断是否上锁时可以加上看看其中值距现在是否超过一个设定的时间,如果超过则将其删除重新设置锁。       

    三、代码改造

    Redis+Lua锁的实现

    package han.zhang.utils; import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.data.redis.core.script.DigestUtils;import org.springframework.data.redis.core.script.RedisScript;import java.util.Collections;import java.util.UUID;public class RedisLock {    private static final LogUtils logger = LogUtils.getLogger(RedisLock.class);    private final StringRedisTemplate stringRedisTemplate;    private final String lockKey;    private final String lockValue;    private boolean locked = false;        private static final RedisScript<Boolean> SETNX_AND_EXPIRE_SCRIPT;    static {        StringBuilder sb = new StringBuilder();        sb.append("if (redis.call('setnx', KEYS[1], ARGV[1]) == 1) then\n");        sb.append("\tredis.call('expire', KEYS[1], tonumber(ARGV[2]))\n");        sb.append("\treturn true\n");        sb.append("else\n");        sb.append("\treturn false\n");        sb.append("end");        SETNX_AND_EXPIRE_SCRIPT = new RedisScriptImpl<>(sb.toString(), Boolean.class);    }    private static final RedisScript<Boolean> DEL_IF_GET_EQUALS;        sb.append("if (redis.call('get', KEYS[1]) == ARGV[1]) then\n");        sb.append("\tredis.call('del', KEYS[1])\n");        DEL_IF_GET_EQUALS = new RedisScriptImpl<>(sb.toString(), Boolean.class);    public RedisLock(StringRedisTemplate stringRedisTemplate, String lockKey) {        this.stringRedisTemplate = stringRedisTemplate;        this.lockKey = lockKey;        this.lockValue = UUID.randomUUID().toString() + "." + System.currentTimeMillis();    private boolean doTryLock(int lockSeconds) {        if (locked) {            throw new IllegalStateException("already locked!");        }        locked = stringRedisTemplate.execute(SETNX_AND_EXPIRE_SCRIPT, Collections.singletonList(lockKey), lockValue,                String.valueOf(lockSeconds));        return locked;     * 尝试获得锁,成功返回true,如果失败立即返回false     *     * @param lockSeconds 加锁的时间(秒),超过这个时间后锁会自动释放    public boolean tryLock(int lockSeconds) {        try {            return doTryLock(lockSeconds);        } catch (Exception e) {            logger.error("tryLock Error", e);            return false;     * 轮询的方式去获得锁,成功返回true,超过轮询次数或异常返回false     * @param lockSeconds       加锁的时间(秒),超过这个时间后锁会自动释放     * @param tryIntervalMillis 轮询的时间间隔(毫秒)     * @param maxTryCount       最大的轮询次数    public boolean tryLock(final int lockSeconds, final long tryIntervalMillis, final int maxTryCount) {        int tryCount = 0;        while (true) {            if (++tryCount >= maxTryCount) {                // 获取锁超时                return false;            }            try {                if (doTryLock(lockSeconds)) {                    return true;                }            } catch (Exception e) {                logger.error("tryLock Error", e);                Thread.sleep(tryIntervalMillis);            } catch (InterruptedException e) {                logger.error("tryLock interrupted", e);     * 解锁操作    public void unlock() {        if (!locked) {            throw new IllegalStateException("not locked yet!");        locked = false;        // 忽略结果        stringRedisTemplate.execute(DEL_IF_GET_EQUALS, Collections.singletonList(lockKey), lockValue);    private static class RedisScriptImpl<T> implements RedisScript<T> {        private final String script;        private final String sha1;        private final Class<T> resultType;        public RedisScriptImpl(String script, Class<T> resultType) {            this.script = script;            this.sha1 = DigestUtils.sha1DigestAsHex(script);            this.resultType = resultType;        @Override        public String getSha1() {            return sha1;        public Class<T> getResultType() {            return resultType;        public String getScriptAsString() {            return script;}

    借鉴锁实现Redis+Lua计数器

    (1)工具类            

    package han.zhang.utils; import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.data.redis.core.script.DigestUtils;import org.springframework.data.redis.core.script.RedisScript;import java.util.Collections;public class CountUtil {    private static final LogUtils logger = LogUtils.getLogger(CountUtil.class);    private final StringRedisTemplate stringRedisTemplate;        private static final RedisScript<Boolean> SET_AND_EXPIRE_SCRIPT;    static {        StringBuilder sb = new StringBuilder();        sb.append("local visitTimes = redis.call('incr', KEYS[1])\n");        sb.append("if (visitTimes == 1) then\n");        sb.append("\tredis.call('expire', KEYS[1], tonumber(ARGV[1]))\n");        sb.append("\treturn false\n");        sb.append("elseif(visitTimes > tonumber(ARGV[2])) then\n");        sb.append("\treturn true\n");        sb.append("else\n");        sb.append("end");        SET_AND_EXPIRE_SCRIPT = new RedisScriptImpl<>(sb.toString(), Boolean.class);    }    public CountUtil(StringRedisTemplate stringRedisTemplate) {        this.stringRedisTemplate = stringRedisTemplate;    public boolean isOverMaxVisitTimes(String key, int seconds, int maxTimes) throws Exception {        try {            return stringRedisTemplate.execute(SET_AND_EXPIRE_SCRIPT, Collections.singletonList(key), String.valueOf(seconds), String.valueOf(maxTimes));        } catch (Exception e) {            logger.error("RedisBusiness>>>isOverMaxVisitTimes; get visit times Exception; key:" + key + "result:" + e.getMessage());            throw new Exception("already Over MaxVisitTimes");        }    private static class RedisScriptImpl<T> implements RedisScript<T> {        private final String script;        private final String sha1;        private final Class<T> resultType;        public RedisScriptImpl(String script, Class<T> resultType) {            this.script = script;            this.sha1 = DigestUtils.sha1DigestAsHex(script);            this.resultType = resultType;        @Override        public String getSha1() {            return sha1;        public Class<T> getResultType() {            return resultType;        public String getScriptAsString() {            return script;}

    (2)调用测试代码

     public void run(String... strings) {        CountUtil countUtil = new CountUtil(SpringUtils.getStringRedisTemplate());        try {            for (int i = 0; i < 10; i++) {                boolean overMax = countUtil.isOverMaxVisitTimes("zhanghantest", 600, 2);                if (overMax) {                    System.out.println("超过i:" + i + ":" + overMax);                } else {                    System.out.println("没超过i:" + i + ":" + overMax);                }            }        } catch (Exception e) {            logger.error("Exception {}", e.getMessage());        }    }

    (3)测试结果

    如何使用Redis+Lua脚本实现计数器接口防刷功能

    以上是“如何使用Redis+Lua脚本实现计数器接口防刷功能”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注编程网行业资讯频道!

    免责声明:

    ① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

    ② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

    如何使用Redis+Lua脚本实现计数器接口防刷功能

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档

    猜你喜欢

    如何使用Redis+Lua脚本实现计数器接口防刷功能

    这篇文章主要介绍如何使用Redis+Lua脚本实现计数器接口防刷功能,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!【实现过程】一、问题分析 如果set命令设置上,但是在设置失效时间时由于网络抖动等原因导致没有设置成功
    2023-06-29

    如何利用Redis和Go语言实现分布式计数器功能

    如何利用Redis和Go语言实现分布式计数器功能介绍:在分布式系统中,计数器是一种常见的功能需求。分布式计数器可以用于统计网站的访问量、消息队列的消费次数等场景下。Redis是一种高性能的内存数据库,而Go语言是一种轻量级的编程语言,结合这
    2023-10-22

    编程热搜

    • Python 学习之路 - Python
      一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
      Python 学习之路 - Python
    • chatgpt的中文全称是什么
      chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
      chatgpt的中文全称是什么
    • C/C++中extern函数使用详解
    • C/C++可变参数的使用
      可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
      C/C++可变参数的使用
    • css样式文件该放在哪里
    • php中数组下标必须是连续的吗
    • Python 3 教程
      Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
      Python 3 教程
    • Python pip包管理
      一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
      Python pip包管理
    • ubuntu如何重新编译内核
    • 改善Java代码之慎用java动态编译

    目录