分布式锁方案

2020-12-30  本文已影响0人  麦大大吃不胖

by shihang.mai

场景:告警单生成,一线人员抢单

1. mysql分布式锁

表结构

字段 备注
order_id 告警单id
user_id 用户id
分布式事务-mysql
  1. 执行lock()操作时,尝试向mysql中插入记录,插入成功表明获得锁,反之失败,并休眠一定时间,继续lock()
  2. 获取锁后,执行业务代码,最后unlock(),删除表中记录
  3. 为防止获取到锁的线程,在获取到锁之后,挂了,无法进行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-自行写

  1. 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);
  1. 在释放锁时,不能直接删除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();

红锁原理


红锁原理

红锁问题

  1. 时钟偏移,delta(服务器间时间同步)
  2. 挂了redis,延时启动,必须大于锁有效时间


    使用红锁,延时启动原因

例如5台redis,当A线程获取到锁,然后执行到一半GC,STW导致锁超时,那么线程B也获取到锁。显然不行

redis锁问题:

https://www.cnblogs.com/youngdeng/p/12883790.html

4. integration

利用aop+ integration实现无侵入式分布式锁
待完善

5. zookeeper

最好的同步锁方式,详见zookeeper文章

上一篇 下一篇

猜你喜欢

热点阅读