分布式锁,说简单就是在分布式项目上的用的锁
给大家介绍三种分布式锁的实现方案
- Mysql的乐观锁
- Redis的分布式锁
- Zookeeper的分布式锁
- 数据库的乐观锁:基于数据表添加某个字段,又称版本号,每次对数据进行操作版本号都有所改变,这样多线程同时操作的时候的判断条件多了个版本号的判断,就可以防止一定的安全问题.(代码可见文章底部)
- 乐观锁:之所以叫乐观,是在线程访问的时候总是认为没有其它线程访问,所以没有上锁,但是会在更新的时候通过版本号机制进行判断;
- 反之,悲观锁认为每次都是多线程在访问,每次都加锁,别人在访问的时候就会一直等待直到拿到锁,这样会导致效率上的问题(阻塞)(数据表的行锁,表锁以及synchronized的实现都是悲观锁)
-- 可能会发生的异常情况
-- 线程1查询,当前left_count为1,则有记录
select * from t_bonus where id = 10001 and left_count > 0
-- 线程2查询,当前left_count为1,也有记录
select * from t_bonus where id = 10001 and left_count > 0
-- 线程1完成领取记录,修改left_count为0,
update t_bonus set left_count = left_count - 1 where id = 10001
-- 线程2完成领取记录,修改left_count为-1,产生脏数据
update t_bonus set left_count = left_count - 1 where id = 10001
通过乐观锁实现
-- 添加版本号控制字段
ALTER TABLE table ADD COLUMN version INT DEFAULT '0' NOT NULL AFTER t_bonus;
-- 线程1查询,当前left_count为1,则有记录,当前版本号为1234
select left_count, version from t_bonus where id = 10001 and left_count > 0
-- 线程2查询,当前left_count为1,有记录,当前版本号为1234
select left_count, version from t_bonus where id = 10001 and left_count > 0
-- 线程1,更新完成后当前的version为1235,update状态为1,更新成功
update t_bonus set version = 1235, left_count = left_count-1 where id = 10001 and version = 1234
-- 线程2,更新由于当前的version为1235,udpate状态为0,更新失败,再针对相关业务做异常处理
update t_bonus set version = 1235, left_count = left_count-1 where id = 10001 and version = 1234
- Redis实现分布式锁:本质就是往Redis中存储数据,只不过使用的命令有些不一样
- 通过 4 个命令,判断返回值来进行上锁,判断超时,设置新值,解锁
- 上锁就是存入一个key value value就是设计的过期时间
- 可以使用工具类调用命令,也可以世界使用redisTemplate.xxx 或者 redisTemplate.excute(重写方法(这个比较麻烦,涉及到序列化))
- setNX 上锁 成功返回 1 失败返回 0 ==>key存在,不做任何动作
- getSET 设置新的值 返回旧值 ==>没有key 返回nil
- get 返回现在的value ==>没有key 返回nil
- del 删除
/**
* 加锁
* @param key redis key
* @param expire 过期时间,单位秒
* @return true:加锁成功,false,加锁失败
*/
public static boolean lock2(String key, int expire) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
long value = System.currentTimeMillis() + expire;
long status = redisService.setnx(key, String.valueOf(value));
if(status == 1) {
return true;
}
long oldExpireTime = Long.parseLong(redisService.get(key, "0"));
if(oldExpireTime < System.currentTimeMillis()) {
//超时
long newExpireTime = System.currentTimeMillis() + expire;
long currentExpireTime = Long.parseLong(redisService.getSet(key, String.valueOf(newExpireTime)));
if(currentExpireTime == oldExpireTime) {
return true;
}
}
return false;
}
public static void unLock2(String key) {
RedisService redisService = SpringUtils.getBean(RedisService.class);
long oldExpireTime = Long.parseLong(redisService.get(key, "0"));
if(oldExpireTime > System.currentTimeMillis()) {
redisService.del(key);
}
}
- Zookeeper分布式锁又可以有两大类,这个暂时不做解释