美文网首页
分布式锁方案

分布式锁方案

作者: 麦大大吃不胖 | 来源:发表于2020-12-30 15:14 被阅读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文章

相关文章

  • 3.10:分布式锁

    本文将梳理微服务架构下,分布式锁的常用方案。整体包含以下三部分: 分布式锁的提出 分布式锁主流方案 分布式锁选择 ...

  • 缓存击穿解决方案

    缓存击穿解决方案 解决方案分别有: 后台刷新 检查更新 mysql分布式锁 redis分布式锁 zookeeper...

  • 基于redis的分布式锁

    分布式锁实现方案 基于数据库实现分布式锁 基于缓存(redis,memcached,tair)实现分布式锁 基于Z...

  • 032 某金服面试题

    分布式事务 分布式锁方案和区别 分布式缓存 分布式选举问题 分库分表的方案 MVCC 方案 MySQL 的事务的实...

  • 分布式锁

    为什么要用分布式锁? 分布式锁是悲观锁的实现; 如果采用乐观锁的方案就用不着分布式锁了。 能用乐观锁的地方尽量用乐...

  • Java开发基于zookeeper分布式锁的解决方案之分布式消息

    基于zookeeper分布式锁的解决方案 zookeeper 分布式锁解决方案 基于问题三中分析图中加入zooke...

  • 分布式锁解决方案

    redis 分布式锁解决方案 还有可以使用 redission分布式锁 参考:https://www.cnblog...

  • 分布式锁

    前面小结过java锁,这次在来总结下分布式锁 Java锁如下 分布式锁方案 基于数据库 基于Redis 基于Zoo...

  • 3、分布式锁

    对于分布式锁可以从几个问题入手: 分布式锁应用的场景是什么样的呢?分布式锁的实现是怎么样的呢?分布式锁的实现方案应...

  • 82zookeeper 分布式锁(二)避免羊群效应

    分布式解决方案: 分布式锁常见问题: 1,Zookeeper如何实现分布式锁; 1,重试策略;2,超时策略;3,续...

网友评论

      本文标题:分布式锁方案

      本文链接:https://www.haomeiwen.com/subject/wonziktx.html