MySQL分库分表篇

1 传统项目结构

076.png

2 数据库性能瓶颈

① 数据库连接
数据库连接是非常稀少的资源,MySQL数据库默认100个连接,单机最大1500连接。如果一个库里既有用户相关的数据又有商品、订单相关的数据,当海量用户同时操作时,数据库连接就很可能成为瓶颈。

② 数据量
MySQL单库数据量在5000万以内性能比较好,超过阈值后性能会随着数据量的增大而变弱。MySQL单表的数据量是500w-1000w之间性能比较好,超过1000w性能也会下降。

③ 硬件问题
因为单个服务的磁盘空间是有限制的,如果并发压力下所有的请求都访问同一个节点,肯定会对磁盘IO造成非常大的影响。

3 数据库性能优化

① 参数优化
② 缓存、索引
③ 读写分离
④ 分库分表 (最终方案)

4 分库分表介绍

4.1 使用背景

表的数量达到了几百上千张表时,众多的业务模块都访问这个数据库,压力会比较大,考虑对其进行分库。
表的数据达到了几千万级别,在做很多操作都比较吃力,考虑对其进行分库或者分表

4.2 数据切分方案

数据的切分(Sharding)根据其切分规则的类型,可以分为两种垂直切分和水平切分模式

4.2.1垂直切分

按照业务模块进行切分,将不同模块的表切分到不同的数据库中。

4.2.1.1 分库

077.png

4.2.1.2 分表

按照字段将大表拆分成小表,另当表中含有Blob、Clob(用于存头像、小图片等)等二进制类型的字段时,因其不能使用索引,考虑性能问题需将其拆分出来。

078.png

4.2.2 水平切分

将一张大表按照一定的切分规则,按照行切分成不同的表或者切分到不同的库中

4.2.2.1 范围式拆分

好处:增删数据库实例时数据迁移是部分迁移,扩展能力强。
坏处:热点数据分布不均,访问压力不能负载均衡。

079.png
080.png

4.2.2.2 hash式拆分

好处:热点数据分布均匀,访问压力能负载均衡。
坏处:增删数据库实例时数据都要迁移,扩展能力差。

4.2.2.3 水平切分规则

① 按照ID取模:对ID进行取模,余数决定该行数据切分到哪个表或者库中。
② 按照日期:按照年月日,将数据切分到不同的表或者库中。
③ 按照范围:可以对某一列按照范围进行切分,不同的范围切分到不同的表或者数据库中。

4.2.3 切分原则

① 能不切分尽量不要切分。
② 如果要切分一定要选择合适的切分规则,提前规划好。
③ 数据切分尽量通过数据冗余或表分组(Table Group)来降低跨库 Join 的可能。

4.2.4 说明

垂直切分是程序员切分,水平切分是利用TDDL、Cobar、Mycat 、sharding-jdbc等进行切分。

4.3 分库分表需要解决的问题

4.3.1 分布式事务问题

解决方案:
① 采用补偿事务,例如TCC来解决分布式事务问题。
② 用记录日志等方式来解决分布式事务问题。

4.3.2 分布式主键ID冲突问题

解决方案:
① 利用Redis的incr命令生成主键。
② 用UUID生成主键(不建议:字段比较长、不好排序)。
③ 利用snowflake算法生成主键( https://www.sohu.com/a/232008315_453160 )。

4.3.3 跨库join问题

081.png

解决方案:
① 将有E-R关系的表存储到一个库中。
② 对于数据量少的表建成全局表,分布到各个库中
③ 对于必须跨库join的,最多支持跨两张表的跨库join

4.4 案例分析

情况:
有用户表user(uid、name、city、sex、age、timestamp),共5亿条数据,机器为x86 64位系统,查询维度比较单一

问题:
分几张表?PartitionKey如何选择?

分析:
根据分表原则,单行数据大于100字节则1千万一张表,单行数据小于100字节则5千万一张表,用户表单行数据小于100字节,单张表可存5千条记录,5亿除以5千万等于10,向上取整,共分为16张表。city、timestamp做为PartitionKey会造成热点数据分布不均匀,故使用uid做为PartitionKey,算法为uid模以16

4.5 分库分表实现技术

① 阿里的TDDL、Cobar
② 基于阿里Cobar开发的
③ 当当网的sharding-jdbc

5 Sharding JDBC

5.1 Sharding JDBC 简介

官方网站:http://shardingsphere.apache.org/index_zh.html
Apache Sharding Sphere(Incubator) 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(规划中)这3款相互独立,却又能够混合部署配合使用的产品组成。

5.1.1 Sharding JDBC 架构

082.png

5.1.2 Sharding JDBC 对多数据库的支持

083.png

5.1.3 Sharding JDBC 核心概念

数据分片:将数据按照一定的规则进行切分得到数据分片,数据分片分为垂直分片和水平分片。

分片键:用于分片的数据库字段,是将数据库(表)水平拆分的关键字段。

一致性hash环:


084.png

逻辑表:水平拆分的数据库(表)的相同逻辑和数据结构表的总称。
真实表:在分片的数据库中真实存在的物理表。
数据节点:数据分片的最小单元。由数据源名称和数据表组成。
绑定表:指分片规则一致的主表和子表。例如: t_order 表和t_order_item 表,均按照order_id 分片,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。
广播表:指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致。适用于数据量不 大且需要与海量数据的表进行关联查询的场景,例如:字典表。

图片.png
图片2.png

5.2 分片算法与分片策略

5.2.1 分片算法

Sharding-JDBC的分片算法有精确分片算法、范围分片算法、复合分片算法、Hint分片算法四种。

5.2.1.1 精确分片算法

用于处理使用单一键作为分片键的=与IN进行分片的场景。需要配合StandardShardingStrategy使用。

public class MyPreciseShardingAlgorithm implements PreciseShardingAlgorithm {
    @Override
    public String doSharding(Collection collection, PreciseShardingValue preciseShardingValue) {
        for (String each : collection) { 
            if(each.endsWith(Long.parseLong(preciseShardingValue.getValue().toString()) % 2+"")){
                return each;
            }
        }
        throw new IllegalArgumentException();
    }
}

5.2.1.2 范围分片算法

用于处理使用单一键作为分片键的BETWEEN AND进行分片的场景。需要配合StandardShardingStrategy使用。

public class MyRangeShardingAlgorithm implements RangeShardingAlgorithm { 
    @Override
    public Collection doSharding(Collection collection, RangeShardingValue rangeShardingValue) {
        log.info("Range collection:" 
                + JSON.toJSONString(collection) 
                + ",rangeShardingValue:" 
                + JSON.toJSONString(rangeShardingValue));
        Collection collect = new ArrayList<>();
        Range valueRange = rangeShardingValue.getValueRange(); 
        for (Long i = valueRange.lowerEndpoint(); i <=valueRange.upperEndpoint(); i++) {
            for (String each : collection) {
                if (each.endsWith(i % collection.size() + "")) { 
                    collect.add(each);
                }
            }
        }
        return collect;
    }
}

5.2.1.3 复合分片算法

用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合ComplexShardingStrategy使用。

public class MyComplexShardingAlgorithm implements ComplexKeysShardingAlgorithm{

    @Override
    public Collection doSharding(Collection collection, Collection shardingValues) {
        log.info("collection:" 
            + JSON.toJSONString(collection) 
            + ",shardingValues:" 
            + JSON.toJSONString(shardingValues));

        Collection orderIdValues = getShardingValue(shardingValues, "order_id");
        Collection userIdValues = getShardingValue(shardingValues, "user_id");
        List shardingSuffix = new ArrayList<>();
        /**例如:根据user_id + order_id 双分片键来进行分表*/
        //Set> valueResult = Sets.cartesianProduct(userIdValues, orderIdValues);
        for (Long userIdVal : userIdValues) {
            for (Long orderIdVal : orderIdValues) {
                String suffix = userIdVal % 2 + "_" + orderIdVal % 2; 
                collection.forEach(x -> {if (x.endsWith(suffix)) { shardingSuffix.add(x);}});
            }
        }
        return shardingSuffix;
    }

    private Collection getShardingValue(Collection shardingValues, final String key) {
        Collection valueSet = new ArrayList<>(); 
        Iterator iterator = shardingValues.iterator(); 
        while (iterator.hasNext()) {
            ShardingValue next = iterator.next();
            if (next instanceof ListShardingValue) { 
                ListShardingValue value = (ListShardingValue) next;
                /**例如:根据user_id + order_id 双分片键来进行分表*/
                if (value.getColumnName().equals(key)) { 
                    return value.getValues();
                }
            }
        }
        return valueSet;
    }
    
}

5.2.1.4 Hint分片算法

用于处理使用Hint行分片的场景。需要配合HintShardingStrategy使用。

5.2.2 分片策略

Sharding-JDBC的分片策略有标准分片策略、复合分片策略、行表达式分片策略、Hint分片策略四种。

5.2.2.1 标准分片策略

提供对SQL语句中的=、IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持单分片键,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。RangeShardingAlgorithm是可选的,用于处理BETWEEN AND分片。如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理。

5.2.2.2 复合分片策略

提供对SQL语句中的=、IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。

5.2.2.3 行表达式分片策略

使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发。
行表达式的使用非常直观,只需要在配置中使用->{ expression } TODO标识行表达式即可。 目前支持数据节点和分片算法这两个部分的配置。行表达式的内容使用的是Groovy的语法, Groovy能够支持的所有操作,行表达式均能够支持。

5.2.2.4 Hint分片策略

通过Hint而非SQL解析的方式分片的策略。对于分片字段非SQL决定,而由其他外置条件决定的场景,可使用SQL Hint灵活的注入分片字段。

5.3 Sharding JDBC核心组件

5.3.1 解析引擎

085.png
086.png

SQL语句经过解析引擎的词法解析、语法解析,形成语法树。

5.3.2 路由引擎

087.png

标准路由是ShardingSphere最为推荐使用的分片方式,它的适用范围是不包含关联查询或仅包含绑定 表之间关联查询的SQL。当分片运算符是等于号时,路由结果将落入单库(表),当分片运算符是BETWEEN或IN时,则路由结果不一定落入唯一的库(表),因此一条逻辑SQL最终可能被拆分为多条用于执行的真实SQL。

5.3.3 改写引擎

088.png

工程师面向逻辑库与逻辑表书写的SQL,并不能够直接在真实的数据库中执行,SQL改写用于将逻辑SQL改写为在真实数据库中可以正确执行的SQL。 它包括正确性改写和优化改写两部分。

5.3.4 执行引擎

089.png

ShardingSphere采用一套自动化的执行引擎,负责将路由和改写完成之后的真实SQL安全且高效发送到底层数据源执行。执行引擎的目标是自动化的平衡资源控制与执行效率。执行引擎分为准备和执行两个阶段

5.3.5 归并引擎

090.png

将从各个数据节点获取的多数据结果集,组合成为一个结果集并正确的返回至请求客户端,称为结果归并。
ShardingSphere支持的结果归并从功能上分为遍历、排序、分组、分页和聚合5种类型,它们是组合而 非互斥的关系。

你可能感兴趣的:(MySQL分库分表篇)