为应用程序选择正确的数据库

2020-03-11 购物者 互联网

工业:电子商务

作者:刘春辉和赵红(Shopee的数据库管理员)

Transcreator:Caitin陈编辑器:汤姆政府高级官员

为应用程序选择正确的数据库

购物者是东南亚及台湾领先的电子商务平台。这是一个为该地区量身定制的平台,通过强大的支付和物流支持,为客户提供简单、安全、快速的网上购物体验。

随着业务的蓬勃发展,我们的团队在扩展后端系统以满足需求方面面临着严峻的挑战。我们的前一篇文章介绍了我们如何使用蒂德,一个开源的,与mysql兼容的,混合事务处理和分析处理(HTAP)数据库,扩展我们的系统,以便我们可以为用户提供更好的服务,而不必担心数据库容量。

市场上有很多可用的数据库。你如何选择正确的?在这篇文章中,我将与你分享我们的想法。我希望这篇文章可以帮助您比较多个数据库,并寻找合适的适合您的应用程序。

在本文中,我将讨论:

我们在Shopee使用哪些数据库?

在Shopee,我们使用以下数据库:

  • MySQL和蒂德是我们的关系数据库。MySQL存储了我们的大部分数据,我们正在部署越来越多的TiDB集群。
  • 雷迪斯广泛应用于各种Shopee应用。
  • 一些应用程序和团队还使用其他数据库,如HBase和鼠兔.但是我们不会在本文中讨论这些数据库。

数据库选择的重要性

我们的业务发展很快。今年,我们每周创建的数据库数量是一两年前的十倍。生产环境中的逻辑数据库的数量每年大约增长三到五倍。2019年,这一数字增加了5倍以上。

在数据库进入生产环境之前,数据库管理员(dba)和研发团队会对其物理和逻辑设计进行粗略评估。

根据我们的经验,如果我们在设计阶段做出错误的决定,我们将在以后花费大量的时间和精力来纠正它。因此,我们必须制定一个简洁高效的数据库选择策略,以确保我们在大多数情况下都能做出正确的决策。

我们的数据库选择策略

在Shopee,我们使用这种通用的数据库选择策略:

  • 我们默认使用MySQL,但我们试图减少tb规模的MySQL实例。
  • 在可能的情况下,我们尝试使用TiDB。我们从2018年开始使用TiDB,特别推荐在不适合分片的数据量大的场景下使用。
  • 必要时,我们引入Redis来平滑一些关系数据库的峰值流量。稍后我会详细解释。

选择数据库时要考虑的因素

当我们从各种产品中选择数据库时,我们会问自己:

  • 在未来12-18个月内,我们的新数据库的数据量是否会超过1tb ?

    如果是,我们应该考虑MySQL共享或TIDB。

  • 一个MySQL表是否有超过1000万行或10gb的数据?

    如果是,我们可以使用MySQL分片或者采用TiDB来实现水平可伸缩性避免费力的分片。这是因为我们发现,当一个MySQL表包含超过1000万行的数据,或者一个表占用超过10gb的磁盘空间时,数据库可能会变得更慢,而且很难维护。例如,一些SQL查询很难优化,在线数据定义语言(DDL)成为一个巨大的挑战。

  • 一个MySQL节点的每秒写查询(QPS)是否会超过每秒1,000个写?

    如果是,我们可以尝试以下方法:

    • 应用MySQL分片将写流量分配到多个MySQL主数据库。

    • 使用TiDB将写分散到更多节点。

    • 考虑引入Redis或消息队列作为写缓存来实现异步写。

      您可能认为每秒1,000个写操作的阈值太低了。我们将它设为1000,因为:

    • 在我们将应用程序投入生产之前,我们的估计可能是不准确的。在正常情况下,每秒写1000次。但当我们进行大型促销活动时,写QPS可能会跃升至每秒10,000次。最好设置一个保守的参考值。

    • 我们允许研发团队使用较大的文本字段。当单个行长度增加到某一点时,主数据库的写性能和辅助数据库的复制性能可能会大大降低。因此,我们不能期望单个节点有很高的写速率。

  • 应用程序是否要求第99个百分位数的响应时间在1毫秒(ms)之内?

    如果是,我们最好不要直接读或写数据库。我们可以使用Redis作为缓冲层。前端直接读写Redis,保证高速I/O。

    根据我们使用MySQL的经验,在大多数情况下,在我们优化MySQL服务器选项、表模式设计、SQL语句和应用程序代码之后,第99个百分位数的响应时间可以低于10毫秒。

    TiDB的计算层和存储层是分离的,多个组件一起工作来执行SQL语句。因此,我们预计TiDB的第99个百分位响应时间约为100毫秒。

碎片还是不碎片?

在Shopee,我们有一个包含数十个检查点的列表,以帮助我们评估新的数据库设计,其中“分片还是不分片”是一个重要的问题。长期以来,MySQL分片是我们水平扩展数据库的唯一方式。在我们向Shopee介绍TiDB之后,我们得到了一个新的“无切分”选项。

根据我们的经验,在某些情况下,MySQL分片弊大于利。我们不得不承担日常开发、运营和维护的额外费用。在数据库选择阶段,dba和r&d需要识别以下场景,并找到具体的解决方法:

  • 当我们无法准确估计数据库的容量时

    例如,在线日志数据库最近三个月的增量数据的大小超过了过去三年的增量数据的大小。如果我们实现了分片,我们需要一次又一次地重新分片这种数据库。每个分片过程都很复杂,需要付出很多努力。

    根据我们的经验,TiDB是一个理想的日志存储解决方案。目前,在Shopee,在TiDB中存储日志是一种常见的做法。

  • 当我们使用数据库运行多维复杂查询时

    以电子商务订单数据库为例。每个子系统需要根据买方、卖方、订单状态和支付方法过滤数据。如果按买家分片,查询卖家信息会很困难,反之亦然。

    一方面,我们为最重要的查询维度创建了单独的异构索引数据库。另一方面,我们在TiDB上实现了订单聚合表,将分散在不同分片中的订单数据聚合到一个TiDB表中。这使得需要全表扫描的复杂SQL查询可以直接在TiDB的聚合表上运行。

  • 当数据在数据库中分布不均匀时

    对于像“喜欢”和“关注”这样的社交应用数据,如果我们按用户分片数据库,数据可能会不均匀分布。少数分片的数据量可能比其他分片的数据量高得多。这些数据量大的分片也是读写的热点。这很容易导致性能瓶颈。

    一种常见的做法是重新存储:将数据切片到更多的分区中,以减少每个碎片的数据大小和读写流量。

    最近,我们开始将一些数据迁移到TiDB。从理论上讲,如果TiDB表的主键分散,热数据可以均匀分布在TiKV中地区(TiKV中数据存储的基本单元)。

    总的来说,MySQL分片可以解决数据库的横向扩展问题,但在开发、操作和维护方面存在一些痛点。我们试图缓解和解决MySQL分片框架下的问题。同时,我们尝试建立基于tidb的解决方案,在其中我们不需要分片数据库。在这方面,我们已经取得了进展。

数据库选择的经验教训

到目前为止,我已经描述了Shopee用于数据库选择和相关关键指标的基本策略。在这一部分,我将与你分享我们所学到的。

使用MySQL作为内存数据库

在Shopee,我们有时使用MySQL作为内存数据库。应用程序进入生产环境后,研发团队可能首先关注应用程序逻辑,而数据库访问层的代码可能不是最优的。

因此,在项目的早期阶段,缓慢的查询和频繁的读写是常见的问题。为了解决这些问题,我们试图确保内存空间足够大,以便将所有热数据加载到MySQL缓冲池中。这有助于缓解一些应用程序性能问题。

根据我们的统计,80%的Shopee生产数据库的数据小于50gb。因为我们的数据库服务器的内存大小大于50gb,所以我们可以通过应用程序的试错期。当我们进入数据爆发阶段时,我们可以让研发团队优化数据库。

减少t级数据库

在我们所有的生产数据库中,2.5%拥有超过1tb的数据。这些TB规模的数据库的平均数据大小为2tb。最大的存储容量超过4tb。DBA的首要任务是不断减少这些tb级数据库的数据量。

为了解决数据库数据量激增的问题,我们可以尝试MySQL分片和TiDB。我们还可以归档旧数据和升级硬件资源。

归档旧数据

旧数据占用了大量磁盘空间,但它们并不经常被读写。这意味着它们可能不是“热门数据”。如果应用程序所有者允许,我们通常会将旧数据归档到一个单独的MySQL实例。应用程序需要将读写迁移到新实例。新实例将旧数据按年或月存储在不同的表中,以避免单个表太大。对于新实例,我们还可以启用InnoDB透明页面压缩,以减少磁盘占用。

TiDB是一个很好的数据归档选择。从理论上讲,TiDB集群可以无限扩展,用户不需要担心有限的磁盘容量。TiDB在计算层和存储层都具有弹性的水平可伸缩性。因此,我们可以根据数据增长和应用读写流量逐步增加服务器。这有助于确保有效的硬件使用,并防止在存档数据库的早期生产阶段许多资源处于空闲状态。

扩大

您可能想知道,当MySQL的数据大小达到1tb,并且磁盘空间不足时会发生什么。我们是否可以将磁盘空间增加一倍,并增加内存容量,从而为工程师赢得更多的时间来对数据库进行分片?

实际上,当数据库拥有tb级的数据时,可能很难为已经存在很长时间的应用程序实现数据库分片。如果可以对旧数据进行归档以保持稳定的数据量(但数据库仍然存储tb级的数据),我们还可以升级硬件以提高数据库性能。

使用Redis平滑高峰流量

我们有两种使用Redis来处理高并发读写的方法:

  • 先写入缓存,然后写入数据库
  • 写入数据库,然后写入缓存

先写入缓存,然后写入数据库

应用前端直接读写Redis。应用程序平滑地后端并异步地将数据持久化到MySQL或TiDB。在这种情况下,MySQL和TiDB作为Redis数据的持久化层。当我们设计一个系统时,如果我们在生产环境中预测高并发读和写,它会使用Redis作为缓冲层。

对于Shopee的一些社交相关应用程序来说,它们在大型促销活动期间的峰值流量是平时的数十倍甚至数百倍。这些应用程序是典型的性能关键应用程序。如果研发团队没有预料到,应用程序仍然直接对数据库进行读写,那么当推广活动中流量激增时,数据库就会崩溃。在这种情况下,Redis是缓解后端数据库流量高峰的理想解决方案。

如果整个Redis集群宕机怎么办?我们有两个解决方案:

  • 使应用程序直接读写数据库。这可能会影响性能,但它会保持大多数数据的完整性。一些数据关键型应用程序倾向于采用这种解决方案。
  • 切换流量到一个新的Redis集群恢复服务,然后开始积累数据。此外,还可以运行另一个应用程序从数据库加载一些旧数据到Redis。一些应用程序具有高并发操作,但可以容忍数据丢失。他们可以采用这个解决方案。

写入数据库,然后写入缓存

我们仍然使用应用程序来读取和写入数据库。我们可以使用Shopee的数据事件中心(DEC),这是一个不断解析MySQL binlog,重新组织结果,然后写入Redis的中间件。这样,密集的只读流量就可以转移到Redis。这大大减少了数据库上的负载。

当我们重建数据到Redis时,我们可以为特定的查询模式定制数据结构。SQL不适合实现某些查询。有时候,使用Redis来运行这些查询会更有效。

另外,与应用程序同时向数据库和Redis写入数据相比,通过解析MySQL binlog在Redis中重建数据更有益。它的应用实现很简单,所以开发人员不需要知道从数据库到Redis的数据复制逻辑。

但它的缺点是写延迟。数据写入MySQL主数据库,然后发送到Redis。在这个过程中,可能会有几十毫秒的延迟。如果我们想这样使用Redis,我们需要知道应用程序是否接受这样的延迟。

当我们实时查询新订单时,我们通常通过这种方式避免在MySQL主节点上进行高频只读查询。为了避免次要复制延迟的影响,我们必须将对Shopee订单表的一些关键列的查询路由到MySQL主数据库。在一场大规模的宣传活动中,初选可能会被淹没。因此,我们改变了我们的方法,首先将新的订单数据写入MySQL,然后将binlog解析结果转换到Redis。通过这种方式,我们有效地缓解了MySQL初选的压力。

重构数据结构和代码,而不是直接迁移

当开发人员将数据从MySQL迁移到TiDB时,dba经常提醒开发人员调整数据结构和代码到TiDB。

下面是一个例子。一个系统以前是用MySQL分片实现的。该解决方案将所有数据平均划分为1,000个表。迁移到TiDB之后,我们停止了分片,并将这些表合并到一个表中。

在我们完成迁移和应用程序恢复服务后,我们发现SQL查询的性能出现了严重的抖动。在高并发流量中,这个查询甚至会导致整个TiDB集群挂起。

我们分析了这个问题,发现:

  • 查询运行得相当频繁。在高峰期,它占所有只读查询的90%。

  • 该查询是一个复杂的SQL查询,需要进行全表扫描。很难通过添加索引来优化查询。在迁移到TiDB之前,MySQL有1000个表。当我们执行查询时,只扫描了一个小表,而我们有20多个MySQL辅助服务器来服务查询。尽管如此,随着数据量的增加和热数据量超过内存大小,所有的MySQL辅助服务器都不堪重负。

    在数据迁移到TiDB并将1000个表合并为一个表之后,查询必须扫描一个更大的表。大量中间结果集从TiKV节点传递到SQL节点。因此,性能是不可取的。

基于以上分析,研发团队引入了Redis。当他们将binlog解析结果转换为Redis时,他们为查询定制了一个数据结构。他们将90%的只读查询切换到Redis。结果,查询变得更快、更稳定,TiDB的存储和计算节点显著减少。

TiDB是与MySQL语法高度兼容. 这简化了从MySQL到TiDB的迁移。但是TiDB是一个新的数据库,它的实现与MySQL完全不同。因此,我们需要根据TiDB的特点和具体的应用场景,制定一个定制的解决方案。

我们在Shopee如何使用TiDB

蒂德是一个支持HTAP工作负载的开源NewSQL数据库。它兼容MySQL,具有水平可伸缩性、强一致性和高可用性。看到其架构

本节概述Shopee的TiDB集群状态以及我们使用TiDB的场景。

集群状态

  • 截至2019年底,Shopee已在生产环境中部署了20多个TiDB集群。有400多个节点。
  • 数据大小为200tb + TB。
  • 我们主要使用TiDB 2.1在生产环境中,并开始尝试TiDB 3.0在某些应用程序中。
  • 最大的集群有40多个节点,数据容量约为30 TB。
  • 这些TiDB集群在多个系统中运行,如用户、项目、订单和风险控制。

应用场景

在Shopee,我们在三个场景中使用TiDB:

  • 日志存储

    • 用例:审计日志和风险控制系统
    • 前端应用程序将日志数据写入卡夫卡.后端应用异步使用Kafka消息并将其转换为TiDB。像管理后端网站这样的应用程序可以直接从TiDB读取日志。
    • 我们可以根据需要添加存储和计算节点。操作和维护比MySQL分片简单。
  • MySQL分片的数据聚合

    • 用例:订单数据聚合和项目数据聚合
    • 应用数据写入MySQL分片。我们开发了一个名为Data Event Center (DEC)的中间件来解析MySQL binlog并异步复制数据到TiDB。商业智能(BI)和管理后端系统等应用程序在TiDB上运行复杂的查询。他们不需要考虑分片规则,直接读取TiDB中的聚合表。
  • 应用程序直接读写TiDB

    • 用例:Shopee聊天系统
    • 应用程序直接对TiDB进行读写操作。我们不需要分片。这使得实现应用程序变得更容易。我们已经设置TiDB Binlog为一些集群和解析的binlog Kafka组件。然后,其他应用程序可以订阅数据更改。

结论

这篇文章涵盖了Shopee关于如何选择关系数据库的想法,以及我们使用MySQL、TiDB和Redis的经验。

简单地说,如果您的数据规模很小,并且您的应用程序还处于早期阶段,那么MySQL对您来说是一个不错的选择。您也不需要为了分片而破坏应用程序设计。这是因为随着业务的发展和数据量的增长,您可以顺利地从MySQL迁移到TiDB。在应用程序开发仍然灵活的情况下,您的应用程序可以水平伸缩。同时,你可以利用Redis来加快查询速度,减轻对数据库的压力,这样你就可以更多地关注吞吐量和强一致性。

我们选择TiDB是因为它是开源的,水平扩展的,并且与MySQL兼容的特性。在过去的两年里,我们见证了TiDB的快速发展和显著进步。TiDB是Shopee最重要的数据库基础设施之一,我们正在越来越多的场景中使用它。未来,TiDB将为Shopee提供更多流量。

我们要感谢PingCAP成员和TiDB社区,感谢他们建立了TiDB,并为我们提供了有益的支持。

关于Shopee

Shopee是东南亚和台湾地区领先的电子商务平台。2015年,它在7个市场推出,用于连接该地区的消费者、卖家和企业。

Shopee提供了一种简单、安全、吸引人的体验,每天都有数百万人在享受。它提供了广泛的产品分类,由集成支付和物流支持,以及为每个市场量身定制的流行娱乐功能。Shopee也是该地区数字经济的重要贡献者,致力于帮助品牌和企业家在电子商务领域取得成功。

Shopee是全球领先的消费者互联网公司Sea Limited (NYSE:SE)的一部分。除了Shopee, Sea的其他核心业务还包括数字娱乐部门Garena和数字金融服务部门SeaMoney。Sea的使命是用科技改善消费者和小企业的生活。

准备好开始使用TiDB了吗?