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

news2024/11/24 17:07:40

商城订单服务的实现

数据量

在设计系统,我们预估订单的数量每个月订单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的负载更加均衡。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/472420.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

归一化层(BatchNorm、LayerNorm、InstanceNorm、GroupNorm)

参考博客 BatchNormalization、LayerNormalization、InstanceNorm、GroupNorm、SwitchableNorm总结 PyTorch学习之归一化层(BatchNorm、LayerNorm、InstanceNorm、GroupNorm) BN,LN,IN,GN从学术化上解释差异&#xf…

前端常见报错问题处理及技术点收集

一、报错问题收集 1、页面停留半小时左右不动卡死报错问题 Uncaught (in promise) TypeError: Failed to fetch dynamically imported module: http://10.233.54.161/assets/index.f8110bbc.js Promise.then (async) E main.c19f562f.js:39 f main.c19f562f.js:39 z.onClick…

Chatgpt聊天机器人系统开发

智能聊天ChatGPT的主要功能包括: 对话生成:生成连贯、自然的对话回复,与用户进行自然而流畅的对话。 意图识别:识别用户的意图和需求,并提供相应的回复或建议。 语义理解:理解用户的语言表达&a…

网络设备正常运行时间监控

什么是正常运行时间监控 正常运行时间是衡量服务器或任何网络组件对其最终用户的可用性的指标。定期检查网络设备可用性的过程称为正常运行时间监控。正常运行时间监控有助于确保所有组件保持正常运行,而不会停机。 正常运行时间监控是关键的网络监控功能&#xf…

Docker基础知识全解析

​ Docker是一个开源的容器化平台,可以让开发者在容器中构建、打包、运行和发布应用程序,从而实现应用程序的快速部署和可移植性。Docker将应用程序和依赖项打包在一个轻量级的可移植容器中,这个容器可以在任何平台上运行,不会受到…

Java 创建线程池的三种方式

一、 Java 创建线程池主要有以下三种方式 1. 默认线程池 ForkJoinPool 2. 通过调用执行器 Executors中的静态方法 3. 通过 ThreadPoolExector import java.util.concurrent.*;// 自定义线程工厂 class MyThreadFactory implements ThreadFactory {Override//ThreadFactory 主要…

从零开始学习Linux运维,成为IT领域翘楚(一)

文章目录 🔥Linux概述🔥Linux下载安装🔥Linux三种网络配置🔥Linux 远程登录 🔥Linux概述 Linux内核最初只是由芬兰人林纳斯托瓦兹1991年在赫尔辛基大学上学时出于个人爱好而编写的。 Linux特点 首先Linux作为自由软件…

递归实现指数型枚举

77. 组合 方法&#xff1a;递归 class Solution { private:vector<vector<int>> res;vector<int> path;void solve(int n, int k, int idx) {if (path.size() k) {res.push_back(path);return ;}for (int i idx; i < n - (k-path.size()) 1; i) {pat…

java 自定义Annotation注解

目录 1.声明注解 注解声明为interface&#xff08;注&#xff1a;这与interface接口没有任何关系&#xff09; 内部定义成员通常用value表示 使用 可以指定成员的默认值&#xff0c;使用default定义 介绍 2.JDK中的元注解 Retention&#xff1a; Target&#xff1a; …

用于高负载多站点网络的 WordPress Multisite Cron

在易服客建站平台创建免费网站 500M免费空间&#xff0c;可升级为10GB电子商务网站 创建免费网站 用于高负载多站点网络的 WordPress Multisite Cron 发布于 2023年3月18日 你也许知道WordPress 内置 CRON 的工作方式与传统 CRON 不同。 它不是在指定时间触发&#xff0c…

辨析 变更请求、批准的变更请求、实施批准的变更请求

变更请求、批准的变更请求、实施批准的变更请求辨析 辨析各种变更请求&#xff0c;不服来辨。 变更请求 定义&#xff1a;对正规受控的文件或计划(范围、进度、成本、政策、过程、计划或程序)等的变更&#xff0c;以反映修改或增加的意见或内容 根据变更请求的工作内容可将变…

python-使用Qchart总结3-绘制曲线图

1.将画好的图表关联 解释说明图 2.新建一个文件画曲线图&#xff0c;并关联到UI的py文件上&#xff0c;上代码 import sys from PyQt5.Qt import * from PyQt5.QtChart import QChartView, QChart, QValueAxis, QSplineSeries from PyQt5.QtGui import QPainter, QColor, QFon…

PHP实现使用foreach、for等语句实现数组遍历的功能举例

目录 前言 一、什么是数组 二、遍历数组for语句案例 1.1运行流程&#xff08;思想&#xff09; 1.2代码段 1.3运行截图 三、输出数组的键名和值,foreach语句案例 1.1运行流程&#xff08;思想&#xff09; 1.2代码段 1.3运行截图 前言 1.若有选择&#xff0c;可实现…

二十三种设计模式第二篇--工厂模式

上篇我们了解了6条设计模式的准则&#xff0c;我相信如果你想了解设计模式&#xff0c;那么你迈出的第一步&#xff0c;我会将上一篇文档里边的6大准则进行一篇有关的代码展示&#xff0c;当然这是题外话了&#xff0c;本篇我们将重点围绕工厂模式进行讲解&#xff0c;天哪&…

Shell+VCS学习1

Shell脚本常见问题 mkdir rmdir rm mkdir 创建文件夹 mkdir -p filename-p 确保目录名称存在&#xff0c;不存在的就建一个。 mkdir -p runoob2/test若 runoob2 目录原本不存在&#xff0c;则建立一个。&#xff08;注&#xff1a;本例若不加 -p 参数&#xff0c;且原本 ru…

【C++】反向迭代器的实现

文章目录 1.迭代器的分类2.反向迭代器的使用3.反向迭代器的模拟实现4.list类的反向迭代器实现 1.迭代器的分类 我们随便打开一个容器&#xff0c;看迭代器相关的接口&#xff0c;都可以发现&#xff0c;支持迭代器的容器&#xff0c;其迭代器有以下几类 正向迭代器const正向迭…

软件测试必备的Linux知识(一)

1. Linux 概述 1.1 测试人员为什么学习linux 对于软件测试人员来说&#xff0c;我们测试的任何产品都是基于操作系统。比如我们每天都在使用的QQ软件&#xff0c;它有windows、ios、Android、Mac OS等版本&#xff0c;需要把QQ安装在各个平台上&#xff0c;才能进行相应的测试…

03 KVM虚拟机镜像制作

文章目录 03 KVM虚拟机镜像制作3.1 概述3.2 制作镜像3.2.1 使用root用户安装qemu-img软件包3.2.2 使用qemu-img工具的创建镜像文件 3.3 修改镜像磁盘空间大小3.3.1 查询当前虚拟机镜像磁盘空间大小3.3.2 修改镜像磁盘空间大小3.3.3 查询修改后的镜像磁盘空间大小 03 KVM虚拟机镜…

WPS作图常见问题+LATLEX

【LaTex】LaTex的下载与安装&#xff08;超详细、超简洁&#xff09; 表格 1、打开WPS表格&#xff0c;切换至“开始”选项卡&#xff0c;单击“绘图边框”按钮&#xff0c;如下图。 2、鼠标变成如下图一样的笔后&#xff0c;按照斜线表头的方向拉动鼠标&#xff0c;然后就给…

【2023程序员必看】前端行业分析

“前端已死&#xff1f;”|“情绪焦虑&#xff1f;” 最近经常在知乎、脉脉等平台上看到有人在渲染前端就业危机&#xff0c;甚至使用“前端已死”的字眼&#xff0c;颇有“语不惊人死不休”的意味。 “前端已死”更多的是一种焦虑情绪的表达。现阶段的市场行情确实不太好&am…