场景
超低价预约大型线上活动,在某天9:00~9:15期间,用户可以前往详情页半价预约抢购一款热门商品。根据市场部门的策划方案,这次活动的运营目标是几十万左右的预约量。
设计目标
设计的目标是:用户1分钟内就完成90%的预约量,即90万预约。那么推算出目标的TPS(吞吐量)就是9万/60=1.5万
原来预约就是个简单的功能,并没有做高并发设计。对它做了一次压力测试,结果最大的TPS是2200左右,与需求值差距较大。
思路
写缓存的思路是后台服务接收到用户请求时,如果请求校验没问题,数据并不会直接落库,而是先存储在缓存层中,缓存层中写请求达到一定数量时再进行批量落库。利用写缓存比数据库高几个量级的吞吐能力来承受洪峰流量,再匀速迁移数据到数据库
写请求和批量落库是同步还是异步
优缺点比较
同步
- 优点:用户预约成功后,可在“我的预约”页面立即看到预约数据
- 缺点:用户提交预约后,还需要等待一段时间才能返回结果,且这个时间不定
异步:
- 优点:用户能快速知道提交结果
- 缺点:用户提交完成后,如果查看“我的预约”页面,可能会出现没有数据的情况
复杂度:
同步:写请求提交数据时,写请求的线程被堵塞或者等待,待批量落库完成后再发送信号给写请求的线程,这个线程获得落库完成的信号后,返回预约成功提示给用户
会引申出一系列问题
- 用户要等多久,需要设置时间窗,比如100ms批量落库一次
- 批量落库超时怎么处理 设置写请求阻塞的超时时间
- 批量落库失败 是否重试,重试几次
异步不需要考虑 超时处理、重试,更合适
用户体验优化:预约成功,进入预约详情页,页面定时调接口查批量落库状态,成功弹出成功标志,跳转下一页
如何触发批量落库
两种:
写请求满足特定次数就落库一次,如10个请求
缺点:如果访问数据库的次数未凑齐N次,用户的预约就一直无法落库。
每隔一个时间窗口落库一次,比如每隔一秒落库一次
缺点:如果某个瞬间流量太大,在那个时间窗口落库的数据就会很多
同时采用两种
1)每收集一次写请求,就插入预约数据到缓存中,再判断缓存中预约的总数是否达到一定数量,达到后直接触发批量落库。
2)开一个定时器,每隔一秒触发一次批量落库
![[Pasted image 20240620075523.png]]
缓冲数据存储在哪里
redis
为啥不用MQ:
想要使用批量落库的功能,知道如何一次性从Redis中取多个数据项(mget),但是还没有试过批量消费MQ的消息
缓存层的并发操作注意点
如果多个Insert语句同时执行,它们会根据排队情况按顺序执行,也可以与Select语句并发执行,但是由于减少了IO,会更快
批量落库失败的处理
三个步骤:
- 当前线程从缓存中获取所有数据
- 当前线程批量保存数据到数据库
- 当前线程从缓存中删除对应数据
![[Pasted image 20240620081454.png]]