Redis分布式锁实现

1.Controller中创建Redis锁

java 复制代码
//1 获取锁,setnx
        //得到一个 uuid 值,作为锁的值
        String uuid = UUID.randomUUID().toString();


        Boolean lock =
                redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
        //2 获取锁成功
        if (lock) {

            //准备删除锁脚本
            //String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            //DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            //redisScript.setScriptText(script);
            //redisScript.setResultType(Long.class);

            //写自己的业务-就可以有多个操作了
            Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);
            if (decrement < 0) {//说明这个商品已经没有库存
                //说明当前秒杀的商品,已经没有库存
                entryStockMap.put(goodsId, true);
                //恢复库存为0
                redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);
                //释放锁.-lua为什么使用redis+lua脚本释放锁前面讲过
                redisTemplate.execute(script, Arrays.asList("lock"), uuid);
                model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
                return "secKillFail";//错误页面
            }

            //释放分布式锁
            redisTemplate.execute(script, Arrays.asList("lock"), uuid);

        } else {
            //3 获取锁失败,返回个信息[本次抢购失败,请再次抢购...]
            model.addAttribute("errmsg", RespBeanEnum.SEC_KILL_RETRY.getMessage());
            return "secKillFail";//错误页面
        }

2.在application.yml同目录下创建lua脚本

java 复制代码
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else return 0
end

3.增加配置执行脚本

java 复制代码
package com.hspedu.seckill.config;

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

//把session信息提取出来存到redis中
//主要实现序列化, 这里是以常规操作
@Configuration
public class RedisConfig {

    //自定义 RedisTemplate对象, 注入到容器
    //后面我们操作Redis时,就使用自定义的 RedisTemplate对象
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        //设置相应key的序列化
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //value序列化
        //redis默认是jdk的序列化是二进制,这里使用的是通用的json数据,不用传具体的序列化的对象
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        //设置相应的hash序列化
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        //注入连接工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        System.out.println("测试--> redisTemplate" + redisTemplate.hashCode());
        return redisTemplate;
    }

    //增加执行脚本
    @Bean
    public DefaultRedisScript<Long> script() {

        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        //设置要执行的lua脚本位置, 把lock.lua文件放在resources
        redisScript.setLocation(new ClassPathResource("lock.lua"));
        redisScript.setResultType(Long.class);
        return redisScript;
    }
}

4.Controller装配 RedisScript

@Resource private RedisScript script;
使用lua脚本的目的,是为了读取到当前程序中的锁,和redis中的锁进行对比。

避免A用户因为锁生效时间超时以后自动删除了A当前用户拿到的锁,进而在操作完成时,主动去删除锁时此时有个B用户正好生成了一把锁,那么A用户删除的是B用户的锁,造成数据不一致。

当前程序的锁就是A用户的锁,同时redis存放的也是A用户的锁,就不会造成锁的误删,从而保证了原子性。

相关推荐
夕除38 分钟前
Redis 过期策略
redis·笔记·面试
小此方40 分钟前
Re:Mysql数据库基础篇(八):详细讲解复合查询、多表查询、自连接、多行、多列子查询与合并查询
数据库·mysql
xszivvvv42 分钟前
Mariadb数据库frm文件异常,如何恢复数据
数据库·mariadb
smile_life_1 小时前
MySQL 设置 防止账户被暴力破解
数据库·mysql
ShineWinsu2 小时前
对于Qt:QRadioButton常见属性以及QButtonGroup的解析
数据库·c++·qt·microsoft·gui·qradiobutton·qbuttongroup
测试盐2 小时前
oracle数据库卡顿排查
数据库·oracle
滕州市燕猫虎计算机科技工作室个体工商户2 小时前
MySQL、MVCC、多版本并发控制
数据库·mysql·mvcc·数据库事务
明航咨询-陈老师2 小时前
ITSS运行维护服务能力成熟度认证技术纵深:标准模型、等级路径、2026审核趋势与CS/CMMI对比
服务器·数据库·cmmi
CodeGL3 小时前
开源的分布式任务调度平台:XXL-Job
分布式·开源
缓慢更新3 小时前
AI 辅助生成单测:从接口定义到用例的流水线
java·服务器·前端·网络·数据库·人工智能