系统设计常见组件 - 并发锁、分布式锁与无锁编程
常见的锁分类类型
并发锁
乐观锁
乐观锁是指实现方在并发产生的竞争条件还是乐观的,只是在发现数据被改变的时候才放弃修改。
常见的实现模式是结合数据库进行。
具体的实现方式是:
前提条件:
- 在需要做乐观锁并发控制的表定义上添加修改版本号
version
字段的定义 - DB的事务隔离级别为RR
- 在修改临界区资源之前先查询该资源(临界区资源数值、version 字段数值)
- 根据之前查询到的version并做更新条件进行数值更新操作,同时将version版本加1
悲观锁
悲观锁是指实现方在并发产生的竞争条件悲观的,所以在数据更新之前是需要锁住临界区资源防止被其他资源修改。
常见的实现也是结合关系型数据库进行。
具体实现方式是:
前提条件:
- 查询操作(主要是考虑幂等性)和更新操作需要在同一事务中
具体到Java版的实现细节就是:
下面以一个购物下单的功能场景为例:
1 | CREATE TABLE `order` ( |
这里的表需要定义为InnoDB存储引擎,用来支持事务。
查询订单操作的SQL写为:
1 | select id from `order` where `order_no`= 'xxxx' for update |
这里的 SELECT FOR UPDATE
主要是基于gap lock机制的悲观锁实现,所以会存在阻塞的潜在问题。
在创建订单的service 层代码:
1 |
|
分布式锁
数据库悲观锁实现方式
同上
Redis SETNX
DELE
指令实现方式
Python 版本实现
获取锁
Python 版本实现释放锁
Python 版本实现
Java 版本实现
获取锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20private 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
9private 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 | <dependency> |
创建配置 redisson - cluster mode configuration:
1 |
|
使用 redisson - distributed locks and synchronizers
1 | private void redissonUsage() { |
当然 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
各种锁的使用场景
分布式锁使用场景
分布式锁一般会在下面的两个场景下使用。
- 高并发场景下保证共享资源操作的原子性
- 避免分布式环境下不同节点执行重复任务
第一个场景不难理解,最常见的就是电商平台的库存扣减。
第二个场景,举个例子,例如在分布式的环境下可能有多个短信发送服务实例,如果发送的逻辑不做协调和控制就可能出现多个短信服务向同一个用户发送的情况。