redisに基づいて分散錠を実現する(二)


分散式錠の解決方法
  • は、データベーステーブルに基づいて楽観的なロックを行い、分散式のロックに用いられる.(小合併に適用)
  • は、分散ロックのためのmemcachedのadd()方法を使用する.
  • は、分散ロックのためのmemcachedのcas()方法を使用する.(非常用)
  • は、分布式錠のためにredisのsetnx()、expire()の方法を使用する.
  • は、分布式錠のためにredisのsetnx()、get()、getset()の方法を使用する.
  • は、redisのウォッチ、multi、execコマンドを使用して、分散式のロックに使用します.(非常用)
  • は、zookeeperを使用して、分散式ロックに使用されます.(非常用)
  • ここでは主に第四種類と第五種類を紹介します.
    前に提供した二つの方法は実は問題があります.デッドロックか、サーバー時間同期に依存します.Redis 2.6.12バージョンから、SETコマンドはパラメータによって、SETNX、SETEX、PSETEXの3つのコマンドの効果を実現することができます.このように、私たちのロック操作は一つのセットコマンドで実現できます.直接に原子的な操作です.デッドロックのリスクもないし、サーバーの時間同期にも依存しないので、この二つの問題を完璧に解決できます.redis文書に詳細があります.http://doc.redisfans.com/string/set.html
    redisのSET reource-name anystring NX EX max-lock-time方式を使用して、分散式ロックに使用します.
    原理
    命令SET reource-name anystring NX EX max-lock-timeはRedisでロックを実現する簡単な方法です.
    クライアントは以上のコマンドを実行します.
  • サーバがOKに戻ると、このクライアントはロックを取得する.
  • サーバがNILに戻ると、クライアントがロックを取得できなくなり、後で再試行することができます.
  • に設定された期限が過ぎたら、ロックは自動的に解除されます.
  • 以下の修正により、このロックをより強固にすることができます.
  • は、固定された文字列をキーの値として用いず、予測不可能な長ランダム文字列をパスワード列として設定する.
  • は、DELコマンドを使用してロックを解除するのではなく、Luaスクリプトを送信します.このスクリプトは、クライアントから入力された値とキーの一連のパスワードが一致するときのみ、キーを削除します.これらの2つの変更は、既存のロックを持つクライアントが誤って削除することを防ぐことができます.
  • 以下は簡単なロック解除スクリプトの例である.
    if redis.call("get",KEYS[1]) == ARGV[1]
    then
        return redis.call("del",KEYS[1])
    else
        return 0
    end
    
    あり得る問題
    redisがevalコマンドをサポートすることを保証します.
    具体的に実現する
    ロックは具体的にRedis Lockを実現します.
    package com.xiaolyuh.redis.lock;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.dao.DataAccessException;
    import org.springframework.data.redis.connection.RedisConnection;
    import org.springframework.data.redis.core.RedisCallback;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.util.Assert;
    import org.springframework.util.StringUtils;
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisCluster;
    import redis.clients.jedis.JedisCommands;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
    import java.util.UUID;
    
    /**
     * Redis    
     *    SET resource-name anystring NX EX max-lock-time   
     * 

    * Redis SET 。 * http://doc.redisfans.com/string/set.html *

    * , Redis 2.6.12 SET , * SET key value [EX seconds] [PX milliseconds] [NX|XX], : *

    * EX seconds — key ; * PX milliseconds — key ; * NX — key value , key , SETNX。 * XX — key value , key , SETEX。 *

    * SET resource-name anystring NX EX max-lock-time Redis 。 *

    * : *

    * OK , 。 * NIL , , 。 * * @author yuhao.wangwang * @version 1.0 * @date 2017 11 3 10:21:27 */ public class RedisLock3 { private static Logger logger = LoggerFactory.getLogger(RedisLock3.class); private StringRedisTemplate redisTemplate; /** * key value , key , SETNX。 */ public static final String NX = "NX"; /** * seconds — key , EXPIRE key seconds */ public static final String EX = "EX"; /** * set */ public static final String OK = "OK"; /** * (ms ) */ private static final long TIME_OUT = 100; /** * (s) */ public static final int EXPIRE = 60; /** * lua */ public static final String UNLOCK_LUA; static { StringBuilder sb = new StringBuilder(); sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] "); sb.append("then "); sb.append(" return redis.call(\"del\",KEYS[1]) "); sb.append("else "); sb.append(" return 0 "); sb.append("end "); UNLOCK_LUA = sb.toString(); } /** * key */ private String lockKey; /** * key */ private String lockKeyLog = ""; /** * */ private String lockValue; /** * (s) */ private int expireTime = EXPIRE; /** * (ms) */ private long timeOut = TIME_OUT; /** * */ private volatile boolean locked = false; final Random random = new Random(); /** * * * @param redisTemplate * @param lockKey key(Redis Key) */ public RedisLock3(StringRedisTemplate redisTemplate, String lockKey) { this.redisTemplate = redisTemplate; this.lockKey = lockKey + "_lock"; } /** * , * * @param redisTemplate * @param lockKey key(Redis Key) * @param expireTime ( : ) */ public RedisLock3(StringRedisTemplate redisTemplate, String lockKey, int expireTime) { this(redisTemplate, lockKey); this.expireTime = expireTime; } /** * , * * @param redisTemplate * @param lockKey key(Redis Key) * @param timeOut ( : ) */ public RedisLock3(StringRedisTemplate redisTemplate, String lockKey, long timeOut) { this(redisTemplate, lockKey); this.timeOut = timeOut; } /** * * * @param redisTemplate * @param lockKey key(Redis Key) * @param expireTime ( : ) * @param timeOut ( : ) */ public RedisLock3(StringRedisTemplate redisTemplate, String lockKey, int expireTime, long timeOut) { this(redisTemplate, lockKey, expireTime); this.timeOut = timeOut; } /** * * * @return */ public boolean tryLock() { // key lockValue = UUID.randomUUID().toString(); // , long timeout = timeOut * 1000000; // , long nowTime = System.nanoTime(); while ((System.nanoTime() - nowTime) < timeout) { if (OK.equalsIgnoreCase(this.set(lockKey, lockValue, expireTime))) { locked = true; // return true; } // seleep(10, 50000); } return locked; } /** * * * @return */ public boolean lock() { lockValue = UUID.randomUUID().toString(); // ( ms) String result = set(lockKey, lockValue, expireTime); locked = OK.equalsIgnoreCase(result); return locked; } /** * * * @return */ public boolean lockBlock() { lockValue = UUID.randomUUID().toString(); while (true) { // ( ms) String result = set(lockKey, lockValue, expireTime); if (OK.equalsIgnoreCase(result)) { locked = true; return locked; } // seleep(10, 50000); } } /** * *

    * , : *

    * , (non-guessable) , (token)。 * DEL , Lua , , 。 * 。 */ public Boolean unlock() { // // if (locked) { try { return redisTemplate.execute((RedisConnection connection) -> { Object nativeConnection = connection.getNativeConnection(); Long result = 0L; List keys = new ArrayList<>(); keys.add(lockKey); List values = new ArrayList<>(); values.add(lockValue); // if (nativeConnection instanceof JedisCluster) { result = (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, values); } // if (nativeConnection instanceof Jedis) { result = (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, values); } if (result == 0 && !StringUtils.isEmpty(lockKeyLog)) { logger.debug("Redis , {} ! :{}", lockKeyLog, System.currentTimeMillis()); } locked = result == 0; return result == 1; }); } catch (Throwable e) { logger.warn("Redis EVAL , :{}", e.getMessage()); String value = this.get(lockKey, String.class); if (lockValue.equals(value)) { redisTemplate.delete(lockKey); return true; } return false; } } return true; } /** * * * @return * @Title: isLock * @author yuhao.wang */ public boolean isLock() { return locked; } /** * redisTemplate set *

    * SET resource-name anystring NX EX max-lock-time Redis 。 *

    * : *

    * OK , 。 * NIL , , 。 * * @param key Key * @param value * @param seconds ( ) * @return */ private String set(final String key, final String value, final long seconds) { Assert.isTrue(!StringUtils.isEmpty(key), "key "); return redisTemplate.execute(new RedisCallback() { @Override public String doInRedis(RedisConnection connection) throws DataAccessException { Object nativeConnection = connection.getNativeConnection(); String result = null; if (nativeConnection instanceof JedisCommands) { result = ((JedisCommands) nativeConnection).set(key, value, NX, EX, seconds); } if (!StringUtils.isEmpty(lockKeyLog) && !StringUtils.isEmpty(result)) { logger.info(" {} :{}", lockKeyLog, System.currentTimeMillis()); } return result; } }); } /** * redis * * @param key key * @param aClass class * @return T */ private T get(final String key, Class aClass) { Assert.isTrue(!StringUtils.isEmpty(key), "key "); return redisTemplate.execute((RedisConnection connection) -> { Object nativeConnection = connection.getNativeConnection(); Object result = null; if (nativeConnection instanceof JedisCommands) { result = ((JedisCommands) nativeConnection).get(key); } return (T) result; }); } /** * @param millis * @param nanos * @Title: seleep * @Description: * @author yuhao.wang */ private void seleep(long millis, int nanos) { try { Thread.sleep(millis, random.nextInt(nanos)); } catch (InterruptedException e) { logger.info(" :", e); } } public String getLockKeyLog() { return lockKeyLog; } public void setLockKeyLog(String lockKeyLog) { this.lockKeyLog = lockKeyLog; } public int getExpireTime() { return expireTime; } public void setExpireTime(int expireTime) { this.expireTime = expireTime; } public long getTimeOut() { return timeOut; } public void setTimeOut(long timeOut) { this.timeOut = timeOut; } }

    呼び出し方法:
    public void redisLock3(int i) {
        RedisLock3 redisLock3 = new RedisLock3(redisTemplate, "redisLock:" + i % 10, 5 * 60, 500);
        try {
            long now = System.currentTimeMillis();
            if (redisLock3.tryLock()) {
                logger.info("=" + (System.currentTimeMillis() - now));
                // TODO            
                logger.info("j:" + j++);
            } else {
                logger.info("k:" + k++);
            }
        } catch (Exception e) {
            logger.info(e.getMessage(), e);
        } finally {
            redisLock2.unlock();
        }
    }
    
    この種類のredisに対して分散式のロックを実現する方案はやはり問題があります.つまりロックを取ってから業務ロジックを実行するコードはredisロックの有効時間内しかないです.このロックの有効時間は必ず業務に合わせて評価します.
    ソース: https://github.com/wyh-spring-ecosystem-student/spring-boot-student/tree/releases
    spring-book-student-data-redis-distributed-lock工程
    参考:
  • http://www.cnblogs.com/PurpleDream/p/5559352.html
  • https://www.cnblogs.com/0201zcr/p/5942748.html
  • http://zhangtielei.com/posts/blog-redlock-reasoning.html
  • http://strawhatfy.github.io/2015/07/09/Distributed%20locks%20with%20Redis/
  • http://blog.csdn.net/supper10090/article/details/77851512