Redis锁使用记录

项目背景:项目上有个接口,日志显示短时间内被多次调用,导致相应错误,故需要限制用户不能在短时间内多次调用接口。可以从前端或后端同时限制:前端做按钮点击后的loading效果,这里主要介绍后端接口如何通过redis锁限制。

什么是redis锁?

我们都知道redis是一个可以存储key-value的数据库,我们往redis里面存一个key,就相当于上了一把锁,当我们能在redis里面找到这个key,就相当于找到这个锁啦!同时,我们还应该知道往redis里设置key的时候,是可以同时设置过期时间,也可以手动删除这个key的。这样我们就有两种解锁的方式。

redis配置?

这里简单说明下redis大概有哪些配置:

配置文件

配置类

工具类

如何使用redis锁?

 前面说到redis锁就是一个key,那么我们可以在工具类里写这两个方法设置key

    /**
     * set nx,上锁
     * @param key 一般设为lock
     *@param value 一般使用uuid
     *@param time 缓存时间,单位为s
     */
    public boolean setNx(String key, String value, int time){
        return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS));
    }

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     * @param timeout 时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit)
    {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

业务代码:通过用户id来设置key值,这样每个用户都有一把锁,上锁成功继续执行业务代码,最后finally里面删除key,解锁。

String lock = "lock" + SecurityUtils.getUserId();
        boolean isLock = redisService.setNx(lock, String.valueOf(SecurityUtils.getUserId()), 10);
        if (isLock) {
            try {
                // 业务代码
            } finally {
                redisService.deleteObject(lock);
            }
        } else {
            log.info("请勿重复访问程序:{}", lock);
        }

注意点(坑):我一开始是这么写的,设置key(上锁),查询key(判断是否上锁),解锁(删除key)

        String lock = "lock" + SecurityUtils.getUserId();
        log.info("获取锁,key:{},value:{}", lock, redisService.getCacheObject(lock));
        try {
            if (Objects.isNull(redisService.getCacheObject(lock))) {
                // 上锁
                redisService.setCacheObject(lock, SecurityUtils.getUserId(), 10L, TimeUnit.SECONDS);
                log.info("设置锁,key:{},value:{}", lock, redisService.getCacheObject(lock));


            } else {
                log.info("程序执行中,请勿重复访问");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            log.info("解锁,key:{},value:{}", lock, redisService.getCacheObject(lock));
            redisService.deleteObject(lock);
        }

实际测试:我用apifox(接口测试工具)使用多个线程调用接口,发现查询key的时候,多个线程会出现同时查不到key的情况。

 这样会出现什么问题呢?通过key是否存在来判断是否上锁,不准确(没卵用),因为3个线程同时进去的时候,都还没有设置这个key,所以他们3个都会执行业务逻辑。那么这是为啥呢?有没有大佬给回答一下?按道理说,用户的点击速度不可能比redis设置key的速度快,但这样的结果显然不能接受。

解决问题:虽然没搞懂为啥,但是找到了解决方案,那就是通过setNx方法的返回值来判断是否上锁。奇怪的是:多个线程同时访问redis,拿到的值的时效性可能不准确,但他们却能准确的知道值是否存在。

工具类方法补充:

    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key)
    {
        return redisTemplate.delete(key);
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值