林静
JAVA 技术专家
- 货拉拉技术中心核心基础设施部Java技术专家,对数据库中间件研发有深刻的理解和丰富的实战经验。
- 曾任职摩托罗拉子公司UniqueSoft Java专家,主导自动逆向工程系统Java方向研发; 曾任职阿里本地生活中间件技术专家,负责DAL中间件的研发,同时负责多活体系中全局控制中心和数据层的建设。
今天的分享主要包含以下几个方面的内容:
一直以来,都有这样的观点——随着云时代的到来,基础设施都会被云接管,基础设施都没了,自然也就没必要自建中间件了。 而我的观点恰恰相反,正是云时代的到来,我们才更需要自建中间件。
为什么这么说呢,主要有这样两个原因:一个是技术适配问题,我们的原生环境和云环境不可能是完全一致的,想要业务平滑上云就必然要由中间件做适配;另一个更重要的原因是保持厂商中立,各个云厂商的服务并没有统一的标准,一旦企业习惯了某些独一无二的服务,企业也就失去了自由选择云平台的能力。所以自建中间件是云时代的必然之路。而自建数据库中间件正是其中不可或缺的一环—— “适配混合云,数据库中间件的建设之路”,正是这次我要和大家探讨的主题!
一、背景
- 业务体量持续增长遇到单库瓶颈;
- 业务线不断增多,数据库总量不断膨胀;
- 多语言异构,技术底座不断演进,新老服务过度;
- 多云混合云部署。
和大多数上升期的企业一样,随着业务蓬勃发展, 技术底盘也迎来了自己的挑战。 从量变到质变,很多原来习以为常的解决方案变得不再那么完美,DB就是一个典型的例子。 原先单个DB毫无压力的好日子不见了,在高并发、海量数据的压力下,DB无论是吞吐量还是存储都开始遇到瓶颈。 而当DB在高水位的时候,也是最容易出故障的时候。
随着业务不断丰富和多元化,原来的单体架构也在向微服务架构演进,而PHP为主的技术栈也由于各方面的考虑向Java技术栈过渡。在DB层面表现出来的就是大量的拆库、迁库、建库工作。熟悉这块的同学都知道“拆库、迁库”是一个耗时耗力又高风险的操作。
而混合云背景,又加码了问题的复杂度。混合云意味着我们的架构设计必须具有跨云特性,需要能适配不同的云环境,不依赖特定的云厂商的独有厂品。从业务研发视角看,就是统一的解决方案,不需要重复开发的代码来适配底层环境。
二、混合云下的问题与挑战
- 各云厂商的RDS存在各种差异 ;
- 现有线性DB扩容方案不能跨云 ;
- DB频繁拆分合并,风险高容易影响业务 ;
- DB层产品变更成本高,无法及时享受新技术红利。
刚才介绍了背景,我们把内容再捋一捋。
RDS在一个云平台一般有多种选择,而多云的情况可能就有一种逛淘宝的感觉了。一般来说协议上会兼容mysql,但具体到某个特定SQL语法,主从部署方案等细节的时候,就会有各种细微的差别。比如我们要限制所有SQL删除数据必须有where条件,有的产品不支持这个功能,有的产品可能支持动态修改,而有的产品只能通过重启来修改。
刚才我们提到单机瓶颈,云厂商都提供了各自的解决方案。早期的云厂商会提供自己的数据库中间件,比如阿里云的DRDS,通过分库分表的方式来支持数据库的水平扩展。经过一段时间的沉淀和演进,现在云厂商推出了数种新型的分布式数据库产品,提供了更好的可运维性,我们不用关心部署的细节,而直接享受它线性扩展的能力。这些新产品在单云环境下都是非常合适的解决方案,而问题是这些方案并不能跨云,这些方案都依赖各个云厂商的独有产品。
还有一个问题是变更。我们就以拆库为例子,两个业务公用一个数据库,现在需要拆分出来。那么具体执行顺序怎么安排呢?
① 同步数据到新库;
② 业务中断;
③ 业务服务使用新串重启;
④ 业务恢复。
这个过程中我们依赖业务具有自我中断的能力,另外我们会长时间中断业务,需要确定是不是每一个业务节点都重启了,否则就是数据不一致的灾难。万一出问题需要回滚,这个冗长的步骤又要再来一次。这个时候我们就会想,我们要是能够灵活的控制连接串,动态设置它指向哪里的DB就好了。一把就全切过去,有问题马上切回来,中间业务服务都不用重启。
最后我们再关心一下成本问题。云厂商由于其强大的技术背景以及市场竞争压力,总能推陈出新,不断推出产品的性价比更高的新产品,来替代老产品。我们对低廉的价格很感兴趣,但变更的风险又让我们不敢轻举妄动。
三、混合云下数据库中间件建设
怎么解决上述问题呢?核心是解耦和适配。怎么理解呢?我们遇到的大部分问题都是底层的RDS和业务服务高耦合,这种情况在过去其实非常合理的架构模式,而在云时代已经不能满足我们对可运维性,高可用的要求。因此我们需要通过自建数据库中间件来解耦业务服务和数据层,同时由数据库中间件来适配DB层,给业务服务提供统一一致的体验。
从这张图,我们可以看到借由数据库中间件,业务层和DB层实现了解耦。这样的架构下,DB层的变得更灵活也更安全。我们可以灵活地选择DB层的产品,甚至做出跨云的设计。
回到刚才的DB拆分问题,在新的架构下,我们就可以这样处理:
- 业务服务使用新串(指向老库)重启;
- 同步数据到新库;
- DB流量中断;
- 流量指向新库;
业务服务只需要修改链接串,而不用再做任何其他的操作。中断时间变得非常短,只要完成增量数据同步就可以恢复数据库的访问。同时回滚操作也在DBA团队闭环了,原本需要几方配合的变更,变成了DBA团队内部闭环的动作,大大降低了复杂度,无论是效率,还是安全程度,都得到了非常大的提升。
1、核心功能与特性
前面说解耦是由自建数据库中间件带来的架构灵活性来解决的,那么适配又是怎么做的呢?适配主要体现在数据库中间件的具体功能上,我们在设计实现数据库中间件功能的时候就需要有这样的考量。下面我们来详细讲讲这几个功能点:
1)模版式分库分表,读写分离
分库分表可以是说目前性价比最高的数据库线性扩容方案,主流的分布式数据库中间件都是采用这样的方案。
- 提供给业务研发的视角是一个使用的视角。站在业务研发的角度,这是一个可伸缩的新型mysql数据库,DB容量可以随着业务的发展按需扩容。
- 提供给DBA的视角是一个运维的视角。这是一个由分库和分表组成的集群,根据实际DB压力来调节分库分表的数量。
我们可以发现无论是业务研发视角,还是DBA视角,都是比较直观的,不会引入特别抽象的概念。一条SQL实际执行其实是非常复杂的,其中有语法解析,分库分表规则映射,新SQL生成、新SQL下发到分库执行,结果聚合等等。这些复杂过程虽然重要,但从使用者的角度不是价值而是负担,因此我们把细节全部交给数据库中间件来隐藏起来。
我们给业务研发同学交付的是和普通DB一样的连接串。我们给DBA同学提供的是新建逻辑库的API,参数是分库分表的数量,而具体的哈希函数选择,分表分布等全部提供了默认模版。这里正是企业自建数据库中间件的特点,我们不会考虑做一个需要用户自己调参的大而全的产品,而是迎合自身业务的特色直接给出一个最佳实践的直观产品。
这样的设计是非常灵活的。可以遇见不遥远的未来,会出现一种公认高性价比且稳定的新型数据库,我们完全可以把逻辑库的实现替换掉,而业务服务无需重启直接享用。
2)支持多租户,资源利用更高效
多租户是一个云时代中间件必备的能力。多租户核心在于资源池化,按需共享,能够非常有效的提高资源利用率,简单来说就是省钱。
多租户这个特性可以帮助数据库中间件适应各种使用场景。比如在DEV环境,我们可以用一个数据库中间件节点来代理所有的测试DB,使业务研发在测试开发阶段保持和生产一致的体验,而完全不用担心资源浪费问题。
多租户在设计实现上,主要需要考虑两个方面的隔离:一个是配置隔离,不同逻辑库的动态配置变更应该互不影响;另一个是资源隔离,不同逻辑库的SQL执行不应该争抢资源。
3)SQL链路追踪与SQL治理
SQL链路追踪在一个企业级的环境中是非常实用且重要的功能,抓到一条问题SQL后,能够迅速定位具体来源,将大大提升线上排障效率,加快故障恢复,减小损失。这个能力显然不是单独一个中间件能搞定的,而是需要和一个成熟的技术底盘结合起来才能实现。
这里需要一提的是,和所有的中间件一样,数据库中间件产品只有成为企业技术体系的一部分才能发挥全部的价值,即可以获得包括监控,配置中心,注册中心的支持,也给整个技术体系提供DB层的埋点数据和治理能力。而成为一个数据孤岛的话,肯定会有功能和运维性的短板,甚至成为企业技术体系里的黑洞。
- SQL治理能力包括: 紧故障防护能力、应急故障处理能力;
- 故障防护能力包括:消峰限流、连接管理、SQL拦截审计等;
- 应急故障处理能力包括:指定SQL限流拦截、SQL Index hint注入、强制绑定主库等;
整套SQL治理能力工具集能够适配各种云环境,有效保障DB健康稳定。
2、核心技术指标
这里列了一些比较关键的指标,我们来简单解读下:
- 线性扩容上限——单机容量*1024
- 多租户上限——单集群理论上限达10K
这两个指标主要考量这样一个问题:我们现在的架构方案是否能够满足企业未来2-3年的发展,假设未来3年我们的体量翻10倍,我们的架构能否够平稳应对。从数据层这个角度来讲,答案是肯定的,以自建数据库中间件为基础的数据库层架构,可以平稳支撑未来百倍的业务增长。
- DB迁移影响 — 秒级中断
DB迁移影响这个指标代表的是我们的迁移能力的成熟度。也可以这么说,它代表在业务无损的前提下,我们迁移能力的自由度。秒级中断的能力,可以帮助我们做到业务低峰期无损迁移。最理想的情况肯定是全天可操作,这也是我们努力的方向。
- 低延迟—99.999%延迟小于10ms
低延迟指标其实包含了2个维度的考量:一个RT平均值的小,一个RT抖动的小。做为数据库中间件,稳定性肯定第一位的,我们一般选择RT抖动做为衡量稳定性的核心指标。在技术上我们选择了Netty NIO的多路复用框架以及Java16 ZGC来实现这个维度指标的提升。
- 高可用—99.999%服务可用
高可用的量化指标一般用X个9来表达,X个9表示在系统1年时间的使用过程中,系统可以正常使用时间与总时间(1年)之比。一般企业级软件要求是4个9,也就是全年允许挂50分钟,5个9的话就是全年只能挂5分钟,而我们的数据库中间件上线16个月一直保持0故障,算是我们做的比较满意的部分。
- 低成本— 成本占用不到RDS的5%
所谓低成本,自己做的蛋糕肯定比买的便宜。当然这只是开个玩笑,这个肯定不能这么考虑。这里涉及到三个成本,软硬件成本+运维成本+研发成本。前两个相信大家都比较熟悉了。
我主要分享下对第三个成本的观点:和过去大家什么都想要自己开发相反,现在我们往往会过分夸大自研投入的成本,而直接放弃自研的选项。在中间件领域开源氛围浓郁的大环境下,我们其实比以往更容易培养中间件人才,利用社区资源我们也更有把握解决相关的难题,因此研发的成本并不会太高。而我们在研发实践后也能够反馈社区,形成正循环,这是一个双赢选择。
3、可运维性与技术设计
可运维性是衡量一个中间件产品设计是否“好用”的重要维度。虽然是针对产品上线后的生命周期,却是在设计阶段决定。可运维性的内容比较丰富,在总结反思我们产品的建设过程后,总结了以下4点。
1)面向故障设计
- 非核心依赖可降级
- 核心依赖做好冗余
- 建设系统“自证清白”能力
- 最后一道防线手动SOP
面向故障设计应该是大家比较熟悉的概念了。特别是在体量上来以后,故障就变成是必然,只有一台机器的时候我们说今天它有可能坏,而我们有10000台的时候,我们会肯定的说今天必然要坏一台。特别值得一提的是,我们有时候会神化云环境的稳定性,以为上云了就不会有故障了。云环境的稳定性不是说不出故障,而是有一套完整机制来故障恢复,是一种反脆弱的设计。而有些故障特别是硬件故障,并没有快速的恢复办法,比如交换机坏了,光发现问题就需要不短的时间,更何况恢复。所以无论是什么环境,我们都要设计好故障的预案。
在数据库中间件的场景里,可降级的依赖一般指影响旁路的功能,动态修改配置,监控等。不可降级的部分包括关键数据,硬件设备等。对于数据我们肯定要做好备份,而硬件问题换个角度其实更多是单节点故障,集群化是非常合适的解决方案。
在一个大的系统里发生故障,往往是到处都冒烟,但就是找不到真正出问题的点。这个时候“自证清白”就显的非常重要,如果所有组件能快速确定自己的状态,故障点清晰可见了。当然“自证清白”并不是那么容易的,真实的情况往往是大盘故障了,所有人都摸不清自己的模块是不是有问题,然后各种看日志,查监控,可即使这样的付出,还是很难得出结论。“自证清白”是一个重要又非常有挑战的能力,需要对本领域有非常深的理解,同时又需要监控报警等基础能力配合。在数据库中间件这里,主要关注内部的工作线程负载,外部表现的RT,以及网络的丢包这三个关键数据。
手动SOP属于兜底手段了,比如数据库中间件保留了本地文件启动能力。
2)产品标准化
- 针对不同使用特征,分级群隔离
- 使用统一硬软件标准
- 尽量复用企业现有标准
- 接入标准化
- 功能简单正交
标准化是一种追求全局最优的设计理念。
一个是功能设计的标准化。 作为基础中间件,我们的功能必须是简单正交的,这样就能够在实现功能的时候更专注保证质量,同时也留出进一步的编排的空间。举个例子,我们提供了限制指定SQL pqs的功能,也提供了上报异常SQL(类似SQL长度过长)的能力。这个时候如果在上层控制面做一个自动限制异常SQL的能力,就会非常顺利,即容易实现也可以方便的灰度回滚。而我们如果一开始就在数据库中间件这里憋大招,就很难做到这样自如,反而很有可能会因为功能相互影响,复杂度太高,引入bug等破坏迭代节奏。
另一个是外部依赖的标准化。 比如尽量使用公司统一的ECS机型,能够最大的避免机器没库存的问题。使用公司统一的基础组件,能够免去额外维护的成本,同时提高的产品开发效率和稳定性。
最后是用户使用的标准化。 我们应该避免提供太多的不确定给用户,而是只给一个标准答案。我们应该统一出一套最佳实践来,在这个基础上做好配套的优化。这样用户可以轻松接入,并把这个过程轻松复制给其他人。提升效率的同时,也避免了因不合理的使用姿势可能引发的故障。
3)排障智能化
- 服务监控,系统监控,外部依赖监控
- 链路追踪
- 自动报警
监控的重要性毋庸置疑,我们需要在一开始就把监控埋点纳入到开发计划中,而不是在某次故障后,才开始补充各种监控指标。
监控埋点的要点在于能够全面覆盖产品主要流程,包括数据流和控制流,正常流程和异常流程。这个时候容易进入的误区是,过早的对埋点数据做处理。比如内部有一个工作队列,比起根据某个阈值打一个队列是否繁忙的点,更合适的做法是直接打出队列长度,这样我们可以使用图表来展示队列的繁忙度变化,也可以灵活的设置告警。告警可是0值的空闲异常告警,也可以是100的繁忙异常告警。
无论是报警,链路还是监控都是要服务于排障的,所以我们要站在的方便排障的角度,来设计和验收这些指标埋点。当我们怀疑某处的打点是否必要时,可以看看是否对线上排障有帮助。
线上排障我们说要“智能化”,其实我们真正想做到的是“傻瓜式”的排障预案。能够在故障发生第一时间收到报警,然后通过链路追踪工具直接找到根因,最后通过预案按部就班处理。
4)管理自动化
- 最终“消灭”人工环节
- 用户自助
自动化提升效率是我们的共识,自动化能够帮助我们从大量的重复劳动中解放出来,提升工作效率。
那么如果量不是那么大,也不是那么重复的部分呢?是否还有必要自动化,比如审核等动作。
我认为是必要的。当我们发现某个部分不能自动化,想用人工的方式“糊弄”过去的时候,往往这就是我们没有考虑清楚的地方,不彻底解决这个问题,这个问题就会像狗皮膏药一样一直拉低我们的用户体验。我们之前就遇到过这样情况:新建逻辑库可能会影响老逻辑库,怕用户填错信息导致异常,我们就设置了好几道人工审核来review。而事实上人工审核并没有解决问题,反而阻碍了整体的自动化。最后我们重新设计了逻辑库的配置隔离和回滚能力,自然自动化也不成问题了。
“消灭”人工环节很大程度是在扫清我们产品的潜在问题,是我们假装看不见的地方。
用户自助也是类似的,一个能够自助的系统意味什么呢?意味我们的系统是健壮稳定的,我们有良好的隔离设计,有强大的容错能力。把自助当成目标,是建设更好的产品的良好推动力。
总的来说,可运维性影响软件后期所要投入的“隐性成本”,是一种高回报的长远的投资。
四、解决的问题及收益
这里总结下自建数据库中间件带来的收益。
- 提升研发效率;
- 提升运维效率;
- 提升系统稳定性;
- 保持厂商中立,实现“云自由”。