by shihang.mai
场景:告警单生成,一线人员抢单
1. mysql分布式锁
表结构
字段 | 备注 |
---|---|
order_id | 告警单id |
user_id | 用户id |

- 执行lock()操作时,尝试向mysql中插入记录,插入成功表明获得锁,反之失败,并休眠一定时间,继续lock()
- 获取锁后,执行业务代码,最后unlock(),删除表中记录
- 为防止获取到锁的线程,在获取到锁之后,挂了,无法进行unlock(),同时导致其他线程也无法获取到锁,补偿措施:利用触发器过了一定时间自动清除记录
核心代码
public class MysqlLock implements Lock {
@Autowired
private TblOrderLockDao mapper;
private ThreadLocal<TblOrderLock> orderLockThreadLocal ;
@Override
public void lock() {
// 1、尝试加锁
if(tryLock()) {
System.out.println("尝试加锁");
return;
}
// 2.休眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 3.递归再次调用
lock();
}
/**
* 非阻塞式加锁,成功,就成功,失败就失败。直接返回
*/
@Override
public boolean tryLock() {
try {
TblOrderLock tblOrderLock = orderLockThreadLocal.get();
mapper.insertSelective(tblOrderLock);
System.out.println("加锁对象:"+orderLockThreadLocal.get());
return true;
}catch (Exception e) {
return false;
}
}
@Override
public void unlock() {
mapper.deleteByPrimaryKey(orderLockThreadLocal.get().getOrderId());
System.out.println("解锁对象:"+orderLockThreadLocal.get());
orderLockThreadLocal.remove();
}
@Override
public void lockInterruptibly() throws InterruptedException {
// TODO Auto-generated method stub
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
// TODO Auto-generated method stub
return false;
}
@Override
public Condition newCondition() {
// TODO Auto-generated method stub
return null;
}
}
本质:利用主键冲突来实现锁
2. redis-自行写
- key必须加超时时间
//没加超时
boolean lockStatus = stringRedisTemplate.opsForValue().setIfAbsent(lock.intern(), userId +"");
//加超时-但不是原子操作
boolean lockStatus = stringRedisTemplate.opsForValue().setIfAbsent(lock.intern(), userId +"");
stringRedisTemplate.expire(lock.intern(), 30L, TimeUnit.SECONDS);
//加超时-原子操作
boolean lockStatus = stringRedisTemplate.opsForValue().setIfAbsent(lock.intern(), userId+"", 30L, TimeUnit.SECONDS);
- 在释放锁时,不能直接删除key
在释放锁时,如直接删除key,在以下情况下会出问题
设置key超时时间为5分钟,但拿到锁的线程A执行了6分钟,这时另外一个线程B获取到锁,A执行释放锁,那么就会导致线程A释放了B的锁
故应在释放时,先获取key的值,看是否是同一个userId
if((userId+"").equals(stringRedisTemplate.opsForValue().get(lock.intern()))) {
stringRedisTemplate.delete(lock.intern());
}
本质:redis的setnx,并设置超时时间
3. redisson
引入jar,这个jar可以单节点方式(单redis,加哨兵redis)和多节点(redLock)进行分布式锁
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.3.2</version>
</dependency>
3.1 单节点
加入bean
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("127.0.0.1:6379").setDatabase(0);
return Redisson.create(config);
}
获取锁与释放锁
//获取锁
RLock rlock = redissonClient.getLock(lock.intern());
rlock.lock();
//释放锁
rlock.unlock();
代码默认设置key 超时时间30秒,过时间的3分之一(10秒)续期。
存在单节点问题,解决:引入哨兵
3.2 哨兵
加入bean
@Bean(name = "redisson")
@Order(1)
public Redisson getRedisson(){
Config config = new Config();
config.useSentinelServers()
.setMasterName(properties.getMasterName())
.addSentinelAddress(properties.getAddress())
.setDatabase(0);
return (Redisson) Redisson.create(config);
}
获取锁与释放锁
//获取锁
RLock rLock = redisson.getLock(lockKey);
rLock.lock();
//释放锁
rlock.unlock();
加入哨兵,确实可以解决单节点问题,但是当节点加了锁,还没来得及同步给从机,那么会导致资源给到另外一个线程。解决:红锁
3.3 红锁
3个独立的redis,3个bean
@Bean(name = "redissonRed1")
@Primary
public RedissonClient redissonRed1(){
Config config = new Config();
config.useSingleServer().setAddress("127.0.0.1:6379").setDatabase(0);
return Redisson.create(config);
}
@Bean(name = "redissonRed2")
public RedissonClient redissonRed2(){
Config config = new Config();
config.useSingleServer().setAddress("127.0.0.1:6380").setDatabase(0);
return Redisson.create(config);
}
@Bean(name = "redissonRed3")
public RedissonClient redissonRed3(){
Config config = new Config();
config.useSingleServer().setAddress("127.0.0.1:6381").setDatabase(0);
return Redisson.create(config);
}
//获取锁
RLock rLock1 = redissonRed1.getLock(lockKey);
RLock rLock2 = redissonRed2.getLock(lockKey);
RLock rLock3 = redissonRed3.getLock(lockKey);
RedissonRedLock rLock = new RedissonRedLock(rLock1,rLock2,rLock3);
rLock.lock();
//释放锁
rLock.unlock();
红锁原理

红锁问题
- 时钟偏移,delta(服务器间时间同步)
-
挂了redis,延时启动,必须大于锁有效时间
使用红锁,延时启动原因
例如5台redis,当A线程获取到锁,然后执行到一半GC,STW导致锁超时,那么线程B也获取到锁。显然不行
redis锁问题:
https://www.cnblogs.com/youngdeng/p/12883790.html
4. integration
利用aop+ integration实现无侵入式分布式锁
待完善
5. zookeeper
最好的同步锁方式,详见zookeeper文章
网友评论