說明:
用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);
}
}
世界只因有你2017-04-25 09:05:18
對於這種計數的業務,應先計數,再做業務。如果業務完成,發現計數器已經大於限定值了,就傻眼了。
我的方案是:
由於是並發導致的數據不一致,可以考慮用 Redis 的 INCR
命令增 1,並獲得最新值(這是一次操作,所以不會造成不一致):
如果大於 3,表示已經超過了,結束;
小於等於3,再執行業務(可能還要考慮業務執行失敗回滾計數器)。
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);