分布式锁

  • 什么是分布式锁
    • 线程锁
      • 主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头,显示锁Lock是共享某个变量(state)。
    • 进程锁
      • 为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronized等线程锁实现进程锁。
    • 分布式锁
      • 当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。
    • 红黑锁
  • 使用场景
    • 线程间并发问题和进程间并发问题都是可以通过分布式锁解决的,但是强烈不建议这样做!因为采用分布式锁解决这些小问题是非常消耗资源的!分布式锁应该用来解决分布式情况下的多进程并发问题才是最合适的。
    • 有这样一个情境,线程A和线程B都共享某个变量X。
      • 如果是单机情况下(单JVM),线程之间共享内存,只要使用线程锁就可以解决并发问题。
      • 如果是分布式情况下(多JVM),线程A和线程B很可能不是在同一JVM中,这样线程锁就无法起到作用了,这时候就要用到分布式锁来解决。
  • 分布式锁的实现
    • 实现方式
      • 数据库
      • Redis(setnx命令)
      • Memcached(add命令)
      • Zookeeper(临时节点)
        • 算法对数据的完整性要求很高。在分布式的zookeeper中,数据是很难丢失的。所以数据/锁的完整性更高
    • 分布式锁实现的关键是在分布式的应用服务器外,搭建一个存储服务器,存储锁信息,这时候我们很容易就想到了Redis。首先我们要搭建一个Redis服务器,用Redis服务器来存储锁信息。
    • 在实现的时候要注意的几个关键点:
      • 1、锁信息必须是会过期超时的,不能让一个线程长期占有一个锁而导致死锁;
      • 2、同一时刻只能有一个线程获取到锁。
    • 使用Redis
      • 如果是Redis集群的话,需要使用RedLock算法来保证数据完整性及宕机的情况(不如Zookeeper好)
      • 自行实现
        • 要考虑一些问题,如setnx和expire的非原子性、超时后使用del 导致误删其他线程的锁、出现并发的可能性。
        • 加锁:setnx(key,1)
        • 解锁:del(key)
        • 锁超时:expire(key, 30)
      • 使用redisson(无法保证服务端的宕机数据丢失)
        • Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。

        • redisson直接提供了Lock数据类型来操作分布式锁,很方便,否则就要自己用Redis的setnx命令来实现了分布式锁了。而且redisson还提供了RedissonMultiLock、RedissonRedLock、RLock(公平锁、可重入锁?)等多种锁,还提供了tryLock()等实用函数。

        • 在redisson中不需要set指令

          RBucket<Object> bucket = redisson.getBucket("a");
          bucket.set("bb");
          
        • 所有的值都是结构体

          • 如前面的RBucket、后面会用的Lock,redisson提供了十几种结构体供我们使用,当取值时,redisson也会自动将值转换成对应的结构体。所以如果使用redisson取redisTemplate放入的值,就要小心报错。
        • 初始化

          • 借助JedisConnectionFactory的连接中的参数来初始化redisson连接(否则就要在配置文件中配置相关参数了)。

            import org.redisson.Redisson;
            import org.redisson.api.RedissonClient;
            import org.redisson.config.Config;
            import org.springframework.beans.factory.annotation.Autowired;
            import org.springframework.beans.factory.annotation.Qualifier;
            import org.springframework.context.annotation.Bean;
            import org.springframework.context.annotation.Configuration;
            import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
            import org.springframework.data.redis.serializer.GenericToStringSerializer;
            import org.springframework.data.redis.serializer.StringRedisSerializer;
            import org.springframework.data.redis.core.RedisTemplate;
            
            @Configuration
            public class RedisLockConfig
            {
            
                @Autowired
                private JedisConnectionFactory jcf;
            
                @Qualifier("RedissonLockClient")
                @Bean
                public RedissonClient redisson()
                {
                    Config config = new Config();
            
                    config.useSingleServer().setAddress("redis://" + jcf.getHostName() + ":" + jcf.getPort())
                            .setPassword(jcf.getPassword());
                    return Redisson.create(config);
                }
            
                @Qualifier("RedisLockTemplate")
                @Bean
                public RedisTemplate<String, Object> redisTemplate()
                {
                    final RedisTemplate<String, Object> template = new RedisTemplate<>();
                    template.setConnectionFactory(jcf);
                    template.setKeySerializer(new StringRedisSerializer());
                    template.setHashValueSerializer(new GenericToStringSerializer<Object>(Object.class));
                    template.setValueSerializer(new GenericToStringSerializer<Object>(Object.class));
                    return template;
                }
            }
            
        • 使用

          • redisson.getLock("xxx")获得的锁是分布式锁,用于多个JVM中的多线程处理。而这里通过普通的RedisTemplate进行的操作,只是为了记录任务状态,毕竟redisson的行为不可见。

            @Qualifier("RedissonLockClient")
            @Autowired
            private RedissonClient redisson;
            
            @Qualifier("RedisLockTemplate")
            @Autowired
            private RedisTemplate<String, Object> template;
            
            @Scheduled(cron = "${xxx.yyy.zzz}")
            public void bizMethod()
            {
                logger.debug("STEPIN");
                RLock lock = redisson.getLock("bizLock");
                if(!lock.isLocked()) 
                {
                    lock.lock();
                    try
                    {
                        logger.trace("{} : acquired lock", System.identityHashCode(this));
            
                        int year = Calendar.getInstance().get(Calendar.YEAR);
                        int month = Calendar.getInstance().get(Calendar.MONTH);
                        int day = Calendar.getInstance().get(Calendar.DATE);
                        String key = year + "-" + month + "-" + day;
            
                        Object value = template.opsForValue().get(key);
                        if (value == null)
                        {
                            template.opsForValue().set(key, "progressing");
                            logger.trace("{}: {}  is progressing", System.identityHashCode(this), key);
            
                            // logic code
            
                            template.opsForValue().set(key, "done");
                            logger.trace("{}: {}  is done", System.identityHashCode(this), key);
                        }
                        else
                        {
                            logger.trace("{}: {}  is already done!", System.identityHashCode(this), key);
                        }
            
                    }
                    finally
                    {
                        logger.trace("{} : releasing lock", System.identityHashCode(this));
                        lock.unlock();
                    }  
                }
                else 
                {
                    logger.trace("{} : someone already acquired lock", System.identityHashCode(this));
                }
                logger.debug("STEPOUT");
            }
            
            

posted on 2019-12-12 11:07  碎羽love星谊  阅读(123)  评论(0编辑  收藏  举报

导航