Redis 分布式锁

2022-09-29 12:21:38 浏览数 (1)

场景

一般电商网站都会遇到秒杀、特价之类的活动,大促活动有一个共同特点就是访问量激增,在高并发下会出现成千上万人抢购一个商品的场景。虽然在系统设计时会通过限流、异步、排队等方式优化,但整体的并发还是平时的数倍以上,参加活动的商品一般都是限量库存,如何防止库存超卖,避免并发问题呢?分布式锁就是一个解决方案。

“分布式锁”是用来解决分布式应用中“并发冲突”的一种常用手段,实现方式一般有基于zookeeper及基于redis二种

讨论

1、无锁

先看下下面的代码,实现方式01,没有锁,当5个线程同时访问就乱了。

代码语言:javascript复制
private String impl01() {
    String stockStr = stringRedisTemplate.opsForValue().get("stock");
    if (StringUtils.isEmpty(stockStr)) return "库存为空";
    int stock = Integer.parseInt(stockStr);
    if (stock > 0) {
        int newStock = stock - 1;
        stringRedisTemplate.opsForValue().set("stock", newStock   "");
        System.out.printf("扣减成功 from %s to %s n", stock, newStock);
        return "扣减成功";
    } else {
        System.out.printf("扣减失败 from %s n", stock);
        return "扣减失败";
    }
}

首先想到是加 synchronized。 而 synchronized 锁是属于JVM级别的,也就是在在单个机器上有效。而在微服务架构下,一般都是集群部署,一个服务会部署到不同计算机上。就又有问题了。

2、redis 分布式锁

使用SETNX实现分布式锁,

代码语言:javascript复制
    try{
      // 先获得 锁,如果获得失败,则提示错误码
      Boolean lock_lock = stringRedisTemplate.opsForValue().setIfAbsent("LOCK_LOCK", "1");
      if (!lock_lock) return "获得锁失败,无法继续";

      ....这里是上面的业务代码。略

    }finally{
      // 完事后释放 锁
      stringRedisTemplate.delete("LOCK_LOCK");
    }

不过它也是有缺陷的: 超时 设置的10s时间 时,导致未完成任务,就把锁释放了,就乱套了。准确的10s真的合适吗, 如果在真正高并发的场景下,可能锁就会面临“一直失效”或“永久失效”。 1.存在请求释放锁时释放掉的并不是自己的锁 2.超时时间过短,存在代码未执行完便自动释放

3、Redisson实现分布式锁

Redisson 是一个连接 Redis 的客户端包。充分的利用了 Redis 键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。比如 锁 等。

引入依赖

代码语言:javascript复制
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.6.5</version>
</dependency>

代码

代码语言:javascript复制
private synchronized String impl06() {
       RLock rLock = redisson.getLock("LOCK_LOCK_2");
       try {
           rLock.lock();

           //----------------------
           String res = "";
           String stockStr = stringRedisTemplate.opsForValue().get("stock");
           if (StringUtils.isEmpty(stockStr)) return "库存为空";
           int stock = Integer.parseInt(stockStr);
           if (stock > 0) {
               int newStock = stock - 1;
               stringRedisTemplate.opsForValue().set("stock", newStock   "");
               res = String.format("扣减成功 from %s to %s n", stock, newStock);
               System.out.println(res);
           } else {
               res = String.format("扣减失败 from %s n", stock);
               System.out.println(res);
           }
           return res;
       } finally {
           // 完事后释放 锁
           rLock.unlock();
       }
   }

@Bean
RedissonClient Redisson() {
    Config config = new Config();
    config.useSingleServer()
            .setAddress("redis://192.168.1.111:6379")
            .setDatabase(1);
    return Redisson.create(config);
}

在思考解决方案时我们首先想到CAP原则(一致性、可用性、分区容错性),

  • 那么现在的Redis就是满足AP(可用性、分区容错性),
  • 如果想要解决该问题我们就需要寻找满足CP(一致性、分区容错性)的分布式系统。比如 zookeeper

并发量没有那么高,可以用zookeeper来做分布式锁,但是它的并发能力远远不如Redis。如果你对并发要求比较高的话,那就用Redis

0 人点赞