RedisのSETNXの使用

17042 ワード

一.紹介する


Redisでは、SETNXとは、「SET if Not eXists」の略で、つまり存在しない場合にのみ設定され、それを利用してロックの効果を実現することができます.
SETNX key value
キーの値をvalueに設定し、キーが存在しない場合にのみ使用します.与えられたkeyが既に存在する場合、SETNXは何もしない.

二.オプションコマンド


SETコマンドでは、コマンドの動作を変更するオプションがたくさんあります.以下にSETコマンドで使用可能なオプションの基本構文を示します.
redis 127.0.0.1:6379> SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]

(1)指定した有効期限を秒単位で設定します.
ShellEX seconds −

(2)指定した有効期限をミリ秒単位で設定
PX milliseconds

(3)キーが存在しない場合のみキーを設定
NX

(4)キーが存在する場合のみ設定
XX


redis 127.0.0.1:6379> SET mykey “redis” EX 60 NX OK

Shell上記の例では、キー「mykey」が存在しない場合に、キーの値を60秒で設定します.

三.redisTemplateを使用してSetNxを操作する


1.setNxの設定
  /**
     * @Author lss0555
     * @Description  setNx    
     **/
    @Override
    public boolean setNx(String key,String value, long time) {
        try {
            RedisCallback<String> callback = (connection) -> {
                JedisCommands commands = (JedisCommands) connection.getNativeConnection();
                return commands.set(key, value, "NX", "PX", time);
            };
            String result = redisTemplate.execute(callback);

            return !StringUtils.isEmpty(result);
        } catch (Exception e) {
            logger.error("set redis occured an exception", e);
        }
        return false;
    }

2.getNxの取得
/**
     * @Description  getNx
     **/
    @Override
    public String getNx(String key) {
        try {
            RedisCallback<String> callback = (connection) -> {
                JedisCommands commands = (JedisCommands) connection.getNativeConnection();
                return commands.get(key);
            };
            String result = redisTemplate.execute(callback);
            return result;
        } catch (Exception e) {
            logger.error("get redis occured an exception", e);
        }
        return "";
    }

3.luaスクリプトredisでvalueに一致するkeyを削除
分散ロックの応用ではluaスクリプトを用いてredisにおけるvalueに一致するkeyを削除することで,メソッド実行時間が長すぎるためredisロックが自動的に期限切れになった場合に他のスレッドのロックを誤削除することを避けることができ,spring独自の実行スクリプトメソッドではクラスタモードが実行スクリプトを支えない異常を直接投げ出すため,元のredisのconnectionを取得してスクリプトを実行するしかない.
1.スクリプトを使用するメリット(1)ネットワークオーバーヘッドを削減し、Luaスクリプトでは複数のコマンドを同じスクリプトに配置して(2)原子操作を実行できます.redisはスクリプト全体を1つとして実行し、その間に他のコマンドに挿入されません.(3)クライアントから送信されたスクリプトは常にredisに格納される多重化は,他のクライアントが同じ論理を達成するためにこのスクリプトを多重化できることを意味する.
2.使用例
@Override
    public boolean unlcok(String key,String value) {
        try {
            List<String> keys = new ArrayList<>();
            keys.add(key);
            List<String> args = new ArrayList<>();
            args.add(value);          
            RedisCallback<Long> callback = (connection) -> {
                Object nativeConnection = connection.getNativeConnection();
                //                     ,         ,        
                //     
                if (nativeConnection instanceof JedisCluster) {
                    return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
                }

                //     
                else if (nativeConnection instanceof Jedis) {
                    return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
                }
                return 0L;
            };
            Long result = redisTemplate.execute(callback);
            return result != null && result > 0;
        } catch (Exception e) {
            logger.error("release lock occured an exception", e);
        } finally {
            //    ThreadLocal    ,      
            //lockFlag.remove();
        }
        return false;
    }
    
    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();
    }