spring-data-redis - redis並發計數
phpcn_u1582
phpcn_u1582 2017-04-25 09:03:18
0
2
1119

說明:
用redis計數器存取使用者的操作次數,最多3次,但是並發會超過3次,下面的程式碼對嗎?

BoundValueOperations<String, String> operations = redisTemplate.boundValueOps("key1");
        String key1 = operations.get();
        if (StringUtils.isEmpty(key1)) {
            service.do();//这里是业务逻辑操作成功之后,计数器加1
            operations.increment(1);
        } else {
            if (Integer.parseInt(key1) < 2) {
                service.do();//这里是业务逻辑操作成功之后,计数器加1
                operations.increment(1);
            }
        }
phpcn_u1582
phpcn_u1582

全部回覆(2)
世界只因有你

對於這種計數的業務,應先計數,再做業務。如果業務完成,發現計數器已經大於限定值了,就傻眼了。

我的方案是:
由於是並發導致的數據不一致,可以考慮用 Redis 的 INCR 命令增 1,並獲得最新值(這是一次操作,所以不會造成不一致):

  • 如果大於 3,表示已經超過了,結束;

  • 小於等於3,再執行業務(可能還要考慮業務執行失敗回滾計數器)。

左手右手慢动作

透過java客戶端實現上述功能有一定的缺陷,在高並發的情況下,資料可能有不一致的情況;建議使用lua腳本封裝整個邏輯,保證操作的原子性;可以透過SCRIPT LOAD的方式將腳本緩存到伺服器,透過sha1校驗值+參數(Key,ARG)來執行,減輕網路傳輸;

給你一個範例:
1.功能需求:key如果不存在,則set並返回false,並設定超時時間和count=1;達到超時時間或達到指定的count數,返回false 並重新設定超時時間和count=1;如果沒有達到超時時間& 沒有達到指定的count,回傳true,並且incr count

redis.lua腳本如下:

local key = KEYS[1];
local expire = tonumber(KEYS[2]);
local number = tonumber(KEYS[3]);
local count = tonumber(redis.call("GET",key));
if count == nil then
        redis.call("SETEX",KEYS[1],expire,"1");
        return false;
else
        if count +1 >= number then;
                redis.call("SETEX",KEYS[1],expire,"1");
                return false;
        else
                redis.call("INCR",KEYS[1]);
                return true;
        end
end

java程式碼如下:

   public static final String REDIS_LUA = "redis.call('select',1);local key = KEYS[1];local expire = tonumber(KEYS[2]);" +
            "local number = tonumber(KEYS[3]);local count = tonumber(redis.call('GET',key));" +
            "if count == nil then redis.call('SETEX',KEYS[1],expire,'1');return 0;" +
            "else if count +1 >= number then redis.call('SETEX',KEYS[1],expire,'1');return 0;" +
            "else redis.call('INCR',KEYS[1]);return 1;end;end;";
    
    private static final String redisScript;
    

    static {
        try {
            Conf.load();
        } catch (Exception e) {
            e.printStackTrace();
        }
        jedisPool = new JedisPool(Conf.getRedisHost(), Conf.getRedisPort());
        redisScript = loadLuaScript();
    }
   private static String loadLuaScript() {
        Jedis jedis = null;
        String redisScript = null;
        try {
            jedis = jedisPool.getResource();
            redisScript = jedis.scriptLoad(REDIS_LUA);
        } catch (Exception e) {
            if (jedis != null) {
                jedisPool.returnResource(jedis);
            }
        } finally {
            if (jedis != null) {
                jedisPool.returnResource(jedis);
            }
        }
        return redisScript;
    }
    
    
  
调用脚本
 Object result = jedis.evalsha(redisScript, ARGS_LENGTH
                    , key, expireTime, countVal);
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板