redisクラスタの導入と分散ロックの実現

20184 ワード

一、redisクラスタの配置
  • インストールredisインストールフォルダにredis-tribがあることを確認する.rbファイル、rudyによりredisクラスタ
  • を構築
  • ruby環境をインストール環境変数を構成し、gem install redisインストールredis依存
  • 詳細環境インストールチュートリアル:クリックしてリンクを開く
    クラスタ構築
    redisクラスタは最小3つのプライマリノードを含み、各ノードは異なるサーバ上に配置されるべきである.ここでは、3つのプライマリノードと3つのスレーブノードのredisクラスタを確立し、ローカルマシン上に配置し、それぞれ異なるポート(700070070071007010172007201)を傍受し、7000、7100、7200は3つのプライマリノードである.7001、7101、7201は、それぞれ対応するスレーブノードである.
    redisインストールディレクトリにclusterフォルダを作成し、このディレクトリの下でこの6つのredisノードに6つのフォルダを新規作成します.名前はそれぞれのポート名で、作成したファイルにredis_のように作成します.7000.confプロファイル、内容:
    port 7000
    #     IP  ,   127.0.0.1
    #bind 172.16.10.49
    appendonly yes
    appendfilename "appendonly_7000.aof"
    
    # //            ,       
    maxmemory 200mb
    maxmemory-policy allkeys-lru
    
    cluster-enabled yes
    cluster-config-file nodes_7000.conf
    cluster-node-timeout 15000
    cluster-slave-validity-factor 10
    cluster-migration-barrier 1
    cluster-require-full-coverage yes
    

    ポート番号7000はそれぞれ対応するポート番号に変更され、IPアドレスはここでテストするとデフォルト127.0.0.1で、パソコンのローカルipアドレスを使用することをお勧めします.cluster-config-fileパラメータはredisクラスタノードのプロファイル名を指定し、クラスタを作成して生成します.内容は次のとおりです.
    0adf165a965376913d6a4d426784371e6b0d7cf6 127.0.0.1:7101 slave 2ec1abbf3bcd7758281bcc7b6762532084c0b60c 0 1521124105758 5 connected
    8d8f817d997b57ccb10049208738d5ebfdd5c2fe 127.0.0.1:7001 master - 0 1521124104759 7 connected 0-5460
    9334416471942fd042f704ae8a345ab6c1adcd37 127.0.0.1:7201 slave b7fedf227aec4fc2911bdf7de2566b73e0a88045 0 1521124107358 9 connected
    1030b137672c69175e7cf1d99be325329e016422 127.0.0.1:7000 myself,slave 8d8f817d997b57ccb10049208738d5ebfdd5c2fe 0 0 1 connected
    b7fedf227aec4fc2911bdf7de2566b73e0a88045 127.0.0.1:7200 master - 0 1521124100754 9 connected 10923-16383
    2ec1abbf3bcd7758281bcc7b6762532084c0b60c 127.0.0.1:7100 master - 0 1521124106758 2 connected 5461-10922
    vars currentEpoch 9 lastVoteEpoch 0
    

    クラスタを構成した後、IPアドレスポートなどの各ノードの情報を変更したい場合は、ここで変更できますが、各ノードの下で変更する必要があります.
     
    次に、次のコマンドを使用して各サービスをインストールし、起動します.
    インストールredis-server--service-install cluster/7100/redis_7100.conf --service-name redis7100   
    redis-server--service-start--service-name redis 7100を起動
    サービスredis-server--service-uninstall--service-name redis 7100の削除
    現在、6つのredisサービスが開始されていますが、6つのredisは接続されておらず、クラスタは実装されていません.
    クラスタの作成:
    ruby redis-trib.rb create --replicas 1 127.0.0.1:700 0 127.0.0.1:7100 127.0.0.1:7200 127.0.0.1:7001 127.0.0.1:7101 127.0.0.1:7201
    redis-tribを使用rbコマンドはクラスタを作成し、rubyは削除できます.コマンドが正常に実行されると、クラスタが作成されます.
    接続クラスタ:
    redis-cli -c -p 7200 -h 127.0.0.1
    ここではどのノードにも接続できます.
    redisクラスタ動作
    set keya valuea
    1、keyaに対して先に値を計算する
    2、16384(総スロット点数)に対して余剰を取る(スロットポイントを得る)
    3、スロットポイントで対応するノードを見つける
    4、このノードでset keya valueaを実行する
    ノードは、プライマリノードとセカンダリノードに分けられ、1つのプライマリノードの下に複数のセカンダリノードがあります.スロット値はプライマリノードに割り当てられ、redisのkey-valueは
    スロットポイントに格納されます.
    プライマリノードが停止すると、スレーブノードがプライマリノードになり、プライマリノードがスレーブノードにない場合、クラスタはfail状態にあるか、半数以上のプライマリノードが選択されます.
    ノードがオフになり、クラスタもfail状態になります.
    プライマリノードはキー-valueをスレーブノードに書き込みます
     
    二、redisは分布式ロックを実現する
    redis分布式の実現原理:
    1、setNX操作により、keyが存在する場合、操作しない;存在しない場合、set値を設定し、ロックの反発性を保証します.
    2、valueロックの期限を設定し、ロックが超過した場合、getAndSet操作を行い、古い値を先にgetし、新しい値をセットし、デッドロックが発生しないようにする.ここでkeyの有効期間を設定することでデッドロックを回避することもできるが、setNxとexprise(有効期間を設定する)は非原子的に動作し、ロックに有効時間が設定されていないという問題が発生し、デッドロックが発生する可能性がある.
    実装:
    Spring boot jdeisによるredsiクラスタの接続
    redisプロファイル:
    default.redis.maxRedirects=3
    #         。   :maxTotal,   :maxActive
    default.redis.maxTotal=20
    #            
    default.redis.maxIdle=10
    #            
    default.redis.minIdle=1
    #         ,          ,       。  ,   ;   -1.      。   :maxWaitMillis,   :maxWait
    default.redis.maxWaitMillis=3000
    #         ,                。  (-1)     
    default.redis.minEvictableIdleTimeMillis=-1
    #  “    ”      ,            。   3
    default.redis.numTestsPerEvictionRun=3
    #“    ”    ,     ,   。     ,     “    ”。   -1
    default.redis.timeBetweenEvictionRunsMillis=-1
    #      “  ”   ,        ,            ,         。   false。       
    default.redis.testOnBorrow=false
    #     
    default.redis.timeout=15000
    #       
    default.redis.usePool=true
    #host&port
    #       ip      
    default.redis.nodes[0]=127.0.0.1:7000
    default.redis.nodes[1]=127.0.0.1:7001
    default.redis.nodes[2]=127.0.0.1:7100
    default.redis.nodes[3]=127.0.0.1:7101
    default.redis.nodes[4]=127.0.0.1:7200
    default.redis.nodes[5]=127.0.0.1:7201
    

    @ConfigurationProperties注記で構成情報を読み込みます.
    @Component
    @ConfigurationProperties(prefix = "default.redis")
    public class RedisProperties {
        private int maxRedirects;
        private int maxTotal;
        private int maxIdle;
        private int minIdle;
        private int maxWaitMillis;
        private int minEvictableIdleTimeMillis;
        private int numTestsPerEvictionRun;
        private int timeBetweenEvictionRunsMillis;
        private boolean testOnBorrow;
        private int timeout;
        private boolean usePool;
        private List nodes;
    
    
        public int getMaxRedirects() {
            return maxRedirects;
        }
    
        public void setMaxRedirects(int maxRedirects) {
            this.maxRedirects = maxRedirects;
        }
    
        public int getMaxTotal() {
            return maxTotal;
        }
    
        public void setMaxTotal(int maxTotal) {
            this.maxTotal = maxTotal;
        }
    
        public int getMaxIdle() {
            return maxIdle;
        }
    
        public void setMaxIdle(int maxIdle) {
            this.maxIdle = maxIdle;
        }
    
        public int getMinIdle() {
            return minIdle;
        }
    
        public void setMinIdle(int minIdle) {
            this.minIdle = minIdle;
        }
    
        public int getMaxWaitMillis() {
            return maxWaitMillis;
        }
    
        public void setMaxWaitMillis(int maxWaitMillis) {
            this.maxWaitMillis = maxWaitMillis;
        }
    
        public int getMinEvictableIdleTimeMillis() {
            return minEvictableIdleTimeMillis;
        }
    
        public void setMinEvictableIdleTimeMillis(int minEvictableIdleTimeMillis) {
            this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
        }
    
        public int getNumTestsPerEvictionRun() {
            return numTestsPerEvictionRun;
        }
    
        public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {
            this.numTestsPerEvictionRun = numTestsPerEvictionRun;
        }
    
        public int getTimeBetweenEvictionRunsMillis() {
            return timeBetweenEvictionRunsMillis;
        }
    
        public void setTimeBetweenEvictionRunsMillis(int timeBetweenEvictionRunsMillis) {
            this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
        }
    
        public boolean isTestOnBorrow() {
            return testOnBorrow;
        }
    
        public void setTestOnBorrow(boolean testOnBorrow) {
            this.testOnBorrow = testOnBorrow;
        }
    
        public int getTimeout() {
            return timeout;
        }
    
        public void setTimeout(int timeout) {
            this.timeout = timeout;
        }
    
        public boolean isUsePool() {
            return usePool;
        }
    
        public void setUsePool(boolean usePool) {
            this.usePool = usePool;
        }
    
        public List getNodes() {
            return nodes;
        }
    
        public void setNodes(List nodes) {
            this.nodes = nodes;
        }
    
    }
    

    redis構成クラス、redisTemplateの生成
    @Configuration
    public class RedisConfig {
    
        @Autowired
        private RedisProperties redisProperties;
    
        @Bean
        public RedisClusterConfiguration redisClusterConfiguration() {
            RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();
            redisClusterConfiguration.setClusterNodes(getRedisNode());
            redisClusterConfiguration.setMaxRedirects(redisProperties.getMaxRedirects());
            return redisClusterConfiguration;
        }
    
        @Bean
        public JedisPoolConfig jedisPoolConfig() {
            JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
            jedisPoolConfig.setMaxIdle(redisProperties.getMaxIdle());
            jedisPoolConfig.setMaxTotal(redisProperties.getMaxTotal());
            jedisPoolConfig.setMinIdle(redisProperties.getMinIdle());
            jedisPoolConfig.setMaxWaitMillis(redisProperties.getMaxWaitMillis());
            jedisPoolConfig.setNumTestsPerEvictionRun(redisProperties.getNumTestsPerEvictionRun());
            jedisPoolConfig.setTimeBetweenEvictionRunsMillis(redisProperties.getTimeBetweenEvictionRunsMillis());
            jedisPoolConfig.setTestOnBorrow(redisProperties.isTestOnBorrow());
            return jedisPoolConfig;
        }
    
        @Bean
        public JedisConnectionFactory jedisConnectionFactory(RedisClusterConfiguration redisClusterConfiguration, JedisPoolConfig jedisPoolConfig) {
            JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisClusterConfiguration, jedisPoolConfig);
            jedisConnectionFactory.setTimeout(redisProperties.getTimeout());
            return jedisConnectionFactory;
        }
    
        @Bean
        public RedisTemplate redisTemplate(JedisConnectionFactory jedisConnectionFactory) {
            RedisTemplate redisTemplate = new RedisTemplate();
            redisTemplate.setConnectionFactory(jedisConnectionFactory);
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setValueSerializer(new StringRedisSerializer());
            return redisTemplate;
        }
    
        private List getRedisNode() {
            List nodes = redisProperties.getNodes();
            if (CommonUtils.isNotEmpty(nodes)) {
                List redisNodes = nodes.stream().map(node -> {
                    String[] ss = node.split(":");
                    return new RedisNode(ss[0], Integer.valueOf(ss[1]));
                }).collect(Collectors.toList());
                return redisNodes;
            }
            return new ArrayList<>();
        }
    }
    

    redisロックの実装:
    @Component
    public class RedisLock {
    
        private static final Logger log = LoggerFactory.getLogger(RedisLock.class);
        /*         30s */
        private static final int DEFAULT_LOCK_EXPIRSE_MILL_SECONDS = 30 * 1000;
        /*            10s */
        private static final int DEFAULT_LOCK_WAIT_DEFAULT_TIME_OUT = 10 * 1000;
        /*               */
        private static final int DEFAULT_LOOP_WAIT_TIME = 150;
        /*   key   */
        private static final String LOCK_PREFIX = "LOCK_";
    
        /*          */
        private boolean lock = false;
        /*   key */
        private String lockKey;
        /*       (ms) */
        private int lockExpirseTimeout;
        /*         (ms) */
        private int lockWaitTimeout;
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        public boolean isLock() {
            return lock;
        }
    
        public void setLock(boolean lock) {
            this.lock = lock;
        }
    
        public String getLockKey() {
            return lockKey;
        }
    
        public void setLockKey(String lockKey) {
            this.lockKey = LOCK_PREFIX + lockKey;
        }
    
        public int getLockExpirseTimeout() {
            return lockExpirseTimeout;
        }
    
        public void setLockExpirseTimeout(int lockExpirseTimeout) {
            this.lockExpirseTimeout = lockExpirseTimeout;
        }
    
        public int getLockWaitTimeout() {
            return lockWaitTimeout;
        }
    
        public void setLockWaitTimeout(int lockWaitTimeout) {
            this.lockWaitTimeout = lockWaitTimeout;
        }
    
        public void setRedisTemplate(RedisTemplate redisTemplate) {
            this.redisTemplate = redisTemplate;
        }
    
        public RedisLock() {
        }
    
        public RedisLock(String lockKey, int lockExpirseTimeout, int lockWaitTimeout) {
            this.lockKey = LOCK_PREFIX + lockKey;
            this.lockExpirseTimeout = lockExpirseTimeout;
            this.lockWaitTimeout = lockWaitTimeout;
        }
    
        public RedisLock newInstance(String lockKey) {
            RedisLock redisLock = new RedisLock(lockKey, DEFAULT_LOCK_EXPIRSE_MILL_SECONDS, DEFAULT_LOCK_WAIT_DEFAULT_TIME_OUT);
            redisLock.setRedisTemplate(this.redisTemplate);
            return redisLock;
        }
    
        public RedisLock newInstance(String lockKey, int lockExpirseTimeout, int lockWaitTimeout) {
            if (lockExpirseTimeout == 0 || lockWaitTimeout == 0) {
                lockExpirseTimeout = DEFAULT_LOCK_EXPIRSE_MILL_SECONDS;
                lockWaitTimeout = DEFAULT_LOCK_WAIT_DEFAULT_TIME_OUT;
            }
            RedisLock redisLock = new RedisLock(lockKey, lockExpirseTimeout, lockWaitTimeout);
            redisLock.setRedisTemplate(this.redisTemplate);
            return redisLock;
        }
    
        public boolean setIfAbsent(String expirseTimeStr) {
            // setIfAbsent  jedis setNx  
            return this.redisTemplate.opsForValue().setIfAbsent(this.lockKey, expirseTimeStr);
        }
    
        public String getAndSet(String expiresTimeStr) {
            //       ,      ,    
            return (String) this.redisTemplate.opsForValue().getAndSet(this.lockKey, expiresTimeStr);
        }
    
        /**
         * 1、        ,        
         * 2、setNx  ,  
         * 3、  ,    ,        ,  true;    ,      value(    )
         * 4、  value             ,  getAndSet  ,    value,    value;  ,       ,    2;
         * 5、    3 4   value  ,    ,        ,  true;  ,      ,    value,       ,    2。
         */
        public boolean lock() {
            log.info("{}-----     ...", Thread.currentThread().getName());
            int lockWaitMillSeconds = this.lockWaitTimeout;
            // key   ,  key     
            String redisValue = String.valueOf(System.currentTimeMillis() + this.lockExpirseTimeout);
            while (lockWaitMillSeconds > 0) {
                lock = setIfAbsent(redisValue);
                if (lock) {
                    //    ,       ,             ,      ,     value            
                    this.redisTemplate.expire(this.lockKey, lockExpirseTimeout, TimeUnit.MILLISECONDS);
                    log.info("{}-----   ", Thread.currentThread().getName());
                    return lock;
                } else {
                    //    ,        
                    String oldValue = (String) this.redisTemplate.opsForValue().get(this.lockKey);
                    if (CommonUtils.isNotEmpty(oldValue) && Long.parseLong(oldValue) < System.currentTimeMillis()) {
                        //             ,      ,   value,   
                        String currentRedisValue = getAndSet(String.valueOf(lockExpirseTimeout + System.currentTimeMillis()));
                        //         ,             ,  
                        if (currentRedisValue.equals(oldValue)) {
                            //                     ,    ,         
                            redisTemplate.expire(this.lockKey, lockExpirseTimeout, TimeUnit.MILLISECONDS);
                            log.info("{}-----   ", Thread.currentThread().getName());
                            this.lock = true;
                            return this.lock;
                        } else {
                            //                 ,     value
                            redisTemplate.opsForValue().set(this.lockKey, currentRedisValue);
                        }
                    }
                }
                //               
                lockWaitMillSeconds -= DEFAULT_LOOP_WAIT_TIME;
                try {
                    log.info("{}-----  {}ms ,      ...", Thread.currentThread().getName(), DEFAULT_LOOP_WAIT_TIME);
                    //      ,            ,                         ,            
                    Thread.sleep(DEFAULT_LOOP_WAIT_TIME);
                } catch (InterruptedException e) {
                    log.error("redis          ", e);
                }
            }
            log.info("{}-----     ,     ", Thread.currentThread().getName());
            return false;
        }
    
        public void unlock() {
            if (lock) {
                this.redisTemplate.delete(this.lockKey);
                this.lock = false;
            }
        }
    
    }
    

    ロック処理:
    1、現在のシステム時間を取得し、ロックの有効期限2、setNx操作を計算し、ロック3、ロックが成功した場合、ロックの有効期限を設定し、trueを返す.ロックの取り外しに失敗し、現在のロックのvalue(有効期限)4を取り出し、valueが空でなく、現在のシステム時間より小さい場合はgetAndSet操作を行い、valueを再設定し、古いvalueを取り出す.そうでなければ、待機間隔時間後、ステップ2を繰り返す.5、ステップ3と4で取り出したvalueが同じであれば、ロックが成功し、ロックの期限を設定し、trueを返す.そうでなければ、他の人がロックに成功し、ロックのvalueを復元し、間隔を待ってから、手順2を繰り返します.
    ここでロックの有効期限を設定するのは、後で複雑な論理の実行を減らすためだけです.
    テスト:
    テストクラス
    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest(classes = Application.class)
    public class TestRedis {
        private CountDownLatch countDownLatch = new CountDownLatch(2);
    
        @Test
        public void testRedisLock() throws InterruptedException {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    RedisLock lock = ((RedisLock) SpringContextUtil.getBean("redisLock")).newInstance("test");
                    if (lock.lock()) {
                        System.out.println("work1   ");
                        System.out.println("work1   15s...");
                        try {
                            Thread.sleep(15000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("work1    ,   ");
                        lock.unlock();
                    }
                    countDownLatch.countDown();
                }
            },"work1").start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    RedisLock lock = ((RedisLock) SpringContextUtil.getBean("redisLock")).newInstance("test");
                    if (lock.lock()) {
                        System.out.println("work2   ");
                        System.out.println("work2   5s...");
                        try {
                            Thread.sleep(5000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("work2    ,   ");
                        lock.unlock();
                    }
                    countDownLatch.countDown();
                }
            }, "work2").start();
            //         ,      
            countDownLatch.await();
        }
    }
    

     
    ここではユニットテストで2つのスレッドwork 1とwork 2,work 1シミュレーション15 s,work 2シミュレーション5 sを開始する.ここではjuntDownLatchを使用して、2つのワークスレッドが完了するまでメインスレッドが停止しないように、メインスレッドを2つのワークスレッドが完了するまで待機させます.
     
    上記のテストには2つの結果があります.1つ目は、work 2が先にロックを取得し、5 s、work 1が待機している(デフォルト待機タイムアウト時間10 s)、待機中に1値がロックを取得しようとしている(デフォルト間隔150 ms)、デフォルトロックの有効期間30 sです.明らかに、5 s後にwork 2が作業解除ロックを完了し、work 1がロックを取得し、work 1とwork 2は正常に作業を完了しています.結果は次のとおりです.
    2018-03-16 14:16:50,579 5576 [work1] INFO  c.s.component.redis.lock.RedisLock - work1-----     ...
    2018-03-16 14:16:50,579 5576 [work2] INFO  c.s.component.redis.lock.RedisLock - work2-----     ...
    2018-03-16 14:16:50,598 5595 [work1] INFO  c.s.component.redis.lock.RedisLock - work1-----  150ms ,      ...
    2018-03-16 14:16:50,599 5596 [work2] INFO  c.s.component.redis.lock.RedisLock - work2-----   
    work2   
    work2   5s...
    2018-03-16 14:16:50,748 5745 [work1] INFO  c.s.component.redis.lock.RedisLock - work1-----  150ms ,      ...
    。。。           。。。
    2018-03-16 14:16:55,571 10568 [work1] INFO  c.s.component.redis.lock.RedisLock - work1-----  150ms ,      ...
    work2    ,   
    2018-03-16 14:16:55,722 10719 [work1] INFO  c.s.component.redis.lock.RedisLock - work1-----   
    work1   
    work1   15s...
    work1    ,   

     
    第2の結果として、ワーク1はまずロックを取得し、作業15 s、ワーク2は待機している.ここで、ワーク1の作業時間はデフォルトの待機タイムアウト時間10 sを超えているため、ワーク2はワーク1が作業を完了する前にロックを解除する前に、待機しているためにロックを取得できず、作業を完了できない.結果は以下の通りである.
     
    2018-03-16 14:22:45,292 5448 [work1] INFO  c.s.component.redis.lock.RedisLock - work1-----     ...
    2018-03-16 14:22:45,292 5448 [work2] INFO  c.s.component.redis.lock.RedisLock - work2-----     ...
    2018-03-16 14:22:45,308 5464 [work2] INFO  c.s.component.redis.lock.RedisLock - work2-----  150ms ,      ...
    2018-03-16 14:22:45,309 5465 [work1] INFO  c.s.component.redis.lock.RedisLock - work1-----   
    work1   
    work1   15s...
    2018-03-16 14:22:45,459 5615 [work2] INFO  c.s.component.redis.lock.RedisLock - work2-----  150ms ,      ...
    。。。           。。。
    2018-03-16 14:22:55,251 15407 [work2] INFO  c.s.component.redis.lock.RedisLock - work2-----  150ms ,      ...
    2018-03-16 14:22:55,401 15557 [work2] INFO  c.s.component.redis.lock.RedisLock - work2-----     ,     
    。。。        5s。。。
    work1    ,