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<Long> {
    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Long> 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<Long> { 
    @Override
    public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {
        log.info("Range collection:" 
                + JSON.toJSONString(collection) 
                + ",rangeShardingValue:" 
                + JSON.toJSONString(rangeShardingValue));
        Collection<String> collect = new ArrayList<>();
        Range<Long> 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<String> doSharding(Collection<String> collection, Collection<ShardingValue> shardingValues) {
        log.info("collection:" 
            + JSON.toJSONString(collection) 
            + ",shardingValues:" 
            + JSON.toJSONString(shardingValues));

        Collection<Long> orderIdValues = getShardingValue(shardingValues, "order_id");
        Collection<Long> userIdValues = getShardingValue(shardingValues, "user_id");
        List<String> shardingSuffix = new ArrayList<>();
        /**例如:根据user_id + order_id 双分片键来进行分表*/
        //Set<List<Integer>> 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<Long> getShardingValue(Collection<ShardingValue> shardingValues, final String key) {
        Collection<Long> valueSet = new ArrayList<>(); 
        Iterator<ShardingValue> 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 } 或->{ 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种类型,它们是组合而 非互斥的关系。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容