50万奖金+官方证书,深圳国际金融科技大赛正式启动,点击报名 了解详情
写点什么

架构演进实践: 从 0 到 4000 高并发请求背后的努力!

  • 2020-06-20
  • 本文字数:4501 字

    阅读完需:约 15 分钟

架构演进实践:从0到4000高并发请求背后的努力!

来自:即时通讯网


http://www.52im.net/thread-2141-1-1.html


达达创立于 2014 年 5 月,业务覆盖全国 37 个城市,拥有 130 万注册众包配送员,日均配送百万单,是全国领先的最后三公里物流配送平台。达达的业务模式与滴滴以及 Uber 很相似,以众包的方式利用社会闲散人力资源,解决 O2O 最后三公里即时性配送难题(2016 年 4 月,达达已经与京东到家合并)。


达达的业务组成简单直接——商家下单、配送员接单和配送,也正因为理解起来简单,使得达达的业务量在短时间能实现爆发式增长。而支撑业务快速增长的背后,正是达达技术团队持续不断的快速技术迭代的结果,本文正好借此机会,总结并分享了这一系列技术演进的第一手实践资料,希望能给同样奋斗在互联网创业一线的你带来启发。

技术背景

达达业务主要包含两部分:


  • 商家发单;

  • 配送员接单配送;


达达的业务逻辑看起来非常简单直接,如下图所示:



达达的业务规模增长极大,在 1 年左右的时间从零增长到每天近百万单,给后端带来极大的访问压力。压力主要分为两类:读压力、写压力。读压力来源于配送员在 APP 中抢单,高频刷新查询周围的订单,每天访问量几亿次,高峰期 QPS 高达数千次/秒。写压力来源于商家发单、达达接单、取货、完成等操作。达达业务读的压力远大于写压力,读请求量约是写请求量的 30 倍以上。


下图是达达在成长初期,每天的访问量变化趋图,可见增长极快:



下图是达达在成长初期,高峰期请求 QPS 的变化趋势图,可见增长极快:



极速增长的业务,对技术的要求越来越高,我们必须在架构上做好充分的准备,才能迎接业务的挑战。接下来,我们一起看看达达的后台架构是如何演化的。

最初的技术架构:简单直接

作为创业公司,最重要的一点是敏捷,快速实现产品,对外提供服务,于是我们选择了公有云服务,保证快速实施和可扩展性,节省了自建机房等时间。在技术选型上,为快速的响应业务需求,业务系统使用 Python 做为开发语言,数据库使用 MySQL。


如下图所示,应用层的几大系统都访问一个数据库:


中期架构优化:读写分离

数据库瓶颈越来越严重

随着业务的发展,访问量的极速增长,上述的方案很快不能满足性能需求:每次请求的响应时间越来越长,比如配送员在 app 中刷新周围订单,响应时间从最初的 500 毫秒增加到了 2 秒以上。业务高峰期,系统甚至出现过宕机,一些商家和配送员甚至因此而怀疑我们的服务质量。在这生死存亡的关键时刻,通过监控,我们发现高期峰 MySQL CPU 使用率已接近 80%,磁盘 IO 使用率接近 90%,Slow Query 从每天 1 百条上升到 1 万条,而且一天比一天严重。数据库俨然已成为瓶颈,我们必须得快速做架构升级。


如下是数据库一周的 qps 变化图,可见数据库压力的增长极快:


我们的读写分离方案

当 Web 应用服务出现性能瓶颈的时候,由于服务本身无状态(stateless),我们可以通过加机器的水平扩展方式来解决。而数据库显然无法通过简单的添加机器来实现扩展,因此我们采取了 MySQL 主从同步和应用服务端读写分离的方案。


MySQL 支持主从同步,实时将主库的数据增量复制到从库,而且一个主库可以连接多个从库同步。


利用 MySQL 的此特性,我们在应用服务端对每次请求做读写判断:


  • 若是写请求,则把这次请求内的所有 DB 操作发向主库;

  • 若是读请求,则把这次请求内的所有 DB 操作发向从库。



实现读写分离后,数据库的压力减少了许多,CPU 使用率和 IO 使用率都降到了 5%内,Slow Query 也趋近于 0。


主从同步、读写分离给我们主要带来如下两个好处:


  • 减轻了主库(写)压力:达达的业务主要来源于读操作,做读写分离后,读压力转移到了从库,主库的压力减小了数十倍;

  • 从库(读)可水平扩展(加从库机器):因系统压力主要是读请求,而从库又可水平扩展,当从库压力太时,可直接添加从库机器,缓解读请求压力。


如下是优化后数据库 QPS 的变化图:


读写分离前主库的 select QPS



读写分离后主库的 select QPS


新状况出现:主从延迟问题

当然,没有一个方案是万能的。


读写分离,暂时解决了 MySQL 压力问题,同时也带来了新的挑战:


  • 业务高峰期,商家发完订单,在我的订单列表中却看不到当发的订单(典型的 read after write);

  • 系统内部偶尔也会出现一些查询不到数据的异常。


通过监控,我们发现,业务高峰期 MySQL 可能会出现主从延迟,极端情况,主从延迟高达 10 秒。


那如何监控主从同步状态?在从库机器上,执行 show slave status,查看 Seconds_Behind_Master 值,代表主从同步从库落后主库的时间,单位为秒,若同从同步无延迟,这个值为 0。MySQL 主从延迟一个重要的原因之一是主从复制是单线程串行执行。


那如何为避免或解决主从延迟?我们做了如下一些优化:


  • 优化 MySQL 参数,比如增大 innodb_buffer_pool_size,让更多操作在 MySQL 内存中完成,减少磁盘操作;

  • 使用高性能 CPU 主机;

  • 数据库使用物理主机,避免使用虚拟云主机,提升 IO 性能;

  • 使用 SSD 磁盘,提升 IO 性能。SSD 的随机 IO 性能约是 SATA 硬盘的 10 倍;

  • 业务代码优化,将实时性要求高的某些操作,使用主库做读操作。

主库的写操作变的越来越慢

读写分离很好的解决读压力问题,每次读压力增加,可以通过加从库的方式水平扩展。但是写操作的压力随着业务爆发式的增长没有很有效的缓解办法,比如商家发单起来越慢,严重影响了商家的使用体验。我们监控发现,数据库写操作越来越慢,一次普通的 insert 操作,甚至可能会执行 1 秒以上。



可见磁盘 IO 使用率已经非常高,高峰期 IO 响应时间最大达到 636 毫秒,IO 使用率最高达到 100%


同时,业务越来越复杂,多个应用系统使用同一个数据库,其中一个很小的非核心功能出现 Slow query,常常影响主库上的其它核心业务功能。


我们有一个应用系统在 MySQL 中记录日志,日志量非常大,近 1 亿行记录,而这张表的 ID 是 UUID,某一天高峰期,整个系统突然变慢,进而引发了宕机。监控发现,这张表 insert 极慢,拖慢了整个 MySQL Master,进而拖跨了整个系统。(当然在 MySQL 中记日志不是一种好的设计,因此我们开发了大数据日志系统。另一方面,UUID 做主键是个糟糕的选择,在下文的水平分库中,针对 ID 的生成,有更深入的讲述)。

进一步对主库进行拆分,优化主库写操作慢的问题

这时,主库成为了性能瓶颈,我们意识到,必需得再一次做架构升级,将主库做拆分:


  • 一方面以提升性能;

  • 另一方面减少系统间的相互影响,以提升系统稳定性;


这一次,我们将系统按业务进行了垂直拆分。


如下图所示,将最初庞大的数据库按业务拆分成不同的业务数据库,每个系统仅访问对应业务的数据库,避免或减少跨库访问:



下图是垂直拆分后,数据库主库的压力,可见磁盘 IO 使用率已降低了许多,高峰期 IO 响应时间在 2.33 毫秒内,IO 使用率最高只到 22.8%:



未来是美好的,道路是曲折的。


垂直分库过程,也遇到不少挑战,最大的挑战是: 不能跨库 join,同时需要对现有代码重构 。单库时,可以简单的使用 join 关联表查询;拆库后,拆分后的数据库在不同的实例上,就不能跨库使用 join 了。


比如在 CRM 系统中,需要通过商家名查询某个商家的所有订单,在垂直分库前,可以 join 商家和订单表做查询,如下如示:



分库后,则要重构代码,先通过商家名查询商家 id,再通过商家 Id 查询订单表,如下所示:



垂直分库过程中的经验教训,使我们制定了 SQL 最佳实践,其中一条便是 程序中禁用或少用 join,而应该在程序中组装数据,让 SQL 更简单 。一方面为以后进一步垂直拆分业务做准备,另一方面也避免了 MySQL 中 join 的性能较低的问题。


经过一个星期紧锣密鼓的底层架构调整,以及业务代码重构,终于完成了数据库的垂直拆分。拆分之后,每个应用程序只访问对应的数据库,一方面将单点数据库拆分成了多个,分摊了主库写压力;另一方面,拆分后的数据库各自独立,实现了业务隔离,不再互相影响。

为未来做准备,进一步升级架构:水平分库(sharding)

通过上一节的分享,我们知道:


  • 读写分离,通过从库水平扩展,解决了读压力;

  • 垂直分库通过按业务拆分主库,缓存了写压力。


但技术团队是否就此高枕无忧?答案是:NO。


上述架构依然存在以下隐患:


  • 单表数据量越来越大:如订单表,单表记录数很快将过亿,超出 MySQL 的极限,影响读写性能;

  • 核心业务库的写压力越来越大:已不能再进一次垂直拆分,MySQL 主库不具备水平扩展的能力;


以前,系统压力逼迫我们架构升级,这一次,我们需提前做好架构升级,实现数据库的水平扩展(sharding)。我们的业务类似于 Uber,而 Uber 在公司成立的 5 年后(2014)年才实施了水平分库,但我们的业务发展要求我们在成立 18 月就要开始实施水平分库。



水平分库面临的第一个问题是,按什么逻辑进行拆分:


  • 一种方案是按城市拆分,一个城市的所有数据在一个数据库中;

  • 另一种方案是按订单 ID 平均拆分数据;


按城市拆分的优点是数据聚合度比较高,做聚合查询比较简单,实现也相对简单,缺点是数据分布不均匀,某些城市的数据量极大,产生热点,而这些热点以后可能还要被迫再次拆分。


按订单 ID 拆分则正相反,优点是数据分布均匀,不会出现一个数据库数据极大或极小的情况,缺点是数据太分散,不利于做聚合查询。比如,按订单 ID 拆分后,一个商家的订单可能分布在不同的数据库中,查询一个商家的所有订单,可能需要查询多个数据库。针对这种情况,一种解决方案是将需要聚合查询的数据做冗余表,冗余的表不做拆分,同时在业务开发过程中,减少聚合查询。


反复权衡利弊,并参考了 Uber 等公司的分库方案后,我们最后决定按订单 ID 做水平分库


从架构上,我们将系统分为三层:


  • 应用层:即各类业务应用系统;

  • 数据访问层:统一的数据访问接口,对上层应用层屏蔽读写分库、分库、缓存等技术细节;

  • 数据层:对 DB 数据进行分片,并可动态的添加 shard 分片。


水平分库的技术关键点在于数据访问层的设计。


数据访问层主要包含三部分:


  • ID 生成器:生成每张表的主键;

  • 数据源路由:将每次 DB 操作路由到不同的 shard 数据源上;

  • 缓存:采用 Redis 实现数据的缓存,提升性能。


ID 生成器是整个水平分库的核心,它决定了如何拆分数据,以及查询存储-检索数据:


  • ID 需要跨库全局唯一,否则会引发业务层的冲突;

  • 此外,ID 必须是数字且升序,这主要是考虑到升序的 ID 能保证 MySQL 的性能;

  • 同时,ID 生成器必须非常稳定,因为任何故障都会影响所有的数据库操作;


我们的 ID 的生成策略借鉴了 Instagram 的 ID 生成算法。



如上图所示,方案说明如下:


  • 整个 ID 的二进制长度为 64 位;

  • 前 36 位使用时间戳,以保证 ID 是升序增加;

  • 中间 13 位是分库标识,用来标识当前这个 ID 对应的记录在哪个数据库中;

  • 后 15 位为 MySQL 自增序列,以保证在同一秒内并发时,ID 不会重复。每个 shard 库都有一个自增序列表,生成自增序列时,从自增序列表中获取当前自增序列值,并加 1,做为当前 ID 的后 15 位。

写在最后

创业是与时间赛跑的过程,前期为了快速满足业务需求,我们采用简单高效的方案,如使用云服务、应用服务直接访问单点 DB。


后期随着系统压力增大,性能和稳定性逐渐纳入考虑范围,而 DB 最容易出现性能瓶颈,我们采用读写分离、垂直分库、水平分库等方案。


面对高性能和高稳定性,架构升级需要尽可能超前完成,否则,系统随时可能出现系统响应变慢甚至宕机的情况。


2020-06-20 18:371566

评论

发布
暂无评论
发现更多内容

专访丨用友网络副总裁邹达:如何应对创新型数字化挑战?

YonBuilder低代码开发平台

为什么企业对私有化部署IM如此青睐有加?

BeeWorks

腾讯WeTest微信小程序上线啦!产品资讯一手掌握!

WeTest

低代码平台常见的安全隐患,J2PaaS低代码平台如何解决?

J2PaaS低代码平台

低代码开发 低代码平台 企业级低代码平台 J2PaaS低代码平台

政企上云网络适配复杂,看华为云Stack有妙招

华为云开发者联盟

数据中心 云网络 华为云Stack 政企上云 L3GW服务

龙蜥开发者说:学无止境的 Linux ,以及我的第一个定制版本发布之路 | 第4期

OpenAnolis小助手

Linux 龙蜥社区 开发者说 宝贵经历

3月月更中奖名单新鲜出炉!快来看有没有你呀!

InfoQ写作社区官方

3月月更 热门活动

《数字经济全景白皮书》Z世代用户洞察篇(1)重磅发布!

易观分析

Z世代

EMAS隐私合规检测专项服务,从确保形式合规及实质合规规避风险

移动研发平台EMAS

阿里云 开发 数据安全 移动开发 隐私合规

TASKCTL产品安装常见问题

敏捷调度TASKCTL

分布式 kettle ETL ETL任务 调度任务

软件开发中的风险如何处理?

源字节1号

微信小程序 软件开发

Module Federation在客服工单业务中的最佳实践

得物技术

前端 Module 模块 iframe Federation

后端开发【一大波干货知识】定时器方案红黑树,时间轮,最小堆

Linux服务器开发

定时器 后端开发 红黑树 时间轮 Linux服务器开发

为什么都是技术合伙人被踢出局?

方云AI研发绩效

团队管理 研发管理 CTO SaaS

【IT运维】国内优秀的IT运维企业有哪些?

行云管家

云计算 运维 网络运维 IT运维

jackson学习之三:常用API操作

程序员欣宸

4月月更

中国设计师品牌Le Arome乐欧幕靠什么做到爆款10分钟售罄?

科技大数据

如何以卫语句取代嵌套条件表达式

华为云开发者联盟

条件表达式 卫语句 嵌套条件表达式 代码结构

玩转LiteOS组件:Openexif

华为云开发者联盟

LiteOS Huawei LiteOS Openexif Exif JPEG文件

Flink 在众安保险金融业务的应用

Apache Flink

大数据 flink 编程 流计算 实时计算

想减少代码量,快设置一个有感知的 Aware Spring Bean

华为云开发者联盟

spring bean Aware 接口

机器人流程自动化评估体系全面助力垂直行业智能化转型

王吉伟频道

RPA 机器人流程自动化 信通院

京东运动露营活动亮相首钢园,精彩持续整个四月

科技新消息

如何在 Zadig 上玩转自动化测试,为业务质量保障提供最大价值

Zadig

云原生 软件测试 CI/CD 软件交付

企业怎样有效地进行文档管理

小炮

企业 文档管理

结合实际案例谈谈项目管理经验

云智慧AIOps社区

学习 项目管理 pmp 软考 沟通技巧

浅谈Java虚拟机(HotSpot)的内存回收相关细节

CRMEB

接口自动化的关键思路和解决方案,本文全讲清楚了

Liam

Jmeter Postman API 测试工具 接口自动化测试

行云管家荣膺《中国网络安全行业全景图(第九版)》收录

行云管家

网络安全 行云管家 安全牛

Zadig 基于 OPA 实现 RBAC 和 ABAC 权限管理技术方案详解

Zadig

云原生 CI/CD 软件交付

在APICloud开发平台使用友盟统计功能教程

YonBuilder低代码开发平台

APP开发 APICloud 友盟

架构演进实践:从0到4000高并发请求背后的努力!_文化 & 方法_技术琐话_InfoQ精选文章