系统设计基础 — 资料库分片与复写 (Database Sharding & Replication)

2023年2月4日

💎 加入 E+ 成长计划 如果你想要透过更深入、更具系统性的内容来成长,或是想要有社群一同交流,欢迎

在负载均衡器 (load balancer) 单元有提到,当网站的流量变大时,我们可以透过负载均衡器,把请求分派给多个不同的服务器。不过,当我们怎么做,却只有一个资料库时,多个服务器会同时会同时向资料库做操作。而资料库本身也是硬体,硬体也有其上限,能同时处理的请求并非无限,或是能存的资料也非无限。因此,当流量大起来时,很可能单一资料库会不堪负荷。这时候我们会需要进一步思考如何扩增我们的资料库。

扩增资料库有哪些方法?

如果要扩增资料库,我们可以尝试以下几种可能性:

垂直扩展

白话来说就是提升硬体性质,假如本来用 8GB 的 RAM,不够的话就升级成 16GB,再不够的话就提升到 32GB,以此类推。同理,储存空间不够,那就换一个更大的存储空间。这种方式在处理不是真的那么大的流量,或许还堪用。但如果真的是巨量的流量,因为硬体能升级的程度有其上限,很可能再强大的硬体也不够用。因此实务上,如果是真的很大量的流量,通常不会选这方案。

资料库分割 (database partitioning)

如果一个资料库里的东西太多,导致要捞资料出来时间太长,我们可以把资料库分割成多个小的表(或者资料库实例),这样一来在比较小的表(或资料库)中,要捞出资料的速度也能加快。同时因为有了多个资料库实例,能够容纳的整体资料量也因此提升。而资料库分割中,其中水平的分割,如果是把资料库分割到多个资料库实例,又会被叫资料库切片 (database sharding),而这也是此篇要谈论的重点之一。

什么是资料库切片?为什么要用它?

Database Sharding
Database Sharding
圖片來源:https://www.digitalocean.com/community/tutorials/understanding-database-sharding

资料库切片 (database sharding) 就是把资料库切成多个,并创建多个资料库实例,把资料分散地放在不同的资料库中。从上面这张来自 Digital Ocean 的图可以看到,本来一个有 A、B、C、D 四笔资料,我们切成 A 与 B 放在一个 shard,而 C 与 D 放在另一个 shard。这边的每个 shard 都会是一个资料库实例。

从上面这简要的例子,我们可以推出资料库切片的几个好处

  • 如果单一资料库能同时执行的操作有限,例如某资料库一秒钟能处理 1000 个写的请求,当我们切片成多个实例,那就能突破这个限制。
  • 如果切片分的好,能进一步提升性能。举例来说,某个航班查询的应用程式,若把国际航班放一个 shard、国内航班放一个 shard,当今天要找国际航班时,不用从一个大的资料库捞,而是仅需要从国际航班的 shard 捞,因为单一个 shard 的资料量比较小,这样捞也会比较快。
  • 更高的可用性 (availability) 避免单一实例的风险。假如今天只有一个资料库,那么资料库挂了就什么资料都没办法写、没办法捞。但如果切片成多个,假如国际航班的资料库挂了,起码国内航班的还可以用,不会所有的都不能用。

资料库切片的方法

将资料库切成数个小的资料库,shard key (中文会翻成片键)。 shard key 的分法常见的有几种,一种是按照顺序,例如 0 - 10 岁的族群放一个 shard,11 - 20 岁的使用者的放一个 shard,以此累推。业务需求也是常见的分法,例如国际航班的放一个 shard,国内航班放一个 shard。或是用杂凑 (hashing) 的方式来分,好的杂凑演算法应该能均匀分布,同时杂凑是 O(1) 操作,当有了 shard key,要找到对应的 shard 也会很快。

分片完,但如何解决单点故障问题?

当我们把资料库分片成多个小的资料库,这时仍会遇到单点故障(Single Point of Failure) 的问题,换句话说,如果某个资料库挂机(例如存有国际航班的资料库挂了),则该资料库的资料就没办法被服务器请求。这种问题该如何解决呢?这时我们可以透过把资料库复制多份,放到多台不同机器来避免这问题。

Single-primary Replication

  • 有一个是主要资料库 (primary database),只有该资料库可以写,其他资料库则只可以读。这个做法会遇到仅能最终一致性(eventual consistency) 的问题,因为资料库同步需要时间,很可能某个写入的请求已经发生了,但因为还没同步该资料到读的资料库,所以与此同时的读取请求发生时,可能不是读到最新的资料,而是要等到同步后才会是最新的。在某些应用程式这没问题,例如 Facebook 别人的留言,你过几秒后才看到也不会怎么样;但在一些需要高度精准的应用程式 (例如金融类的) 则不太适合。
  • 除此之外,这种方法仅适合大量读、少量写的情境,因为读可以被多个资料库分散负担,不过如果应用程式是需要支援同时多个写的,例如直播超级杯时的聊天室,可能同时上万人留言,这时大量同时的写入请求,都是由该主要资料库来承担,这也会是有风险的。除此之外,如果主要资料库挂机,那也会变得没有资料库可以写,因为剩下的资料库都只可以读。这种情况该如何解决呢?

multi-primary replication

  • 如果说只有一台主要资料库可以写,同样会有单点故障的可能,那或许我们可以让能够写的资料库变成多台。这个概念又称作 multi-primary replication。
  • 然而这种方法会遇到,如果有同时多个写入,如何同步资料库的问题。以及跨资料库遇到 race condition 该如何处理的问题。
🧵 如果你想收到最即时的内容更新,可以在 FacebookInstagram 上追踪我们