数据库(Mysql) 实现分布式锁
基于MySql的InnoDB引擎,只有通过索引进行检索的时候才会使用行级锁,否则会使用表级锁.这个索引一定要创建成唯一索引,否则会出现多个重载方法之间无法同时被访问的问题
以下方案无法实现公平锁. 求大佬指点思路!!!
库表(t_lock
)设计
字段 | 类型 | 是否可为空 | 描述 |
---|---|---|---|
id | int | 否 | 主键,自增 |
relock | int | 否 | 锁定次数 为0时,表示未分配锁 |
method | String | 否 | 加锁方法 |
ip | String | 否 | 获取锁服务ip |
lock_time | date | 否 | 锁定时间(设计超时时间) |
每个事务锁在表中为一条数据。
-
服务启动,查询t_lock表。准备获取锁。
select * from t_lock;
-
若表中无数据(服务初次启动),则进行添加:
INSERT INTO `t_lock`(`id`, `relock`, `method`, `ip`, `lock_time`) VALUES (1, 1, 'test', '127.0.0.1', NOW());
若添加成功,则认为获取到锁。
-
若表中有数据,则relock是否为 0。
1) 若锁定次数为0, 则可以自由获取锁。获取锁执行sql获取锁。UPDATE `t_lock` SET `relock` = 1, `method` = 'test', `ip` = '127.0.0.1', `lock_time` = NOW() WHERE `ip` = '127.0.0.1' AND method='test' AND relock = 0;
- 若锁定次数不为0, 则判断ip和method是否一致。若一致,则可以进行重入。执行sql重入锁。
UPDATE `t_lock` SET `relock` = 2, `method` = 'test', `ip` = '127.0.0.1', `lock_time` = NOW() WHERE `ip` = '127.0.0.1' AND method='test' AND relock = 1; --假定表中锁定次数为 1
若不一致,则轮询获取锁。获取锁规则为:1. 锁超时 2. 其他人释放锁。
锁超时: 锁定时间和当前时间的比较。若时间偏差过大(业务系统自己配置),则可以强制获取锁。 其他人释放锁: relock = 0
UPDATE `t_lock` SET `relock` = 1, `method` = 'test', `ip` = '127.0.0.1', `lock_time` = NOW() WHERE `ip` = '127.0.0.1' AND method = 'test' AND ( ( relock = 2 AND NOW() > DATE_ADD( '2001-09-11 07:35:25', INTERVAL 5 MINUTE )) OR relock = 0 ); --假定表中锁定次数为 2, 超时时间5分钟
考虑到各个服务器时间可能和数据库时间不一致。 所以使用数据库时间。传入时间为首次查询得到的数据库时间。
以上update操作,若成功,则获取到相应锁。
-
锁释放。
UPDATE `t_lock` SET `relock` = 0, `method` = 'test', `ip` = '127.0.0.1', `lock_time` = NOW() WHERE `ip` = '127.0.0.1' AND method='test' AND relock = 1; --假定表中锁定次数为 1
若被重入,则需多次释放。
注: 此设计 无法体现公平。
异常情况
服务重启或宕机
- 重启或宕机后:两种选择 1) 检查锁表,若ip和method一致,则清空锁标记(relock = 0)。2)检查锁表,ip和method一致,则进行重入。(导致所标记无法释放至0),其他线程获取锁,会进行锁超时判断。
数据库宕机
- 数据库应设计为主主同步。防止单点。
Spring提供了@ScheduledLock 防止定时任务在分布式环境重复执行
支持多种数据源(jdbc, redis,zk,mongodb …),详情参照:
https://blog.youkuaiyun.com/qq_36341209/article/details/87375794