秒杀在互联网电商中很常见。比如,淘宝“双十一”,你在和上万人一起抢购一件性价比超高的衣服?春运时我们都会去 12306 订购火车票。在这些高并发场景下都需要保证高性能和高可用。
先思考一下,如果由你来设计高并发秒杀,怎么保证衣服不会超卖?以前在抢票时经常遇到页面打不开的情况,让你来设计 12306 系统,如何保证在千万人访问的同时也能支持正常抢票呢?
其实,秒杀本质上是要解决两个问题 :并发读(减少直接读库),并发写(层层设卡- 保db)。前辈大牛们总结出了“三五”原则(观点参考自许令波《如何设计一个秒杀系统》)。
1.稳:指高可用
做好降级限流措施,防止最坏情况发生。要设计好兜底方案,保证不掉链子,秒杀活动顺利完成。
2.准:指一致性
商品、订单要最终一致性。成交多一台少一台都不行。
3.快:指高性能
整个请求链路上都要做协同的优化,包括:
a. 浏览器优化技术:合理布局,页面缓存,减少http请求数,页面压缩,减少 cookie 传输
b. 设计数据的动静分离方案(秒杀时不需要刷新整个页面,而只需要点击抢购按钮)
c. 热点的发现与隔离(最关键的详情和交易系统增加本地缓存,提前缓存秒杀商品的信息)
d. 请求的削峰与分层过滤(比如秒杀答题提交,消息队列缓冲瞬时流量或线程池加锁等待)
e. 服务端的极致优化(比如部分缓存前置在nginx层,业务代码优化,可能需要牺牲通用性)
1. 数据要尽量少
数据在网络上传输需要时间,服务器在写网络时通常都要做压缩和字符编码,这些都非常消耗CPU。
系统依赖的数据能少就少。因为这些数据一般是和后台服务(涉及数据的序列化和反序列化,增加延时)以及数据库(容易成为一个瓶颈)打交道的。
2. 请求数要尽量少
秒杀页面做好缓存(CSS/JavaScript、图片),减少http请求数,尽量减少“额外请求”。
建立连接要做三次握手,一些请求(例如 JavaScript)还需要串行加载等。另外,如果不同请求的域名不一样的话,还涉及这些域名的 DNS 解析,可能会耗时更久。
3. 路径要尽量短
缩短请求路径不仅可以增加可用性,同样可以有效提升性能(减少中间节点可以减少数据的序列化与反序列化),并减少延时(可以减少网络传输耗时)。
要缩短访问路径有一种办法,就是多个相互强依赖的应用合并部署在一起,把远程过程调用(RPC)变成 JVM 内部之间的方法调用。
4. 依赖要尽量少
依赖一般分为“强”依赖(比如秒杀页面必须强依赖商品信息、用户信息)和“弱”依赖(优惠券、成交列表)。“弱”依赖属于可有可无的信息,在紧急情况下就可以抛弃掉。
要减少依赖,我们可以给系统进行分级,防止重要的系统被不重要的系统拖垮。避免最重要的系统强依赖不那么重要的系统。
例如支付系统不应该强依赖优惠券,在极端情况下可以把优惠券给降级,防止支付系统被优惠券系统给拖垮。
5. 不要有单点
架构上单点是大忌(单点意味着没有备份,风险不可控),设计分布式系统最重要的原则就是“消除单点”。
应用无状态化是有效避免单点的一种方式。但有例外,存储服务本身很难无状态化,因为数据要存储在磁盘上,本身就要和机器绑定,这种场景要冗余多个备份来解决单点问题。
架构是一门平衡的艺术,最好的架构离不开特定的业务场景,抛开业务谈架构一切都将是空谈。如何优化,优化到什么程度需要依量(流量、体量)而行。
秒杀系统演进也是一个渐进的过程,并非一蹴而就,你在工作中积累了哪些经验又踩到了哪些坑呢?欢迎留言讨论。