抢购秒杀处理方案、分析热点数据

一、抢购秒杀处理方案

特点:秒杀活动对稀缺或者特价的商品进行定时定量售卖,吸引成大量的消费者进行抢购,但又只有少部分
消费者可以下单成功。因此,秒杀活动将在较短时间内产生比平时大数十倍,上百倍的页面访问流量
和下单请求流量。

秒杀3阶段
1、秒杀前:用户不断刷新商品详情页,页面请求达到瞬间峰值
2、秒杀开始:用户点击秒杀按钮,下单请求达到瞬时峰值
3、秒杀后:少部分成功下单的用户不断刷新订单或者退单,大部分用户继续刷新商品详情页等待机会

本质:抢购/秒杀主要是解决热点数据高并发读写的问题。

裁剪:抢购/秒杀的过程就是一个不断对请求 ‘剪枝叶’ 的过程。
1、尽可能减少用户到应用服务端的读写请求(客户端做一部分拦截)
2、应用到达服务端的请求要减少对后端存储系统访问(服务端LocalCache拦截一部分)
3、需要请求存储系统的请求尽可能减少对数据库的访问(使用redis拦截绝大多数)
4、最终的请求达到数据库(也可以做一个消息队列兜底、万一后端存储无响应,应用服务器要有兜底方案)

基本原则
1、数据少(静态化、CDN、前端资源合并,页面动静分离,LocalCache)
2、路径短(前端到末端路径可能端、尽量减少对不同系统的依赖,支持限流降级)
3、禁单点(应用服务器无状态化水平扩展、存储服务避免热点)

扣减库存的时机
1、下单减库存(避免恶意下单不付款,保证大并发请求库存数量数据不能负数)
2、付款减库存(下单成功付不了款影响体验)
3、预扣库存超时释放(可以结合Quartz、xxl-job等框架进行清理超时库存,还有做好安全和反作弊)

Redis实现方案
1、String结构:直接使用incr/decr/incrby/decrby,注意:redis目前不支持上下界的限制、如果要避免负数或者关联关系的库存sku扣减只能使用Lua。
2、List结构:每一个商品是一个List,每一个Node是一个库存单元、扣减库存使用lpop/rpop命令,直接返回nil(key not exist)
3、Set/Hash结构:一般用来去重,现在用户只能购买指定格式(hincrby 计数,hget 判断已购买数量)、注意要把用户 UID 映射到多个 key 来读写,一定不能都放到某一个 key 里(热点)
4、业务场景允许情况下,热点商品可以使用多个key、随机选择、用户UID做映射(不同的用户等级也可以设置不同的库存量)

TairString:支持高并发CAS的String
1、携带Version得String:保证并发更新的原子性、通过Version来实现更新,乐观锁、不能与Redis普通String混用
2、使用exIncr/exIncrBy:抢购/秒(有上下界)
3、exSet->exCAS:减少网络交互

二、分析热点数据(JDhotkey)

热点数据产生原因:在拥有大量并发用户的系统中,热key一直以来都是一个不可避免的问题。或许是突然某些商品成了爆款,或许是海量用户突然涌入某个店铺,或许是秒杀时瞬间大量开启的爬虫用户, 这些突发的无法预先感知的热key都是系统潜在的巨大风险。

热点数据有哪些
1 、MySQL等数据库会被频繁访问的热数据
如爆款商品的skuId。
2 、redis的被密集访问的key
如爆款商品的各维度信息,skuId、shopId等。
3 、机器人、爬虫、刷子用户
如用户的userId、uuid、ip等。
4 、某个接口地址
如sku/query或者更精细维度的。
5、 用户id+接口信息
如userId + /sku/query,这代表某个用户访问某个接口的频率。
6 、服务器id+接口信息
如ip + /sku/query,这代表某台服务器某个接口被访问的频率。
7 、用户id+接口信息+具体商品
如userId + /sku/query + skuId,这代表某个用户访问某个商品的频率。

核心功能:热数据探测并推送至集群各个服务器

1 mysql热数据本地缓存
2 redis热数据本地缓存
3 黑名单用户本地缓存
4 爬虫用户限流
5 接口、用户维度限流
6 单机接口、用户维度限流
7 集群用户维度限流
8 集群接口维度限流

热点数据解决方法

我们分别以redis的热key、刷子用户、限流等典型的场景来看。

Redis热key:(本地缓存+淘汰策略或者热点key时推送到jvm)

这种以往的解决方式比较百花齐放,比较常见的有:
1)上二级缓存,读取到redis的key-value信息后,就直接写入到jvm缓存一份,设置个过期时间,设置个淘汰策略譬如队列满时淘汰最先加入的。或者使用guava cache或caffeine cache进行单机本地缓存,整体命中率偏低。
2)改写redis源码加入热点探测功能,有热key实时推送到jvm。问题主要是不通用,且有一定难度。
3)改写jedis、letture等redis客户端的jar,通过本地计算来探测热点key,是热key的就本地缓存起来并通知集群内其他机器。
4)其他
刷子爬虫用户:
常见的有:
1)日常累积后,将这批黑名单通过配置中心推送到jvm内存。存在滞后无法实时感知的问题。
2)通过本地累加,进行实时计算,单位时间内超过阈值的算刷子。如果服务器比较多,存在用户请求被分散,本地计算达不到甄别刷子的问题。
3)引入其他组件如redis,进行集中式累加计算,超过阈值的拉取到本地内存。问题就是需要频繁读写redis,依旧存在redis的性能瓶颈问题。

限流
1)单机维度的接口限流多采用本地累加计数
2)集群维度的多采用第三方中间件,如sentinel
3)网关层的,如Nginx+lua

综上,我们会发现虽然它们都可以归结到热key这个领域内,但是并没有一个统一的解决方案,我们更期望于有一个统一的框架,它能解决所有的对热key有实时感知的场景,最好是无论是什么key、是什么维度,只要我拼接好这个字符串,把它交给框架去探测,设定好判定为热的阈值(如2秒该字符串出现20次),则毫秒时间内,该热key就能进入到应用的jvm内存中,并且在整个服务集群内保持一致性,要有都有,要删全删。

优势

热key问题归根到底就是如何找到热key,并将热key放到jvm内存的问题。只要该key在内存里,我们就能极快地来对它做逻辑,内存访问和redis访问的速度不在一个量级。
譬如刷子用户,我们可以对其屏蔽、降级、限制访问速度。热接口,我们可以进行限流,返回默认值。redis的热key,我们可以极大地提高访问速度。
以redis访问key为例,我们可以很容易的计算出性能指标,譬如有1000台服务器,某key所在的redis集群能支撑20万/s的访问,那么平均每台机器每秒大概能访问该key200次,超过的部分就会进入等待。由于redis的瓶颈,将极大地限制server的性能。
而如果该key是在本地内存中,读取一个内存中的值,每秒多少个万次都是很正常的,不存在任何数据层的瓶颈。当然,如果通过增加redis集群规模的形式,也能提升数据的访问上限,但问题是事先不知道热key在哪里,而全量增加redis的规模,带来的成本提升又不可接受。

在这里插入图片描述

参考:https://gitee.com/jd-platform-opensource/hotkey

参考:https://developer.aliyun.com/learning/trainingcamp/redis/1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值