在限量搶購或者限時秒殺類情境中,除了要有效應對秒殺前後的流量高峰,還需要防止發生接受的下單量超過商品限購數量的問題,Tair(企業版)的TairString資料結構支援簡潔高效的限流器,可以很好地解決訂單超量問題。本文介紹的方案也適用於其它需要限速或者限流的情境。
搶購限流器
exString是Tair新增的資料結構,比原生Redis String功能更加強大,除了位元位(bit)操作外能夠覆蓋原生Redis String的所有功能。
TairString的EXINCRBY/EXINCRBYFLOAT命令與原生Redis String的INCRBY/INCRBYFLOAT命令功能類似,都可對value進行遞增或遞減運算,但EXINCRBY/EXINCRBYFLOAT支援更多選項,例如EX、NX、VER、MIN、MAX等,詳細說明請參見exString。下文介紹的方案涉及MIN與MAX兩個選項:
選項 | 說明 |
MIN | 設定TairString value的最小值。 |
MAX | 設定TairString value的最大值。 |
使用原生Redis String實現搶購,代碼邏輯複雜,一旦管理不當,容易出現漏網訂單,即明明商品已經搶完,卻還有使用者收到搶購成功的提示,造成不良影響,而使用TairString,只需要非常簡單的代碼即可實現嚴謹的訂單數量限制,虛擬碼如下:
if(EXINCRBY(key_iphone, -1, MIN:0) == "would overflow")
run_out();
限流計數器
與搶購限流器類似,使用EXINCRBY命令的MAX選項可以實現限流計數器,虛擬碼如下:
if(EXINCRBY(rate_limitor, 1, MAX:1000) == "would overflow")
traffic_control();
限流計數器的應用情境很多,例如並發限流、訪問頻率限制、密碼修改次數限制等等。以並發限流為例,在請求的並發量突然超過系統的效能限制時,為了防止服務徹底崩潰引發更大的問題,採用限速器限制並發量,保證系統處理能力內的請求得到及時回應,是一種較合理的臨時解決方案。例如配置QPS(Query Per Second)限制,您可以使用TairString的EXINCRBY命令,通過簡單的代碼設定一個並發限流器:
/**
* tryAcquire is thread-safe and will increment the key from 0 to the upper bound within an interval of time,
* and return failure once it exceeds
* @param key the key
* @param upperBound the max value
* @param interval the time interval
* @return acquire success: true; fail: false
*/
public static boolean tryAcquire(String key, int upperBound, int interval) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.eval("if redis.call('exists', KEYS[1]) == 1 "
+ "then return redis.call('EXINCRBY', KEYS[1], '1', 'MAX', ARGV[1], 'KEEPTTL') "
+ "else return redis.call('EXSET', KEYS[1], 0, 'EX', ARGV[2]) end",
Arrays.asList(key), Arrays.asList(String.valueOf(upperBound), String.valueOf(interval)));
return true;
} catch (Exception e) {
if (e.getMessage().contains("increment or decrement would overflow")) {
return false;
}
e.printStackTrace();
}
return false;
}