Skip to content

Distributed_Locks

睿驰 edited this page Aug 6, 2021 · 13 revisions

什么是分布式锁?

分布式锁,是用来控制分布式系统中互斥访问共享资源的一种手段,从而避免并行导致的结果不可控。基本的实现原理和单进程锁是一致的,通过一个共享标识来确定唯一性,对共享标识进行修改时能够保证原子性和和对锁服务调用方的可见性。由于分布式环境需要考虑各种异常因素,为实现一个靠谱的分布式锁服务引入了一定的复杂度。

分布式锁服务一般需要能够保证:

  • 同一时刻只能有一个线程持有锁;
  • 锁能够可重入;
  • 不会发生死锁;
  • 具备阻塞锁特性,且能够及时从阻塞状态被唤醒(可选);
  • 锁服务保证高性能和高可用;

当前使用较多的分布式锁方案主要基于redis、zookeeper 提供的功能特性加以封装来实现的,至于这两种锁方案的优缺点这里不赘述了,可以参考InfoQ上这篇文章如何实现靠谱的分布式锁?

juice分布式锁实现

juice框架提供了基于Redis实现的分布式,参见juice-lock模块,它具备以下特性:

  • 同一时刻只能有一个线程持有锁;
  • 锁能够可重入;
  • 不会发生死锁;
  • 具备阻塞锁特性;
  • 高性能

完整示例代码juice-samples

使用教程

首先,pom.xml添加juice-spring-boot-starter依赖:

<dependency>
    <groupId>io.infinityclub</groupId>
    <artifactId>juice-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

application.yml增加redis配置:

# redis
spring:
  redis:
    database: 0
    host: localhost
    password: admin
    port: 6379
    ssl: false
    lettuce:
      pool:
        max-wait: 5000ms
        maxActive: 20
        maxIdle: 8
        minIdle: 1
    timeout: 3000ms

juice-spring-boot-starter会自动装配juice-lock模块并向Spring容器中注入DistributedLockClient,开发直接使用DistributedLockClient类即可。

1、DistributedLock

代码如下:

import juice.contracts.ResultDTO;
import juice.lock.DistributedLock;
import juice.lock.DistributedLockManager;
import juice.lock.RedLock;
import juice.util.RandomUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁示例
 * @author Ricky Fung
 */
@RestController
@RequestMapping("/api/dist-lock")
public class DistributedLockController {
    private final Logger LOG = LoggerFactory.getLogger(this.getClass());

    private int maxWaitTime = 3;

    @Resource
    private DistributedLockManager distributedLockManager;

    @GetMapping("/lock")
    public ResultDTO lock(@RequestParam(value = "productId", required = false) Integer productId) {
        if (productId == null) {
            return ResultDTO.invalidParam("productId必传");
        }
        String key = String.format("lock:%s", productId);
        DistributedLock lock = distributedLockManager.getLock(key);
        boolean success = lock.tryLock(0, 10, TimeUnit.SECONDS);
        if (!success) {
            LOG.info("商品秒杀-提交请求, productId={} 加锁失败, key={}", productId, key);
            return ResultDTO.invalidParam("加锁失败");
        }
        try {
            //模拟业务处理逻辑
            long time = RandomUtils.nextLong(1, maxWaitTime);
            TimeUnit.SECONDS.sleep(time);

            LOG.info("商品秒杀-提交请求, productId={} 加锁成功", productId);
            return ResultDTO.ok();
        } catch (Exception e) {
            LOG.error("商品秒杀-提交请求异常", e);
        } finally {
            lock.unlock();
        }
        return ResultDTO.systemError();
    }
}

2、MultiLock

某些场景下我们需要同时加N把锁,只有当这N把锁都加锁成功了 才执行某段代码,此时就可以使用MultiLock

代码如下:

    @Resource
    private DistributedLockManager distributedLockManager;

    @GetMapping("/multi-lock")
    public ResultDTO multiLock(@RequestParam(value = "productId", required = false) Integer productId) {
        if (productId == null) {
            return ResultDTO.invalidParam("productId必传");
        }

        DistributedLock lock1 = distributedLockManager.getLock(String.format("multi_lock:%s_%s", productId, 1));
        DistributedLock lock2 = distributedLockManager.getLock(String.format("multi_lock:%s_%s", productId, 2));
        DistributedLock lock3 = distributedLockManager.getLock(String.format("multi_lock:%s_%s", productId, 3));

        DistributedLock multiLock = distributedLockManager.getMultiLock(lock1, lock2, lock3);
        //DistributedLock multiLock = new MultiLock(lock1, lock2, lock3);

        boolean success = multiLock.tryLock(0, 10, TimeUnit.SECONDS);
        if (!success) {
            LOG.info("分布式锁-多锁-提交请求, productId={} 加锁失败", productId);
            return ResultDTO.invalidParam("加锁失败");
        }
        try {
            //模拟业务处理逻辑
            long time = RandomUtils.nextLong(1, maxWaitTime);
            TimeUnit.SECONDS.sleep(time);

            LOG.info("分布式锁-多锁-提交请求, productId={} 加锁成功", productId);
            return ResultDTO.ok();
        } catch (Exception e) {
            LOG.error("分布式锁-多锁-提交请求异常", e);
        } finally {
            multiLock.unlock();
        }
        return ResultDTO.systemError();
    }

3、RedLock

代码如下:

    @Resource
    private DistributedLockManager distributedLockManager;

    @GetMapping("/red-lock")
    public ResultDTO redLock(@RequestParam(value = "productId", required = false) Integer productId) {
        if (productId == null) {
            return ResultDTO.invalidParam("productId必传");
        }

        DistributedLock lock1 = distributedLockManager.getLock(String.format("red_lock:%s_%s", productId, 1));
        DistributedLock lock2 = distributedLockManager.getLock(String.format("red_lock:%s_%s", productId, 2));
        DistributedLock lock3 = distributedLockManager.getLock(String.format("red_lock:%s_%s", productId, 3));

        DistributedLock redLock = new RedLock(lock1, lock2, lock3);
        boolean success = redLock.tryLock(0, 10, TimeUnit.SECONDS);
        if (!success) {
            LOG.info("分布式锁-红锁-提交请求, productId={} 加锁失败", productId);
            return ResultDTO.invalidParam("加锁失败");
        }
        try {
            //模拟业务处理逻辑
            long time = RandomUtils.nextLong(1, maxWaitTime);
            TimeUnit.SECONDS.sleep(time);

            LOG.info("分布式锁-红锁-提交请求, productId={} 加锁成功", productId);
            return ResultDTO.ok();
        } catch (Exception e) {
            LOG.error("分布式锁-红锁-提交请求异常", e);
        } finally {
            redLock.unlock();
        }
        return ResultDTO.systemError();
    }

二、注解方式

第一步:

/**
 * @author Ricky Fung
 */
//激活分布式锁
@EnableDistributedLock
//激活动态数据源
@EnableDynamicDataSource
@SpringBootApplication
public class JuiceApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(JuiceApplication.class, args);
        System.out.println(context);
    }
}

第二步,在需要加锁的地方使用 @DLock 注解,key支持SpEL表达式。

/**
 * 分布式锁示例
 * @author Ricky Fung
 */
@RestController
@RequestMapping("/api/dist-lock")
public class DistributedLockController {
    private final Logger LOG = LoggerFactory.getLogger(this.getClass());

    private int maxWaitTime = 20;

    private int leaseTime = 45;

    @Resource
    private DistributedLockManager distributedLockManager;

    @DLock(prefix = "lock:sec_kill", key = "#productId + '_' + #skuId", leaseTime = 15, timeUnit = TimeUnit.SECONDS)
    @GetMapping("/stock")
    public ResultDTO lockStock(@RequestParam(value = "productId", required = false) Integer productId,
                               @RequestParam(value = "skuId", required = false) Integer skuId) {
        LOG.info("商品秒杀【注解配置】-提交请求开始, productId={}, skuId={}", productId, skuId);
        try {
            //模拟业务处理逻辑
            long time = RandomUtils.nextLong(1, maxWaitTime);
            TimeUnit.SECONDS.sleep(time);

            LOG.info("商品秒杀【注解配置】-提交请求, productId={} 加锁成功", productId);
            return ResultDTO.ok();
        } catch (Exception e) {
            LOG.error("商品秒杀【注解配置】-提交请求异常", e);
        }
        return ResultDTO.systemError();
    }
}

相关资料

Clone this wiki locally