搜尋

首頁  >  問答  >  主體

spring-data-redis - redis並發計數

說明:
用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_u1582phpcn_u15822874 天前1169

全部回覆(2)我來回復

  • 世界只因有你

    世界只因有你2017-04-25 09:05:18

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

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

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

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

    回覆
    0
  • ringa_lee

    ringa_lee2017-04-25 09:05:18

    透過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);

    回覆
    0
  • 取消回覆