分布式 秒杀活动

库存的扣减

这里讲一下基本思路,可以通过把一个热 key 拆解成多个 key 的方式,避免热点问题。这种设计涉及到对库存进行再细分,以及子库存挪动,非常复杂,而且边界问题比较多,容易出现少卖或者超卖问题,一般不推荐这种方法。

另一个思路就是对单 SKU 的库存直接在 Redis 单分片上进行扣减,实际上,库存系统在秒杀链路的末端,真正到库存的流量是有限的,单片的 Redis OPS 能承受得了。然后,我们可以针对单 SKU 的库存扣减进行限流,保证库存单片 Redis 的压力。这样双管齐下,单 SKU 的库存 Redis 扣减压力就是可控的了。

秒杀怎么玩

1、预约,活动开始前,先开放一段时间预约,让用户优先进行预约,然后才能获得参加抢购活动资格。
2、风控,踢掉黄牛和不良行为的人。
3、限购,控制个人维度下一段时间内的购买数量。让更多人快乐的抢购。

秒杀的隔离策略

秒杀系统的特点倒逼我们不得不做流量隔离。如果不做隔离,任由流量互相横冲直撞,将会对电商平台造成很大的影响。隔离的措施概括下来有三种:业务隔离、系统隔离和数据隔离。

1、业务隔离
从业务上看,它是和普通商品完全不一样的售卖流程,它需要一个提报过程。大部分的电商平台,会有一个专门的提报系统,商家或者业务可以根据自己的运营计划在提报系统里进行活动提报,提供参与秒杀的商品编号、活动起止时间、库存量、限购规则、风控规则以及参与活动群体的地域分布、预计人数、会员级别等基本信息。
2、系统隔离
理论上讲,需要把交易链路上涉及到的系统都单独复制部署一套,隔离干净,但这样做成本比较高。
所以比较常见的实践是对会被流量冲击比较大的核心系统进行物理隔离,而相对链路末端的一些系统,经过前面的削峰之后,流量比较可控了,这些系统就可以不做物理隔离。
商品详情页,我们需要申请独立的秒杀详情页域名,独立的 Nginx 负载均衡器,以及独立的详情页后端服务,并采用 Dubbo 独立分组的方式单独提供秒杀服务。
3、数据隔离
在数据层面,我们也应该进行相应的隔离,否则如果共用缓存或者共用数据库,一旦瞬时流量把它们冲垮,照样会影响无辜商品的交易。

秒杀的削峰和限流

一、流量削峰
延缓并发请求,甚至过滤掉无效的请求,让真正可以下单的请求越少越好。削峰的本质,一是让服务端处理变得更加平稳,二是节省服务器的机器成本。
常用的削峰手段:验证码、问答题、消息队列、分层过滤和限流。这些方式使流量峰值变得更加平滑,但也在一定程度上降低了抢购体验,容易引发用户咨询和投诉。

验证码和问答题
一是快速拦截掉部分刷子流量,防止机器作弊,起到防刷的作用;二是延缓并发,对流量进行削峰。

消息队列
当服务 A 依赖服务 B 时,正常情况下服务 A 会直接通过 RPC 调用服务 B 的接口,当服务 A 调用的流量可控,且服务 B 的 TP99 和 QPS 能满足调用时,这是最简单直接的调用方式,没什么问题,目前大部分的微服务间调用也都是这样做的。但是,试想一下,如果服务 A 的流量非常高(假设 10 万 QPS),远远大于服务 B 所能支持的能力(假设 1 万 QPS),那么服务 B 的 CPU 很快就会升高,TP99 也随之变高,最终服务 B 被服务 A 的流量冲垮。

二、限流
限流是系统自我保护的最直接手段,再厉害的系统,总有所能承载的能力上限,一旦流量突破这个上限,就会引起实例宕机,进而发生系统雪崩,带来灾难性后果。
限流常用的算法有令牌桶和漏桶。

Nginx 网关层的限流
1、基于 Ip 进行限流。
根据 IP 地址设置限流时要慎重,会存在误杀的情况,特别像公司内用户,他们的出口 IP 就那么几个,很容易就触发了限流,所以我一般在参与阿里、苏宁或京东的秒杀活动时,都会切换到 4G 网络,避免用公司网络。
2、基于用户的 userId 进行限流。

应用层限流
1、线程池限流
Java 原生的线程池原理相信你非常清楚,我们可以通过自定义线程池,配置最大连接数,以请求处理队列长度以及拒绝策略等参数来达到限流的目的。当处理队列满,而且最大线程都在处理时,多余的请求就会被拒绝策略丢弃,也就是被限流了。
2、API 限流
实际上服务提供的 QPS 能力是和后端处理的响应时长有关系的,在并发数恒定的情况下,TP99 越低,QPS 就越高。
基于令牌桶的限流实现,就是验证令牌桶响应极快,QPS高。有令牌桶的用户再进行下单交易,达到限流作用。

降级
1、写服务降级,牺牲数据一致性获取更高的性能
在流量不高的时候,我们的写请求可以直接先落入 MySQL 数据库,再通过监听数据库的 Binlog 变化,把数据更新进 Redis 缓存。
降级:牺牲一致性,先读写Redis缓存,MQ异步写入MySQL。
2、读服务降级,故障场景下紧急降级快速止损
我们给 Redis 缓存之外,又增加了 ES 缓存。当然了,你可以建立多个缓存副本,比如主 Redis 缓存外,再建立副 Redis 缓存,或者再增加 ES 缓存,这些都可以的,不过相应会增加你的资源成本和代码编写的复杂度。
降级:假设当秒杀的 Redis 缓存出现故障时,我们就可以通过降级开关,快速将读请求降级到 ES 上。或者当 Redis 和 ES 同时出现故障时(现实中很少出现同时故障的场景),我们还是可以通过降级开关将流量切换到数据库上,让数据库暂时承压来完成读请求服务。
可见,在做高可用系统设计时,降级路径是多么的重要,它会是你关键时候的保命开关,让你在突发故障时有路可退。
3、简化系统功能,干掉一些不必要的流程,舍弃非核心功能
商品除了基本信息外,还有很多附加的信息,比如(是否收藏)、(收藏总数)、(商品排行榜)、评价、推荐,礼品卡、优惠券。
在秒杀场景下,这些信息是否有必要就需要视情况而定了,秒杀系统要求尽量简单,交互越少,数据越小,链路越短,离用户越近,响应就越快,因此非核心的功能在秒杀场景下都是可以降级的。

热点数据(读热点问题和写热点问题)
1、增加热点数据的副本数;
增加 Redis 从的副本数,
2、让热点数据离用户越近越好。
把热点数据再上移,在 Tomcat 集群做热点数据的本地缓存,也就是让业务层的每个实例里都有份数据副本,读请求数据的时候,无需去 Redis 获取,直接从本地缓存里取。这时候,数据的副本数和 Tomcat 实例一样多,另外请求链路减少了一层,而且也减少了对 Redis 单片 QPS 上限的依赖,具有更高的可靠性和更高的性能。
本地缓存的实现比较简单,可以用 HashMap、Ehcache,或者 Google 提供的 Guava 组件。

容灾
容灾不仅仅是秒杀系统需要考虑的,但凡重要的系统,都要在方案设计时考虑容灾问题。容灾,一般是指搭建多套(两套或以上)相同的系统,当其中一个系统出现故障时,其他系统能快速进行接管,从而持续提供 7*24 不间断业务。
同城双活,异地多活

防刷
1、防刷:Nginx 有条件限流
Nginx 限流 限制IP,UserId 每秒请求数量
2、防刷:Token 机制
对于有先后顺序的接口调用,我们要求进入下个接口之前,要在上个接口获得令牌,不然就认定为非法请求。
同时这种方式也可以防止多端操作对数据的篡改。
Token = UserId+步骤编号+自定义加密key等(商品编号+活动开始时间) 一些信息 做了 MD5。
3、防刷:黑名单机制
黑名单机制分为本地黑名单和集群黑名单两种,接下来我们会重点介绍本地黑名单。该机制顾名思义,就是通过黑名单的方式来拦截非法请求的,但我们的核心问题是黑名单从哪里来呢?
总体来说,有两个来源:一个是从外部导入,可以是风控,也可以是别的渠道;而另一个就是自力更生,自己生成自己用。
实现一套“逮捕机制”,即利用 Lua 的共享缓存功能,去统计 1 秒内这个用户或者 IP 的请求频率,如果达到了我们设定的阈值,我们就认定其为黑产,然后将其放入到本地缓存黑名单。黑名单可以被所有接口共享,这样用户一旦被认定为黑产,其针对所有接口的请求,都将直接被全部拦截,实现刷子流量的 0 通过。

风控
风控的建设过程,其实就是一个不断完善用户画像的过程,而用户画像是建立风控的基础。一个用户画像的基础要素包括手机号、设备号、身份、IP、地址等,一些延展的信息还包括信贷记录、购物记录、履信记录、工作信息、社保信息等等。这些数据的收集,仅仅依靠单平台是无法做到的,这也是为什么风控的建立需要多平台、广业务、深覆盖,因为只有这样,才能够尽可能多地拿到用户数据。
有了这些数据,所谓的风控,其实就是针对某个用户,在不同的业务场景下,检查用户画像中的某些数据,是否触碰了红线,或者是某几项综合数据,是否触碰了红线。而有了完善的用户画像,那些黑产用户,在风控的照妖镜下,自然也就无处遁形了。

限购(在秒杀之前,把不满足限购的用户过滤掉)
1、商品维度限制:最基本的限制就是商品活动库存的限制,即每次参加秒杀活动的商品投放量。如果再细分,还可以支持针对不同地区做投放的场景,比如我只想在北京、上海、广州、深圳这些一线城市投放,那么就只有收货地址是这些城市的用户才能参与抢购,而且各地区库存量是隔离的,互不影响。
2、个人维度限制:就是以个人维度来做限制,这里不单单指同一用户 ID,还会从同一手机号、同一收货地址、同一设备 IP 等维度来做限制。比如限制同一手机号每天只能下 1 单,每单只能购买 1 件,并且一个月内只能购买 2 件等。个人维度的限购,体现了秒杀的公平性。

活动库存扣减方案
1、数据库的行锁机制
这种方式的优点是简单安全,但是其性能比较差,无法适用于我们秒杀业务场景,在请求量比较小的业务场景下,是可以考虑的。
2、分布式锁的方式
Redis分布式锁,设置锁的有效期问题上让人头大。时间短锁不住,时间长业务抛异常就得等,动态修改锁时间业务复杂。
3、Redis Lua 脚本
Lua 脚本并不会自动帮你完成回滚操作,如果存在回滚问题慎用。

  • 44
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值