场景
  电商平台需要进行持久化所有平台产生的订单数据。同时,基于所有的订单数据,系统又需要向外提供面向多种角色:消费者、店家、平台三类人群的多元化的查询服务。消费者可以查询自己的历史订单,商家可以统计热销产品,平台也可以分析用户行为、平台交易规模等。主要查询方式涵盖订单的多维度检索,以及订单数据的分析、统计等,例如:
 面向消费者:【A消费者】* 【近1年】* 【产品名含’电脑’字段】订单查询;
 面向店家:【B店家】* 【近1个月】*【每个产品】销售量排名;

技术点
  在订单场景中,技术上通常需要考虑的技术点,主要包含如下几个方面:
查询能力】:需要具备丰富的查询类型,如多维度、范围、模糊查询等,同时具备排序、统计等功能
数据量】:存储海量数据的同时,满足强一致、高可用、低成本等要求
服务性能】:应对高并发请求高并发的同时,保证低延迟

方案
  应对订单场景,通常会采用MySQL传统方案。借助关系型数据库强大的查询能力,用户可直接通过SQL语句实现订单数据的多维度查询、数据统计等。MySQL、SQL Server等,都是以“行”为单位进行存储,为了快速检索,也都采用了B树或其他索引技术。从原理上来讲,表中的数据越多,索引树的范围越大,磁盘读取也越多,性能也就越低。所谓数据膨胀,分为横向、纵向两种,横向即不断迭代引入的新字段维度,纵向即总的存储数据量。在面对这两种订单数据膨胀上,单MySQL方案逐渐变得吃力。 SQL + NoSQL的组合方案便应运而生,借助两个数据库各自的优势分别解决不同场景各自的需求。但组合方案牺牲空间成本,同时也增加了开发工作量与运维复杂度。在保证数据一致性上产生额外开销。
  目前经常使用的关系型数据库如MySQL、SQL Server等,都是以“行”为单位进行存储,为了快速检索,也都采用了B树或其他索引技术。从原理上来讲,表中的数据越多,索引树的范围越大,磁盘读取也越多,性能也就越低。
读和写分离
  主数据库用于写入,读数据库(多个)用于对外提供查询,通过数据复制的方式将主数据库的数据同步到读库。该架构提升了数据库的读写能力,但对于主数据库的写入能力依然没法扩展。

MySQL分库分表方案
  MySQL自身拥有强大的数据查询、分析功能,基于MySQL创建订单系统,可以应对订单数据多维查询、统计场景。伴随着订单数据量的增加,可采取分库、分表方案应对,通过这种伪分布式方案,解决数据膨胀带来的问题。依据数据库分区的思路,可以将不同的数据分散到不同的库中,每个库存储的数据都不同,这样就可以将单一库的压力分散到多个库中,从而提升整个数据库的服务能力,即分库分表技术
  若按照“字段(列)”分区,每个库/表存储不同的的字段,即schema不同,就是“垂直拆分”;若按“数据记录(行)”分区,每个库/表的schema一致,但存储的数据不同,就是“水平拆分”。

亿级订单表数据存储
垂直切分
优点:

  • 拆分后业务清晰,拆分规则明确
  • 系统之间整合或扩展容易
  • 数据维护简单

缺点:

  • 部分业务表无法 join,只能通过接口方式解决,提高了系统复杂度
  • 受每种业务不同的限制存在单库性能瓶颈,不易数据扩展跟性能提高
  • 事务处理复杂

由于垂直切分是按照业务的分类将表分散到不同的库,所以有些业务表会过于庞大,存在单库读写与存储瓶颈,所以就需要水平拆分来做解决。
亿级订单表数据存储
水平拆分
优点:

  • 拆分规则抽象好,join 操作基本可以数据库做
  • 不存在单库大数据,高并发的性能瓶颈
  • 应用端改造较少
  • 提高了系统的稳定性跟负载能力

缺点:

  • 拆分规则难以抽象
  • 分片事务一致性难以解决
  • 数据多次扩展难度跟维护量极大
  • 跨库 join 性能较差

切分共同的问题
垂直切分跟水平切分的共同缺点:

  • 引入分布式事务的问题
  • 跨节点 Join 的问题
  • 多数据源管理问题

一般来讲业务存在着复杂 join 的场景是难以切分的,往往业务独立的易于切分。如何切分,切分到何种程度是考验技术架构的一个难题。

切分的一些原则
【1】能不切分尽量不要切分
【2】如果要切分一定要选择合适的切分规则,提前规划好
【3】数据切分尽量通过数据冗余或表分组(Table Group)来降低跨库 Join 的可能
【4】由于数据库中间件对数据 join 实现的优劣难以把握,而且实现高性能难度极大,业务读取尽量少使用多表 join

这样做的好处就是解决了数据存储容量的问题,但也带来了诸多弊端例如对于“水平拆分”:
  【如何能做到数据的平均拆分,防止某一库压力过大
  系统开发者要结合业务特点来确定分库分表键,比如以UserID为分库分表键,采用Hash取模的方式将数据散列到不同的库中。但并不是所有场景都适合用UserID作为分库分表键的,若存在“大卖家”,则该UserID可能有很多条记录,若简单的按照上述方法进行拆分,则可能导致数据倾斜。一般来说,会将一段时间以前的数据归档(比如某个UserID三个月之前的数据),存放到类似HBase这种非关系型数据库中,以此来解决上述问题。

  【分库分表之后要求每个查询的where子句中必须携带分库分表键,但并非每个查询都能携带分库分表键
  比如订单库按照订单号Hash取模之后存储,此时分库分表键为订单号,想查询某位买家所有的订单,查询时就没有了分库分表键,就会出现“全表扫描”的情况。一般解决这种问题的方法是建立“异构索引表”,即采用异步机制将原表内的每次一创建或更新,都换一个维度保存一份完整的数据表或索引表,拿空间换时间。订单库按照订单号Hash取模之后存储,同时也按照UserID维度进行Hash取模,再存储一份数据,那么想要获取某一UserID的全部订单时,就将UserID作为分库分表键传进去即可,避免了全表扫描。
  但数据一旦达到瓶颈,便需要重新创建更大规模的分库+数据的全量迁移,麻烦就会不断出现,仅仅依靠MySQL的传统订单方案短板凸显。
1、数据纵向(数据规模)膨胀:采用分库分表方案,MySQL在部署时需要预估分库规模,数据量一旦达到上限后,重新部署并做数据全量迁移
2、数据横向(字段维度)膨胀:schema需预定义,迭代新增新字段变更复杂。而维度到达一定量后影响数据库性能

MySql+HBASE方案
  通过实时数据、历史数据分存的方案,可以一定程度解决数据量膨胀问题。该方案将数据归类成两部分存储:实时数据、历史数据。同时通过数据同步服务,将过期数据同步至历史数据。
1、实时订单数据(例如:近3个月的订单):将实时订单存入MySQL数据库。实时订单的总量膨胀的速度得到了限制,同时保证了实时数据的多维查询、分析能力;
2、历史订单数据(例如:3个月以前的订单):将历史订单数据存入HBASE,借助于HBASE这一分布式NoSQL数据库,有效应对了订单数据膨胀困扰。也保证了历史订单数据的持久化。但是,该方案牺牲了历史订单数据对用户、商家、平台的使用价值,假设了历史数据的需求频率极低。但是一旦有需求,便需要全表扫描,查询速度慢、IO成本很高。而维护数据同步又带来了数据一致性、同步运维成本飙升等难题。

MySQL+Elasticsearch方案
  MySQL+Elasticsearch,该方案同样是将数据分两部分存储,可以一定程度解决订单索引维度增长问题。用户自己维护数据同步服务,保证两部分数据的一致性。
1、全量数据:将全量的订单数据存入MySQL数据库,订单ID之外的数据整体存为一个字段。该全量数据作为持久化存储,也用于非索引字段的反查
2、查询数据:仅将需要检索的字段存入Elasticsearch(基于Lucene分布式索引数据库),借助于Elasticsearch的索引能力,提供可以应付维度膨胀的订单数据,然后必要时反查MySQL获取订单完整信息
  该方案应付了数据维度膨胀带来的困扰,但是随着订单量的不断膨胀,MySQL扩展性差的问题再次暴露出来。同时数据同步至Elasticsearch的方案,开发、运维成本很高,方案选择也存在弊端。

相关文章:

  • 2021-09-10
  • 2021-08-01
  • 2021-08-07
  • 2021-09-28
  • 2021-12-12
  • 2021-11-17
  • 2021-11-02
猜你喜欢
  • 2019-09-09
  • 2021-11-18
  • 2021-08-26
  • 2021-10-08
  • 2021-08-18
  • 2021-11-07
  • 2021-04-12
相关资源
相似解决方案