当前位置:网站首页>系统设计.秒杀系统
系统设计.秒杀系统
2022-08-04 03:49:00 【闲猫】
秒杀
秒杀是以压倒性优势一招致命或在极短时间(比如一秒钟)内解决对手,或者称瞬秒(瞬间秒杀)。该词最初来自网络游戏,形容一瞬间杀死一个游戏角色之快。
电商系统中的秒杀是指,短时间内抢够商品的场景。这是一个营销策略,通常销售时间区间较短,价格稍低,前期进行了大量宣传,商品优先。以此来引流,用定量的商品价格优惠来换取足够大的影响。
架构中的秒杀是指,为了应付短时间内,大量请求,抢夺资源优先场景的架构模式。
电商秒杀页面
场景:
- 秒杀界面+商品详情+购物车下单
- 秒杀界面+点击“秒杀”界面下单
直观认识一下
点击进入详情页,有的则直接下单。
然后加入购物车,下单,支付。
秒杀业务准备
秒杀虽然可以成为一种模式,但落地起来还是有挺多细微的差异。老规矩,先同再异。
要进行一场秒杀活动,业务需要进行实际的规划,时机选定,提前的营销造势(广告需要得是活动网页的入口,最好能有收藏和提醒功能),根据实际业务流量数据和以往营销经验预测流量增长(如:往日的广告营销费用每W元产生多少收益),设计活动前后策略(活动前是否涨价,活动后是否根据销售情况延长活动等),以及活动当时的造势(如:某包的双十一营销额展示) ,以及组织技术的支持。
秒杀说白了是一种销售活动,而技术只是支撑而已,不存在哪一种架构或者现有系统可以支撑所有秒杀活动,只能做技术上的借鉴而已。原因是决定秒杀的是营销策略,而不是技术。有些场景技术反过来会影响销售策略的决定。
反例:我要搞一次秒杀,流量不确定,你帮忙设计一下这个系统,尽可能无限扩展。分析如下:
- 这类需求 不能直接输入研发进行开发
- 这类决定更似中高层进行的战略任务,需要进行分解才可以落地
- 秒杀活动 ,啥时候搞,实现营销策略,活动指标预测,活动商品品类,物流支持,供应链调度,仓库发货准备,现有系统支持 等,你会发现秒杀活动就不是单单技术能搞定的,需要更高的层次进行组织,而技术上的一些参数就是取决于上面非技术的因素。
- 一个好的战略或者战术行动,需要组织上的保障。如果真的是高层次需求直接输入技术团队,那么技术团队一定是搞不定的。如果你是该技术团队的Leader,你遇到这种情况你会咋办?
下面针对比较现有比较通用的秒杀场景进行技术支持需要进行哪些工作。
问题分析
流程:
- 秒杀活动页面和详情产品页 展示
- 加入购物车下单,15min付款倒计时
- 付款
分析:
- 活动页 PV最高,大部分流量的入口
- 热点商品详情页面展示,活动PV*点击率
- 商品详情 中价格,预约人数 等动态数据 需要从后端接口获取
- 下单需要锁定商品,减库并生成订单
- 库存需要设定下限,如:当前库存1W件,如超卖可以7天内调货或者生成1K件,所以库存可设置为:0~1.1W;或者-1k~1W。
- 下单后,锁定商品15min,如果没有付款则释放商品
- 付款,并改变订单状态
- 没有说出来的需求,系统需要稳定,不能突然崩溃
- 性能尽可能高一点,这样用户体验能好点,活动页面和详情页需要展示,但下单或者付款可以慢一点,但功能必须需要可用
- 秒杀虽然有用户和PV预测,但活动真的超出系统支撑,必须保证系统可用性,并给与用户友好提示
- 再糟糕一点,即使秒杀系统宕机,也不能影响其他功能
问题&方案
活动页展示
活动页可为静态html和海报图片,为了保证访问并发量和速度,后端可以用Nginx做静态数据的代理,并不经过后端。
优化:可以根据实际情况使用CDN
商品详情页
详情页的高并发解决,首先需要分离出活动商品,页面动静分离,然后静态化详情页,并将页面缓存到CDN,动态数据通过接口(数据整合)访问。
分离活动商品:将所有商品进行静态化,并放在CDN进行缓存技术上可行的,商业上不现实。需要将本次活动涉及的商品分离出来,针对这部分数据专门处理。
页面动静分离:一般的详情页面,包含大量的数据是通过接口访问获取的。或者类似JSP模板技术,每次访问都经过组装后才返回,耗时耗力,针对热点商品需要极可能将页面静态化,部分动态数据通过接口来访问。
页面静态化并缓存到CDN:如果涉及大的商城将全部活动的热点商品静态页面放在缓存也是划算的,就需要考虑就哪些放在CDN中,按照什么key还缓存,过期失效,命中率问题。
CDN失效问题:有活动会控制在秒级别活动范围内,超过这个时间就不应该访问到,这种失效的控制对CDN系统要求很高。
数据整合方案:分离出动静态数据之后,前端如何组织数据页就是一个新的问题,主要在于动态数据的加载处理,通常有两种方案:ESI(Edge Side Includes)方案和 CSI(Client Side Include)方案。
ESI 方案:Web 代理服务器上请求动态数据,并将动态数据插入到静态页面中,用户看到页面时已经是一个完整的页面。这种方式对服务端性能要求高,但用户体验较好
CSI 方案:Web 代理服务器上只返回静态页面,前端单独发起一个异步 JS 请求动态数据。这种方式对服务端性能友好,但用户体验稍差
经验:动静分离对于性能的提升,抽象起来只有两点,一是数据要尽量少,以便减少没必要的请求,二是路径要尽量短,以便提高单次请求的效率。
详情数据接口
- 高并发读场景
- 调用链路尽可能短,减少依赖
- 必要时可直接使用servlet编程
- 针对热点数据可实现缓存到redis
- 接口限流
- 可添加黑名单,将恶意刷流量者拉入黑名单
- 拦截无效请求
热点数据
- 热点数据分为 静态和动态数据两种
- 静态数据:大促前可以确定的数据,比如:参与促销的商品,商家等,或者通过技术手段分析出来的TOP N商品数据。
- 动态数据:无法提前预测获取到的数据,冷热数据往往随着场景变动而变化,需要根据实际访问的数量流量统计加载冷数据到缓存,防止redis缓存击穿,流量打到DB上。
- 热点数据发现:
一个常见的实现思路是:
1.异步采集交易链路各个环节的热点 Key 信息,如 Nginx采集访问URL或 Agent 采集热点日志(一些中间件本身已具备热点发现能力),提前识别潜在的热点数据
2.聚合分析热点数据,达到一定规则的热点数据,通过订阅分发推送到链路系统,各系统根据自身需求决定如何处理热点数据,或限流或缓存,从而实现热点保护
需要注意的是:
1.热点数据采集最好采用异步方式,一方面不会影响业务的核心交易链路,一方面可以保证采集方式的通用性
2.热点发现最好做到秒级实时,这样动态发现才有意义,实际上也是对核心节点的数据采集和分析能力提出了较高的要求
热点隔离
热点数据识别出来之后,第一原则就是将热点数据隔离出来,不要让 1% 影响到另外的 99%,可以基于以下几个层次实现热点隔离:
业务隔离。秒杀作为一种营销活动,卖家需要单独报名,从技术上来说,系统可以提前对已知热点做缓存预热
系统隔离。系统隔离是运行时隔离,通过分组部署和另外 99% 进行分离,另外秒杀也可以申请单独的域名,入口层就让请求落到不同的集群中
数据隔离。秒杀数据作为热点数据,可以启用单独的缓存集群或者DB服务组,从而更好的实现横向或纵向能力扩展
当然,实现隔离还有很多种办法。比如,可以按照用户来区分,为不同的用户分配不同的 Cookie,入口层路由到不同的服务接口中;再比如,域名保持一致,但后端调用不同的服务接口;又或者在数据层给数据打标进行区分等等,这些措施的目的都是把已经识别的热点请求和普通请求区分开来。
热点优化
热点数据隔离之后,也就方便对这 1% 的请求做针对性的优化,方式无外乎两种:
缓存:热点缓存是最为有效的办法。如果热点数据做了动静分离,那么可以长期缓存静态数据
限流:流量限制更多是一种保护机制。需要注意的是,各服务要时刻关注请求是否触发限流并及时进行review
高并发读
- 热点数据 高并发读,使用缓存
- 分层校验:请求链路分层过滤请求,只在“漏斗” 最末端进行有效处理,从而缩短系统瓶颈的影响路径
- 读请求不校验秒杀资格,商品专题,答题,验证码等,只在写的时候校验,保持最准数据的一致性
热点操作
- 零点刷新、零点下单、零点添加购物车等都属于热点操作。
- 热点操作是用户的行为,不好改变,但可以做一些限制保护,比如用户频繁刷新页面时进行提示阻断。
- 多次刷新:浏览器缓存js,css,html文件,以及动态的缓存数据,每次刷新获取本地缓存文件;动态数据通过定时器请求;后端对请求进行延迟缓存,比如:某用户是否秒杀到商品结果为3S,只有redis中没有数据才进行查库或者其他处理。
- 下单按钮控制:可以在前端限制秒杀下单(按照界面不同)后,置灰或者enable。
库存一致性
扣除库存时机:
- 下单后即扣库存:可能被人可以下单不支付;可以给一定时间15min内锁定库存;也可限制每个人购买商品件数
- 支付后扣库存:容易超卖,支付后却没有库存,用户体验更差
Mysql直接扣除库存:
- update table1 set count = count -1 where id = 101; 会导致超卖
- update table1 set count = count -1 where id = 101 and account > 1;可能会没有扣库存
- 如果先查询判断,然后在update,操作不原子
- 加分布式锁,通途量上不去
- 数据库写是瓶颈
Redis
- redisClient.incrby(productId, -1); 可能超卖
- get,判断是否>1, incrby 不原子操作
- 加分布式锁,性能上不去
- 方案:可用lua脚本保证上面逻辑在redis端的原子操作
StringBuilder lua = new StringBuilder();
lua.append("if (redis.call('exists', KEYS[1]) == 1) then");
lua.append(" local stock = tonumber(redis.call('get', KEYS[1]));");
lua.append(" if (stock == -1) then");
lua.append(" return 1;");
lua.append(" end;");
lua.append(" if (stock > 0) then");
lua.append(" redis.call('incrby', KEYS[1], -1);");
lua.append(" return stock;");
lua.append(" end;");
lua.append(" return 0;");
lua.append("end;");
lua.append("return -1;");
redisClient.evel(lua.toString());
高并发写
有效性判断
针对一些恶意的刷单操作,可在前端增加验证码,答题等操作;在后端控制每个用户每分钟刷新的次数,是否已下单。
削峰填谷
瞬时流量解决方案MQ;可以使用二级MQ,一级MQ只进行数据的写入;二级MQ进行下单操作。通过JOB补充机制解决消息丢失,重复等问题
极限流量处理:限流
如果流量超过系统吞吐量(包括web+MQ的综合能力),则需要限流
限流类型:同一用户限制;同一IP限制;同一接口限制
技术实现:nginx;后端redis;sentinel第三方组件
延缓请求
使用验证码和答题可以拉长请求
业务上也可以分批秒杀,分散流量
B计划
高可用建设,其实是一个系统工程,贯穿在系统建设的整个生命周期。
具体来说,系统的高可用建设涉及架构阶段、编码阶段、测试阶段、发布阶段、运行阶段,以及故障发生时,逐一进行分析:
架构阶段:考虑系统的可扩展性和容错性,避免出现单点问题。例如多地单元化部署,即使某个IDC甚至地市出现故障,仍不会影响系统运转
编码阶段:保证代码的健壮性,例如RPC调用时,设置合理的超时退出机制,防止被其他系统拖垮,同时也要对无法预料的返回错误进行默认的处理
测试阶段:保证CI的覆盖度以及Sonar的容错率,对基础质量进行二次校验,并定期产出整体质量的趋势报告
发布阶段:系统部署最容易暴露错误,因此要有前置的checklist模版、中置的上下游周知机制以及后置的回滚机制
运行阶段:系统多数时间处于运行态,最重要的是运行时的实时监控,及时发现问题、准确报警并能提供详细数据,以便排查问题
故障发生:首要目标是及时止损,防止影响面扩大,然后定位原因、解决问题,最后恢复服务
对于日常运维而言,高可用更多是针对运行阶段而言的,此阶段需要额外进行加强建设,主要有以下几种手段:
预防:建立常态压测体系,定期对服务进行单点压测以及全链路压测,摸排水位
管控:做好线上运行的降级、限流和熔断保护。需要注意的是,无论是限流、降级还是熔断,对业务都是有损的,所以在进行操作前,一定要和上下游业务确认好再进行。就拿限流来说,哪些业务可以限、什么情况下限、限流时间多长、什么情况下进行恢复,都要和业务方反复确认
监控:建立性能基线,记录性能的变化趋势;建立报警体系,发现问题及时预警
恢复:遇到故障能够及时止损,并提供快速的数据订正工具,不一定要好,但一定要有
在系统建设的整个生命周期中,每个环节中都可能犯错,甚至有些环节犯的错,后面是无法弥补的或者成本极高的。所以高可用是一个系统工程,必须放到整个生命周期中进行全面考虑。同时,考虑到服务的增长性,高可用更需要长期规划并进行体系化建设。
END
边栏推荐
猜你喜欢
【项目实现】Boost搜索引擎
[Medical Insurance Science] To maintain the safety of medical insurance funds, we can do this
网络工程师入门必懂华为认证体系,附系统学习路线分享
机器学习之视频学习【更新】
仿牛客论坛项目梳理
SQL injection in #, - +, - % 20, % 23 is what mean?
Deep learning -- CNN clothing image classification, for example, discussed how to evaluate neural network model
Gigabit 2 X light 8 electricity management industrial Ethernet switches WEB management - a key Ring Ring net switch
Implementing a server-side message active push solution based on SSE
基本表单验证流程
随机推荐
Reproduce 20-character short domain name bypass
【机器学习】21天挑战赛学习笔记(一)
网络工程师入门必懂华为认证体系,附系统学习路线分享
用户与用户互发红包/支付宝C2C/B2C现金红包php源码示例/H5方式/兼容苹果/安卓
三分建设,七分管理!产品、系统、组织三管齐下节能降耗
KingbaseES数据库启动失败,报“内存段超过可用内存”
Mini program + new retail, play the new way of playing in the industry!
"Introduction to nlp + actual combat: Chapter 8: Using Pytorch to realize handwritten digit recognition"
劝退背后。
创新互融|华秋赋能助力OpenHarmony生态硬件开发落地
2022年最新海南建筑八大员(材料员)模拟考试试题及答案
企业直播风起:目睹聚焦产品,微赞拥抱生态
移动端响应式适配的方法
Why use Selenium for automated testing
mq应用场景介绍
函数,递归以及dom简单操作
【id类型和NSObject指针 ObjectIve-C中】
Sfdp 超级表单开发平台 V6.0.5 正式发布
sql注入一般流程(附例题)
SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropri