• Redis分布式锁


    1.配置

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

    2 .  1,0版存在的问题→  final块的判断del删除操作不是原子性的

    @RestController
    public class GoodController {
        private static final String REDIS_LOCK = "redisLock";
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @RequestMapping("/buy")
        public String buy() {
            String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
            try {
                Boolean flag = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value,10L, TimeUnit.SECONDS);
    
                if (!flag) {
                    return "抢锁失败";
                }
                String result = (String) redisTemplate.opsForValue().get("goods:001");
                int goodsNumber = result == null ? 0 : Integer.parseInt(result);
                if (goodsNumber > 0) {
                    int realNumber = goodsNumber - 1;
                    redisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
                    System.out.println("成功买到商品,库存还剩下" + realNumber);
                } else {
                    System.out.println("商品已卖完");
                }
                return "商品已卖完,活动结束";
    
            } finally {
                //判断加锁与解锁是不是同一个客户端
                if(redisTemplate.opsForValue().get(REDIS_LOCK).equals(value)) {
                    //若在此时,这把锁突然不是这个客户端的,则会误解锁 ,因为这把不是原子操作
                    redisTemplate.delete(REDIS_LOCK);
                }
    
            }
        }
    }

    3 .  2,0版使用Redis自身事务解决→  final块的判断del删除操作不是原子性的

    @RestController
    public class GoodController {
        private static final String REDIS_LOCK = "redisLock";
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @RequestMapping("/buy")
        public String buy() {
            String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
            try {
                Boolean flag = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value,10L, TimeUnit.SECONDS);
    
                if (!flag) {
                    return "抢锁失败";
                }
                String result = (String) redisTemplate.opsForValue().get("goods:001");
                int goodsNumber = result == null ? 0 : Integer.parseInt(result);
                if (goodsNumber > 0) {
                    int realNumber = goodsNumber - 1;
                    redisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
                    System.out.println("成功买到商品,库存还剩下" + realNumber);
                } else {
                    System.out.println("商品已卖完");
                }
                return "商品已卖完,活动结束";
    
            } finally {
                while (true) {
                    redisTemplate.watch(REDIS_LOCK);
                    if(redisTemplate.opsForValue().get(REDIS_LOCK).equals(value)) {
                        redisTemplate.setEnableTransactionSupport(true);
                        redisTemplate.multi();
                        redisTemplate.delete(REDIS_LOCK);
                        List list = redisTemplate.exec();
                        if(list == null) {
                            continue;
                        }
                    }
                    redisTemplate.unwatch();
                    break;
                }
            }
        }
    }

    4.使用lua脚本来解决→  final块的判断del删除操作不是原子性的

    @RestController
    public class GoodController {
        private static final String REDIS_LOCK = "redisLock";
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @RequestMapping("/buy")
        public String buy() throws Exception {
            String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
            try {
                Boolean flag = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value,10L, TimeUnit.SECONDS);
    
                if (!flag) {
                    return "抢锁失败";
                }
                String result = (String) redisTemplate.opsForValue().get("goods:001");
                int goodsNumber = result == null ? 0 : Integer.parseInt(result);
                if (goodsNumber > 0) {
                    int realNumber = goodsNumber - 1;
                    redisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
                    System.out.println("成功买到商品,库存还剩下" + realNumber);
                } else {
                    System.out.println("商品已卖完");
                }
                return "商品已卖完,活动结束";
    
            } 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 o = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(value));
                    if("1".equals(o.toString())) {
                        System.out.println("------del redis lock ok");
                    } else {
                        System.out.println("-------del redis lock error");
                    }
                } finally {
                    if(null != jedis) {
                        jedis.close();
                    }
                }
            }
        }
    }

    5.分布式锁如何续期? 确保redisLock过期时间大于业务的执行时间的问题.

    1.Redis (AP) ,redis异步复制造成的锁丢失,比如:主节点没来得及把刚刚set进来的这条数据给从节点,就挂了,,它是先返回结果再同步给从节点.跟zookeeper不一样.

    2.Zookeeper(CP) ,主节点先同步完从节点再返回结果

    综合上述,redis集群环境下,我们自己写的也不ok,直觉上RedLock之Redisson落地实现,(最终版)

    @RestController
    public class GoodController {
        private static final String REDIS_LOCK = "redisLock";
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Autowired
        private Redisson redisson;
    
        @RequestMapping("/buy")
        public String buy() throws Exception {
            String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
            RLock redissonLock = redisson.getLock(REDIS_LOCK);
            redissonLock.lock();
            try {
                String result = (String) redisTemplate.opsForValue().get("goods:001");
                int goodsNumber = result == null ? 0 : Integer.parseInt(result);
                if (goodsNumber > 0) {
                    int realNumber = goodsNumber - 1;
                    redisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
                    System.out.println("成功买到商品,库存还剩下" + realNumber);
                } else {
                    System.out.println("商品已卖完");
                }
                return "商品已卖完,活动结束";
    
            } finally {
                if (redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()) {
                    redissonLock.unlock();
                }
            }
        }
    }
  • 相关阅读:
    结构体
    Springmvc中异常处理
    SpringMVC的Controller方法返回值的3种类型
    SpringMVC的参数绑定
    @RequestParam用法与@PathVariable用法的区别
    springMVC架构(三大组件)
    springMVC入门程序开发步骤
    @RequestMapping的三个用法
    web.xml标签
    小笔记2(Servlet)
  • 原文地址:https://www.cnblogs.com/liuyi13535496566/p/16393383.html
Copyright © 2020-2023  润新知