架构思考
- 从整体思考问题,秒杀无外乎解决两个核心问题,高并发读,高并发写,对应到架构设计,就是高可用、一致性和高性能的要求。
- 从架构视角来看,秒杀系统本质也是一个高性能、高一致、高可用的三高系统
三高特性的介绍
- 高性能:涉及高并发读和高并发写的支持,如何支撑高并发,如何抵抗高IOPS?核心优化理念其实是类似的:高并发读就尽量"少读"或"读少",高并发写就数据拆分,以便减少并发写出现的冲突。
- 一致性:秒杀的核心关注是商品库存,有限的商品在同一时间被多个请求同时扣减,而且要保证准确性,显而易见是一个难题。如何做到既不多又不少?
- 高可用:大型分布式系统在实际运行过程中面对的工况是非常复杂的,业务流量的突增、依赖服务的不稳定、应用自身的瓶颈、物理资源的损坏等方方面面都会对系统的运行带来大大小小的的冲击。
保障应用在复杂工况环境下还能高效稳定运行,如何预防和面对突发问题,系统设计时应该从哪些方面着手?
(一致性) CAP一致性理论
三者成为了“矛盾论”,而CAP原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾,回到我们的主体:秒杀三要素,它们三个可不完全等同于CAP三要素,甚至比它们的要求更高,甚至是基于前三者的一个更高层次的水平要求。
热点数据
热点数据的处理三步走,一是热点识别,二是热点隔离,三是热点优化。
热点操作
零点刷新、零点下单、零点添加购物车等都属于热点操作。热点操作是用户的行为,不好改变,但可以做一些限制保护,比如用户频繁刷新页面时进行提示阻断。
热点识别
热点数据分为静态热点和动态热点,具体如下:
静态热点
能够提前预测的热点数据。
- 针对于相关历史数据信息分析出热点数据模型
- 通过卖家报名的方式提前筛选;
- 可以通过技术手段提前预测;
- 比如对访问的商品进行大数据计算,然后统计出TOP N的商品,即可视为热点数据。
动态热点
无法提前预测的热点数据。
冷热数据往往是随实际业务场景发生交替变化的,例如,临时做一个广告,就有可能导致一件商品在短时间内被大量购买。
- 此类商品访问较少,即使在缓存系统中一段时间后也会被逐出或过期掉,甚至在db中也是冷数据。
- 瞬时流量的涌入,往往导致缓存被击穿,请求直接到达DB,引发DB压力过大。
实现热点数据的动态发现能力,一个常见的实现思路是:
- 异步采集交易链路各个环节的热点 Key 信息,如 Nginx采集访问URL或 Agent 采集热点日志(一些中间件本身已具备热点发现能力),提前识别潜在的热点数据。
- 聚合分析热点数据,达到一定规则的热点数据,通过订阅分发推送到链路系统,各系统根据自身需求决定如何处理热点数据,或限流或缓存,从而实现热点保护
注意:
- 热点数据采集最好采用异步方式,一方面不会影响业务的核心交易链路,一方面可以保证采集方式的通用性
热点隔离
热点数据识别出来之后,第一原则就是将热点数据隔离出来,不要让 1% 影响到另外的 99%,可以基于以下几个层次实现热点隔离:
- 业务隔离。秒杀作为一种营销活动,卖家需要单独报名,从技术上来说,系统可以提前对已知热点做缓存预热
- 系统隔离。系统隔离是运行时隔离,通过分组部署和另外 99% 进行分离,另外秒杀也可以申请单独的域名,入口层就让请求落到不同的集群中
- 数据隔离。秒杀数据作为热点数据,可以启用单独的缓存集群或者DB服务组,从而更好的实现横向或纵向能力扩展
当然,实现隔离还有很多种办法。比如,可以按照用户来区分,为不同的用户分配不同的 Cookie,入口层路由到不同的服务接口中;再比如,域名保持一致,但后端调用不同的服务接口;又或者在数据层给数据打标进行区分等等,这些措施的目的都是把已经识别的热点请求和普通请求区分开来。
热点优化
热点数据隔离之后,也就方便对这 1% 的请求做针对性的优化,方式无外乎两种:
- 缓存:热点缓存是最为有效的办法。如果热点数据做了动静分离,那么可以长期缓存静态数据(之前在上一篇文章解说过了)
- 限流:流量限制更多是一种保护机制。需要注意的是,各服务要时刻关注请求是否触发限流并及时进行review(之前在上一篇文章已经介绍过了)
DB层面的单行记录做并发排队机制
业界中,阿里的数据库团队开发了针对InnoDB 层上的补丁程序(patch),可以基于DB层对单行记录做并发排队,从而实现秒杀场景下的定制优化——注意,排队和锁竞争是有区别的,如果熟悉 MySQL的话,就会知道 InnoDB 内部的死锁检测,以及 MySQL Server 和 InnoDB 的切换都是比较消耗性能的。
数据高可用
数据库隔离
- 相比上一篇文章的技术隔离机制,秒杀活动持续时间短,瞬时数据量大。为了不影响现有数据库的正常业务,可以建立新的库或者表来处理。
- 在秒杀结束以后,需要把这部分数据同步到主业务系统中,或者查询表中。如果数据量特别巨大,到千万级别甚至上亿,建议使用分表或者分库。
- 由于大量的数据操作是插入,有少部分的修改操作。如果使用关系型数据来存储,建议用专门的表来存放,不建议使用业务系统正在使用的表。
- 数据隔离是必须的,一旦秒杀系统挂了,不会影响到正常业务系统,这个风险意识要有。表的设计除了 ID 以外,最好不要设置其他主键,保证能够快速地插入。
尽量将请求拦截在系统上游传统业务系统之所以挂,请求都压倒了后端数据层,数据读写锁冲突严重,并发高响应慢,几乎所有请求都超时,流量虽大,下单成功的有效流量甚小。
读多写少的常用多使用缓存这是一个典型的读多写少的应用场景,非常适合使用缓存。
数据合并
由于是用的专用表存储,在秒杀活动完毕以后,需要将其和现有的数据做合并。其实,交易已经完成,合并的目的也就是查询。
这个合并需要根据具体情况来分析,如果对于那些“只读”的数据,对于做了读写分离的公司,可以导入到专门负责读的数据库或者 NoSQL 数据库中。
分表分库
MySQL 单表推荐的存储量是 500W 条记录(经验数字),如果估算的时候超过了这个数据,建议做分表。如果服务的连接数较多,建议进行分库的操作。
数据库调优
数据库实现其实是一行存储(MySQL),因此会有大量线程来竞争 InnoDB 行锁。但并发越高,等待线程就会越多,TPS 下降,RT 上升,吞吐量会受到严重影响。具体可以参考 独一无二的「MySQL 调优金字塔」相信也许你拥有了它,你就很可能拥有了全世界。
总结一下
- 确定负载。这个和关键服务的思路一致,不是每个服务都有高负载的,我们的测试其实是要关注那些负载量大的服务,或者是一段时间内系统中某些服务的负载有波动。这些都是测试目标。
- 确定监视点,实际上就是对关注的参数进行监视,例如 CPU 负载,内存使用率,系统吞吐量等等。
- 产生负载,这里需要从生产环境去获取一些真实的数据作为负载数据源,这部分数据源根据目标系统的承受要求由脚本驱动,对系统进行冲击。
「绝密档案」“爆料”完整秒杀架构的设计到技术关键点的“完结收官”!
- 分布式事务系统
- 事务处理去重法
- 事务处理幂等性
- 服务降级分析
- 降低冲击法(延时处理)
- 延时队列机制(单点法)
- 延时队列机制(分布式)