文章目录
  1. 1. 常见的锁分类类型
    1. 1.1. 并发锁
      1. 1.1.1. 乐观锁
      2. 1.1.2. 悲观锁
    2. 1.2. 分布式锁
      1. 1.2.1. 数据库悲观锁实现方式
      2. 1.2.2. Redis SETNX DELE 指令实现方式
      3. 1.2.3. Redis 集群模式下的解决方案
        1. 1.2.3.1. Java 语言实现 redisson
        2. 1.2.3.2. Python 语言同步实现 redlock-py
        3. 1.2.3.3. Python 语言异步实现 aioredlock
      4. 1.2.4. Zookeeper实现方式
    3. 1.3. 无锁并发
      1. 1.3.1. CAS
  2. 2. 各种锁的使用场景
    1. 2.1. 分布式锁使用场景
  3. 3. 参考资料

常见的锁分类类型

并发锁

乐观锁

乐观锁是指实现方在并发产生的竞争条件还是乐观的,只是在发现数据被改变的时候才放弃修改。
常见的实现模式是结合数据库进行。
具体的实现方式是:
前提条件:

  1. 在需要做乐观锁并发控制的表定义上添加修改版本号version字段的定义
  2. DB的事务隔离级别为RR
  • 在修改临界区资源之前先查询该资源(临界区资源数值、version 字段数值)
  • 根据之前查询到的version并做更新条件进行数值更新操作,同时将version版本加1

悲观锁

悲观锁是指实现方在并发产生的竞争条件悲观的,所以在数据更新之前是需要锁住临界区资源防止被其他资源修改。
常见的实现也是结合关系型数据库进行。
具体实现方式是:
前提条件:

  1. 查询操作(主要是考虑幂等性)和更新操作需要在同一事务中

具体到Java版的实现细节就是:
下面以一个购物下单的功能场景为例:

1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE `order`  (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_no` int(11) DEFAULT NULL,
`pay_money` decimal(10, 2) DEFAULT NULL,
`status` int(4) DEFAULT NULL,
`create_date` datetime(0) DEFAULT NULL,
`delete_flag` int(4) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_status`(`status`) USING BTREE,
INDEX `idx_order`(`order_no`) USING BTREE
) ENGINE = InnoDB

这里的表需要定义为InnoDB存储引擎,用来支持事务。

查询订单操作的SQL写为:

1
select id from `order` where `order_no`= 'xxxx' for update

这里的 SELECT FOR UPDATE 主要是基于gap lock机制的悲观锁实现,所以会存在阻塞的潜在问题。

在创建订单的service 层代码:

1
2
3
4
5
6
7
8
9
10
@Transactional
public int addOrderRecord(Order order) {
if (orderDao.selectOrderRecord(order) == null) {
int result = orderDao.addOrderRecord(order);
if (result > 0) {
return 1;
}
}
return 0;
}

分布式锁

数据库悲观锁实现方式

同上

Redis SETNX DELE 指令实现方式

Redis 中可以使用 SETDELE 指令来实现。

  • Python 版本实现

  • Java 版本实现

    • 获取锁

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      private static final String LOCK_SUCCESS = "OK";
      private static final String SET_IF_NOT_EXIST = "NX";
      private static final String SET_WITH_EXPIRE_TIME = "PX";

      /**
      * Acquire distributed lock
      *
      * @param jedis Redis client
      * @param lockKey lock key
      * @param requestId unique request ID
      * @param expireTime lock expire time
      * @return acquire success or no
      */
      public static boolean acquireDistributedLock(
      Jedis jedis, String lockKey, String requestId, int expireTime) {

      String result =
      jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
      return LOCK_SUCCESS.equals(result);
      }
    • 释放锁

      1
      2
      3
      4
      5
      6
      7
      8
      9
      private static final String SCRIPT_UNLOCK =
      "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

      public boolean releaseLock(Jedis jedis, String lockKey, String requestId) {
      List<String> keys = Collections.singletonList(lockKey);
      List<String> args = Collections.singletonList(requestId);
      Object evalResult = jedis.eval(SCRIPT_UNLOCK, keys, args);
      return Integer.valueOf(1).equals(evalResult);
      }

Redis 集群模式下的解决方案

在集群下的实现,目前官方提供了多个基于 redlock 算法的语言实现版本,具体可以查看官网 Distributed locks with Redis - Implementations

更多的 redlock 算法细节可以参考官方 RedisLab 的这篇文章 Redis Best Practices - Communication Patterns - Redlock

Java 语言实现 redisson

引入依赖:

1
2
3
4
5
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.8.2</version>
</dependency>

创建配置 redisson - cluster mode configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config
.useClusterServers()
.setScanInterval(2000)// cluster state scan interval in milliseconds
// use "rediss://" for SSL connection
.addNodeAddress("redis://127.0.0.1:6000")
.setPassword("000000")
.addNodeAddress("redis://127.0.0.1:6001")
.setPassword("000000")
.addNodeAddress("redis://127.0.0.1:6002")
.setPassword("000000");
return Redisson.create(config);
}

使用 redisson - distributed locks and synchronizers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void redissonUsage() {
long waitTimeout = 10;
long leaseTime = 1;
RLock lock1 = redissonClient1.getLock("lock1");
RLock lock2 = redissonClient2.getLock("lock2");
RLock lock3 = redissonClient3.getLock("lock3");

RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
try {
redLock.tryLock(waitTimeout, leaseTime, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
redLock.unlock();
}
}

当然 Redisson 还提供 异步支持 和 Reactive、RxJava 响应式编程范式支持,从这个层面上来看 Redisson 是 Java 生态中可优先考虑的 Redis 客户端。
以上的例子主要是考虑与 Spring-Data-Redis 的整合,如果考虑与 Spring-boot 进行整合,可以参考 redisson redisson-spring-boot-starter
更多框架整合的信息可参考 Redisson - Integration with frameworks

Python 语言同步实现 redlock-py
Python 语言异步实现 aioredlock

Zookeeper实现方式

// TODO

无锁并发

CAS

Compare And Set (CAS) 是一种无锁并发处理的机制。

// TODO

各种锁的使用场景

分布式锁使用场景

分布式锁一般会在下面的两个场景下使用。

  • 高并发场景下保证共享资源操作的原子性
  • 避免分布式环境下不同节点执行重复任务

第一个场景不难理解,最常见的就是电商平台的库存扣减。
第二个场景,举个例子,例如在分布式的环境下可能有多个短信发送服务实例,如果发送的逻辑不做协调和控制就可能出现多个短信服务向同一个用户发送的情况。

参考资料

文章目录
  1. 1. 常见的锁分类类型
    1. 1.1. 并发锁
      1. 1.1.1. 乐观锁
      2. 1.1.2. 悲观锁
    2. 1.2. 分布式锁
      1. 1.2.1. 数据库悲观锁实现方式
      2. 1.2.2. Redis SETNX DELE 指令实现方式
      3. 1.2.3. Redis 集群模式下的解决方案
        1. 1.2.3.1. Java 语言实现 redisson
        2. 1.2.3.2. Python 语言同步实现 redlock-py
        3. 1.2.3.3. Python 语言异步实现 aioredlock
      4. 1.2.4. Zookeeper实现方式
    3. 1.3. 无锁并发
      1. 1.3.1. CAS
  2. 2. 各种锁的使用场景
    1. 2.1. 分布式锁使用场景
  3. 3. 参考资料