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();
            }
        }
    }
}

 

posted on 2022-06-20 15:26  从精通到陌生  阅读(63)  评论(0编辑  收藏  举报