在限量抢购或者限时秒杀类场景中,除了要有效应对秒杀前后的流量高峰,还需要防止发生接受的下单量超过商品限购数量的问题,云原生内存数据库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;
}