redis2.分布式锁

本文介绍了Redis分布式锁的实现,包括加锁、释放锁的操作,强调了加锁的原子性、设置过期时间的重要性,以及处理异常、防止张冠李戴等问题。还讨论了Redis的主从同步可能导致的锁丢失问题,并提出了Redisson作为终极解决方案,确保并发解锁时判断锁归属。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自尚硅谷------阳哥

1.redis的分布式锁

加锁:

stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value);//setnx

释放锁:

stringRedisTemplate.delete(REDIS_LOCK_KEY);//释放锁

2.出异常无法释放锁,则强制完成删锁,防止阻塞

也就是把释放锁加入finally{}代码块中

3.加过期时间,处理服务器突发宕机

stringRedisTemplate.expire(REDIS_LOCK_KEY,10L, TimeUnit.SECONDS);

4.设置key+过期时间分开了,必须要合并成一行具备原子性:

加锁和设置过期时间合并为同一语句保证原子性:

Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value,10L, TimeUnit.SECONDS);

5.★张冠李戴,删了其他线程的锁,解决:

先判断再删除
finally {
                if (value.**equalsIgnoreCase**(stringRedisTemplate.opsForValue().get(REDIS_LOCK_KEY))){
                    stringRedisTemplate.delete(REDIS_LOCK_KEY);//释放锁
}

6.finally块的判断+del删除操作不是原子性的

不用lua脚本,如何实现(实际工作中100%用lua!)???

解决:redis事务版:

在这里插入图片描述

finally {
            while (true)
            {
                stringRedisTemplate.watch(REDIS_LOCK_KEY); //加事务,乐观锁
                if (value.equalsIgnoreCase(stringRedisTemplate.opsForValue().get(REDIS_LOCK_KEY))){
                    stringRedisTemplate.setEnableTransactionSupport(true);
                    stringRedisTemplate.multi();//开始事务
                    stringRedisTemplate.delete(REDIS_LOCK_KEY);
                    List<Object> list = stringRedisTemplate.exec();
                    if (list == null) {  //如果等于null,就是没有删掉,删除失败,再回去while循环那再重新执行删除
                        continue;
                    }
                }
                //如果删除成功,释放监控器,并且breank跳出当前循环
                stringRedisTemplate.unwatch();
                break;
            }
        }

lua脚本版:

Redis可以通过eval命令保证代码执行的原子性

jedis工具配置类:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class RedisUtils {

    private static JedisPool jedisPool;

    static {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(20);
        jedisPoolConfig.setMaxIdle(10);
       
        jedisPool = new JedisPool(jedisPoolConfig,"ip",6379,100000);
    }

    public static Jedis getJedis() throws Exception{
        if (null!=jedisPool){
            return jedisPool.getResource();
        }
        throw new Exception("Jedispool is not ok");
    }
}
lua:
finally {
            Jedis jedis = RedisUtils.getJedis();

            String script = "if redis.call('get', KEYS[1]) == ARGV[1]"+"then "
                    +"return redis.call('del', KEYS[1])"+"else "+ "  return 0 " + "end";
                try{
                    Object result = jedis.eval(script, Collections.singletonList(REDIS_LOCK_KEY), Collections.singletonList(value));
                    if ("1".equals(result.toString())){
                        System.out.println("------del REDIS_LOCK_KEY success");
                    }else {
                        System.out.println("------del REDIS_LOCK_KEY error");
                    }
                    }finally {
                        if (null != jedis){
                            jedis.close();
                        }
                    }
        }

7.解决redisLock过期时间小于业务执行时间的问题:Redis分布式锁如何续期?

8.redis异步复制造成的锁丢失

比如:主节点没来的及把刚刚set进来这条数据给从节点,就挂了,从机上位主节点后没有原主节点的存根。

redi :AP 。
zookeeper:CP,没有redis集群异步丢失的问题,保证主从数据一致性。当时牺牲了高可用性能

★★终极解决方案:redisson+超高并发解锁判断是否为本线程的锁

redis集群环境下,我们自己写的也不OK, 直接上RedLock之Redisson落地实现:

redisson配置类:

	@Configuration
    public class RedisConfig {

    @Value("${spring.redis.host}")
    private String redisHost;

        @Bean
        public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory){
            RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
            redisTemplate.setConnectionFactory(connectionFactory);
            return redisTemplate;
        }

    @Bean
    public Redisson redisson(){
        Config config = new Config();
        config.useSingleServer().setAddress("redis://"+redisHost+":6379").setDatabase(0);
        return (Redisson) Redisson.create(config);
    }
}

★★★真正的方案:

避免出现"试图释放的锁不属于当前线程"的问题
@RestController
public class GoodController {


    public static final String REDIS_LOCK_KEY = "lockhhf";

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Value("${server.port}")
    private String serverPort;

    @Autowired
    private Redisson redisson;

    @GetMapping("/buy_goods")
    public String buy_Goods(){

        String value = UUID.randomUUID().toString()+Thread.currentThread().getName();

        RLock redissonLock = redisson.getLock(REDIS_LOCK_KEY);
    ★★★:  redissonLock.lock();
        try{
            String result = stringRedisTemplate.opsForValue().get("goods:001");
            int goodsNumber = result == null ? 0 : Integer.parseInt(result);

            if (goodsNumber > 0){
                int realNumber = goodsNumber - 1;
                stringRedisTemplate.opsForValue().set("goods:001",realNumber + "");
                System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort);
                return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort;
            }else {
                System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort);
            }
            return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort;

        }finally {
    ★★★:还在持有锁的状态,并且是当前线程持有的锁再解锁
            if (redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()){
       ★★★:     redissonLock.unlock();
            }

        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值