-->

架构实战案例解析_19_18_可伸缩架构案例数据太多如何无限扩展你的数据库

你好,我是王静友。

在第十六讲中呢,我和你介绍了很多可伸缩的架构、策略和原则。

那么今天我就通过一号店订单水平分库的实际案例和你具体介绍以及实现系统的可伸缩。

那在二零一三年的时候,随着一号店业务的发展,每日的订单量呢也接近了一百库。

这个时候订单库已经有上亿条记录,订单表呢有上百个字段。

那这些数据呢都是存储在一个oracle数据库里面。

当时呢我们已经实现了订单的服务化改造。

那只有订单服务才能访问这个订单数据库所是随着单量的增长以及在线促销的常态化,这个单一数据库的成储容量和访问性能都已经不能满足业务需求了。

订单数据库已成为系统的瓶颈,所以对这个数据库的拆分势在必行。

对数据库的拆分一般有两种做法,一种是垂直分布,还有一种是水平分布。

那简单来讲垂直分布就是数据库的表太多了。

我们把它分散到多个数据库,一般是根据业务进行划分,把关系密切的表呢放在同一个数据库里面。

这个改造相对比较简单。

还有一种是水平分布,它针对是某些表太大,单个数据库存储不下,或者数据库的读写性能有压力。

那我们通过水平分布,把一张表拆成多张表,每张表存放部分记录分别保存在不同的数据库里面。

那水平分库呢需要对应用做比较大的改造。

你可以去文稿中看一下这两种分库方式的示意图。

它当时呢医药店已经通过服务化实现了订单库的垂直库里。

它的订单库主要包含订单基本信息表、订单商品明细表和订单扩散表。

这里的问题呢不是表的数量太多,而是单表的数据量太大,读写性能很差。

所以医药店最终是通过水平分库,把这三张表的记录分到多个数据库当中,从而分散了数据库的存储和性能压力。

那么水平分布后应用,通过订单服务来访问多个订单数据库。

这里有一张示意图,你可以到文化中看一下,经过改造,原来的一个oracle数据库,被现在的多个mysql数据库给取代了。

每个mysql数据库它包含了一个主库,一个备库和两个存库,它们都支持读写分离。

那组位之间呢是通过mysql自带的同步机制来实现数据同步。

所以你可以发现这个项目实际包含了水平分库和去克两大改造目标。

好,我们接下来先来讨论一下水平分库的具体策略,包括要选择哪个分库维度,数据记录如何划分,以及要分为几个数据库。

首先我们需要考虑根据哪个字段来作为分库的维度。

这个字段选择的标准是尽量避免应用代码和c个性能受到影响。

那具体的说就是现有的sq c分库后,它的访问尽量落在单个数据库里,否则原来的单库访问就变成多库扫描,不但c个性能会受到影响,而且相应的代码也需要进行改造。

那我们具体到订单数据库的拆分,你可能首先会想到按照用户ID来进行拆分,这个结论是没有错,但我们最好还是要有量化的数据支持啊,不能拍脑袋。

这里最好的做法是先收集所有的c口,挑选出VR语句中最常出现的过滤字段。

比如说这里可能有三个候选对象,分别是用户ID、订单、ID和商家ID.那每个字段在c口中呢都会出现三种情况。

第一种是单ID过滤,就是ID等于一个具体的值。

第二种情况呢,是多ID的过滤ID,是不是落在一个数据集合里?第二种情况呢,就这个ID不出现。

最后呢我们分别统计这三个字段使用情况。

那我们假设一共有五百个sq l访问订单库,那这三个候选字段分别会出现这样的情况。

比如说订单ID字段,它的单个ID过滤出现六十次,那多ID过滤呢有八十次,不出现呢有三百六十次。

我在文稿中呢也给你列出了这三个字段在c口中出现的频率,你可以去参考一下。

那从这张表来看,结论非常明显,我们应该选择用户ID来进行分库。

不过等一等这个只是静态分析。

那我们知道每个c口它的访问频率是不一样的。

所以我们还要分析每个sql的实际访问量。

在项目中呢,我们分析了top十五的执行次数最多的CQL,它一共占总执行次数的百分之八十五,具有足够的代表性。

那按照执行的次数,如果我们使用用户ID进行分库,这些c口呢百分八八十会落到单个的数据库,百分之三会落到多个数据库。

那只有百分之二呢需要遍历所有的数据库。

那所以说从sql动态执行次数的角度来看,用户ID分库也明显优于使用其他两个ID进行分库。

那这样子呢通过前面的量化分析,我们知道按照用户ID分库是最优的选择。

同时我们也大致知道了风库对信有系统会造成多大的影响。

比如说在我们这个例子中,百分之八十五的circle会落到单个数据库。

那么这部分的数据访问相对于不分库来说,执行性能会得到一定的优化。

这样也解决了我们之前对风库是否有效果的疑问,也决定了分库的信息。

好,分库维度确定了以后,那我们是如何把这些记录分到各个库里面呢?一般有两种数据分法,第一种呢根据ID范围进行分库。

比如说我们把用户ID在一到九百九十九之间的记录分到第一个库,那么一千到一千九百九十九之间的分到第二个库啊,以此类推。

第二种分法呢是根据ID取模进行类推。

比如说把用户ID按照实取模余数为零的记录放到第一个库,余数为一的放到第二个库,以此类推。

这两种分法呢各自存在优缺点。

我这里呢放了一张表格,分别从数据库的数量、访问性能、数据库思路量调整,还有热点数据,四个角度比较了这两种分库方式的优缺点。

你可以到文稿中看一下,这里就不具体展开了。

那在实践中呢为了运维方便,选择id取模进行分,分库的做法比较多。

同时呢为了数据迁移方便,一般分库的数量是按照倍数增加的。

比如说一开始是四个库,第二次分类呢是八个库,再分成六六库库等等。

这样对于某个库的数据,在分的的候候的一半半数据会移到新库,剩余的呢不用动。

那与此相反,如果我们每次只增加一个库所,所有记记呢都要按照新的模数进行调整。

那在这个项目中,我们结合订单数据的实际情况,最后采用的是取模的方式来拆分记录。

那现在我们确定了记录要怎么分,但具体要分成几个数据库呢?那分库的数量首先和单功能处理的记录数有关。

那一般来说,尔西克数据库超过了五千万条记录,二六个数据库超过了一亿条记录,那数据库的压力呢就比较大。

当然,这也和字段数量、字段的长度和查询模式有关系。

那这满足前面记录数量限制的前提下,如果分库的数量太少,我们达不到分散存储和减轻md b性能压力的目的。

但如果分库的数量太多,它的好处呢是单库的访问性能比较好。

但对于跨多个库的访问应用程序,同时需要访问多个库。

如果我们并发的访问这些数据库,就意味着要消耗更多的现成资源。

那如果是串行的访问方式,执行的时间呢却会大大的增加。

另外呢分库的数量还直接影响了硬件的投入,多一个数据库就意味着要多投入硬件设备。

所以具体分多少库需要做一个综合评估。

一般初次分库,我建议你分成四到八据库。

而在这个项目中呢,我们是拆分为六个数据库,这样可以满足较长一段时间的订单业务需求。

不过,水平分库解决了单个数据库性能和容量的瓶颈,同时也给我们带来了一系列新的问题,包括数据库路由分页以及字段映射的问题。

首先是分库路由的问题。

那分库从某种意义上来说,意味着DB scheme要改变了,必然会影响应用。

但这种改变呢和业务没有关系。

所以我们要尽量保证分库相关的逻辑都在数据访问层处理。

对上层的订单服务,透明服务的代码呢不需要改造。

那当然要完全做到这一点会非常困难。

那么具体哪些改动应该由奥层负责,哪些由订单服务负责?这里呢我给你一些可行的建议。

那对于单库访问,比如说查询杂件已经指定了用户ID,那么这个c口只要访问特定的库就可以了。

这个时候呢应该有del层自动路由到特定库。

当数据库二次分裂的时候,我们也只需要修改取模的因子就可以了。

应用的代码呢不会受到影响。

那对于简单的多库查询,l层呢可以负责汇总各个风控返回的记录。

这个时候呢这个改动还是对订单服务透明。

那对于带聚合运算的多库查询,比如说c个语句中带glop by auder by max average等关键字,建议你可以让deallson汇总单个返回回的接口,然有上层应用做进一步的处理。

这样做呢有两方面的原因,一方面呢是因为让dealochson支持所有可能的聚合场景,这个实践逻辑会非常复杂。

另外一方面呢,从营销的实践来看,这样的聚合场景呢也不多,在上层应用做针对性的处理会更加灵活。

还有二层可以进一步细分为底层的dej DBC驱动程序和偏上面的数据访问层。

如果我们基于JDBC层面实现分库路由这个系统开发难度比较大,灵活性比较低,目前呢也没有很好的成功案例。

在实践中呢,我们一般是基于持久层框架,把它们进一步分装为DDL,也就是分布式数据访问层。

在这个DDL层实现分库路由。

那一号键呢就是基于阿巴列斯进一步分装,得到这个DDL.那好了,我们再来说一下分页出现的问题。

那水平分库后分页查询问题比较突出,因为有些分页查询需要遍历所有的库。

那这里举个例子,假设我们要按时间顺序展示某个商家的订单,那每页呢有一百条记录。

那由于是按照商家查询,我们需要便利所有数据库。

那假设这个库数量总数是八,我们来看一下水平分库后的分页逻辑。

那如果是取第一页数据,我们就需要从每个库里按时间顺序取前一百条记录。

那么八个库汇总后共有八百条记录。

然后呢,我们对这八百条记录在应用里进行二次排序。

最后取前一百条。

那如果是取第十页数据,我们就需要从每个库里取前一千条记录汇总后共有八千条记录。

然后呢,我们要对这个八千条记录进行二次排序,取它的第九百条到一千条之间的记录。

那你可以看到分库情况下,对于每个数据库我们要取更多的记录,并且汇总后还要在应用里做二次排序。

那么越是靠后的分页系统,要耗费更多的内存和执行时间。

而对比在不分库的情况下,无论是去哪一页,我们只要从单个DB里面取一百条记录,也无需在应用内部呢做二次排序,非常的简单。

那么,我们如何解决分库情况下的分页问题呢?这个就需要具体情况具体分析。

如果我们是为前台应用提供分页,我们可以限定用户只能看到前面n页,这个限制呢在业务上也是合理的。

一般来说看后面的分页啊意义不是很大。

如果用户一定要看,我们可以要求用户缩小范围重新查询。

那如果是后台批出类任务,要求分批获取数据,我们可以加大分页的大小,比如每次获取五千条记录,这样可以减少分页的访问次数。

那最后在分库设计时,一般我们还有配套的大数据平台,负责汇总所有分库的记录。

这样有些分页查询,我们就可以考虑走大数据平台。

那最后一个分库带来的问题是分库的字段,映射丰库的字段只能有一个。

比如这里我们用的是用户ID,如果给定用户ID,这个查询呢会落到具体某个库。

但我们知道,在订单符务里,根据订单ID查询的场景也很多见。

由于订单ID它不是分库字段,如果不对它做特殊处理系统呢,会盲目的查询所有分能,从而带来不必要资源开销。

所以呢这里我们为订单ID和用户ID创建性能保存在一个单库的定位。

这样呢我们就可以根据订单ID,找到相应的用户ID,从而实现单库的定位。

那look up表呢,它的记录是和订单库的记录总数相等,但它只有两个字段,所以存储和查询性能都不是问题。

这个表呢在单独数据库里存放,在实际使用的时候,我们可以通过分布式缓存来优化look up表的查询性能。

那此外呢,对新增的订单,我们除了选订单表,我们同时还要写look up表。

那通过前面的分析呢,最后我们得到了订单水平分布的总体技术架构啊,你可以到文稿中看一下这里,我大致说一下。

那上升应用呢,它是通过订单服务访问数据库。

那订单服务下面的分库代理呢,它实现了分库相关的功能,包括聚合运算订单ID到用户ID的映射,做到这个分库逻辑呢对订单服务透明。

那刚才说的lock up表,它是用于订单ID和用户ID的映射,保证订单服务按订单ID访问的时候可以直接落到单个库。

那这里的catch呢是look up表数据的缓存。

那底下的DDL层呢,它提供了库的路由,可以根据用户ID定位到某个库对多库的访问呢DDL也可以支持可选的多线程并发访问模式。

并且这个DDL层呢,它支持简单的记录汇总。

那口号表的初始数据呢,它来自现有的分库数据。

我们在新增订单记录时有分库代理呢异步去写入。

那我们知道订单表示系统的核心业务表,它的水平拆分会影响很多业务。

那订单服务本身的代码改造也很大,很容易导致依赖订单服务的应用出现问题。

所以我们在上线时呢必须谨慎考虑。

所以为了保证订单水平分布的总体改造可以安全落地。

那整个方案的实施过程呢,它是这样子的。

首先我们实现oracle和mysql两道库并行,所有数据的读写先指向oracle库。

然后呢,我们通过数据同步程序把数据从oracle库拆分到多个mysql库。

然如说我们三分钟增量同步一致。

其次呢,我们选择几个对数据实时性要求不高的访问场景。

比如说访问历史订单,订单服务呢,它转向访问mysq数据库,这样我们可以检验整套方案的可行性。

那最后呢经过大量的测试,如果性能和功能都没有问题,我们再一次性的把所有实时数据的读写转向mysql废弃orlogo数据库。

这里呢我们把上线其实分成了两个阶段。

第一阶段,我们把部分非实时的功能切换到mysql数据库。

这个阶段呢主要是为了验证技术这个阶段,包括了分库代理DDL和lock le表等基础设施的改造。

那第二个阶段呢主要是验证业务功能。

我们把所有订单场景全面接入mysql.那医药店两个阶段上线呢都是一次性成功的,特别是第二阶段上线。

我们一共有一百多个依赖订单服务的应用。

那通过简单的重启就完成了系统的升级,中间也没有一个应用出现问题。

那医药店在完成订单水平分布的同时呢,也实现了去oracle.硬件设备呢也从原先的小型机换成了普通的s八六服务器。

那我们通过水平分库和去oracle,不但支持订单量的未来增长,并且总体成本呢也大幅下降。

那由于这个去oracle和订单分库一起实施,这个带来双重的性能影响,我们也花了很大精力做性能测试。

为了模拟真实的线上场景,我们通过TCP copy把现场实际的查询流量引到测试环境。

那先后经过十三轮的性能测试,最终六个mysql数据库相对一个六个数据库。

在当时的数据量下,这个执行时间呢基本持平。

这样呢我们在性能不降低的情况下,通过水平分库优化的架构,实现了订单处理能力的水平扩展。

一二点最终是根据用户ID后三位取模进行分库。

那初始呢它分成六个库,这样理论上可以支持多达七百六十八个库。

同时呢我们还改造了订单ID的生成规则,让它包含用户ID的后三位。

这样新订单ID本身就就包含了库位所需的信息,无需做look up up映射机制。

随随着老订单逐步归档了,历史库在前面给出的架构中呢,look up相关的部分就可以逐渐废弃了。

那如果我们要扩充数据库数量,比如说从六个升到十二个怎么做呢?这个我们可以分三步走。

首先我们增加六个mysql实令,然后把现有六个库的数据同步到新的库,比如说零号库同步到六号库,一号库同步到新的七号库等等。

然后呢我们在配置文件里把分库的取模从六变成十二。

最后呢通过数据库脚本,每个库删掉一半数据。

比如说对于零号库,我们删掉用户ID十二取模等于六的记录啊。

对,六号库呢我们删掉用户ID十二取模后等于零的记录。

那你可以看到通过这样的分库方式,整个数据库的扩展是非常容易的。

这里不涉及复杂的数据,跨库迁移工作。

那订单的水平分布它是一项系统性工作,我们需要大胆设计,谨慎的落地,你需要把握好下面几个要点。

首先呢,你需要在风控策略的指导下,结合实际情况,在每个方面做出最合适的选择。

其次呢,对特殊的场景,比如说分页查询,你需要具体问题具体解决。

最后呢你需要做好总体规划,控制好落地步骤,包括对系统改造性能、测试性据迁移、上线实施等各个环节做好衔接,保证业务做好衔接。

最后我来对今天的分享内容做个总结。

今天我和你分享了一号店订单水平分布的实际案例,并给出了具体的做法和原因,相信你已经掌握了。

如果通过对数据库的水平拆分来保证对统的高性能和改增缩。

那水平分布时,针对有状态的成熟节点进行水平扩展,相对无状态的节点系统改造的复杂性比较高。

那考虑的点呢也比较多。

那通过今天的分享,希望你以后在设计一个复杂方案的时候,能够更全面的考虑相关的细节,提升架构设计能力。

那最后给你留一道思考题,你公司的数据库有什么瓶颈吗?那计划对它做什么样的改造呢?那欢迎在留言区和我互动,我会第一时间给你反馈。

如果觉得有收获,也欢迎你把这篇文章分享给你的朋友,感谢收听,我们下一期再见。