第一代

静态资源上CDN。

前端部分用户点击一次就置灰,提示”你点击的太频繁了哦”,然后对域名发起请求,DNS解析后打到后端实例上。

后端部分,需要选择一门性能较好的语言,解释型语言比如:Python、PHP肯定就先排除了,考虑到可维护性肯定要选一门使用人数多一点的编译型语言:Go、C++、Java,综合可维护性、开发效率、并发友好度来说,Golang 会是一个比较不错的选择,框架部分可以使用Gin+Gorm。

服务部署的话直接部署到公网机器上即可,数据存储挂一个 MySQL 即可。

MySQL 上个好点的 SSD,RAID 冗余高可用。

会在 5K QPS 的时候,MySQL 开始比较吃力。

第二代: 优化MySQL

在第一代基础上读写分离:Master-Replica-ReadonlySlave,先查读库,还有的话再去查主库加事务下单

MySQL 使用 5.7 的 MySQL-Group-Replication 架构,第二代是单主结构,MGR 实现了 Paxos 分布式共识算法,集群中多个节点可以写入。

第三代:引入Redis

使用 Redis 来作为中心式存储来保存订单数量的数据,单实例可以到 5W QPS,是单机 MySQL 的十倍。

Redis master 读写分离架构一个集群,和 MySQL 一样:Master-Replica-ReadonlySlave,多机共同提供服务,可支撑的 QPS 再上一个量级,到 50W QPS不成问题。

第四代:服务内部限流

很多时候,用户可以接受点了秒杀之后过会儿才返回结果,而秒杀的绝大部分流量都会集中在头一秒钟,可以在服务内部实现一个FIFO限流桶,事先通过压测测量出 Redis 安全水位,每台实例限流 Redis 总安全水位/ 服务实例数量,等待超过 4 秒钟的返回超时错误。

这样一定程度能削减峰值流量平摊到相对低峰期内。

然后当发现库存变零了之后,就在内存中缓存一个值一秒钟,代表已经没有库存了直接返回失败就好,一秒钟过期是因为之后可能有人又释放之前锁定的库存,不过这种设计不是特别的强公平,需要跟业务沟通好需求。

这样一轮下来,整体秒杀服务扛个 200W QPS 不成问题。

可以再加一个终极降级的开关,当系统开始大量报错摇摇欲坠时,比例丢弃一些用户请求。

第五代:Keepalived+LVS+Nginx流量接入高可用

目前我们的系统设计仍然是Go程序直接挂在公网机器上对外提供服务,这样会有很大的问题:

  1. DNS调节反应慢,服务宕机无法迅速摘掉
  2. 浪费公网IP资源
  3. 流量分布不一定均匀

所以我们可以考虑引入七层负载均衡层:Nginx、Caddy、HAProxy等,

Nginx 使用人数最插件丰富多资料最全所以我们优先考虑使用这个,可以使用默认的轮询或者fair算法来实现负载均衡。

单台 Nginx 如果挂了也还是会有问题,我们可以使用 Keepalived IP漂移技术来保证在 DNS 上注册的单个 IP 的高可用。

Nginx 机器多了之后公网 IP 资源和虚IP资源仍然是一笔不小的开销,这个时候我们可以再引入一层四层负载均衡,只解到四层的包,工作更加简单因此可以承担更大的流量,我们只需要部署少量几个公网虚IP即可承担这个系统的流量。

具体选型可以使用 LVS,或者爱奇艺的 DPVS 也是一个不错的选择,不过如果不差钱的话,硬负载均衡器肯定是性能上的首选。

外层可以再挂一层CDN动态加速,构建多机房共同提供服务。

第六代:限流反作弊,去除无用流量

Nginx 可以实现 uid 或者 ip 限流,但是这很明显不是中心式的。

可以收集 Nginx 日志,从中解析出客户端 IP,UID 等信息统一扔到 Kafka 中交由反作弊服务进行实时计算,最终维护一个黑名单的 redis map,服务每次都先去访问一下这个 redis map 如果是异常流量就直接拒绝。

CDN动态加速+多机房分流

IP漂移:一台机器挂了之后,依赖DNS去恢复是非常困难的,可以通过 ARP 实现 IP 漂移来实现机器高可用。Keepalived

四层负载均衡:工作比七层更简单,性能消耗小,有利于收敛所有的TCP/UDP流量

七层负载均衡:Nginx、Caddy、HAProxy、Apache 负载均衡

服务逻辑:Gin