披荆斩棘,饿了么数据库高可用架构演进!
本文将和大家分享饿了么作为高速发展的互联网企业之一,在发展历程中数据库技术如何跟随企业发展并不断满足业务的需求。
分享内容大致涉及到以下五点:
- 数据库架构怎么满足业务、支撑业务发展
- 怎么提高数据库的可用性
- 如何对数据流进行相应的控制和保护
- 规模大了以后如何提高数据库运维的效率
- 一些个人认为重要原则的总结
首先简单介绍一下饿了么的概况,点过外卖的同学应该都知道饿了么吧?
饿了么发展最快阶段也是最近四五年的事情,我是 2015 年进入饿了么的,那时每天才几十万的订单,服务器也不多。
到了 2016 年时每天订单达到了几百万,商户也更多了;而 2017 年不管是订单、运单都是千万以上,直到现在都还在快速增长。
这么多数据的产生对底层数据存储是非常大的挑战,而且那个时候要在非常短的时间内应对业务的爆发性的增长,所以当时底层的技术挑战也是非常大的。
数据库架构
垂直拆分
在数据库架构方面饿了么开始的时候也是比较原始的阶段,最初是一主多从的架构;发展到后面发现订单数据库很难再满足业务往上增长的需求了。
过百万之后一主不管几从都很难满足业务的需求(因为写太大),这就面临着需要拆分的情况,需要把热点业务单独拆出来,把一套数据库拆成多套,进行垂直的业务拆分。
数据库架构-垂直
拆分根据什么原则呢?又怎么来预算订单库现在的架构能承载多少的 TPS 和 QPS 呢?
我们当时是结合订单量、对应的 QPS、TPS 数据,再根据半年后的增长情况来推算出每个业务大概会产生出多少的 QPS、TPS。
然后结合每套集群能承载的 TPS、QPS 数量(基于压测),就能估算出需要拆分成什么样的结构、以及拆分后每一套(半年后)大致需要承载多少 TPS、QPS。
当时按业务垂直拆分后,这一套方案承载了 200、300 万订单的规模,垂直拆分的优势是代价小见效快(业务代码改动并不大),能快速有效的支撑业务。
水平拆分
虽然按垂直架构拆分完了,但是热点的地方依然还是热点,比如说订单随着下单量的增长依然会变成很大的瓶颈。
那如何打破瓶颈呢?我们需要把订单这个热点再单独拆分成多套。
数据库架构-水平
也就是行业里说得比较多的“水平拆分”,把原来的一张订单表在底层拆成 1000 张小表,放在不同的集群上。
这样即使这一个订单的量再大,也能够通过不断水平扩容机器来将压力拆分到更多的集群上,从而满足了热点的性能承载。
我们可以通过压测计算出每套集群能承载出多少 QPS 和 TPS,再结合现在的业务情况就能估算出多少订单会对应产生多少的 QPS 和 TPS。
进而也能知道拆分成多少套集群能承载多少的业务量,所以也就知道了要扩多少机器才能满足半年或者一年后的业务增长。
如果说底层因为扩容做了分片策略,但是这个改动对业务不是透明的话,那就意味着业务需要做很多改造来适应底层的分片逻辑,一般业务是很难接受的。
所以,我们的水平拆分为了对业务做到透明,需要做一层代理层(我们叫 DAL),代理层会帮助业务代码做到对数据库底层拆分逻辑的访问透明,业务看到的还是一张订单表,但是底层变了 1000 张表。
完成水平扩容后基本上所谓的热点也不会存在太大的瓶颈,如果再往上增长的话可以继续拆小,继续添加更多的机器来承载。
多活架构
垂直拆分之后,我们就没有性能瓶颈了吗?其实机房也会成为我们的瓶颈。一般来讲企业都是租赁供应商的机房,一个机房能进多少机器不是无限的,供应商也不可能给你预留太多的位置。
当你服务所在的机房放不进去机器以后,你会发现虽然技术架构能满足水平扩容,但是在物理上不能再加机器了,性能瓶颈依然会到来。
为打破单个机房面临的容量瓶颈,我们需要扩容到更多的机房,所以我们做了“多活架构”。
数据库架构-多活
在每个机房数据库都是简单的主从架构,下单的时候北京用户可以在第一个机房下单,上海用户可以在第二个机房下单。
这样的话下单的高峰压力会分散到多个点去,同一套集群在两个机房都有部署,意味着承载的性能变大了。
这个时候就可以在两个机房放机器,如果一个机房的机器满了放不下,我们可以把流量引到另一个机房,扩容另一边机房,这样就打破了单个机房对容量的限制。
我们多活逻辑是根据用户所在的位置决定他在哪个机房下单,可能他今天在北京明天在上海,下的单在不同的机房。
要让用户看到所有的数据,就必须要让数据双向流通起来,所以多机房之间做数据的相互流通是数据库做多活的必备条件。
有很多企业做的是热备,只是在一边下单,但是另一边是 backup 的状态,一旦这边出现问题以后再切到那边,这样的架构并不能解决性能问题,而且资源利用率很低(有多少公司出问题真敢切?)。
而我们多活的架构可以在两个机房同时下单,能让性能、资源利用率和可靠性都得到明显提高。
数据库架构层面从垂直拆分、水平拆分到多活后,基本能满足绝大多数企业的业务发展了,这当中我们有两个组件发挥了重要的作用,也一起介绍下。
①DAL
代理层(DAL),最直观的需求是能做分库分表,能做读写分离,还可以做资源隔离、连接数隔离、连接的管理等,更重要的是还能对数据库进行相应的保护。
外卖业务大多数人都是在中午下单,所以 11 点左右是饿了么的业务最高峰。
为了缓解数据库压力,我们会通过 DAL 层做削峰处理,当流量过大时我们会让用户消息做排队的处理,由此缓解对数据库的瞬间冲击。如果流量特别大的时候还可以做限流、熔断等处理。
还有黑白名单机制,大家了解数据库运维的话会知道,如果研发写的 SQL 有问题,放入到数据库里风险会比较高。
如果他现在发了一个删除表的 SQL 命令过来就有风险,我们的 DAL 就会把这类黑名单 SQL 给拒绝。
更高级一点的功能是多维分表以及全局表 Map Table 功能。有些配置表希望在所有机房都有,DAL 上就可以做 Global Table 的功能,可以保证在所有节点上都是同样的数据。
当然,DAL 做完这些功能后,对 SQL 也是有一些限制的。比方说事务,下单不能跨服务片去做事务。
很多传统的应用业务逻辑会把很多东西包在一个事务里完成,但互联网业务应该尽量减少这种应用,在底层分片后,业务事务并不能完全通过数据库里的事务来保障。
可能每个表分片维度不一样,会导致数据分布到不同的机器上,这样就需要跨服务器事务一致性的保障,所以业务就不能再依赖于数据库的事务,需要通过其他的机制来保证。
还有 Order by 、Group by 会受到限制,如果你查 Top10 的话,DAL 只会在一个分片上把 Top10 给到你,但并非是全局的。虽然有这些限制,但是与 DAL 带来的好处比是完全可以接受的。
②DRC
数据同步组件 DRC,实现的功能是在一边机房接受变更日志,并把变更日志传递到其他的机房去,其他机房再能把这个变更应用上。
为什么用 DRC 组件而不用 MySQL 原生复制呢?因为我们的链路是跨机房跨地域的,上海和北京远距离的传输下,使用原生复制的话缺乏灵活性。
比方 MySQL 会产生各种各样的消息,尤其是做维护操作时要加字段的话,会产生大量的变更日志,这时直接传递就会导致网络直接堵死。
在北京和上海的带宽就 5~10G 的情况下,一个 DDL 变更 100G 的表,会把带宽打满,这样很容易造成大的故障。
而且用DRC还可以做很多事情:
- 比如说无用消息的过滤,MySQL 平时会产生很多通知消息,但我们只需要数据变更的消息,就只需要传递变更信息。
- 可以做数据包的压缩,还可以做维护操作的过滤。维护操作可以在两个机房同时进行,并且不希望用复制的方式来传递,这样的话避免了在维护上产生大量的变更消息,导致网络阻塞等问题。
- 再比如说数据发生冲突了该怎么处理?还有怎么避免数据的环路。如果变更日志 A 写在上海机房,但是 A 变更传递到北京机房后,又会更新北京机房的日志,A 又会通过北京机房的变更重新传回到上海机房,这样就是环路了。
DRC 会对相应的变更来源打上标签,这样数据就可以控制不回到自己产生的机房里。
数据库的高可用
下一步是怎么提高数据库的可用性。整个网站的可用性是由多部分完成的,数据库只是其中的一块,所以数据库可用性要做到比整体可用性更高。
比如做三个九的网站可用性,那底层需要四个九,甚至五个九的可用性来保证。
架构
我们都知道物理上的故障是不可避免的,任何一台机器都有可能出现故障,任何一个设备都有可能故障,所以我们需要针对可能出现故障的地方都有相应的高可用方案。
EMHA
一台 Master 机器出故障的时候,我们的 HA 是基于开源 MHA 改造的 EMHA。
在每个机房里 EMHA 管理每个机房 Master 出现故障时的切换,也不光是只负责出故障的切换,还要求控制切换时间在 30s 左右,同时要把故障抛给其他需要通知到的地方。
比如代理就要知道这台 Master 已经挂了后新的 Master 是谁,所以 EMHA 切换时需要把消息扩散出去,让所有需要信息的组件、环节都能接受到信息。
这样就能达到主库挂的时候对业务的影响非常小(可能业务都没有感知),DB 切换同时自动完成切组件的对接,由此来提高可用性。
多分片方案
如果对下单业务没有办法做到机器不出故障,但希望出故障时影响非常小,可以做分片方案。
比如说分成了 10 个片放 10 台机器上,这个时候一台机器出故障影响的是 1 个片,整体只会影响十分之一的业务。
如果你把片分得足够小的话影响的范围会变得更小,我们对关键的业务会进行更细的分片,一个片坏了也只能影响 1/n 的业务。
异地多活
异地多活后一个机房出问题不会受到多大影响,因为机房间切换的时间就在几分钟内能完成,这就能让系统 Online 的时间大大提高。
另外,做重要维护的时候可以把一个机房的流量全切走,在没有流量的机房做相应的维护动作,维护完成之后再把流量切过来,然后操作另外的机房,这样风险特别高的维护操作也不用做关站处理。
一般大型一点的网站做一次关站维护需要的时间很长;以上这些点是从架构上能把可用性一层一层往上提。
下面我们再看下从故障发现和处理的角度怎么提高可用性。
故障
可用性还有很重要的点——既然故障不可避免,那我们就要追求如何快速地发现问题,解决问题。
Trace
全链路跟踪从应用(appid)一直下串到 DB,包括有接入层、应用层、中间层、服务层、代理层、缓存层、数据库层等串联起来。
TraceID 能提供正反向异常互推能力:ID 会从上往下串,不管你在哪一层发现的问题,拿到 ID 就可以查看链路上哪些环节有问题(哪个环节耗时最长或者出异常),这样就可以及时地定位问题。
如果每个地方各查各的话,时间消耗是很长的,有 Trace 系统后,定位问题的效率会提高很多。
还有在数据库层面来看 80%~90% 的问题都是 SQL 问题,如果能及时获取有问题 SQL,判断这个 SQL 的来源,并对某些非关键的问题 SQL 进行限流或者拦截访问的话,就能隔离问题 SQL 的影响,减少 DB 故障。
VDBA
我们在数据库层开发了一个 VDBA 的自动处理程序,它会不停地对所有的数据库进行扫描,根据我们制定的规则判断状态,如果发现有问题的 SlowSQL 会根据引起异常的程度进行限流、查杀、拒绝等操作。
当然 VDBA 能处理的不仅仅是 SlowSQL,还有系统出现堵塞了,有未提交的事务、复制中断、Blocked、binlog 太大了需要清理等都能处理。
很多事情让 VBDA 自动处理后,不仅效率提高了,也大大减少人操作的风险。
在故障处理时加快故障的定位时间和故障自动处理的机制后,可用性会得到明显的提升。
数据流控制
数据流控制也依赖于刚刚所说的一些组建。作为数据的管理人员,理论上应该有自己的手段来控制什么样的数据能进入,什么样的 SQL 能通过,要以什么样的方式来存储等。
把控不是说你写写文档就能把控住的,需要有相应强制的手段和工具。每个业务访问数据库能使用多少连接、帐号权限是什么样的都需要有比较标准的控制,这样能够让所有数据在进来的时候就能够在 DBA 的掌控当中。
数据进来以后需要生产落地存储,落地后的数据也需要再传递到其他地方,这些都需要有相应的控制。
比如说现在大数据要拿数据,我们就可以通过 DRC 的消息来推送给大数据,这样就不需要再扫描数据库来拿数据了。
原来的大数据通过 Sqoop 任务都是隔天隔小时拉取数据,但现在可以做到实时的数据传递,做营销活动时可以实时看到营销的效果。
数据产生后还可能需要对外提供,如要把生产的数据同步到测试环境和开发环境;这个时候可以由 DataBus 来帮你同步数据,生产数据外传需要做数据脱敏和清洗操作(尤其是手机号、身份证号)。
原来是比较麻烦的,现在研发只需要管同步的配置信息就可以了,组件会自动脱敏和清晰,非常方便,也符合安全的规范。
运维提效
重点讲一下关于运维提效:在一个有上千号研发人员的公司,如果只有一堆规范文档之类的来维护规则是很难把控的,因为人员有离职的、新进入的,不可能跟每个人都去宣传,所以必须要有平台来管控。
SQL 治理
首先在 SQL 发布的时候,我们平台上的发布工具里面会内嵌需要遵循的标准,如果表建的时候不符合标准是没法生产提交的,这样就强制地把规则和标准变成硬性要求,SQL 还可以自动实现审核也节省了 DBA 很多时间。
另外,生产一旦出现变慢的 SQL 后,监控系统会马上把消息 Push 给研发,如果影响到生产运作的话会直接拒掉、查杀掉。
比方我们定义超过 30 秒的 SQL 是不允许线上产生的,这类 SQL 会被直接杀掉,这样可以大大减少生产的风险。
自助发布
很多公司的 DBA 大部分时间在审核 SQL 和发布 SQL,而我们的 SQL 都是研发自助发布的,不需要 DBA 操心。
我们平台支持原生、PT 执行、mm-ost 执行(饿了么自行改造的数据库多机房同步发布工具),发布平台会帮他们计算好你的发布大概需要多长时间,甚至会给你判断什么时候是业务低峰(那个时候发布会比较好),这样研发对自己的发布也是比较有把控力的。
自助归档
归档操作也是个比较频繁的需求,一旦生产产生大量数据后,就需要做冷热数据分离,要把不需要经常用的数据搬走。
原来这个操作是比较费劲的,需要 DBA 跟在后面做很多事情,而现在只需要研发自助解决。
如果你的表超过 1000 万就需要部署归档任务了,这个时候会推送消息给研发告诉他你的表已经超过标准了,需要部署归档任务,研发自己就可以在平台上把表的归档规则填上去,完成审批后后台帮你自动地做这件事情了。
还有关于 DB 的备份和恢复,一旦数据库部署到生产后,在后台的系统里会帮你自动地部署备份、恢复任务和自动校验可用性,你还可以在平台上完成数据的回档,一旦数据刷错了、写错了通过平台就能找回。
数据保障和迁移
对 DBA 来讲需要把一个数据库从这台机器搬到另外的机器上,需要把一个大表拆分成多张小表,类似的动作就需要搬数据。
我们做了数据搬迁的工具,你只需要做配置就可以了,配置完成之后可以自动搬数据了,也会减少 DBA 很多工作量。
云实践
现在饿了么所有的开发测试环境都是在云上的,效率比自己做环境高很多,随时需要随时拿,用完随时释放。
另外还可以做弹性,弹性伸缩比较难,现在我们也没有完全实现,但正在朝着这个方向努力。
我们业务的曲线是午高峰和晚高峰,这个时候流量很大,弹性调度需要在业务高峰的时候把机器加上,在业务低峰的时候把机器回收回去,提高机器的利用率。
云机房后面会承载我们主要的流量,云机房的好处是底层管理不需要自己负责,扩容资源比较方便,这样能提高交付的效率。
在云上的机房可以灰度引流,刚开始可以很少的流量去做,当我们觉得它很稳健之后就可以把流量逐步往上迁,这样能逐步把云平台的优势利用起来,把资源动态伸缩的环境利用起来,同时也能控制风险。
所以,现在利用云来提高运维效率是很好的手段。
建议原则
总结一下,从我们做这些事情里面抽取我个人感觉比较重要的点是什么呢?
①最小可用性原则
不管是对帐号的处理、连接的处理、SQL 的标准都应该有比较严格的限制,不能使用太多的资源,也不应该占用太多的资源。
最小可用性原则就是你的连接数平时只用 20 个,那我给你 40 个,有一倍波动的空间就可以了。还有帐号权限只需要增、改、查的权限,这样就不会给你删除的权限。
②Design for Failure
这是我们 CTO 经常讲的,设计环节不管是在运维规划还是代码的环节都应该考虑接受失败的情况。
不管是物理层面还是架构层面的基础设施一定会出现问题,这个时候优良的架构必须要考虑应对错误情况,确保这类波动和短暂的问题能做到容错和隔离,不至于导致整体的崩溃,同时具备快速恢复的能力。
③标准、流程、自动(助)、量化
一开始应该设定好标准,接着把标准拆解成流程,再把流程做成自动化、自助化的处理,进而达到维持整体标准的不变形,同时提高效率的目的,最好能做到可量化。
比如去年我们维护 100 个 DB 实例需要两个 DBA,今年效率提升后也许一个就可以了,量化反过来也能促进运维效率的提升(可以知道哪些环节最消耗人力和资源,针对性的优化后效率就提高了)。
④灰度、限流、熔断、隔离
变更是系统稳定性的很大变数,想要提高整体的可用性必须对变更环节有苛刻的限制要求。
比方我们要求所有的发布必须先灰度,灰度完成之后在发一边的机房,然后再全量化,要有快速回退手段;然后程序要求有过载保护处理,具备限流、熔断和隔离等兜底措施。
⑤稳定、效率、成本
这三点应该是企业对技术部门的核心诉求,也是有追求的技术团队不断努力的方向。
⑥要方向,更要落地
今天介绍的内容经历过的人都知道每一步都不容易,对于基础设施还很薄弱的公司来讲,最重要的还是考虑自己能够用得上的,先要有落脚点,哪怕从最基础的问题开始,把问题一项一项解决。
然后再逐步完善,一步步的改变才能真正让用户、公司感觉到团队的价值。所以讲了这么多,最重要的还是要落地。