【MyCat】通过mycat实现mysql数据库的分库分表及sql防火墙配置

前言:随着业务的不断发展,不论你怎么优化代码和负载均衡,都不得不面对数据库性能的瓶颈,为了让数据库的性能得到极大改善,除了优化Mysql本身的配置,以及SQL语句和索引等优化,更重要的就是对现有数据库进行合理拆分,然后分布在不同的服务器上,以减轻单个服务器的IO压力,本篇就跟大家一起分享一下如何使用MyCat实现对数据库表的垂直拆分和水平拆分.关于MyCat我再简单啰嗦几句,MyCat是基于阿里的开源中间件cobar开发的一款数据库中间件,由于其前身cobar是有过实际千万级数据并发量测试的,所以其性能是十分可靠的,加上是阿里爸爸开发的,所以就不要问太多,学就是了.


一.数据库表的垂直拆分.

1.什么是垂直拆分?

所谓垂直拆分就是指把原来一个库中的多个表,按照一定的拆分逻辑将他们拆分出来,放到不同服务器的不同库中.比如开发了一个类似淘宝的电商平台,一开始是把所有表都放在同一个库中,现在可以把用户模块,商品模块,订单模块相关的表各自拆分到不同的库中.

2.垂直拆分的优缺点?

优点:通过垂直拆分,可以使得业务关系更清晰,同时可以降低单台服务器的IO压力,提高数据库性能.

缺点:拆分后会使得一些联表查询变得困难,好在较新版本的MyCat提供了全局表功能可以较好的解决这个问题.

3.如何进行垂直拆分?

首先你必须清楚现有业务,弄清楚性能瓶颈主要出现在哪几张表中,另外要确定一下如何拆分可以让不同模块之间的关联最小,同样遵循低耦合,高内聚.让拆分后的表所属模块尽量清晰明朗,更为优雅,就像拆分订单模块商品模块这样...

确定好了拆分规则以后,下面我们就一起来实际操作一把.

这里我以大家熟悉的电商平台为例,因为拆分规则比较成熟,大家只需要关心如何配置即可.

先看一下我要拆分的数据库:

再来看一下拆分后的数据库:

   

就是把imooc_db(总库)拆分成了product_db(产品库),order_db(订单库),customer_db(用户库)三个库,分别存放了相关的表.

Mycat关联后的逻辑库:

拆分后通过MyCat建立一个逻辑库,关联到这三个库,该逻辑库相当于是这三个库的视图或者联合,这样对后端应用而言就是无感知的,MyCat屏蔽了分库后的差异,对后端应用而言连接的数据库跟原来直接连imooc_db总库的效果是一样的,但实际上是连接到了三台不同服务器上的不同库里.

垂直拆分可以通过如下五个步骤来实现:

第一步:事实上第一步我已经在上面提到了,就是把一个库拆成订单,商品和用户三个库.

第二步:复制数据库到其它服务器中,我这里准备了四台服务器,一台是一开始未拆分数据库的服务器,其它三台服务器用来存放拆分后的数据库.

这里需要一些MySQL主从复制的基础,如果不会的话可以看我上一篇,MySQL主从复制

我默认大家都会MySQL主从复制,需要将第一台服务器上的表imooc_db分别复制到另外三台服务器上的product_db,order_db,customer_db上,由于上篇已经大篇幅讲过主从复制了,这里我就简单写一下.

#在第一台服务器上创建用户并授权.
create user 'laohan'@'119.1.2.%' identified by 'Laohan0.0';
grant replication slave on *.* to root@'119.1.2.%';

#使用命令导出Mysql日志.
mysqldump --single-transaction --master-data=2 --trigger --routines --all-databases -uroot -p >bakup_imooc_db.sql

#使用scp命令将导出的日志复制到其它3台服务器上
scp bakup_imooc_db.sql 'root'@'119.1.2.2':/home
scp bakup_imooc_db.sql 'root'@'119.1.2.3':/home
scp bakup_imooc_db.sql 'root'@'119.1.2.4':/home

#在另外三台服务器上分别创建对应的数据库.
mysql -uroot -p -e"create database product_db"
mysql -uroot -p -e"create database order_db"
mysql -uroot -p -e"create database customer_db"

#在另外三台服务器上依次使用命令将日志恢复到服务器数据库中,完成数据导入工作.
mysql -uroot -p product_db < bakup_imooc_db.sql
mysql -uroot -p order_db < bakup_imooc_db.sql
mysql -uroot -p customer_db < bakup_imooc_db.sql

#在另外三台服务器上依次使用命令配置主从复制
change master to master_host='119.1.2.2',master_user='laohan',master_password='Laohan0.0',master_log_file='mysql-bin.000004',master_log_pos=1687;
change master to master_host='119.1.2.3',master_user='laohan',master_password='Laohan0.0',master_log_file='mysql-bin.000004',master_log_pos=1687;
change master to master_host='119.1.2.4',master_user='laohan',master_password='Laohan0.0',master_log_file='mysql-bin.000004',master_log_pos=1687;

#在另外三台服务器上依次使用命令更改主从复制库名不一致的问题
change replication filter replicate_rewrite_db=((imooc_db,product_db));
change replication filter replicate_rewrite_db=((imooc_db,order_db));
change replication filter replicate_rewrite_db=((imooc_db,customer_db));

#分别启动三台服务器的主从复制
start slave;

#通过命令观察三台服务器的IO和SQL进程是否都为YES
show slave status \G;
如果都为YES,说明主从复制配置成功.

第三步:配置MyCat垂直分库规则,因为这里暂时不做水平分库,所以只需要配置第一台服务器MyCat 的server.xml和schema.xml,不需要关心rule.xml

配置server.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://io.mycat/">
        <!--system标签内的内容可以直接copy过去,没啥好说的,感兴趣可以在mycat官网去研究配置-->
        <system>
          <property name="serverPort">8066</property>
          <property name="managerPort">9066</property>
          <property name="nonePasswordLogin">0</property>
          <property name="bindIp">0.0.0.0</property>
          <property name="frontWriteQueueSize">2048</property>

          <property name="charset">utf8</property>
          <property name="txIsolation">2</property>
          <property name="processors">8</property>
          <property name="idleTimeout">1800000</property>
          <property name="sqlExecuteTimeout">300</property>
          <property name="useSqlStat">0</property>
          <property name="useGlobleTableCheck">0</property>
          <property name="sequnceHandlerType">2</property>
          <property name="defaultMaxLimit">100</property>
          <property name="maxPacketSize">104857600</property>
        </system>
        <!--重点,用户名,也就是到时候你的应用要连接的逻辑数据库的用户名和密码-->
        <!--建议跟原来拆分前的数据库用户名和密码保持一致,这样对后端来说修改最少-->
        <user name="app_laohan">
                <!--这行表示使用对明文密码加密-->
                <property name="usingDecrypt">1</property>
                <!--这行一串就是加密后的密码,加密需要通过下面命令去生成下面这串密码-->
                <!--进入mycat的lib目录下,使用java -cp Mycat-server-版本号(可用tab键填充).jar io.mycat.DecryptUtil 0:账号:密码-->
                <!--其中0的意思是前端加密,输完后敲回车就会生成下面这串密文-->
                <property name="password">BcuSAASJ9jyuQnyAOGHPKwIbB8YHlgLANvZe49RtFh0Wip66+jMZRDk5piVhp37EjF/xlEW6YvpbuBr3nN0EQw==</property>
                <property name="schemas">imooc_db</property>
        </user>

</mycat:server>

配置schema.xml:

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
        <!--然后配置这块,指明每张表对应的服务器,并把相应的表归属于相应的数据节点,节点名可以自己取,比如ordb-->
        <schema name="imooc_db" checkSQLschema="false" sqlMaxLimit="100">
                <table name="order_master" primaryKey="order_id" dataNode="ordb" />
                <table name="order_detail" primaryKey="order_detail_id" dataNode="ordb" />
                <table name="order_customer_addr" primaryKey="customer_addr_id" dataNode="ordb" />
                <table name="order_cart" primaryKey="cart_id" dataNode="ordb" />
                <!--这里region_info是全局表,所以配置略有不同,它归属于所有数据节点,用逗号隔开,type为global-->
                <table name="region_info" primaryKey="region_id" dataNode="ordb,prodb,custdb" type="global" />
                <table name="shipping_info" primaryKey="ship_id" dataNode="ordb" />
                <table name="warehouse_info" primaryKey="w_id" dataNode="ordb" />
                <table name="warehouse_proudct" primaryKey="wp_id" dataNode="ordb" />

                <table name="product_brand_info" primaryKey="brand_id" dataNode="prodb" />
                <table name="product_category" primaryKey="category_id" dataNode="prodb" />
                <table name="product_comment" primaryKey="comment_id" dataNode="prodb" />
                <table name="product_info" primaryKey="product_id" dataNode="prodb" />
                <table name="product_pic_info" primaryKey="product_pic_id" dataNode="prodb" />
                <table name="product_supplier_info" primaryKey="supplier_id" dataNode="prodb" />

                <table name="customer_balance_log" primaryKey="balance_id" dataNode="custdb" />
                <table name="customer_inf" primaryKey="customer_inf_id" dataNode="custdb" />
                <table name="customer_login" primaryKey="customer_id" dataNode="custdb" />
                <table name="customer_login_log" primaryKey="login_id" dataNode="custdb" />
                <table name="customer_point_log" primaryKey="point_id" dataNode="custdb" />
        </schema>
        <!--最后配置这块,指明每个数据节点对应的服务器名,以及该数据节点对应的实际数据库名-->
        <dataNode name="ordb" dataHost="mysql0132" database="order_db" />
        <dataNode name="prodb" dataHost="mysql0133" database="product_db" />
        <dataNode name="custdb" dataHost="mysql0134" database="customer_db" />

        <!--先配置这块,指明拆分后的三个库对应的服务器Ip等信息,至于writType之类的标签含义需自参阅官网文档:http://www.mycat.io/-->
        <dataHost name="mysql0132" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1">
                <heartbeat>select user() </heartbeat>
                <writeHost host="119.1.2.2" url="119.1.2.2:3306" user="dba" password="Woaiyinyin0.0" />
        </dataHost>

        <dataHost name="mysql0133" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1">
                <heartbeat>select user() </heartbeat>
                <writeHost host="119.1.2.3" url="119.1.2.3:3306" user="dba" password="Woaiyinyin0.0" />
        </dataHost>

        <dataHost name="mysql0134" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1">
                <heartbeat>select user() </heartbeat>
                <writeHost host="119.1.2.4" url="119.1.2.4:3306" user="dba" password="Woaiyinyin0.0" />
        </dataHost>

</mycat:schema>

第四步:通过MyCat访问DB,前面配置好了之后,就启动mycat观察是否配置成功:

#启动MyCat
mycat start

#观察是否启动成功(在Mycat的logs目录下)
tail -f wrapper.log
如果看到:
Mycat server starup successfully.
说明启动成功了.

#然后可以在另外随便哪台服务器上连接到Mycat去访问DB看看(账号和密码为server.xml配置的)

mysql -uapp_laohan -pLaohan0.0 -P8066 -h119.1.2.1

连接进去随便执行几条查询看看是否成功.

第五步:如果第四步访问和查询都没问题,那现在可以停掉三台服务器的主从复制,并删除多余的表

#停止其它三台服务器上的主从复制,在三台服务器上分别执行
stop slave;
reset slave all;

#用Navicat工具或者命令行删除多余的表,比如order_db库中当前是包含imooc_db中的所有表的,现在只需要保留拆分后的表,也就是跟oredr相关的表,其它无关表都可以删除掉了,值得注意的是需要保留一些全局的表,比如字典表,这些表在涉及到联表查询时会用到,除了留下它们外还要对它们进行配置,这些全局表的配置我在schema.xml中已经指明了如何配置了,这里不再赘述.

至此,大功告成,你可以通过在server.xml中配置的账号和密码,通过端口号8066轻松连接到你配置好的逻辑库中,该逻辑库看上去跟原来未拆分的库一模一样,但实际上性能已经有很大飞跃,同时因为全局表的出现,也不会影响你的联表查询.你甚至可以将mycat的端口号也设为3306,账号密码以及逻辑库的名字都与未拆分前保持一致,这样就可以让后端应用无感知的就切换到了Mycat垂直拆分后的数据库上.


二.数据库表的水平拆分

1.什么是数据库表的水平拆分?

水平拆分是在垂直拆分的基础上进一步对现有压力过大的数据库进行横向拆分,比如原来的订单库在生产环境中压力过大,现在把该订单库根据一定的规则拆分成了4个订单库,每个订单库分别存储一部分数据,以减轻IO压力.

2.水平拆分的优缺点

优点:通过水平拆分后可以减少单个数据库中存储数据过多的情况,在查询中可以显著提高效率,同时也可以减轻数据库压力.

缺点:拆分后涉及到一些联表查询会因为数据存在分片不一致而无法完成查询.好在MyCat可以通过配置ER分片来解决此问题.

3.如何进行水平拆分

首先,水平拆分需要注意下面几个原则:

水平拆分主要分为以下几个步骤:

1.根据业务状态,我们发现订单这个模块的数据量比较大,数据库压力过大,需要进行水平拆分,这里挑选出order_master这张表进行水平切分.

2.根据选择分片键的原则:

观察order_master这张表里面的字段后发现,最终选择customer_id的值作为分片键,由于该键是自增的整型数值,所以选择简单取模分片算法比较合适.

3.在确定好了要拆分的表以及分片键和分片算法后,下面进入实操部分.

首先配置在第二台和第三台服务器上创建orderdb01-04这些数据库,并导入order_master表的结构.

修改schema.xml配置:

由于我依旧使用前面垂直拆分后的配置文件,所以在原来的基础上,我所水平拆分的库依旧分布在第二台和第三台服务器上,故无需配置dataHost.根据下面配置中的注释顺序修改部分配置即可.

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

        <schema name="imooc_db" checkSQLschema="false" sqlMaxLimit="100">
                <!--然后修改这里需要拆分的order_master表所对应的数据节点,用逗号隔开即可,分片规则暂时命名为order_master-->
                <table name="order_master" primaryKey="order_id" dataNode="orderdb01,orderdb02,orderdb03,orderdb04" rule="order_master" />
                <table name="order_detail" primaryKey="order_detail_id" dataNode="ordb" />
                <table name="order_customer_addr" primaryKey="customer_addr_id" dataNode="ordb" />
                <table name="order_cart" primaryKey="cart_id" dataNode="ordb" />
                <table name="region_info" primaryKey="region_id" dataNode="ordb,prodb,custdb" type="global" />
                <table name="shipping_info" primaryKey="ship_id" dataNode="ordb" />
                <table name="warehouse_info" primaryKey="w_id" dataNode="ordb" />
                <table name="warehouse_proudct" primaryKey="wp_id" dataNode="ordb" />

                <table name="product_brand_info" primaryKey="brand_id" dataNode="prodb" />
                <table name="product_category" primaryKey="category_id" dataNode="prodb" />
                <table name="product_comment" primaryKey="comment_id" dataNode="prodb" />
                <table name="product_info" primaryKey="product_id" dataNode="prodb" />
                <table name="product_pic_info" primaryKey="product_pic_id" dataNode="prodb" />
                <table name="product_supplier_info" primaryKey="supplier_id" dataNode="prodb" />

                <table name="customer_balance_log" primaryKey="balance_id" dataNode="custdb" />
                <table name="customer_inf" primaryKey="customer_inf_id" dataNode="custdb" />
                <table name="customer_login" primaryKey="customer_id" dataNode="custdb" />
                <table name="customer_login_log" primaryKey="login_id" dataNode="custdb" />
                <table name="customer_point_log" primaryKey="point_id" dataNode="custdb" />

        </schema>

        <dataNode name="ordb" dataHost="mysql0132" database="order_db" />
        <dataNode name="prodb" dataHost="mysql0133" database="product_db" />
        <dataNode name="custdb" dataHost="mysql0134" database="customer_db" />
        <!--首先配置orderdb01-04所分布的数据节点-->
        <dataNode name="orderdb01" dataHost="mysql0132" database="orderdb01" />
        <dataNode name="orderdb02" dataHost="mysql0132" database="orderdb02" />
        <dataNode name="orderdb03" dataHost="mysql0133" database="orderdb03" />
        <dataNode name="orderdb04" dataHost="mysql0133" database="orderdb04" />


        <dataHost name="mysql0132" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1">
                <heartbeat>select user() </heartbeat>
                <writeHost host="119.1.2.2" url="119.1.2.2:3306" user="dba" password="Woaiyinyin0.0" />
        </dataHost>

        <dataHost name="mysql0133" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1">
                <heartbeat>select user() </heartbeat>
                <writeHost host="119.1.2.3" url="119.1.2.3:3306" user="dba" password="Woaiyinyin0.0" />
        </dataHost>

        <dataHost name="mysql0134" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1">
                <heartbeat>select user() </heartbeat>
                <writeHost host="119.1.2.4" url="119.1.2.4:3306" user="dba" password="Woaiyinyin0.0" />
        </dataHost>

</mycat:schema>

修改完成后保存,接下来配置rule.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:rule SYSTEM "rule.dtd">
<mycat:rule xmlns:mycat="http://io.mycat/">
        <!--name要与schema中配置的name一致-->
        <tableRule name="order_master">
                <rule>
                        <!--这里即前面选出的分片键-->
                        <columns>customer_id</columns>
                        <!--指明分片算法为简单取模算法-->
                        <algorithm>mod-long</algorithm>
                </rule>
        </tableRule>
        <!--定义分片函数:简单取模算法,指明它所在的包名-->
        <function name="mod-long" class="io.mycat.route.function.PartitionByMod">
                <!--指明所要分片的数量,这里分了4个库,所以写4-->
                <property name="count">4</property>
        </function>

</mycat:rule>

修改完成后保存并重启Mycat,重启后观察日志是否重启成功,如果成功则可进入测试阶段:

连接Mycat数据库:


mysql -uapp_laohan -pLaohan0.0 -P8066 -h119.1.2.1

然后向Mycat的逻辑库Imooc_db中的order_master中插入几条数据,然后进入orderdb01-04中观察分布情况,如果分布ok,那说明已经配置成功.(建议使用Navicat这类可视化工具进行操作会更直观)


至此尚未大功告成,因为你会发现经过水平拆分后,自增的order_id不是唯一的了,有重复的order_id出现,因此你还需要配置全局的自增ID,MyCat为你提供了配置全局ID的几种方式,由于MyCat也需要做高可用,所以我这里就不讲传统的配置方式了,感兴趣的到下篇去看zookeeper方式的全局ID配置.

前面提到了ER分片也讲一下,由于分片将order_master表中的数据分布在了不同的库中,当需其他表要Jion表order_master查询时可能会出现查询错误的情况,为了解决该问题就需要配ER分片.

场景:

◆适合一些数据比较多的大表.如果数据量不多的情况下使用垂直拆分时提到的全局表策略即可解决.

◆适合跟水平拆分表order_master关联查询比较多的表.

首先要找到需要联表查询比较多的表,然后将该表也做分片,而且配置时要把order_master当爸爸...

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

        <schema name="imooc_db" checkSQLschema="false" sqlMaxLimit="100">
                <!--配置childTable,指明JionKey和ParentKey-->
                <table name="order_master" primaryKey="order_id" dataNode="orderdb01,orderdb02,orderdb03,orderdb04" rule="order_master" >
                    <childTable name=order_detail  primaryKey="order_detail_id" joinKey="order_id" parentKey="order_id"/>
                </table>
<!--后面的配置就省略了,跟上面的一样-->

</mycat:schema>

然后重启Mycat,成功后分别向order_master和order_detail表中插入多条order_id相同的数据,然后通过联表查询测试是否成功.


文末,送点彩蛋,MyCat的SQL拦截和防火墙配置:

需要配置server.xml:


下回分解:如何搭建真正高可用的企业级mycat.

猜你喜欢

转载自blog.csdn.net/lovexiaotaozi/article/details/83022009
今日推荐