007.分布式数据库中间件 MyCat

特别说明: 本人平时混迹于 B 站,不咋回复这里的评论,有问题可以到 B 站视频评论区留言找我
视频地址: https://space.bilibili.com/31137138/favlist?fid=326428938
课件说明: 本次提供的课件是 Spring Cloud Netflix 版微服务架构指南,如果有兴趣想要学习 Spring Cloud Alibaba 版,可以前往 http://www.qfdmy.com 查看相关课程资源
案例代码: https://github.com/topsale/hello-spring-cloud-netflix

概述

Mycat 是什么?从定义和分类来看,它是一个开源的分布式数据库系统,是一个实现了 MySQL 协议的 Server,前端用户可以把它看作是一个数据库代理,用 MySQL 客户端工具和命令行访问,而其后端可以用 MySQL 原生(Native) 协议与多个 MySQL 服务器通信,也可以用 JDBC 协议与大多数主流数据库服务器通信,其核心功能是分表分库,即将一个大表水平分割为 N 个小表,存储在后端 MySQL 服务器里或者其他数据库里

什么是分库分表(数据切分)

简单来说,就是指通过某种特定的条件,将我们存放在同一个数据库中的数据分散存放到多个数据库(主机)上面,以达到分散单台设备负载的效果

数据的切分(Sharding)根据其切分规则的类型,可以分为两种切分模式。一种是按照不同的表(或者 Schema)来切分到不同的数据库(主机)之上,这种切可以称之为数据的垂直(纵向)切分;另外一种则是根据表中的数据的逻辑关系,将同一个表中的数据按照某种条件拆分到多台数据库(主机)上面,这种切分称之为数据的水平(横向)切分

垂直切分的最大特点就是规则简单,实施也更为方便,尤其适合各业务之间的耦合度非常低,相互影响很小,业务逻辑非常清晰的系统。在这种系统中,可以很容易做到将不同业务模块所使用的表分拆到不同的数据库中。根据不同的表来进行拆分,对应用程序的影响也更小,拆分规则也会比较简单清晰

水平切分于垂直切分相比,相对来说稍微复杂一些。因为要将同一个表中的不同数据拆分到不同的数据库中,对于应用程序来说,拆分规则本身就较根据表名来拆分更为复杂,后期的数据维护也会更为复杂一些

垂直切分

一个数据库由很多表的构成,每个表对应着不同的业务,垂直切分是指按照业务将表进行分类,分布到不同的数据库上面,这样也就将数据或者说压力分担到不同的库上面,如下图:

Lusifer_20191207042801.png

系统被切分成了,用户,订单交易,支付几个模块。一个架构设计较好的应用系统,其总体功能肯定是由很多个功能模块所组成的,而每一个功能模块所需要的数据对应到数据库中就是一个或者多个表。而在架构设计中,各个功能模块相互之间的交互点越统一越少,系统的耦合度就越低,系统各个模块的维护性以及扩展性也就越好。这样的系统,实现数据的垂直切分也就越容易

但是往往系统之有些表难以做到完全的独立,存在这扩库 join 的情况,对于这类的表,就需要去做平衡,是数据库让步业务,共用一个数据源,还是分成多个库,业务之间通过接口来做调用。在系统初期,数据量比较少,或者资源有限的情况下,会选择共用数据源,但是当数据发展到了一定的规模,负载很大的情况,就需要必须去做分割

一般来讲业务存在着复杂 join 的场景是难以切分的,往往业务独立的易于切分。 如何切分,切分到何种程度是考验技术架构的一个难题

  • 优点

    • 拆分后业务清晰,拆分规则明确
    • 系统之间整合或扩展容易
    • 数据维护简单
  • 缺点

    • 部分业务表无法 join,只能通过接口方式解决,提高了系统复杂度
    • 受每种业务不同的限制存在单库性能瓶颈,不易数据扩展跟性能提高
    • 事务处理复杂

由于垂直切分是按照业务的分类将表分散到不同的库,所以有些业务表会过于庞大,存在单库读写与存储瓶颈,所以就需要水平拆分来做解决

水平切分

相对于垂直拆分,水平拆分不是将表做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中包含一部分数据。简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中的某些行切分到一个数据库,而另外的某些行又切分到其他的数据库中,如图:

Lusifer_20191207043200.png

拆分数据就需要定义分片规则。关系型数据库是行列的二维模型,拆分的第一原则是找到拆分维度。 比如:从会员的角度来分析,商户订单交易类系统中查询会员某天某月某个订单,那么就需要按照会员结合日期来拆分,不同的数据按照会员 ID 做分组,这样所有的数据查询 join 都会在单库内解决;如果从商户的角度来讲,要查询某个商家某天所有的订单数,就需要按照商户 ID 做拆分;但是如果系统既想按会员拆分,又想按商家数据,则会有一定的困难。如何找到合适的分片规则需要综合考虑衡量。几种典型的分片规则包括:

  • 按照用户 ID 求模,将数据分散到不同的数据库,具有相同数据用户的数据都被分散到一个库中
  • 按照日期,将不同月甚至日的数据分散到不同的库中
  • 按照某个特定的字段求摸,或者根据特定范围段分散到不同的库中

如图,切分原则都是根据业务找到适合的切分规则分散到不同的库,下面用用户 ID 求模举例

Lusifer_20191207043445.png
  • 优点

    • 拆分规则抽象好, join 操作基本可以数据库做
    • 不存在单库大数据,高并发的性能瓶颈
    • 应用端改造较少
    • 提高了系统的稳定性跟负载能力
  • 缺点

    • 拆分规则难以抽象
    • 分片事务一致性难以解决
    • 数据多次扩展难度跟维护量极大
    • 跨库 join 性能较差
  • 垂直与水平拆分的共同缺点

    • 引入分布式事务的问题
    • 跨节点 join 的问题
    • 跨节点合并排序分页问题
    • 多数据源管理问题

数据源管理方案

  • 客户端模式,在每个应用程序模块中配置管理自己需要的一个(或者多个)数据源,直接访问各个数据库,在模块内完成数据的整合
  • 通过中间代理层来统一管理所有的数据源,后端数据库集群对前端应用程序透明

绝大部分人在面对上面这两种解决思路的时候都会倾向于选择第二种,尤其是系统不断变得庞大复杂的时候。确实,这是一个非常正确的选择,虽然短期内需要付出的成本可能会相对更大一些,但是对整个系统的扩展性来说,是非常有帮助的

数据库中间件 MyCat

MyCat 是一个强大的数据库中间件,不仅仅可以用作读写分离、以及分表分库、容灾备份,而且可以用于多租户应用开发、云平台基础设施、让你的架构具备很强的适应性和灵活性,借助于即将发布的 MyCat 智能优化模块,系统的数据访问瓶颈和热点一目了然,根据这些统计分析数据,你可以自动或手工调整后端存储,将不同的表映射到不同存储引擎上,而整个应用的代码一行也不用改变

应用场景

  • 单纯的读写分离,此时配置最为简单,支持读写分离,主从切换
  • 分表分库,对于超过 1000 万的表进行分片,最大支持 1000 亿的单表分片
  • 多租户应用,每个应用一个库,但应用程序只连接 MyCat,从而不改造程序本身,实现多租户化
  • 报表系统,借助于 MyCat 的分表能力,处理大规模报表的统计
  • 替代 Hbase,分析大数据
  • 作为海量数据实时查询的一种简单有效方案,比如 100 亿条频繁查询的记录需要在 3 秒内查询出来结果,除了基于主键的查询,还可能存在范围查询或其他属性查询,此时 MyCat 可能是最简单有效的选择

环境准备

部署方式全部基于 Docker

部署 3 台 MySQL 容器

案例分片方案按照自定义数字范围分片方式(auto-sharding-long)进行数据库分片,其规则要求需要 3 台 MySQL,docker-compose.yml 配置如下

  • mysql-1
version: '3.1'
services:
  mysql-1:
    image: mysql
    container_name: mysql-1
    environment:
      MYSQL_ROOT_PASSWORD: 123456
    command:
      --default-authentication-plugin=mysql_native_password
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_general_ci
      --explicit_defaults_for_timestamp=true
      --lower_case_table_names=1
    ports:
      - 3306:3306
    volumes:
      - ./data:/var/lib/mysql
  • mysql-2
version: '3.1'
services:
  mysql-2:
    image: mysql
    container_name: mysql-2
    environment:
      MYSQL_ROOT_PASSWORD: 123456
    command:
      --default-authentication-plugin=mysql_native_password
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_general_ci
      --explicit_defaults_for_timestamp=true
      --lower_case_table_names=1
    ports:
      - 3307:3306
    volumes:
      - ./data:/var/lib/mysql
  • mysql-3
version: '3.1'
services:
  mysql-3:
    image: mysql
    container_name: mysql-3
    environment:
      MYSQL_ROOT_PASSWORD: 123456
    command:
      --default-authentication-plugin=mysql_native_password
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_general_ci
      --explicit_defaults_for_timestamp=true
      --lower_case_table_names=1
    ports:
      - 3308:3306
    volumes:
      - ./data:/var/lib/mysql

部署 MyCat 数据库中间件

  • 克隆
git clone https://github.com/dekuan/docker.mycat.git
  • 构建
cd docker.mycat
docker-compose build
  • 启动
# 注意:配置完成后再启动
docker-compose up -d

配置 MyCat 数据库分片

  • 服务端用户名密码配置:vi config/mycat/server.xml,找到第 90 行,参考如下内容配置
<mycat:server xmlns:mycat="http://io.mycat/">
    <!-- Mycat 数据库用户名 -->
    <user name="root">
        <!-- Mycat 数据库密码 -->
        <property name="password">123456</property>

        <!-- Mycat 数据库名 -->
        <property name="schemas">myshop</property>

        <!-- 是否使用加密的密码,0 表示不使用加密的密码 -->
        <property name="usingDecrypt">0</property>
    </user>
</mycat:server>
  • 数据节点、数据库、分库分表配置:vi config/mycat/schema.xml,参考如下内容配置
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

        <schema name="myshop" checkSQLschema="true" sqlMaxLimit="100">
                <!-- 需要分片的表
                     在节点 dataNode1, dataNode2, dataNode3 上分片
                     分片规则是 auto-sharding-long 即连续分片规则之自定义数字范围分片
                     对应的分片规则配置文件在 config/mycat/rule.xml
                -->
                <table
                                name="tb_admin"
                                primaryKey="id"
                                dataNode="dataNode1,dataNode2,dataNode3"
                                rule="auto-sharding-long"/>
        </schema>

        <!-- 数据节点 dataNode1,对应的主机 dataHost1, 对应是数据库 myshop_1 -->
        <dataNode name="dataNode1" dataHost="dataHost1" database="myshop_1" />
        <dataNode name="dataNode2" dataHost="dataHost2" database="myshop_2" />
        <dataNode name="dataNode3" dataHost="dataHost3" database="myshop_3" />

        <!-- 主机 dataHost1 -->
        <dataHost name="dataHost1" maxCon="1000" minCon="10" balance="0"
                          writeType="0" dbType="mysql" dbDriver="jdbc" switchType="-1" slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
                <!-- MySQL 数据库的连接串 -->
                <writeHost
                                host="192.168.141.206"
                                url="jdbc:mysql://192.168.141.206:3306?useSSL=false&amp;serverTimezone=UTC&amp;characterEncoding=utf8"
                                user="root" password="123456">
                        <!--<readHost host="192.168.1.104" url="192.168.1.104:3306" user="druid" password="druid" />-->
                </writeHost>
        </dataHost>
        <dataHost name="dataHost2" maxCon="1000" minCon="10" balance="0"
                          writeType="0" dbType="mysql" dbDriver="jdbc" switchType="-1" slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
                <writeHost
                                host="192.168.141.206"
                                url="jdbc:mysql://192.168.141.206:3307?useSSL=false&amp;serverTimezone=UTC&amp;characterEncoding=utf8"
                                user="root" password="123456">
                        <!--<readHost host="192.168.1.104" url="192.168.1.104:3306" user="druid" password="druid" />-->
                </writeHost>
        </dataHost>
        <dataHost name="dataHost3" maxCon="1000" minCon="10" balance="0"
                          writeType="0" dbType="mysql" dbDriver="jdbc" switchType="-1" slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
                <writeHost
                                host="192.168.141.206"
                                url="jdbc:mysql://192.168.141.206:3308?useSSL=false&amp;serverTimezone=UTC&amp;characterEncoding=utf8"
                                user="root" password="123456">
                        <!--<readHost host="192.168.1.104" url="192.168.1.104:3306" user="druid" password="druid" />-->
                </writeHost>
        </dataHost>
        
</mycat:schema>
  • 分片规则配置:vi config/mycat/rule.xml,分别查看第 32 行和第 105 行
<!-- 第 32 行 -->
<tableRule name="auto-sharding-long">
    <rule>
        <!-- 指定分片表列名 -->
        <columns>id</columns>
        <!-- 指定分片函数与 function 对应 -->
        <algorithm>rang-long</algorithm>
    </rule>
</tableRule>
<!-- 第 105 行 -->
<function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong">
    <!-- 函数中的 mapFile 代表规则配置文件的路径,config/mycat/autopartition-long.txt -->
    <property name="mapFile">autopartition-long.txt</property>
</function>
  • 自定义数字范围分片规则:vi config/mycat/autopartition-long.txt
# range start-end ,data node index
# K=1000,M=10000.
# ID 0-5000000 保存在 dataNode1
0-500M=0
# ID 5000000-10000000 保存在 dataNode2
500M-1000M=1
# ID 10000000-15000000 保存在 dataNode3
1000M-1500M=2

测试 MyCat 数据分片

分别创建数据库

分别在 3 台 MySQL 数据库上创建 myshop_1myshop_2myshop_3,按照刚才创建 MySQL 容器的序号创建

通过 MyCat 操作数据库

使用 SQLyog 之类的客户端工具连接 MyCat 数据库,默认端口号为 8066

Lusifer_20191207052658.png
Lusifer_20191207052826.png

创建测试数据

  • 建表语句
create table tb_admin (id int not null primary key,name varchar(100),sharding_id int not null);
  • 测试数据
insert into tb_admin(id, name,sharding_id) values(1000000, 'lixiaohong', 0);
insert into tb_admin(id, name,sharding_id) values(6000000, 'lixiaolu', 1);
insert into tb_admin(id, name,sharding_id) values(7000000, 'pgone', 1);
insert into tb_admin(id, name,sharding_id) values(11000000, 'jianailiang', 2);

检验测试结果

按照上面的配置规则

  • id1000000 的数据应该写入 dataNode1myshop_1.tb_admin 表中
  • id6000000 的数据应该写入 dataNode2myshop_2.tb_admin 表中
  • id7000000 的数据应该写入 dataNode2myshop_2.tb_admin 表中
  • id11000000 的数据应该写入 dataNode3myshop_3.tb_admin 表中

至此这说明分片成功了

特别说明: 本人平时混迹于 B 站,不咋回复这里的评论,有问题可以到 B 站视频评论区留言找我
视频地址: https://space.bilibili.com/31137138/favlist?fid=326428938
课件说明: 本次提供的课件是 Spring Cloud Netflix 版微服务架构指南,如果有兴趣想要学习 Spring Cloud Alibaba 版,可以前往 http://www.qfdmy.com 查看相关课程资源
案例代码: https://github.com/topsale/hello-spring-cloud-netflix

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

推荐阅读更多精彩内容