写点什么

详解数据库分片,大幅提升 Spring Boot 查询 MySQL 性能

作者:高端章鱼哥
  • 2024-01-08
    福建
  • 本文字数:1670 字

    阅读完需:约 5 分钟

详解数据库分片,大幅提升Spring Boot查询MySQL性能

背景

微服务项目中通常包含各种服务。其中一项服务与存储用户相关的数据有关。我们使用 Spring Boot 作为后端,使用 MySQL 数据库。

目标

随着用户基数的增长,服务性能受到了影响,延迟也上升了。由于只有一个数据库和一张表,许多查询和更新由于锁异常返回错误。此外,随着数据库的规模不断扩大,性能进一步下降。因此,需要一种解决方案来处理不断增长的用户基数。

解决方案

表格分片


图片


第一种方法是在单个数据库中创建多个类似的表,并使用 user_id 作为分片键。


我们在 user_id 列出现的任何地方创建了每个表的 10 个副本。因此,代码中需要进行两个更改。第一个更改是获取用户请求中的 user_id。第二个更改是替换由 Hibernate 生成的查询中的表名。


关于第一个更改,获取 user_id 很容易,因为我们已经在请求标头中获取了 user_id。


对于第二个更改,我们扩展了 Hibernate 的 EmptyInterceptor 类,并覆盖了 onPrepareStatement 方法,该方法在准备 SQL 字符串时调用。该方法有一个字符串参数,即 SQL 语句。该 SQL 语句中也包含表名。因此,这里根据请求头中存在的 user_id 用所需的表名替换表名。例如,如果 user_id 为 77。我们取它 10 的模得到 7,并将表名 user_profile 替换为 user_profile_7,因为我们已经在数据库中创建了 10 个副本。以下是扩展 EmptyInterceptor 类的代码。如果您使用的是 spring boot 3,则 EmptyInterceptor 已经弃用,你可以使用 StatementInspector 接口,并覆盖 inspect 方法,并将逻辑从 onPrepareStatement 方法移动到 inspect 方法中。


public class DynamicTableNameSharding extends EmptyInterceptor {    @Override    public String onPrepareStatement(String sql) {        // 替换表名        if (Boolean.parseBoolean(DatabaseEnvironment.TABLE_SHARDING_ENABLED.label)) {            for (String tableName : SHARDED_TABLES) {                if(sql.contains(tableName)) {                    ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();                    String shardingNumber = getSharding(attr);                    sql = sql.replace(tableName, tableName + shardingNumber);                    // 这里不要使用break,因为一条查询可以包含多个表,因此需要更改所有已启用分片的表的名称                }            }        }        return super.onPrepareStatement(sql);    }}
复制代码

在上述函数中,SHARDED_TABLES 是已启用分片的表的列表。getSharding 方法根据请求头中传递的用户 ID 返回分片号。由于在单个查询中存在多个表(例如连接或复杂逻辑),因此我们使用 for 循环来正确替换查询中出现的所有表。


我们还通过扩展 DefaultVisitListener 类,在某些操作中使用了 JOOQ。

数据库分片


图片


虽然通过表格分片提升了一定性能,但还有进一步改进的空间,我们进一步对数据库进行分片。与创建表副本类似,我们创建 10 个数据库服务器/实例的副本,每个服务器都有 10 个表的副本。总共有 100 个表副本。

因此,同时保持 10 个数据库服务器运行也需要路由查询到正确的数据库。


首先,在的 Spring Boot 应用程序中创建了 10 个数据源,每个数据源都有不同的数据库 URL。现在,我们需要一种方法将数据库连接路由到正确的数据源。因此,我们使用了 AbstractRoutingDataSource,它是一个路由 getConnection()调用到其中一个多个目标数据源的抽象 DataSource 实现,这个目标数据源基于一个查找键。然后,我们重写了这个方法 determineCurrentLookupKey。


因此,这个方法返回一个键,用于标识我们已定义的 10 个数据源中的一个特定数据源。因此,我们也更改了一些用于确定表和数据库的逻辑。我们使用个位数字标识数据库服务器,使用十位数来标识表。例如,用户 ID 为 447 将被路由到第 7 个数据库服务器及该服务器上的第 4 个表副本。因此,我们在 10 个数据库服务器上有 100 个表,这大大提高了性能。

结论

在这个例子中,我们既使用了表分片又使用了数据库分片。除此以外,我们可以进一步提高性能,方法是在单个服务器中增加更多的数据库,可能总共有 1000 个表的副本。

发布于: 刚刚阅读数: 3
用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
详解数据库分片,大幅提升Spring Boot查询MySQL性能_MySQL_高端章鱼哥_InfoQ写作社区