分布式锁
简介
锁:在编程中锁(lock)或互斥(mutual)同步机制,用于在多线程(或进程)中对资源的访问限制。锁主要作用是互斥排他、并发控制策略。
分布式锁的实现: 基于数据库实现,基于缓存(Redis)实现,基于Zookeeper实现。
第三方开源实现:curator(基于Zookeeper实现),redission(基于Redis实现)
数据库分布式锁
利用数据库的行锁实现。
支持行锁的数据库:MySql的InnerDB引擎支持行锁, PostgreSQL支持行锁,Oracle等。
例如有个表:
distribute_lock
说明: 为 resource_code 添加唯一索引。用于加行锁时走索引。
加行锁操作:
- 先关闭事务自动提交。
- 例如给资源"testcode" 加锁:
select id, resource_code from distribute_lock where resource_code = 'testcode' for update;
- 释放锁:
commit
举例说明:
conn 1 执行 :select id, resource_code from distribute_lock where resource_code = 'testcode' for update;
conn 2 执行: select id, resource_code from distribute_lock where resource_code = 'testcode' for update;
conn 1 查询成功(获得锁)返回,此时***conn 2*** 等待
conn 1 执行: commit
释放锁
conn 2 查询成功(获得锁)返回
conn 2 执行:commit
释放锁
时序图如下:
┌─────────────┐ ┌───────────────┐ ┌──────────────┐
│ conn 1 │ │ Database │ │ conn 2 │
└──────┬──────┘ └───────┬───────┘ └────────┬─────┘
│ │ │
│ 1. select ... for update │ │
│ │ │
├────────────────────────────────────►│ 2. select ... for update |
│ │◄───────────────────────────────────────┤
│ │ conn2 wait │
│ return │ │
│◄────────────────────────────────────┤ │
│ │ │
│ commit │ │
├────────────────────────────────────►│ return │
│ │───────────────────────────────────────►│
│ │ │
│ │ commit │
│ │◄───────────────────────────────────────┤
│ │ │
高可用场景:
数据库进行主备架构,使用LVS进行VIP。
客户端发现链接断开,进行回滚操作。
高并发场景:
性能瓶颈在于单数据库,可以进行分库分表。
每个库都对应一个备库,将不同的资源分片到不同的库和表中。
数据库扩展性根据分库分表的策略。比较难。
高性能场景:
利用分库分表。
实践部分: 分布式锁_数据库分布式锁实践
Redis 分布式锁
使用redis的单线程原理,通过set命令根据key不存在则设置成功并返回。设置成功即获得锁。
set resource_name my_random_value NX PX 30000
# SET key value [EX seconds] [PX milliseconds] [NX|XX]
EX: seconds, 过期时间
NX:不存在则设置, 将键的过期时间设置为 seconds 毫秒
XX: 只在键已经存在时, 才对键进行设置操作
PX: milliseconds, 将键的过期时间设置为 milliseconds 秒
my_random_value: 设置随机数,校验正确才能删除锁。
校验正确才删除锁需要用到LUA脚本如下:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
问题:为什么进行KEY的过期设置
当心如果一个客户端获取锁之后,没有释放锁,则导致资源不可用了。
问题: 如果业务耗时,在锁过期时候,被自动释放,如何处理?
开启一个线程,定时去延续锁的过期时间。
问题:如果业务线程和监听线程都因系统CPU资源耗尽,处理慢,锁没有自动续期,如何处理?
根据业务情况,判断锁释放失败进行回滚操作。
实践部分: 分布式锁_Redis分布式锁实践
Zookeeper 分布式锁
使用Zookeeper的顺序临时节点特性。创建的节点是顺序编号的且唯一。
最小的顺序号获取锁。
其他去监听前一个顺序节点(既:比他小一个值的顺序号节点),这里主要是为了控制获取锁的顺序。
释放锁,只要删除对应的顺序临时节点即可
其他客户端就可以通过监听事件,获取锁了。
例如:
-
client_a, client_b, client_c 都创建临时节点 resource_
client_a 创建resource_00000000
client_b 创建resource_00000001
client_c 创建resource_00000002 -
客户端查询所有子节点, 判断是否获取锁
client_a创建的 resource_00000000 为最小,则获取锁。
其他客户处分别监听比他小的顺序节点。client_b监听 resource_00000001, client_c监听 resource_00000002. -
client_a 删除节点 resource_00000000 释放锁,则 client_b 获取通知,获取锁。以此类推。
┌────┐
│ / │
└──┬─┘
│ ┌─────────┐
└───┤resources│
└─┬───────┘
│
│ ┌─────────────────┐ create ┌────────────┐
├────┤resource_00000000│◄──────────────────────┤ client_a │
│ └─────────▲───────┘ └────────────┘
│ │ watch
│ └─────────────────────────────────────┐
│ │
│ ┌─────────────────┐ create ┌─────┴──────┐
├────┤resource_00000001│◄──────────────────────┤ client_b │
│ └──────────▲──────┘ └────────────┘
│ │ watch
│ └─────────────────────────────────────┐
│ │
│ ┌─────────────────┐ create ┌──────┴─────┐
├────┤resource_00000002│◄──────────────────────┤ client_c │
│ └─────────────────┘ └────────────┘
│
│ ┌─────────────────┐
└────┤ ... │
└─────────────────┘
实践部分: 分布式锁_Zookeeper分布式锁实践
curator实践
redission实践