商城订单模块实战 - 分库分表实战及海量数据处理

商城订单服务的实现

数据量

在设计系统,我们预估订单的数量每个月订单2000W,一年的订单数可达2.4亿。而每条订单的大小大致为1KB,按照我们在MySQL中学习到的知识,为了让B+树的高度控制在一定范围,保证查询的性能,每个表中的数据不宜超过2000W。在这种情况下,为了存下2.4亿的订单,我们似乎应该将订单表分为16(12往上取最近的2的幂)张表。

但是这样设计,有个问题,我们只考虑了订单表,没有考虑订单详情表。我们预估一张订单下的商品平均为10个,那既是一年的订单详情数可以达到24亿,同样以每表2000W记录计算,应该订单详情表为128(120往上取最近的2的幂)张,而订单表和订单详情表虽然记录数上是一对一的关系,但是表之间还是一对一,也就是说订单表也要为128张。经过再三分析,我们最终将订单表和订单详情表的张数定为32张。

这会导致订单详情表单表的数据量达到8000W,为何要这么设计呢?原因我们后面再说。

选择分片键

既然决定订单系统分库分表,则还有一个重要的问题,那就是如何选择一个合适的列作为分表的依据,该列我们一般称为分片键(Sharding Key)。选择合适的分片键和分片算法非常重要,因为其将直接影响分库分表的效果。

选择分片链有一个最重要的参考因素是我们的业务是如何访问数据的?

比如我们把订单ID作为分片键来诉分订单表。那么拆分之后,如果按照订单ID来查询订单,就需要先根据订单ID和分片算法,计算所要查的这个订单具体在哪个分片上,也就是哪个库的哪张表中,然后再去那个分片执行查询操作即可。

但是当用户打开“我的订单”这个页面的时候,它的查询条件是用户ID,由于这里没有订单ID,因此我们无法知道所要查询的订单具体在哪个分片上,也就没法查了。如果要强行查询的话,那就只能把所有的分片都查询一遍,再合并查询结果,这个过程比较麻烦,而且性能很差,对分页也很不友好。

那么如果是把用户ID作为分片键呢?答案是也会面临同样的问题,使用订单ID作为查询条件时无法定位到具体的分片上。
这个问题的解决办法是,在生成订单ID的时候,把用户ID的后几位作为订单ID的一部分。这样按订单ID查询的时候,就可以根据订单ID中的用户ID找到分片。 所以在我们的系统中订单ID从唯一ID服务获取ID后,还会将用户ID的后两位拼接,形成最终的订单ID。
在这里插入图片描述

然而,系统对订单的查询万式,肯定不只是按订单ID或按用户ID查询两种方式。比如如果有商家希望查询自家家店的订单,有与订单相关的各种报表。对订单做了分库分表,就没法解决了。这个问题又该怎么解决呢?

一般的做法是,把订单里数据同步到其他存储系统中,然后在其他存储系统里解决该问题。比如可以再构建一个以店铺ID作为分片键的只读订单库,专供商家使用。或者数据同步到Hadoop分布式文件系统(HDFS)中,然后通过一些大数据技术生成与订单相关的报表。
在分片算法上,我们知道常用的有按范围,比如时间范围分片,哈希分片,查表法分片。我们这里直接使用哈希分片,对表的个数32直接取模
在这里插入图片描述

一旦做了分库分表,就会极大地限制数据库的查询能力,原本很简单的查询,分库分表之后,可能就没法实现了。分库分表一定是在数据量和并发请求量大到所有招数都无效的情况下,我们才会采用的最后一招。

具体实现

如何在代码中实现读写分离和分库分表呢?一般来说有三种方法。
1)纯手工方式:修改应用程序的DAO层代码,定义多个数据源,在代码中需要访问数据库的每个地方指定每个数据库请求的数据源。
2)组件方式:使用像Sharding-JDBC 这些组件集成在应用程序内,用于代理应用程序的所有数据库请求,并把请求自动路由到对应的数据库实例上。
3)代理方式:在应用程序和数据库实例之间部署一组数据库代理实例,比如Atlas或Sharding-Proxy。对于应用程序来说,数据库代理把自己伪装成一个单节点的MySQL实例,应用程序的所有数据库请求都将发送给代理,代理分离请求,然后将分离后的请求转发给对应的数据库实例。

在这三种方式中一般推荐第二种,使用分离组件的方式。采用这种方式,代码侵入非常少,同时还能兼顾性能和稳定性。如果应用程序是一个逻辑非常简单的微服务,简单到只有几个SQL,或者应用程序使用的编程语言没有合适的读写分离组件,那么也可以考虑通过纯手工的方式。
不推荐使用代理方式(第三种方式),原因是代理方式加长了系统运行时数据库请求的调用链路,会浩成一定的性能损失,而且代理服务本身也可能会出现故障和性能瓶颈等问题。代理方式有一个好处,对应用程序完全透明。

在分片键的选择上,订单信息的查询往往会指定订单的ID或者用户ID,所以order的分片键为表中的id、member_id两个字段。而order_item表通过order_id字段和order的id进行关联,所以它的分片键选择为order_id。对应在代码中有专门的分片算法实现类:OrderShardingAlgorithm和OrderItemShardingAlgorithm,分别用于对订单和订单详情进行分片。

其中的OrderShardingAlgorithm负责对订单进行分片,在实现上获得订单的Id或者member_id的后两位,然后对表的个数进行取模以定位到实际的物理order表。OrderItemShardingAlgorithm负责对订单详情进行分片,实现上与OrderShardingAlgorithm类似。

MySQL应对海量数据

归档历史数据

订单数据会随着时间一直累积的数据,前面我们说过预估订单的数量每个月订单2000W,一年的订单数可达2.4亿,三年可达7.2亿。

数据量越大,数据库就会越慢,这是为什么?我们需要理解造成这个问题的根本原因。无论是“增、删、改、查”中的哪个操作,其本质都是查找数据,因为我们需要先找到数据,然后才能操作数据。

无论采用的是哪种存储系统,一次查询所耗费的时间,都取决于如下两个因素。

  1. 查找的时间复杂度
  2. 数据总量。

查找的时间复杂度又取决于如下两个因素。

  1. 查找算法。
  2. 存储数据的数据结构。

大多数做业务的系统,采用的都是现成的数据库,数据的存储结构和查找算法都是由数据库来实现的,对此,业务系统基本上无法做出任何改变。我们知道MySQL 的 InnoDB存储引擎,其存储结构是B+树,查找算法大多数时候是对树进行查找,查找的时间复杂度就是O(log n),这些都是固定的。我们唯一能改变的就是数据总量了。

所以,解决海量数据导致存储系统慢的问题,方法非常简单,就是一个“拆”字,把大数据拆分成若干份小数据,学名称为“分片”( Shard)。拆开之后,每个分片里的数据就没那么多了,然后让查找尽量落在某一个分片上,以此来提升查找性能。

存档历史订单数据

订单数据一般保存在MySQL 的订单表里,说到拆分MySQL 的表,前面我们不是已经将到了“分库分表”吗?其实分库分表很多的时候并不是首选的方案,应该先考虑归档历史数据。

以京东为例
在这里插入图片描述

可以看到在“我的订单”中查询时,分为了近三个月订单、今年内订单、2021年订单、2020年订单等等,这就是典型的将订单数据归档处理。

所谓归档,也是一种拆分数据的策略。简单地说,就是把大量的历史订单移到另外一张历史订单表或数据存储中。为什这么做呢?订单数据有个特点:具备时间属性的,并且随着系统的运行,数据累计增长越来越多。但其实订单数据在使用上有个特点,最近的数据使用最频繁,超过一定时间的数据很少使用,这被称之为热尾效应。

因为新数据只占数据息量中很少的一部分,所以把新老数据分开之后,新数据的数据量就少很多,查询速度也会因此快很多。虽然与之前的总量相比,老数据没有减少太多,但是因为老数据很少会被访问到,所以即使慢一点儿也不会有太大的问题,而且还可以使用其他的存储系统提升查询速度。

这样拆分数据的另外一个好处是,拆分订单时,系统需要改动的代码非常少。对订单表的大部分操作都是在订单完成之前执行的,这些业务逻辑都是完全不用修改的。即使是像退货退款这类订单完成之后的操作,也是有时限的,这些业务逻辑也不需要修改,还是按照之前那样操作订单即可。

基本上只有查询统计类的功能会查到历史订单,这些都需要稍微做些调整。按照查询条件中的时间范围,选择去订单表还是历史订单中查询就可以了。很多大型互联网电商在逐步发展壮大的过程中,长达数年的时间采用的都是这种订单拆分的方案,正如我们前面看到的京东就是如此。

商城历史订单服务的实现

商城历史订单的归档由tulingmall-order-history服务负责,其中比较关键的是三个Service

既然是历史订单的归档,归档到哪里去呢?我们可以归档到另外的MySQL数据库,也可以归档到另外的存储系统,这个看自己的业务需求即可,在我们的系统中,我们选择归档到MongoDB数据库。

对于数据的迁移归档,我们总是在MySQL中保留3个月的订单数据,超过三个月的数据则迁出。前面我们说过,预估每月订单2000W,一张订单下的商品平均为10个,如果只保留3个月的数据,则订单详情数为6亿,分布到32个表中,每个表容纳的记录数刚好在2000W左右,这也是为什么前面的分库分表将订单表设定为32个的原因。

分布式事务?

考察迁移的过程,我们是逐表批次删除,对于每张订单表,先从MySQL从获得指定批量的数据,写入MongoDB,再从MySQL中删除已写入MongoDB的部分,这里存在着一个多源的数据操作,为了保证数据的一致性,看起来似乎需要分布式事务。但是其实这里并不需要分布式事务,解决的关键在于写入订单数据到MongoDB时,我们要记住同时写入当前迁入数据的最大订单ID,让这两个操作执行在同一个事务之中。

在MySQL执行数据迁移时,总是去MongoDB中获得上次处理的最大OrderId,作为本次迁移的查询起始ID

当然数据写入MongoDB后,还要记得删除MySQL中对应的数据。

在这个过程中,我们需要注意的问题是,尽量不要影响线上的业务。迁移如此大量的数据,或多或少都会影响数据库的性能,因此应该尽量选择在闲时迁移而且每次数据库操作的记录数不宜太多。按照一般的经验,对MySQL的操作的记录条数每次控制在10000一下是比较合适,在我们的系统中缺省是2000条。更重要的是,迁移之前一定要做好备份,这样的话,即使不小心误操作了,也能用备份来恢复。

如何批量删除大量数据

在迁移历史订单数据的过程中,还有一个很重要的细节间题:如何从订单表中删除已经迁走的历史订单数据?

虽然我们是按时间迁出订单表中的数据,但是删除最好还是按ID来删除,并且同样要控制住每次删除的记录条数,太大的数量容易遇到错误。

这样每次删除的时候,由于条件变成了主键比较,而在MySQL的InnoDB存储引擎中,表数据结构就是按照主键组织的一棵B+树,同时B+树本身就是有序的,因此优化后不仅查找变得非常快,而且也不需要再进行额外的排序操作了。

为什么要加一个排序的操作呢?因为按ID排序后,每批删除的记录基本上都是ID连续的一批记录,由于B+树的有序性,这些ID相近的记录,在磁盘的物理文件上,大致也是存放在一起的,这样删除效率会比较高,也便于MySQL回收页。

关于大批量删除数据,还有一个点需要注意一下,执行删除语句后,最好能停顿一小会,因为删除后肯定会牵涉到大量的B+树页面分裂和合并,这个时候MySQL的本身的负载就不小了,停顿一小会,可以让MySQL的负载更加均衡。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 2019年黑马项目-畅购商城springcloud微服务实战是一门以实战为主的课程,旨在通过项目实践的方式,帮助学员深入理解和掌握SpringCloud微服务架构以及相关技术的应用。 课程的主要内容包括搭建基础的微服务架构、使用SpringCloud构建服务注册与发现、实现服务间的负载均衡、实现分布式配置中心、服务间的调用与容错处理、使用网关统一接入服务等。通过这些实战练习,学员不仅能够熟悉SpringCloud架构与组件,还能够了解微服务架构下的常见问题与解决方案。 畅购商城项目是一个典型的电商应用,通过实现该项目,学员可以接触到真实的业务场景与需求,并能够将所学知识应用到实际项目中。课程中通过模块化的方式逐步完善商城的功能,包括用户注册登录、商品浏览、购物车管理、订单生成与支付等。通过这些实践,学员除了掌握SpringCloud微服务的开发技术,还能够了解和掌握电商项目的开发流程和注意事项。 该课程的目标是让学员通过实战项目,全面了解和掌握SpringCloud微服务架构的设计与开发,在此基础上能够独立完成具有较高要求的微服务项目。通过参与实战项目的过程,学员还能够提升团队协作能力、解决问题的能力以及项目管理能力。 通过这门课程的学习,学员将会对SpringCloud微服务架构有更深入的理解,并能够将这些知识应用到实际项目中,提高自己在微服务开发领域的竞争力。 ### 回答2: 2019年黑马项目-畅购商城springcloud微服务实战是一个基于springcloud微服务架构的商城项目。该项目的目标是通过运用微服务的理念和技术,构建一个高可用、可扩展的商城系统。 在该项目中,使用了springcloud的多个组件,如Eureka注册中心、Feign负载均衡、Ribbon客户端负载均衡、Hystrix服务降级和容错、Zuul网关等。这些组件共同协作,实现了系统的弹性伸缩和高可用性。 畅购商城的功能包括商品展示、购物车、订单管理、支付、用户管理等。通过将这些功能拆分成独立的微服务,使得系统更加灵活和可维护。同时,使用分布式事务和消息队列来保障数据的一致性和可靠性。 在项目的开发过程中,采用了敏捷开发的方法,以迭代的方式进行开发和测试。通过使用Jenkins进行持续集成和部署,保证了代码的质量和系统的稳定性。 在项目的实战过程中,面临了许多挑战和困难,如微服务之间的通信、服务的负载均衡、服务的容错等。但通过团队的共同努力和不断的学习,最终成功地完成了该项目的开发和部署。 在该项目的实施过程中,不仅学到了springcloud微服务架构的相关知识和技术,还体会到了团队合作和解决问题的能力。该项目的成功实施,不仅为公司带来了商业价值,也提升了团队的技术水平和项目管理能力。 ### 回答3: 2019年黑马项目-畅购商城springcloud微服务实战是一个以Spring Cloud为基础的微服务项目。微服务架构是一种将应用拆分成多个小型服务的架构模式,这些服务可以独立开发、部署、扩展和管理。 畅购商城项目使用了Spring Cloud的一系列子项目,如Eureka、Ribbon、Feign、Hystrix、Zuul等,来实现各个微服务之间的通信、负载均衡、服务降级与熔断等功能。 在项目中,我们会通过Eureka来实现服务的注册与发现,每个微服务都会向Eureka注册自己的地址,其他微服务可以通过Eureka来发现并调用这些服务。而Ribbon则负责实现客户端的负载均衡,可以轮询、随机、加权等方式分发请求。 Feign是一种声明式的HTTP客户端,它简化了服务间的调用方式。我们只需编写接口,并通过注解来描述需要调用的服务和方法,Feign会自动实现远程调用。 Hystrix是一个容错机制的实现,可以通过断路器来实现服务的降级与熔断,当某个服务出现故障或超时时,Hystrix会快速响应并返回一个可控制的结果,从而保证系统的稳定性。 另外,Zuul作为微服务网关,可以实现请求的统一入口和路由转发,提高系统的安全性和性能。 通过这些Spring Cloud的组件,畅购商城项目可以实现高可用、容错、自动扩展等优质的微服务架构。 总之,2019年黑马项目-畅购商城springcloud微服务实战是一个基于Spring Cloud的微服务项目,通过使用Spring Cloud的各个子项目,可以实现微服务之间的通信、负载均衡、服务降级与熔断等功能,为项目的开发、部署和管理提供了便利。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值