日均百万订单下的高可用苏宁拼购系统架构设计 朱羿全

QCon大会

2019/06/25 发布于 技术 分类

QCon  QCon2019 

文字内容
1. 高可用苏宁拼购系统架构设计 朱羿全 苏宁科技高级技术经理
2. 自我介绍
3. 自我介绍 朱羿全 苏宁科技高级技术经理 • 南京航空航天大学硕士研究生 • 苏宁易购消费者研发中心高级技术经理,主要负责易购各系统架构 优化与大促保障工作。 先后参与主持了易购整站 Https 改造、先知业务监控平台建设、苏宁 拼购平台化技术架构升级等工作。 专注于打造高可靠、高性能、高并发服务系统的技术研究
4. 目录/content 1 2 苏宁拼购系统遇到的瓶颈(要解决什么问题?) 第一阶段 分布式高可用系统设计 • 分层、分割、分布式 • 消息队列应用 • 缓存架构设计 • 数据库架构设计 3 第二阶段 多活系统设计 • 多活服务路由策略 • 多活切换策略 • 多活恢复策略
5. 系统架构难以支撑业务发展 系统架构难以为继 导致 单数据库每天数据量增长超 1亿,峰值超10万Qps。平均 千万级日活用户 千万级日均新增sku 千万级日均订单 每一个月需要进行一次数据 迁移。
6. 服务可用性要求达到99.999% 1年 = 365天 = 8760小时 99.9 = 8760 * 0.1% = 8760 * 0.001 = 8.76小时 99.99 = 8760 * 0.0001 = 0.876小时 = 0.876 * 60 = 52.6分钟 99.999 = 8760 * 0.00001 = 0.0876小时 = 0.0876 * 60 = 5.26分钟
7. 所以要解决的问题 TPS(Transcation per Second) > 100,000 , RT(Response Time) < 50ms SLA(Service Level Agreement) = 99.999%. Problems to be solved
8. SOLUTION 单系统部署 > 多系统分布式部署 > 多活机房部署
9. 第一阶段 单系统 进阶 多系统分布式高可用
10. 拼购原有单系统部署架构 高耦合低内聚 拼购系统 商品活动 频道类目 单数据库 购物车 负载均衡 订单 营销玩法 单redis
11. 分层、分割、分布式 分层:比如数据层通过苏宁自研的数据库中间件实现水平扩展 分割,即业务系统垂直拆分,将一块相对复杂的业务分割成不同的模块单元 DAL APP DAL APP SNDP APP 订单系统 水平拆分 DB 前台 购物车 垂直拆分 DB#1 DB#2 数据库拓扑发现 (SNDS) 购物车 微服务框架 (RSF) 库存 物流 库存 物流 分布式应用和服务,将分层或者分割后的业务分布式部署,独立的应用服务器,数据库,缓存服务器 订单系统
12. 多系统分布式部署 苏宁易购APP 微信小程序 苏宁拼购APP 公众号 广告推广页 其他小程序 负载均衡 • 提供统一服务入口和出口 • 聚合后台服务,节省流量,提升性能 拼购API网关层 网关层 • 非功能特性:安全、权限、流控 • 提供轻量级营销玩法:签到、新人礼 原拼购系统 业务 商品活动 订单 拼购轻应用前台 购物车 频道类目 • 与微信、百度、今日头条等第三方 对外交互 营销玩法 前台 包、膨胀红包 API交互 微服务框架RSF 拼购活动中台 服务 中台 活动服务 库存服务 销量服务 括常规活动创建和下发、 • 提供团相关服务,包括提交订单后生 拼购团中台 • 提供活动相关服务,包 成对应订单、团、团详情信息,支付 订单服务 团服务 批量查询活动、活动库 团详情服务 完成或者取消订单时更新对应信息的 状态等 存扣减 分 数据库中间件SNDP • SNDP 实现了数据分片、 数据库中间件SNRS 管理分布式数据库集群 布 式 数 据 层 活动中台分布式数据库 团中台分布式数据库 活动中台redis数据库 团中台redis数据库 • SNRS实现了应用层与redis集群交互
13. 消息队列的应用 解耦 异步 削峰
14. 拼团逻辑的异步化处理 利用消息队列异步处理订单、团的生成,对流量削峰,让系统可以平缓的处理突增请求 开团用户 PGS 拼购前台完成业务校验 rs f Push Pop PGC PGS S WindQ消息队列 参团用户 消息列队异步化 D B 多线程处理
15. 如何保证消息队列幂等性? 基于redis实现分布式锁 加锁流程 问题: SET lock_key lock_value NX PX max-lock-time • Redis分布式锁过期时间的设置。时间短了,持有锁的线程A还未处理完 解锁流程,lua脚本保障原子性 if redis.get(“lock_key”) == “ lock_value” return redis.del(”lock_key") else return 0 成,锁过期,线程B获得锁,导致资源占用冲突时间过长,如果持有锁的进程宕 机,造成其他等待锁的进程长时间无效等待 • failover问题。假如Redis节点宕机了,那么所有客户端就都无法获得锁了,服 务变得不可用。为了提高可用性,我们可以给这个Redis节点挂一个Slave,当 Master节点不可用的时候,系统自动切到Slave上(failover)。但由于Redis的主 从复制(replication)是异步的,这可能导致在failover过程中丧失锁的安全性。
16. RedLock分布式锁 解决failover问题,N的值越大,安全级别越高,然而性能开销损失越多 线程A 对N个完全独立的redis节点分别获取锁 lock=A lock=A lock=A lock=A lock=A 当N/2+1个节点获取到了锁,才认为最终获取了锁 否则客户端立即向所有redis发起释放锁操作 redis 1 redis 2 redis 3 redis 4 redis 5
17. 基于ZooKeeper的分布式锁 “脑裂”问题:持有锁的线程 A 僵死或网络故障,导致服务端长时间收不到来自客户端的保活心跳,服务端认为客户端进程不 存活主动释放锁,线程 B 抢到锁,线程 A 恢复,同时有两个线程访问共享资源。 获得锁 Parentlock Client 1 Client 2 watch Lock 1 Client 3 watch Lock 2 Lock 3
18. 苏宁拼购的分布式锁实现 综合安全与性能考虑,基于redis分布式锁实现Cluster模式的分布式锁,避免单组redis热点瓶颈。 同时,过期时间动态调整,取决于服务的最大响应时间*2。 redis cluster 同步 lock=A redis_1_master 线程A getShardInfo redis_1_slave 同步 redis_2_master redis_2_slave 同步 redis_3_master redis_3_slave
19. 如何保证拼团消息的可靠性传输? 生产者与队列之间可靠传输 生产者发消息到 MQ,MQ 收到消息后返回确认信息 (ACK)给生产者,生产者收到确认信息后生产过 队列与消费者之间可靠传输 消费者从 MQ 获取到消息后,当业务逻辑处理完 成,向 MQ 返回 ACK 信息。否则,MQ将向消 程完成,如果在一定时间内,生产者没有收到确认信 息,生产者重新发送消息。 重新发送的消息仍然异常会记录异常日志。 费者重发消息
20. 如何解决数据积压的问题? 假如因为Consumer问题,导致队列积压了上千万数据: • 先修复 Consumer 的问题,确保其恢复消费速度,然后将现有 Consumer 都停掉。 • 新建一个 Topic,Partition 是原来的 10 倍,临时建立好原先 10 倍或者 20 倍的 queue 数量。然后 写一个临时的分发数据的 Consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗 时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue。 • 接着临时征用 10 倍的机器来部署 Consumer,每一批 Consumer 消费一个临时 queue 的数据。 • 这种做法相当于是临时将 queue 资源和 Consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数 据。 • 等快速消费完积压数据之后,再恢复原先部署架构,重新用原先的 Consumer 机器来消费消息。
21. 缓存架构的设计 降低数据库压力 提高整体性能
22. Cache Aside Pattern 旁路缓存方案 读请求处理: service 写请求处理: 1.get cache service 3.set 2.read 2.write db-master db-slave Cache miss read 0. sync db-master 1.del cache
23. 拼团逻辑中的数据一致性问题 写后立刻读,脏数据入缓存 优化: • 强一致性场景:走主库查询 service 1.get(miss) cache • 最终一致性场景: 1.get(miss) service 4.set 4.set 2.read 2.read db-master db-master db-slave cache 3. sync db-slave 5.binlog cannal 3.sync 6.del
24. “减库存”中缓存的应用 问题: 由于Mysql innodb独占锁,对同一热点商品行的库存update操作,会造成大量锁等待。导致 单个热点商品会影响整个数据库的性能,导致0.01%的商品影响99.99%的商品的售卖。 设计库存缓存:Key = {ALL, LOCK, REMAIN} 其中,ALL表示商品全量库存,LOCK表示可锁库存,REMAIN表示剩余库存
25. “减库存”中缓存的应用 支付前检查 开团用户 1、扣可锁库存 2、库存锁 支付回调 完成支付 删除库存锁 支付前检查 支付回调 判断 成团 PUSH WindQ消息队列 参团用户 1、扣可锁库存 2、库存锁 删除库存锁 库存锁过期 扫描 DB 定时任务 恢复锁库存 POP 扣减剩余库存 多线程处理 DB
26. 数据库架构设计 突破TPS瓶颈
27. 数据库中间件SNDP的应用 问题: • 单数据库服务器的磁盘IO、网络带宽、CPU负载和内存消耗有限 • Mysql单表数据量超过1000W,性能明显下降,服务器负载太高甚至会导致数据库宕机的情况 拼购使用数据库中间件实现分库分表,读写分离。 select * from pg_order dn1 where memberId= 60000001 memberId= 60000001 DB1@Mysql1 SNDP dn2 memberId= 60000002 DB2@Mysql2 dn3 DB3@Mysql3 memberId= 60000003
28. 如何实现全局唯一ID? 基于SnowFlake 算法,通过机器IP生成机器码,调整位数和纪元 时间,生成适合拼购业务的固定16位全局唯一ID 5bit-序列号 41bit-时间戳 第一个部分,是 1 个 bit:固定0,表示正整数。 第二个部分是 41 个 bit:表示的是时间戳。纪元时间设置 0 - 00000000 00000000 00000000 00000000 00000000 0 - 00000000 00 - 为1420041600000L 第三个部分是 10个 bit:表示的是机器IP信息。 第四个部分是 5 个 bit:表示的序号,就是指定IP机器上这 一毫秒内同时生成的 id 的序号。 10bit-机器IP信息 00000
29. 如何解决复杂查询的问题? 方案一:多分片字段约束 应用场景:针对拼团活动表pg_activity • 根据活动ID批量查询活动 • 商品主数据变更时,根据商品编码查询涉及到的活 动 ACT_ID,item_code mod_1024 4 1024
30. 如何解决复杂查询的问题? 方案二:全量库同步 db_shard_01 pg_order_00 应用场景:拼团成功以后,定时job级联 pg_order_08 查询团满表pg_full_group和订单表 pg_order_1016 pg_order,处理未支付订单,返还积分和 券 … db_shard_02 pg_order_01 pg_order_09 … pg_order_1017 … db_shard_08 pg_order_07 pg_order_15 … pg_order_1023 db_full 数据同步 N:1 pg_order
31. 如何解决复杂查询的问题? 方案三:其他持久化存储方式替代 应用场景:批量处理过期团和过期活动,先通过ES检索时间内的过期数据,再清理DB和ES APP 业务DB APP 业务DB 业务ES RDRS
32. 第二阶段 进阶 多活部署
33. 多活服务路由策略 独占型服务 共享型服务 竞争型服务
34. 独占型服务 用户A 用户B MA_cookieA MA_cookieB • 未登录状态下随机路由 CDN A请求 • 用户每一次鉴权会判断MA值变化 B请求 机房1 以会员为纬度,不同会员被精确调度到指定服 Cell 0 Cell 1 • CDN根据MA _cookie调度 机房2 Cell M Cell M+1 …… Cell N 务单元Cell,典型场景如购物车、订单、支付 等不同Cell之间数据以副本形式,单向同步。 1/N数据分片 1/N数据分片 1/N数据分片 机房1数据库 机房2数据库副本 1/N数据分片 1/N数据分片 1/N数据分片 机房1数据库副本 RDRS 机房2数据库
35. RDRS数据同步 目标表1复制 Source DB Binlog 增量抽取 目标表2复制 消息列队 目标表3复制 Clients SDK Clients SDK Clients SDK TCP Binlog 消费服务 Target DB
36. 共享型服务 用户B 用户A 共 享 型 服 务 所 有 服 务 单 元 Cell 拥 有 相 同 的 数 CDN 据,相互共享。 A请求 B请求 机房1 典型场景如商品信息、价格信息等查询服务。 Cell 1 Cell 0 机房2 Cell M Cell M+1 …… Cell N 读取 M 100%数据 写入 后台管理 共享型数据存储 RDRS S 100%数据
37. 竞争型服务 竞争型服务只有一个数据中心的Cell提供服务, 用户B 用户A CDN 另一个数据中心只作为灾备服务。 B请求 A请求 典型场景如库存扣减。 机房2 竞争型服务统一路由到机房 1,通过RSF路由、MQ等路 由方式 Cell M+1 …… Cell N 机房1 Cell 0 Cell 1 S Cell M 读取 100%数据 M RDRS 100%数据
38. 如何处理组合服务? 当一个服务中包含多种类型的子服务称之为组合服务,其服务路由策略: 共享型服务 + 独占型服务 = 独占型服务 共享型服务 + 竞争型服务 = 竞争型服务 竞争型服务 + 独占型服务 需要引入TCC分布式事务处理
39. “竞争型+独占型”典型场景:用户拼团 用户拼团是拼购最核心的场景逻辑,在 用户A 多 人 拼团的过程中 会生 成 订 单 和 团 数 用户B 据。 参团 开团 • 其中订单数据是到用户纬度的,分别 记录用户的订单信息和团信息,属于 独占型数据; 生成 生成 团 订单A 提交事务 • 而团数据是竞争型的,记录了团的状 态、成团人数等信息。 开启事务 开启事务 修改团状态 生成 团 订单B 提交事务
40. TCC分布式事务 1.tryX成功 tryX confirmX 主业务服务 TCC事务满足BASE(基本可用、软状态、最终一致性) TCC是一个两阶段事务,事务管理器通过Try、Confirm、 Cancel接口来协调多个服务,保证事务最终一致性。 数据库 A cancelX 从 业 务 服 务 2.tryY成功 数据库 启动业务活动 登记业务操作 提交/回滚业务活动 3.confirmX成功 tryY 业务活动 管理器 4.confirmY成功 cancelY B 日志 confirmY 从 业 务 服 务 数据库
41. TCC应用到“竞争型+独占型”服务 团服务属于竞争型服务,由机房1提供 用户A 订单服务属于独占型服务,由机房1、2提供MA提供 开团 参团 用户B CDN 请求A 1.Try用户A订单状态“UPDATING” 2. Try团状态“UPDATING” 3. Confirm用户A订单生成 4. Confirm团生成 5. Try用户B订单状态“UPDATING” 6. Try团状态“UPDATING” 请求B 机房2 机房1 拼购前台 拼购前台’ 事务 管理器 事务 管理器 3 1 订单 服务 4 2 6 7 团 服务 团 服务 5 订单 服务 8 7. Confirm用户B订单生成 8. Confirm团状态更新 订单数据库 订单数据库’ 团数据库 副本 团数据库’ 订单数据库’订单数据库 副本 RDRS
42. 多活机房切换 操作 人员 切换管理平台(B) ACM/Salt(B) SCM(B) SNDS(B/C) SNDP(B/C) 缓存(B/C) MQ/Kafka(B/C) RDRS(B/C) RSF(B/C) DNS(B/C) DC_A故障后通过办公网 络DNS切换到DC_B() 触发基础组件管理节点进行切换,管理节点通过ACM进行切换执行,并进行数据同步前置检查 暂停SLB/RSF/MQ/MyCAT所有请求30sec再进行下面处理,并关闭所有请求约10min直至切换完成 修改主DC标识位(可选) Par数据库切换和CDN切换并行处理 修改CDN对应数据中心 切换DC_B& DC_C的读写数据库 切换SNDP对接的读写数据库 切换DC_B& DC_C的缓存系统 切换DC_B& DC_C的MQ/Kafka集群 切换DC_B& DC_C数据库抽取工具所对接的数据库 Par并行切换RSF和DNS 切换RSF并使之配置生效 切换DC_B& DC_C DNS业务系统域名映射 切换DC_B& DC_C SLB/WAF/DUTS 使得生效 SLB/WAF/DUTS(B/C) CDN
43. 多活机房恢复 SNDS/DBMS 管理员 SNRS Kafka/WindQ 数据恢复() 如果数据可增量恢复,则采用增量恢复,否则采用全量备份恢复() 全量恢复缓存数据() 数据恢复() 全链路/单链路切换() 切换管理平台
44. 总结 ü 第一个阶段,重点达成了TPS和RT的目标(TPS > 100,000 RT < 100ms) ü 第二个阶段,重点达成了SLA的目标(SLA = 99.999%) ü 秉持“灰度原则”和“可降级原则”来做系统升级和架构设计 ü 系统能力提升以压测数据说话,压测场景最大程度还原真实用户场景 ü 系统高可用性以演练效果说话,“随时随地”进行应急预案演练