当然不一定。
虽然很多互联网公司的体量很大、用户非常多,但你千万不要被这些现象迷惑了。实际上,90% 以上的系统能够发展到上百万、上千万数据量已经很不错了。对于千万的数据量,开源的 MySQL 都可以很好地应对,更别说一些商业数据库了。
另外,当数据增长到一定量级后,可以在业务层面做一些处理。比如根据业务特点,对无效数据、软删除数据,以及业务上不会再查询的数据进行统一归档,这也是一个成本低、效果明显的方式了。
说到分库,他当然能够解决存储的问题,假设原先单库只能最多存储2千万的数据量。采用分库之后,存储架构变成下图所示的分库架构,每个分库都可以存储2千万数据量,容量的上限一下提升了。
image.png
虽然容量提升了,但也带来了很多其他问题。
所以在解决容量问题上,可以根据业务场景选择,不要一上来就要考虑分库,分表也是一种选择。
分表是指所有的数据均存在同一个数据库实例中,只是将原先的一张大表按一定规则,划分成多张行数较少的表。
它与分库的区别是,分表后的子表仍在原有库中,而分库则是子表移动到新的数据库实例里并在物理上单独部署。分表的拆分架构如下图所示:
海量数据写入——万级并发的订单系统如何分库?
不管是打车的订单、电商里的支付订单,还是外卖或团购的支付订单,都是后台服务中最重要的一环,关乎公司的营收。因此我将以订单业务作为案例进行分析。
假设订单只是单量多而每一单的数据量较小,这就适合采用分表。单条数据量小但行数多,会导致写入(因为要构建索引)和查询非常慢,但整体对于容量的占用是可控的。
采用分表后,大表变成小表,写入时构建索引的性能消耗会变小,其次小表的查询性能也更好。如果采用了分库,虽然解决了写入和查询的问题,但每张表所占有的磁盘空间很少,也会产生资源浪费。两种方案的对比如下图所示:
image.png
在实际场景里,因为要详细记录用户的提单信息,单个订单记录的数据量均较多,所以不存在行数多但单条数据量小的情况。但在其他写入服务里,经常会出现上述场景,你可以优先采用分表的方案。因为分表除了能解决容量问题,还能在一定程度上解决分库所带来的三个问题。
分表后可以通过 join 等完成一些富查询,相比分库简单得多。
分表的数据仍存储在一个数据库里,不会出现很多分库。无须引入一些分库中间件,因此维护成本和开发成本均较低。
因为在同一个数据库里,也可以很好地解决事务问题。
如果你确定要对数据库进行分库,究竟要如何实现呢?
首先要解决的问题便是如何选择分库维度。
不同的分库维度决定了部分查询是否能直接使用数据库,以及是否存在数据倾斜的问题。
介绍两种常见的不同维度的分库方式:
在业务上,所有的订单数据都是隶属于某一个用户的。可以按订单归属的用户这个字段进行分库,则同一个用户的订单都在某一个分库里。
分库后的场景如下图所示:
海量数据写入——万级并发的订单系统如何分库?
订单模块除了提供提交订单接口外,还会提供给售卖商家对自己店铺的订单进行查询及修改等功能。这些维度的查询和修改需求,在采用了按购买用户进行分库之后,均无法直接满足了。
这里请你思考一个问题,订单模块最重要的功能是什么?
答案是保证客户(即买家)的各项订单功能能够正常使用,比如下单、下单后立刻(无延迟)查看已购的订单信息、待支付、待发货、待配送的订单列表等。
相对来说,订单里的商品售卖方(即卖家)所使用的功能并不是优先级最高的。因为当我们要对卖家和买家的功能做取舍时,卖家是愿意降低优先级的,毕竟卖家是买卖的受益方。
按购买用户划分后,用户的使用场景都可以直接通过分库支持,而不需要通过异构数据(存在数据延迟)等手段解决,对用户来说体验较好。其次,在同一个分库中,便于修改同一用户的多条数据,因此也不存在分布式事务问题。
我们可以通过上述订单案例抽象出一个分库准则:在确定分库字段时应该以直接满足最重要的业务场景为准。
很多其他的业务都参考了这一准则,比如——
上述划分方法虽然直接满足了最重要的场景,但可能会出现数据倾斜的问题,比如出现一个超级客户(如企业客户),购买的订单量非常大,导致某一个分库数据量巨多,就会重现分库前的场景。这属于最极端的情况之一。
对于倾斜的问题,可以采用最细粒度的拆分,即按数据的唯一标示进行拆分。
对于订单来说唯一标示即为订单号。采用订单号进行分库之后,用户的订单会按 Hash 随机均匀地分散到某一个分库里。这样就解决了某一个分库数据不均匀的问题。
比如:
采用最细粒度分库后,虽然解决了数据均衡的问题,但又带来了其他问题。
总之,这两种分库的方式,在解决问题的同时又带来了一些新的问题。在架构中,没有一种方案是可以解决所有问题的,更多的是根据场景去选择更适合自己的方案。