业务场景
对某个套餐或产品进行销售,设置待销售品总数量。销售时不能出现超卖情况,即销售品剩余数量不能为负数。
可以分为以下几个步骤:
- 用户请求进入系统:当用户发起请求时,请求会首先进入负载均衡服务器。
- 负载均衡:负载均衡服务器会根据一定的算法将请求分发给后端多台服务器,以达到负载均衡的目的。
- 业务逻辑处理:后端服务器接收到请求后,进行业务逻辑处理,并根据请求的商品数量、用户身份等信息进行校验。
- 库存扣减:如果库存充足,后端服务器会进行库存扣减操作,并生成订单信息,返回给用户成功的信息;如果库存不足,则返回给用户失败的信息。
- 订单处理:后端服务器会将订单信息保存到数据库中,并进行异步处理,例如发送消息通知用户订单状态。
- 缓存更新:后端服务器会更新缓存中的商品库存信息,以便处理下一次请求。
整个过程会多次访问数据库,下单通常是利用行级锁进行访问限制,抢到锁才能查询数据库和下单。但是秒杀时的大量订单请求,往往使数据库访问阻塞。
业务需求
- 系统高并发,极端热门套餐抢购比率只有1%,比如100个销售品在几秒内抢购完,在前端库存判断需要支持上10w的库存快速判断
- 商品不能出现超售情况
- 多个商户进行销售,保证剩余商品数量的数据一致性
需求分析
- 获取商品剩余数量进行条件判断和更新数据操作,是两个独立的操作,中间有可能有其他应用修改数据从而产生冲突。在冲突的发生时,用户需要合并最新的数据,再进行条件判断和数据修改。将两个独立操作封装成一个原子的服务,保证(剩余数量-销售量)之后,商品剩余量不为负数;.
示例方案:使用LUA脚本实现
由于redis是单线程处理,修改数据不需要上锁,调用LUA脚本可视为一个原子的操作,能高效的执行和计算。参考如下:
local n = tonumber(ARGV[1])
if not n or n == 0 then
return 0
end
local vals = redis.call(\"HMGET\", KEYS[1], \"total\", \"booked\", \"remain\");
local booked = tonumber(vals[2])
local remain = tonumber(vals[3])
if booked <= remain then
redis.call(\"HINCRBY\", KEYS[1], \"booked\", n)
redis.call(\"HINCRBY\", KEYS[1], \"remain\", -n)
return n;
end
return 0