分布式系统进阶四之分库分表

内容分享4天前发布
0 2 0

对于大型的互联网应用来说,数据库单表的记录行数可能到达千万级别甚至是亿级,并且数据库面临着极高的并发访问。采用Master-Slave 复制模式的MySQL架构,只能够对数据库的读进行扩展,而对数据库的写入操作还是聚焦在Master上,并且单个Master挂载的Slave也不可能无限制多,Slave的数量受到Master能力和负载的限制。因此,需要对数据库的吞吐能力进行进一步的扩展,以满足高并发访问与海量数据存储的需要。

数据分区

在分布式系统中,需要将数据划分为多个分区,并将每个分区存储在不同的节点上。这有助于提高系统的可伸缩性和可靠性,由于不同的节点可以独立地处理自己的数据。常见的数据分区策略包括按哈希分区、按范围分区等。

分表策略

对于访问极为频繁且数据量巨大的单表来说,我们第一要做的就是减少单表的记录条数,以便减少数据查询所需要的时间,提高数据库的吞吐,这就是所谓的分表。在分表之前,第一需要选择适当的分表策略,使得数据能够较为均衡地分布到多张表中,并且不影响正常的查询。

对于互联网企业来说,大部份数据都是与用户有关联的,因此,用户ID是最常用的分表字段。由于大部分查询都需要带上用户ID,这样既不影响查询,又能够使数据较为均衡地分布到各个表中。

分布式系统进阶四之分库分表

假设有一张记录用户购买信息的订单表order,由于order表记录条数太多,将被拆分成256张表。拆分的记录根据user_id%256取到对应的表进行存储,前台应用则根据对应的user_id%256,找到对应订单存储的表进行访问。这样一来,user_id便成为一个必需的查询条件,否则将会由于无法定位数据存储的表而无法对数据进行访问。

假设订单order 表结构如下:

CREATE TABLE `order` (
`order_id` bigint(20) unsigned NOT NULL COMMENT '订单ID,分布式ID',
`user_id` bigint(20) unsigned NOT NULL COMMENT '用户ID',
`nick_name` varchar(32) NOT NULL COMMENT '用户昵称',
`shop_id` bigint(20) unsigned NOT NULL COMMENT '店铺ID',
`total` bigint(20) unsigned NOT NULL COMMENT '总金额,分',
`create_time` datetime NOT NULL COMMENT '创建时间',
PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='订单表';

那么分表后来,假设user_id =257,并且shop_id = 100,需要根据shop_id来查询对应的订单信息,则对应的SQL语句如下:

SELECT * FROM order_1 where user_id = 257 and shop_id = 100;

DbTableUtil 分表工具类

DbTableUtil 工具类,可以生成 0001 到 9999张表记录,如订单表 order,使用DbTableUtil 生成表如下:

order_0001,order_0002,order_0003,order_0004………order_9999

public class DbTableUtil {
    private static final int TABLE_SIZE = 256;

    public DbTableUtil() {
    }

    public static String getTableIndex(Object shardKey, int sliceSize) {
        int hashValue = Math.abs(shardKey.hashCode());
        int tableIndex = hashValue % sliceSize + 1;
        return String.format("%04d", tableIndex);
    }

    public static String hash(long key, int sliceSize) {
        long hashValue = Math.abs(key);
        int tableIndex = (int)(hashValue % (long)sliceSize + 1L);
        return String.format("%04d", tableIndex);
    }

    public static String hash(long key) {
        return hash(key, 256);
    }
}

分库策略

其中,order_1根据 257%256计算得出,表明分表之后的第一张order表。

分表能够解决单表数据量过大带来的查询效率低下的问题,但是,却无法给数据库的并发处理能力带来质的提升。面对高并发的读写访问,当数据库Master服务器无法承载写操作压力时,不管如何扩展Slave服务器,此时都没有意义了。因此,我们必须换一种思路,对数据库进行拆分,从而提高数据库的写入能力,这就是所谓的分库。

与分表策略类似,分库也可以采用通过一个关键字段去取模的方式,来对数据库访问进行路由,如图:

分布式系统进阶四之分库分表

分库分表策略

还是之前的订单表,假设user_id字段的值为257,将原有的单库分为256个库,那么应用程序对数据库的访问请求将被路由到第一个库 257%256=1。

有时数据库可能既面临着并发访问的压力,又需要面对海量数据的存储问题,这时需要对数据库即采用分表策略,又采用分库策略,以便同时扩展系统的并发处理能力,以及提升单表的查询性能,这就是所谓的分库分表。

分库分表的策略比前面的仅分表的策略要更为复杂,一种分库分表的路由策略如下:

  • 中间变量=user_id%(库数量 * 每个库的表数量)
  • 库=取整(中间变量/每个库的表数量)
  • 表=中间变量 % 每个库的表数量

同样采用user_id作为路由字段,第一使用user_id对库数量 * 每个库的表的数量取模,得到一个中间变量;然后使用中间变量除以每个库表的数量,取整,得到对应的库;而中间变量对每个库表的数量取模,即得到对应的表。分库分表策略如图:

分布式系统进阶四之分库分表

假设将原来的单库单表 order 拆分成256个库,每个库包含1024个表,那么按照前面所提到的路由策略,对于 user_id= 262145的访问,路由计算过程如下:

  • 中间变量 = 262145 % (256 * 1024 ) = 1;
  • 库= 取整(1/1024) = 0
  • 表= 1%1024 =1

这意味着,对于 user_id= 262145 的订单记录的查询和修改,将被路由到第 0个库的第1个表中执行。

ShardingSphere

下期我们会介绍ShardingSphere,它是一套开源的分布式数据库中间件解决方案。可以大大减轻我们开发的压力。其架构图:

分布式系统进阶四之分库分表

© 版权声明

相关文章

2 条评论

您必须登录才能参与评论!
立即登录
  • 头像
    看见极光啊 投稿者

    收藏了,感谢分享

    无记录
  • 头像
    梦想是全世界没有38 投稿者

    亿级也叫数据?

    无记录