利用Redis设计库存系统的苦与乐
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用Redis设计库存系统的苦与乐相关的知识,希望对你有一定的参考价值。
参考技术A 在秒杀等高并发场景下,既要保证库存安全,也要拥有极高的系统性能。从存储结构上,很多同学会选用Redis,毕竟Redis的单线程操作特性,很好地避免了线程安全的问题,同时具备极高的读写性能。我们先来看下库存系统设计的几大核心要点:
1. 库存安全:既要保证线程安全,也要防止出现超卖
2. 同步响应:业务场景基本不允许异步响应库存扣减结果
3. 性能极限:在seckill场景下,性能总是被要求越高越好
我们来看下如何利用Redis来解决上面的三个问题。
一.库存安全
利用Redis来做库存扣减,避免超限的"方法"很多,坑也很多,我们先来看下常用的陷阱有哪些。
1. 先获取当前库存值进行比较,再进行扣减
defdecr_stock():conn=redis_conn()key="productA"current_storage=conn.get(key)current_storage_int=int(current_storage)ifcurrent_storage_int<=0 :return0result=conn.decr(key)returnresult
我们先在Redis中拿到当前的库存值,然后check是否已经扣减到了零,如果已经扣减到了零,则直接return;否则,就利用Redis的decr原子操作进行扣减,同时返回扣减后的库存值。
这种方法的问题很明显,在并发条件下,会出现脏读,设想一个场景,AB两个请求进来,A获取的库存值为1,B获取的库存值为1,然后两个请求都被发到redis中进行扣减操作,然后这种场景下,A最后得到的库存值为0;但是B最后得到的库存值为-1,超限。
2. 先扣减库存,再做比较,跟进情况是否做回滚
defdecr_stock():conn=redis_conn()key="productA"current=conn.decr(key)ifcurrent>=0:returncurrentelse: #回滚库存conn.incr(key)return0
直接先对库存值进行扣减,得到当前的库存值;然后,对此库存值进行check,如果库存>=0,则返回库存值,如果库存<0,则回滚库存,以便于防止负库存量的存在。
Redis Decr命令:DECR 命令会返回键 key 在执行减1操作之后的值。
这种做法引入了两个新的问题:
1).如果大批量的并发请求过来,redis承受的写操作的量,是加倍的,因为回滚库存的存在导致的。所以这种情况下,高并发量进来,极有可能将redis的写操作打出极限值,然后会出现很多redis写失败的错误警告
2). Redis的Decr操作和回滚操作无法保证原子性,在宕机情况下,容易产生数据不一致
3.先扣库存,然后通过整数溢出控制,根据情况进行回滚
defdecr_stock():conn=redis_conn()key="productA"current=conn.decr(key) #通过整数控制溢出的做法ifcheck_overflow(current):returncurrentelse: #回滚库存conn.incr(key)return0 defcheck_overflow(stock): #如果当前库存未被递减到0,则check_number为int类型,isinstance方法检测结果为true #如果当前库存已被递减到负数,则check_number为long类型,isinstance方法检测结果为falsecheck_number=sys.maxint - stockcheck_result=isinstance(check_number,int)returncheck_result
这种做法和方法2类似,只是比对部分由直接和0比对,变成了通过检测integer是否溢出的方式来进行。这样就彻底解决了高并发情况下,直接和零比对,限制不住的问题了。
虽然此种做法,相对于做法二说来,要靠谱很多,但是仍然解决不了在高并发情况下,redis写并发量加倍的问题,极有可能某个促销活动,在开始的那一刻,直接将redis的写操作打出问题来。
4.基于分布式锁的库存扣减
defdecr_stock():key ="productA" lock = getLock(key)iflocked ==1: current_storage = conn.get(key) current_storage_int = int(current_storage)ifcurrent_storage_int<=0:return0 result = conn.decr(key)returnresultelse:return"someone in it"
Redis在2.8以后支持Lua脚本的原子性操作,可以用来做分布式锁,解决超限的问题。
5. All in Lua
defstorage_scenario_six(): conn = redis_conn()lua =""" local storage = redis.call('get','storage_seckill') if storage ~= false then if tonumber(storage) > 0 then return redis.call('decr','storage_seckill') else return 'storage is zero now, can't perform decr action' end else return redis.call('set','storage_seckill',10) end """result = conn.eval(lua,0) print(result)
二、同步响应
如果只用Redis来进行存储,处理完数据直接返回前端即可。如果还要持久化到DB,要尽量避免直接操作DB,因为DB往往是最大的IO瓶颈,如果要异步落库到DB,比如使用MQ。要注意处理Redis扣减和消息发送的原子性处理。
三、性能
官网上redis的读写性能能到10W/QPS左右,这个量级应该可以解决绝大部分的场景。
但是经常有同学在压测的时候达不到这个性能,主要还是卡在网络环境上,在5W/QPS的时候,带宽就超过10M/s了。所有想追求Redis的极致性能,最好还是在同机房进行调用。
残酷美学:创新的苦与乐
1. 创意和行业无关,成熟度越高,创新越难
2. 好莱坞剧本书写指导
3. 情感记忆曲线,极烂和极好记忆最深刻;产品运营时故意设计成体验烂???
4. 注意力 || 忠诚度
5. 淘宝数据:不要放女生正面照片
6. 需求;解决方案;***连接***
7. 破坏式创新
8. 变身:上帝,穴居人,跨界者,布景师,神经病
9. 新疆比基尼卖的最好
10. 所有的游戏必须有文化背景
11. 构建场景(讲故事),需要有人在顾客屁股后踢一脚
用户 + 需求,用场景链接用户和需求
12. 爱情险
每份520,5年后结婚可领999元
13. 扶老人险
14.
活期自动转入 ->攒工资
活期自动转出 -> 还放贷
15. 精神分裂症
诊断标准: 思维奔逸
16. 人的大部分思维都是“浅”意识的
以上是关于利用Redis设计库存系统的苦与乐的主要内容,如果未能解决你的问题,请参考以下文章