AICon上海|与字节、阿里、腾讯等企业共同探索Agent 时代的落地应用 了解详情
写点什么

架构演进实践: 从 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:371171

评论

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

如何通过Java更改Word中的页面大小和页面方向

在下毛毛雨

Java word 页面布局

CDN省钱大法!华为云开年采购季低价购,CDN0.03元/GB起,短信0.006元/条起

科技怪授

CDN 华为云

五大方面多管齐下,用友助力企业建设世界一流司库体系

用友BIP

强!阿里P9限时开源的实战笔记:SpringBoot2精髓

做梦都在改BUG

Java 面试 微服务 Spring Boot 框架

海泰方圆又双叒叕连获两项隐私计算专利

电子信息发烧客

以“业财合一”构建业务财务体系,让财务更在行,让业务会经营

用友BIP

全网超火Blender零基础教程!从零上手的免费3D软件

Finovy Cloud

blender 3D软件

这是我见过最好的JVM笔记,拿到阿里offer后我哭了

做梦都在改BUG

Java JVM 虚拟机 垃圾回收

前端有边界,但低代码没有

引迈信息

前端 后端 低代码 ChatGPT

强大的录屏:Camtasia 2022 汉化激活版

真大的脸盆

Mac 软件 屏幕录制 录屏软件 录屏工具

华为云视频直播,流量大放“价”,超好用!

科技怪授

“AI 上运动,直播更精彩” 百度智能云联手 Pixellot 创新中国大众体育传播新模式

Baidu AICLOUD

视频云 云导播

代码开源!阿里妈妈展示广告Match底层技术架构最新进展

阿里技术

开源

GitHub 润色框架,让你的GitHub不再索然无味

做梦都在改BUG

GitHub

如何通过优化图片、JS等资源加载项来提高网页的加载速度?

兴科Sinco

前端开发 CDN HTTP 网页加速

企业支出如何一眼看全局,用友BIP很在行

用友BIP

商旅费控

YOWOv2:优秀的实时视频动作检测框架

Zilliz

计算机视觉 构建模型 Milvus

传统企业,如何构建性能测试技术体系

老张

技术 #性能测试

《深入理解高并发编程:JDK核心技术》-冰河新书上市

冰河

并发编程 多线程 高并发 协程 异步编程

国有企业财务数智化转型实践,用友BIP一路随行

用友BIP

财务数智化

阿里表哥甩我一份Redis笔记,看完还进不了阿里让我卖豆腐去

做梦都在改BUG

Java 数据库 nosql redis 缓存

测试人社区——软件测试技术沙龙分享

测试人

人工智能 软件测试 精准测试 工程效能 ChatGPT

《中国奇谭》打动万千观众,一首歌道尽现代人心酸

曲多多(嗨翻屋)版权音乐

音乐 音乐播放

华为工单宝——为企业客户提供更强大的数字化现场作业解决方案

科技怪授

CDN 华为云

面试官:String字符串的最大长度是多少?

做梦都在改BUG

Java

常用MQTT客户端库简介

EMQ映云科技

物联网 IoT mqtt 客户端 企业号 3 月 PK 榜

Copilot 用“粉紫色磨砂UI”和“啊啊啊BGM ”,梦境了这届网友

B Impact

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