Mysql主从复制�搭建

MySQL 的主从复制和 MySQL 的读写分离两者有着紧密联系,首先要部署主从复制,只有主 从复制完成了,才能在此基础上进行数据的读写分离

�基础设置准备

如果是克隆的虚拟机,需要先删除/var/lib/mysql/auto.cnf配置文件,因为此文件下的server-uuid是一致的。
重新开启mysql即可

  1. #操作系统:
  2. centos6.5
  3. #mysql版本:
  4. 5.7
  5. #两台虚拟机:
  6. node1:172.16.13.128(主)
  7. node2:172.16.13.146(从)

在两台数据库中分别创建数据库

--注意两台必须全部执行
create database msb;

在主(node1)服务器进行如下配置

#修改配置文件,执行以下命令打开mysql配置文件
vi /etc/my.cnf
#在mysqld模块中添加如下配置信息
log-bin=master-bin #二进制文件名称
binlog-format=ROW  #二进制日志格式,有row、statement、mixed三种格式,row指的是把改变的内容复制过去,而不是把命令在从服务器上执行一遍,statement指的是在主服务器上执行的SQL语句,在从服务器上执行同样的语句。MySQL默认采用基于语句的复制,效率比较高。mixed指的是默认采用基于语句的复制,一旦发现基于语句的无法精确的复制时,就会采用基于行的复制。
server-id=1           #要求各个服务器的id必须不一样
binlog-do-db=msb   #同步的数据库名称

配置从服务器登录主服务器的账号授权

--授权操作
set global validate_password_policy=0;
set global validate_password_length=1;
grant replication slave on *.* to 'root'@'%' identified by '123456';
--刷新权限
flush privileges;

set global validate_password_policy=0; set global validate_password_length=1; 如果失败,则 修改my.cnf,重新启动mysql服务器以使新设置生效。 my.cnf添加文件如下:

plugin-load-add=validate_password.so
validate-password=FORCE_PLUS_PERMANENT

解释一下: plugin-load-add=validate_password.so:插件的加载方法,每次服务器启动时都必须给出该选项; validate-password=FORCE_PLUS_PERMANENT:validate-password在服务器启动时使用该选项来控制插件的激活。

从服务器的配置

#修改配置文件,执行以下命令打开mysql配置文件
vi /etc/my.cnf
#在mysqld模块中添加如下配置信息
log-bin=master-bin    #二进制文件的名称
binlog-format=ROW    #二进制文件的格式
server-id=2            #服务器的id

重启主服务器的mysql服务

#登录mysql,查看master的状态
show master status

image.png

重启从服务器并进行相关配置

#登录mysql
mysql -uroot -p

#连接主服务器
change master to master_host='172.16.13.128',master_user='root',master_password='123456',master_port=3306,master_log_file='master-bin.000001',master_log_pos=154;

#启动slave
start slave;

#关闭slave同步
#stop slave;

#查看slave的状态
show slave status\G(注意没有分号)

#Slave_IO_Running: Yes
#Slave_SQL_Running: Yes
此两个结果为Yes,则成功。

Mysql主从复制原理

为什么需要主从复制

  • 在业务复杂的系统中,有这么一个情景,有一句sql语句需要锁表,导致暂时不能使用读的服务,那么就很影响运行中的业务,使用主从复制,让主库负责写,从库负责读,这样,即使主库出现了锁表的情景,通过读从库也可以保证业务的正常运作。
  • 做数据的热备。
  • 架构的扩展。业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库的存储,降低磁盘I/O访问的频率,提高单个机器的I/O性能。

    什么是Mysql的主从复制

  • MySQL 主从复制是指数据可以从一个MySQL数据库服务器主节点复制到一个或多个从节点。MySQL 默认采用异步复制方式,这样从节点不用一直访问主服务器来更新自己的数据,数据的更新可以在远程连接上进行,从节点可以复制主数据库中的所有数据库或者特定的数据库,或者特定的表。

    Mysql主从复制原理

  1. master服务器将数据的改变记录二进制binlog日志,当master上的数据发生改变时,则将其改变写入二进制日志中。
  2. slave服务器会在一定时间间隔内对master二进制日志进行探测其是否发生改变,如果发生改变,则开始一个I/O Thread请求master二进制事件。
  3. 同时主节点为每个I/O线程启动一个dump线程,用于向其发送二进制事件,并保存至从节点本地的中继日志中,从节点将启动SQL线程从中继日志中读取二进制日志,在本地重放,使得其数据和主节点的保持一致,最后I/OThread和SQLThread将进入睡眠状态,等待下一次被唤醒。

    也就是说:

  • 从库会生成两个线程,一个I/O线程,一个SQL线程;
  • I/O线程会去请求主库的binlog,并将得到的binlog写到本地的relay-log(中继日志)文件中;
  • 主库会生成一个log dump线程,用来给从库I/O线程传binlog;
  • SQL线程,会读取relay log文件中的日志,并解析成sql语句逐一执行;

    注意

  1. master将操作语句记录到binlog日志中,然后授予slave远程连接的权限(master一定要开启binlog二进制日志功能;通常为了数据安全考虑,slave也开启binlog功能)。
  2. slave开启两个线程:IO线程和SQL线程。其中:IO线程负责读取master的binlog内容到中继日志relay log里;SQL线程负责从relay log日志里读出binlog内容,并更新到slave的数据库里,这样就能保证slave数据和master数据保持一致了。
  3. Mysql复制至少需要两个Mysql的服务,当然Mysql服务可以分布在不同的服务器上,也可以在一台服务器上启动多个服务。
  4. Mysql复制最好确保master和slave服务器上的Mysql版本相同(如果不能满足版本一致,那么要保证master主节点的版本低于slave从节点的版本)。
  5. master和slave两节点间时间需同步。

    �图示

    image.png

    具体步骤

  6. 从库通过手工执行change master to 语句连接主库,提供了连接的用户一切条件(user 、password、port、ip),并且让从库知道,二进制日志的起点位置(file名 position 号);start slave。

  7. �从库的IO线程和主库的dump线程建立连接。
  8. 从库根据change master to 语句提供的file名和position号,IO线程向主库发起binlog的请求。
  9. 主库dump线程根据从库的请求,将本地binlog以events的方式发给从库IO线程。
  10. 从库IO线程接收binlog events,并存放到本地relay-log中,传送过来的信息,会记录到master.info中。
  11. 从库SQL线程应用relay-log,并且把应用过的记录到relay-log.info中,默认情况下,已经应用过的relay 会自动被清理purge。

    Mysql主从同步延时问题分析

    mysql的主从复制都是单线程的操作,主库对所有DDL和DML产生的日志写进binlog,由于binlog是顺序写,所以效率很高。 slave的sql thread线程将主库的DDL和DML操作事件在slave中重放。DML和DDL的IO操作是随机的,不是顺序,所以成本要高很多。 另一方面,由于sql thread也是单线程的,当主库的并发较高时,产生的DML数量超过slave的SQL thread所能处理的速度,或者当slave中有大型query语句产生了锁等待,那么延时就产生了。

解决方案

  1. 业务的持久化层的实现采用分库架构,mysql服务可平行扩展,分散压力。
  2. 单个库读写分离,一主多从,主写从读,分散压力。这样从库压力比主库高,保护主库。
  3. 服务的基础架构在业务和mysql之间加入memcache或者redis的cache层。降低mysql的读压力。
  4. 不同业务的mysql物理上放在不同机器,分散压力。
  5. 使用比主库更好的硬件设备作为slave,mysql压力小,延迟自然会变小。
  6. 使用更加强劲的硬件设备

    �终极解决方案

  • mysql5.7之后使用MTS并行复制技术,永久解决复制延时问题。

    Mysql主备

    逻辑图

    image.png

    主备切换

  • 当主库A出现问题,会自动切到备库A’,同时从库B、C、D重新指向A’的过程

    Mysql5.6后的基于GTID主备切换???

    GTID

    全称是 Global Transaction Identifier,也就是全局事务 ID,是一个事务在提交的时候生成的,是这个事务的唯一标识。

启动GTID

  • mysql在启动的时候,加上参数 gtid_mode=on 和 enforce_gtid_consistency=on 就可以了。

    �Mysql读写分离搭建(Amoeba)

    什么是Amoeba

    Amoeba(变形虫)项目,专注 分布式数据库 proxy 开发。坐落于Client、DB Server(s)之间。 对客户端透明。 具有负载均衡、高可用性、sql过滤、读写分离、可路由相关的query到目标数据库、可并发请求多台数据库合并结果。�

�为什么要用Amoeba

  1. 通过程序实现,网上很多现成的代码,比较复杂,如果添加从服务器要更改多台服务器的代码。
  2. 通过mysql-proxy来实现,由于mysql-proxy的主从读写分离是通过lua脚本来实现,目前lua的脚本的开发跟不上节奏,而且没有完美的现成的脚本,因此导致用于生产环境的话风险比较大,据网上很多人说mysql-proxy的性能不高。
  3. 自己开发接口实现,这种方案门槛高,开发成本高,不是一般的小公司能承担得起。
  4. 利用阿里巴巴的开源项目Amoeba来实现,具有负载均衡、高可用性、sql过滤、读写分离、可路由相关的query到目标数据库,并且安装配置非常简单。国产的开源软件,应该支持,目前正在使用,不发表太多结论,一切等测试完再发表结论吧,哈哈!

    Amoeba的安装

  5. 首先安装jdk、直接使用rpm安装即可

  6. 下载amoeba对应的版本https://sourceforge.net/projects/amoeba/,直接解压即可

    wget http://nchc.dl.sourceforge.net/project/amoeba/Amoeba%20for%20mysql/2.x/amoeba-mysql-binary-2.1.0-RC5.tar.gz
    
  7. 验证Amoeba是否安装成功的命令在 /usr/local/amoeba/bin/ 目录下执行

    ./ameoba
    

    Amoeba的配置

    dbServers.xml

    � ```xml <?xml version=”1.0” encoding=”gbk”?>

<!DOCTYPE amoeba:dbServers SYSTEM “dbserver.dtd”>

    <!-- 
        Each dbServer needs to be configured into a Pool,
        If you need to configure multiple dbServer with load balancing that can be simplified by the following configu

ration: add attribute with name virtual = “true” in dbServer, but the configuration does not allow the element with n ame factoryConfig such as ‘multiPool’ dbServer
—>

<dbServer name="abstractServer" abstractive="true">
    <factoryConfig class="com.meidusa.amoeba.mysql.net.MysqlServerConnectionFactory">
        <property name="connectionManager">${defaultManager}</property>
        <property name="sendBufferSize">64</property>
        <property name="receiveBufferSize">128</property>

        <!-- mysql port -->
        <property name="port">3306</property>

        <!-- mysql schema -->
        <property name="schema">msb</property>

        <!-- mysql user -->
        <property name="user">root</property>

        <property name="password">123456</property>
    </factoryConfig>

    <poolConfig class="com.meidusa.toolkit.common.poolable.PoolableObjectPool">
        <property name="maxActive">500</property>
        <property name="maxIdle">500</property>
        <property name="minIdle">1</property>
        <property name="minEvictableIdleTimeMillis">600000</property>
        <property name="timeBetweenEvictionRunsMillis">600000</property>
        <property name="testOnBorrow">true</property>
        <property name="testOnReturn">true</property>
        <property name="testWhileIdle">true</property>
    </poolConfig>
</dbServer>

<dbServer name="writedb"  parent="abstractServer">
    <factoryConfig>
        <!-- mysql ip -->
        <property name="ipAddress">172.16.13.128</property>
    </factoryConfig>
</dbServer>

<dbServer name="slave"  parent="abstractServer">
    <factoryConfig>
        <!-- mysql ip -->
        <property name="ipAddress">172.16.13.146</property>
    </factoryConfig>
</dbServer>
<dbServer name="myslave" virtual="true">
    <poolConfig class="com.meidusa.amoeba.server.MultipleServerPool">
        <!-- Load balancing strategy: 1=ROUNDROBIN , 2=WEIGHTBASED , 3=HA-->
        <property name="loadbalance">1</property>

        <!-- Separated by commas,such as: server1,server2,server1 -->
        <property name="poolNames">slave</property>
    </poolConfig>
</dbServer>

<a name="fmtmi"></a>
### amoeba.xml
�
```xml
<?xml version="1.0" encoding="gbk"?>

<!DOCTYPE amoeba:configuration SYSTEM "amoeba.dtd">
<amoeba:configuration xmlns:amoeba="http://amoeba.meidusa.com/">

    <proxy>

        <!-- service class must implements com.meidusa.amoeba.service.Service -->
        <service name="Amoeba for Mysql" class="com.meidusa.amoeba.mysql.server.MySQLService">
            <!-- port -->
            <property name="port">8066</property>

            <!-- bind ipAddress -->
            <!-- 
            <property name="ipAddress">127.0.0.1</property>
             -->

            <property name="connectionFactory">
                <bean class="com.meidusa.amoeba.mysql.net.MysqlClientConnectionFactory">
                    <property name="sendBufferSize">128</property>
                    <property name="receiveBufferSize">64</property>
                </bean>
            </property>

            <property name="authenticateProvider">
                <bean class="com.meidusa.amoeba.mysql.server.MysqlClientAuthenticator">

                    <property name="user">root</property>

                    <property name="password">123456</property>

                    <property name="filter">
                        <bean class="com.meidusa.toolkit.net.authenticate.server.IPAccessController">
                            <property name="ipFile">${amoeba.home}/conf/access_list.conf</property>
                        </bean>
                    </property>
                </bean>
            </property>

        </service>

        <runtime class="com.meidusa.amoeba.mysql.context.MysqlRuntimeContext">

            <!-- proxy server client process thread size -->
            <property name="executeThreadSize">128</property>

            <!-- per connection cache prepared statement size  -->
            <property name="statementCacheSize">500</property>

            <!-- default charset -->
            <property name="serverCharset">utf8</property>

            <!-- query timeout( default: 60 second , TimeUnit:second) -->
            <property name="queryTimeout">60</property>
        </runtime>

    </proxy>

    <!-- 
        Each ConnectionManager will start as thread
        manager responsible for the Connection IO read , Death Detection
    -->
    <connectionManagerList>
        <connectionManager name="defaultManager" class="com.meidusa.toolkit.net.MultiConnectionManagerWrapper">
            <property name="subManagerClassName">com.meidusa.toolkit.net.AuthingableConnectionManager</property>
        </connectionManager>
    </connectionManagerList>

        <!-- default using file loader -->
    <dbServerLoader class="com.meidusa.amoeba.context.DBServerConfigFileLoader">
        <property name="configFile">${amoeba.home}/conf/dbServers.xml</property>
    </dbServerLoader>

    <queryRouter class="com.meidusa.amoeba.mysql.parser.MysqlQueryRouter">
        <property name="ruleLoader">
            <bean class="com.meidusa.amoeba.route.TableRuleFileLoader">
                <property name="ruleFile">${amoeba.home}/conf/rule.xml</property>
                <property name="functionFile">${amoeba.home}/conf/ruleFunctionMap.xml</property>
            </bean>
        </property>
        <property name="sqlFunctionFile">${amoeba.home}/conf/functionMap.xml</property>
        <property name="LRUMapSize">1500</property>
        <property name="defaultPool">writedb</property>

        <property name="writePool">writedb</property>
        <property name="readPool">myslave</property>
        <property name="needParse">true</property>
    </queryRouter>
</amoeba:configuration>

启动Amoeba�

/root/amoeba-mysql-3.0.5-RC/bin/launcher

测试Amoeba

--测试的sql
--在安装amoeba的服务器上登录mysql
mysql -h192.168.85.13 -uroot -p123 -P8066
--分别在master、slave、amoeba上登录mysql
use msb
select * from user;
--在amoeba上插入数据
insert into user values(2,2);
--在master和slave上分别查看表中的数据
select * from user;
--将master上的mysql服务停止,继续插入数据会发现插入不成功,但是能够查询
--将master上的msyql服务开启,停止slave上的mysql,发现插入成功,但是不能够查询

�MyCat

分布式数据库数据分片

水平分片

  • 按照某个字段的某种规则分散到多个节点库中,每个节点中包含一部分数据。可以将数据的水平切分简单理解为按照数据行进行切分,就是将表中的某些行却分到一个节点,将另外某些行切分到其他节点,从分布式的整体来看它们是一个整体的表。

    垂直切分

  • 一个数据库由很多表构成,每个表对应不同的业务,垂直切分是指按照业务将表进行分类并分不到不同的节点上,垂直拆分简单明了,拆分规则明确,应用程序模块清晰、明确、容易整合,但是某个表的数据量达到一定程度后扩展起来比较困难。

    混合切分

  • 水平切分和垂直切分的结合。

    Mycat的基本介绍

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

  • Mycat 发展到目前的版本,已经不是一个单纯的 MySQL 代理了,它的后端可以支持 MySQL、 SQL Server、Oracle、 DB2、 PostgreSQL 等主流数据库,也支持 MongoDB 这种新型 NoSQL 方式的存储,未来还会支持更多类型的存储。而在最终用户看来,无论是那种存储方式,在 Mycat 里,都是一个传统的数据库表,支持标准的SQL 语句进行数据的操作,这样一来,对前端业务系统来说,可以大幅降低开发难度,提升开发速度,在测试阶段,可以将一个表定义为任何一种 Mycat 支持的存储方式,比如 MySQL 的 MyASIM 表、内存表、或者MongoDB、 LevelDB 以及号称是世界上最快的内存数据库 MemSQL 上。试想一下,用户表存放在 MemSQL 上,大量读频率远超过写频率的数据如订单的快照数据存放于 InnoDB 中,一些日志数据存放于 MongoDB 中,而且还能把 Oracle 的表跟 MySQL 的表做关联查询,你是否有一种不能呼吸的感觉?而未来,还能通过 Mycat 自动将一些计算分析后的数据灌入到 Hadoop 中,并能用 Mycat+Storm/Spark Stream 引擎做大规模数据分析,看到这里,你大概明白了, Mycat 是什么? Mycat 就是 BigSQL, Big Data On SQL Database。

    对DBA来说Mycat是什么?

  • Mycat就是MySQL Server,而Mycat后面连接的MySQL Server,就好像是MySQL的存储引擎,如InnoDB,MyISAM等,因此,Mycat本身并不存储数据,数据是再后端的MySQL上存储的,因此数据可靠性以及事务都是MySQL保证的,简单说,Mycat就是MySQL最佳伴侣,它再一定程度上让MySQL拥有了能跟Oracle PK的能力。

    对软件工程师来说Mycat是什么?

  • Mycat就是一个近似等于MySQL的数据库服务器,你可以用连接MySQL的方式去连接Mycat,除了端口不同,默认的mycat端口是8066,而不是mysql的3306,因此需要再连接字符串上增加端口信息,大多数情况下,可以用你熟悉的对象映射框架使用mycat,但建议对于分片表,尽量使用基础的SQL语句,因为这样能达到最佳性能,特别是几千万甚至几百亿条记录的情况下。

    对架构师来说Mycat是什么?

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

    Mycat的原理

    mycat的原理中最重要的一个动作是“拦截”,它拦截了用户发送过来的SQL语句,首先对SQL语句做了一些特定的分析:如分片分析、路由分析、读写分离分析、缓存分析等,然后将此SQL发送后端的真实数据库,并将返回的结果做适当的处理,最终再返回给用户。�

image.png

  • �上述图片里,orders表被分为了三个分片datanode(简称dn),这三个分片是分布在两台MySQL Server上(Datahost),即datanode=database@datahost方式,因此你可以用一台到N台服务器来分片,分片规则为(sharding rule)典型的字符串枚举分片规则,一个规则的定义是分片字段(sharding column)+分片函数(rule function),这里的分片字段为prov而分片函数为字符串枚举方式。
  • 当mycat收到一个SQL时,会先解析这个SQL,查找涉及到的表,然后看此表的定义,如果有分片规则,则获取到SQL里分片字段的值,并分配分片函数,得到该SQL对应的分片列表,然后将SQL发往这些分片去执行,最后收集和处理所有分片返回的结果数据,并输出到客户端,以select * from orders where prov = ?语句为例,查到prov=wuhan,按照分片函数,wuhan返回dn1,于是sql就发给了mysql1,去取db1上的查询结果,并返回给用户。
  • 如果上述sql改为select from orders where prov in (wuhan,beijing),那么,sql就会发给MySQL1和MySQL2去执行,然后结果集合并后输出给用户。但通常业务中我们的SQL会有order by以及limit翻页语法,此时就设计到结果集在mycat端的二次处理,这部分代码也比较复杂,而_*最复杂的则属两个表的join_,为此,mycat提出了创新性的ER分片,全局表,HBT(human brain tech)人工只能的catlet,以及结合storm/spark引擎等十八般武艺的解决办法,从而称为目前业界最强大的方案,这就是开源的力量。

    Mycat的应用场景

  1. 单纯的读写分离,此时配置最为简单,支持读写分离,主从切换。
  2. 分库分表,对于超过1000万的表进行分片,最大支持1000亿的单表分片。
  3. 多租户应用,每个应用一个库,但应用程序只连接mycat,从而不改造程序本身,实现多租户化。
  4. 报表系统,借助mycat的分表能力,处理大规模报表的统计。
  5. 整合多数据源。

    数据库中间件对比

    | 对比项目 | mycat | mango | cobar | heisenberg | altas | amoeba | | —- | —- | —- | —- | —- | —- | —- | | 数据切片 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | | 读写分离 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | | 宕机自动切换 | 支持 | 不支持 | 支持 | 不支持 | 半支持,影响写 | 不支持 | | mysql协议 | 前后端支持 | JDBC | 前端支持 | 前后端支持 | 前后端支持 | JDBC | | 支持的数据库 | mysql,oracle,mongodb,postgresql | mysql | mysql | mysql | mysql | mysql,mongodb | | 社区活跃度 | 高 | 活跃 | 停滞 | 低 | 中等 | 停滞 | | 文档资料 | 极丰富 | 较齐全 | 较齐全 | 较少 | 中等 | 缺少 | | 是否开源 | 开源 | 开源 | 开源 | 开源 | 开源 | 开源 | | 是否支持事务 | 弱XA | 支持 | 单库强一致,分布式弱事务 | 单库强一致,多库弱事务 | 单库强一致,分布弱事务 | 不支持 |

�Mycat的安装和使用

环境准备

  • �首先准备四台虚拟机,安装好mysql,方便后续做读写分离和主从复制。

    172.16.13.147 node01
    172.16.13.148 node02
    172.16.13.149 node03
    172.16.13.150 node04
    
  • 安装好jdk�

  • 允许远程访问

    grant all privileges on *.* to 'root'@'%' identified by '123456';
    flush privileges;
    
  • 查看是否可以访问端口

    telnet 172.16.13.147 3306
    -- 如果不行,关闭防火墙
    systemctl disable firewalld
    

    Mycat的安装

  • 从官网下载需要的安装包,并且上传到具体的虚拟机中,我们在使用的时候将包上传到node01这台虚拟机,由node01充当mycat。

  • �下载地址为 http://dl.mycat.org.cn/1.6.7.5/2020-4-10/

    -- 解压文件到/usr/local文件夹下
    tar -zxvf  Mycat-server-1.6.7.5-release-20200422133810-linux.tar.gz -C /usr/local
    -- 配置环境变量
    vi /etc/profile
    -- 添加如下配置信息:
    export MYCAT_HOME=/usr/local/mycat
    export PATH=$MYCAT_HOME/bin:$PATH:$JAVA_HOME/bin
    -- 激活配置
    source /etc/profile
    
  • 当执行到这步的时候,其实就可以启动了,但是为了能正确显示出效果,最好修改下mycat的具体配置,让我们能够正常进行访问。

    配置Mycat

  • 进入到/usr/local/mycat/conf目录下,修改该文件夹下的配置文件。

    修改server.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- - - Licensed under the Apache License, Version 2.0 (the "License"); 
      - you may not use this file except in compliance with the License. - You 
      may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 
      - - Unless required by applicable law or agreed to in writing, software - 
      distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 
      WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the 
      License for the specific language governing permissions and - limitations 
      under the License. -->
    <!DOCTYPE mycat:server SYSTEM "server.dtd">
    <mycat:server xmlns:mycat="http://io.mycat/">
      <user name="root" defaultAccount="true">
          <property name="password">123456</property>
          <property name="schemas">TESTDB</property>
          <property name="defaultSchema">TESTDB</property>
      </user>
    </mycat:server>
    

    修改schema.xml文件

    <?xml version="1.0"?>
    <!DOCTYPE mycat:schema SYSTEM "schema.dtd">
    <mycat:schema xmlns:mycat="http://io.mycat/">
          <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
          </schema>
          <dataNode name="dn1" dataHost="host1" database="msb" />
          <dataHost name="host1" maxCon="1000" minCon="10" balance="0"
                            writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
                  <heartbeat>select user()</heartbeat>
                  <writeHost host="hostM1" url="172.16.13.147:3306" user="root"
                                     password="123456">
                          <readHost host="hostS1" url="172.16.13.148:3306" user="root" password="123456"></readHost>
                  </writeHost>
          </dataHost>
    </mycat:schema>
    

    启动Mycat�

  • mycat的启动有两种方式,一种是控制台启动,一种是后台启动,在初学的时候建议大家使用控制台启动的方式,当配置文件写错之后,可以方便的看到错误,及时修改,但是在生产环境中,使用后台启动的方式比较稳妥。

  • 控制台启动:去mycat/bin目录下执行 ./mycat console
  • 后台启动:去mycat/bin目录下执行 ./mycat start

    �登录验证

    管理窗口的登录

  • �从另外的虚拟机去登录访问当前mycat,输入如下命令即可

    mysql -uroot -p123456 -P 9066 -h 172.16.13.147
    
  • 此时访问的是mycat的管理窗口,可以通过show @@help查看可以执行的命令

    �数据窗口的登录

  • �从另外的虚拟机去登录访问mycat,输入命令如下:

    mysql -uroot -p123456 -P8066 -h 172.16.13.147
    
  • 当都能够成功的时候,意味着mycat已经搭建完成。�

    主从复制(一主一从)

    修改主

  • 在node01上修改/etc/my.cnf的文件

    #mysql服务唯一id,不同的mysql服务必须拥有全局唯一的id
    server-id=1
    #启动二进制日期
    log-bin=mysql-bin
    #设置不要复制的数据库
    binlog-ignore-db=mysql
    binlog-ignore-db=information-schema
    #设置需要复制的数据库
    binlog-do-db=msb
    #设置binlog的格式
    binlog_format=statement
    

    修改从

  • 在node02上修改/etc/my.cnf文件

    #服务器唯一id
    server-id=2
    #启动中继日志
    relay-log=mysql-relay
    

    重启两台mysql服务

    在node01上创建账户并授权slave

    grant replication slave on *.* to 'root'@'%' identified by '123456';
    --在进行授权的时候,如果提示密码的问题,把密码验证取消
    set global validate_password_policy=0;
    set global validate_password_length=1;
    

    查看master的状态

    show master status
    

    image.png

    �在node02上配置需要复制的主机

    CHANGE MASTER TO MASTER_HOST='172.16.13.147',MASTER_USER='root',MASTER_PASSWORD='123456',MASTER_LOG_FILE='mysql-bin.000002',MASTER_LOG_POS=154;
    

    启动从服务器复制功能

    start slave;
    

    �查看从服务器状态

    show slave status\G
    
  • �当执行完成之后,会看到两个关键的属性Slave_IO_Running,Slave_SQL_Running,当这两个属性都是yes的时候,表示主从复制已经准备好了,可以进行具体的操作了

    �主从复制(一主一从验证)

    创建数据库

    --在node01上创建数据库
    create database msb;
    use msb;
    --在node01上创建具体的表
    create table mytbl(id int,name varchar(20));
    --在node01上插入数据
    insert into mytbl values(1,'zhangsan');
    --在node02上验证发现数据已经同步成功,表示主从复制完成
    

    主从复制(双主双从)

    在上述的一主一从的架构设计中,很容易出现单点的问题,所以我们要想让生产环境中的配置足够稳定,可以配置双主双从,解决单点的问题。

  • 在此架构中,可以让一台主机用来处理所有写请求,此时,它的从机和备机,以及备机的从机复制所有读请求,当主机宕机之后,另一台主机负责写请求,两台主机互为备机。

    环境准备

    | 编号 | 角色 | ip | 主机名 | | —- | —- | —- | —- | | 1 | master1 | 172.16.13.147 | node01 | | 2 | slave1 | 172.16.13.148 | node02 | | 3 | master2 | 172.16.13.149 | node03 | | 4 | slave2 | 172.16.13.150 | node04 |

�搭建开始

  • 修改node01上的/etc/my.cnf文件

    #主服务器唯一ID
    server-id=1
    #启用二进制日志
    log-bin=mysql-bin
    # 设置不要复制的数据库(可设置多个)
    binlog-ignore-db=mysql
    binlog-ignore-db=information_schema
    #设置需要复制的数据库
    binlog-do-db=msb
    #设置logbin格式
    binlog_format=STATEMENT
    # 在作为从数据库的时候, 有写入操作也要更新二进制日志文件
    log-slave-updates
    #表示自增长字段每次递增的量,指自增字段的起始值,其默认值是1, 取值范围是1 .. 65535
    auto-increment-increment=2
    # 表示自增长字段从哪个数开始,指字段一次递增多少,他的取值范围是1 .. 65535
    auto-increment-offset=1
    
  • 修改node03上的/etc/my.cnf文件

    #主服务器唯一ID
    server-id=3
    #启用二进制日志
    log-bin=mysql-bin
    # 设置不要复制的数据库(可设置多个)
    binlog-ignore-db=mysql
    binlog-ignore-db=information_schema
    #设置需要复制的数据库
    binlog-do-db=msb
    #设置logbin格式
    binlog_format=STATEMENT
    # 在作为从数据库的时候,有写入操作也要更新二进制日志文件
    log-slave-updates
    #表示自增长字段每次递增的量,指自增字段的起始值,其默认值是1,取值范围是1 .. 65535
    auto-increment-increment=2
    # 表示自增长字段从哪个数开始,指字段一次递增多少,他的取值范围是1 .. 65535
    auto-increment-offset=2
    
  • 修改node02上的/etc/my.cnf文件

    #从服务器唯一ID
    server-id=2
    #启用中继日志
    relay-log=mysql-relay
    
  • 修改node04上的/etc/my.cnf文件

    #从服务器唯一ID
    server-id=4
    #启用中继日志
    relay-log=mysql-relay
    
  • �所有主机重新启动mysql服务

  • 在两台主机node01,node03上授权同步命令

    GRANT REPLICATION SLAVE ON *.* TO 'root'@'%' IDENTIFIED BY '123456';
    

    如果出现如下错误 Your password does not satisfy the current policy requirements 查看mysql初始化密码策略 SHOW VARIABLES LIKE ‘validate_password%’;� —设置密码强度低 set global validate_password_policy=LOW; —设置密码长度 set global validate_password_length=6;

  • �查看两台主机的状态

    show master status\G
    
  • 在node02上执行要复制的主机node01

    CHANGE MASTER TO MASTER_HOST='172.16.13.147',MASTER_USER='root',MASTER_PASSWORD='123456',MASTER_LOG_FILE='mysql-bin.000003',MASTER_LOG_POS=434;
    
  • 在node04上执行要复制的主机node03

    CHANGE MASTER TO MASTER_HOST='172.16.13.149',MASTER_USER='root',MASTER_PASSWORD='123456',MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=434;
    
  • 启动两个从机的slave并且查看状态,当看到两个参数都是yes的时候表示成功

    start slave;
    show slave status\G
    
  • ��完成node01跟node03的相互复制

--在node01上执行
CHANGE MASTER TO MASTER_HOST='172.16.13.149',MASTER_USER='root',MASTER_PASSWORD='123456',MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=434;
--开启slave
start slave
--查看状态
show slave status\G
--在node03上执行
CHANGE MASTER TO MASTER_HOST='172.16.13.147',MASTER_USER='root',MASTER_PASSWORD='123456',MASTER_LOG_FILE='mysql-bin.000003',MASTER_LOG_POS=434;
--开启slave
start slave
--查看状态
show slave status\G

双主双从验证

create database msb;
create table mytbl(id int,name varchar(20));
insert into mytbl values(1,'zhangsan');
--完成上述命令之后可以去其他机器验证是否同步成功

ShardingSphere

ShardingSphere介绍

就是包含了分库分表功能的JDBC,因此我们可以直接把sharding-jdbc当做普通的jdbc来进行使用。

什么是ShardingSphere

  • Apache ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款相互独立,却又能够混合部署配合使用的产品组成。 它们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。

    Sharding-JDBC

  • 定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。

    • 适用于任何基于JDBC的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
    • 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
    • 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer,PostgreSQL以及任何遵循SQL92标准的数据库。

      Sharding-Proxy

  • 定位为透明化的数据库代理端,提供封装了数据库二进制协议的服务端版本,用于完成对异构语言的支持。 目前先提供MySQL/PostgreSQL版本,它可以使用任何兼容MySQL/PostgreSQL协议的访问客户端(如:MySQL Command Client, MySQL Workbench, Navicat等)操作数据,对DBA更加友好。

    • 向应用程序完全透明,可直接当做MySQL/PostgreSQL使用。
    • 适用于任何兼容MySQL/PostgreSQL协议的的客户端。

      Sharding-sidecar

  • 定位为Kubernetes的云原生数据库代理,以Sidecar的形式代理所有对数据库的访问。 通过无中心、零侵入的方案提供与数据库交互的的啮合层,即Database Mesh,又可称数据网格。

  • Database Mesh的关注重点在于如何将分布式的数据访问应用与数据库有机串联起来,它更加关注的是交互,是将杂乱无章的应用与数据库之间的交互有效的梳理。使用Database Mesh,访问数据库的应用和数据库终将形成一个巨大的网格体系,应用和数据库只需在网格体系中对号入座即可,它们都是被啮合层所治理的对象。

    三个组件对比

    image.png

    核心概念

    逻辑表

  • 水平拆分的数据库(表)的相同逻辑和数据结构表的总称。例:订单数据根据主键尾数拆分为10张表,分别是t_order_0t_order_9,他们的逻辑表名为t_order

    真实表

  • 在分片的数据库中真实存在的物理表。即上个示例中的t_order_0t_order_9

    数据节点

  • 数据分片的最小单元。由数据源名称和数据表组成,例:ds_0.t_order_0

    绑定表

  • 指分片规则一致的主表和子表。例如:t_order表和t_order_item表,均按照order_id分片,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。举例说明,如果SQL为:

    SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
    
    • 在不配置绑定表关系时,假设分片键order_id将数值10路由至第0片,将数值11路由至第1片,那么路由后的SQL应该为4条,它们呈现为笛卡尔积: ```sql SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);


   - 在配置绑定表关系后,路由的SQL应该为2条:
```sql
SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
  • 其中t_order在FROM的最左侧,ShardingSphere将会以它作为整个绑定表的主表。 所有路由计算将会只使用主表的策略,那么t_order_item表的分片计算将会使用t_order的条件。故绑定表之间的分区键要完全相同。

    广播表

  • 指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致。适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表。

    分片键

  • 用于分片的数据库字段,是将数据库(表)水平拆分的关键字段。例:将订单表中的订单主键的尾数取模分片,则订单主键为分片字段。 SQL中如果无分片字段,将执行全路由,性能较差。 除了对单分片字段的支持,ShardingSphere也支持根据多个字段进行分片。

    �分片算法

  • 通过分片算法将数据分片,支持通过=>=<=><BETWEENIN分片。分片算法需要应用方开发者自行实现,可实现的灵活度非常高。

  • 目前提供4种分片算法。由于分片算法和业务实现紧密相关,因此并未提供内置分片算法,而是通过分片策略将各种场景提炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。

    精确分片算法

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

    �范围分片算法

  • �对应RangeShardingAlgorithm,用于处理使用单一键作为分片键的BETWEEN AND、>、<、>=、<=进行分片的场景。需要配合StandardShardingStrategy使用。

    �复合分片算法

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

    �Hint分片算法

  • �对应HintShardingAlgorithm,用于处理使用Hint行分片的场景。需要配合HintShardingStrategy使用。

    分片策略

  • 包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。目前提供5种分片策略。

    �标准分片策略

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

    复合分片策略

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

    行表达式分片策略

  • �对应InlineShardingStrategy。使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如: t_user_$->{u_id % 8} 表示t_user表根据u_id模8,而分成8张表,表名称为t_user_0t_user_7

    �Hint分片策略

  • �对应HintShardingStrategy。通过Hint指定分片值而非从SQL中提取分片值的方式进行分片的策略。

    �不分片策略

  • �对应NoneShardingStrategy。不分片的策略。

    环境搭建

    创建一个Springboot的项目

    pom文件为

    ```xml <?xml version=”1.0” encoding=”UTF-8”?> <project xmlns=”http://maven.apache.org/POM/4.0.0“ xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance

       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    

    4.0.0

      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.3.2.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
    

    com.mashibing shardingsphere_demo 0.0.1-SNAPSHOT shardingsphere_demo

    Demo project for Spring Boot 1.8 org.springframework.boot spring-boot-starter-jdbc org.mybatis.spring.boot mybatis-spring-boot-starter 2.1.3 com.alibaba druid 1.1.23 org.apache.shardingsphere sharding-jdbc-spring-boot-starter 4.1.1 mysql mysql-connector-java runtime org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-surefire-plugin true

<a name="jswzf"></a>
## Sharding-jdbc实现水平分表

1. 创建Sharding-sphere数据库
1. 在数据库中创建两张表orders_1和orders_2
1. 分片规则:如果订单编号是偶数添加到orders_1,如果是奇数添加到orders_2
1. 创建实体类
```java
package com.mashibing.shardingsphere_demo.bean;

public class Orders {
    private Integer id;
    private Integer orderType;
    private Integer customerId;
    private Double amount;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getOrderType() {
        return orderType;
    }

    public void setOrderType(Integer orderType) {
        this.orderType = orderType;
    }

    public Integer getCustomerId() {
        return customerId;
    }

    public void setCustomerId(Integer customerId) {
        this.customerId = customerId;
    }

    public Double getAmount() {
        return amount;
    }

    public void setAmount(Double amount) {
        this.amount = amount;
    }

    @Override
    public String toString() {
        return "Orders{" +
                "id=" + id +
                ", orderType=" + orderType +
                ", customerId=" + customerId +
                ", amount=" + amount +
                '}';
    }
}
  1. 创建mapper类 ```java package com.mashibing.shardingsphere_demo.mapper;

import com.mashibing.shardingsphere_demo.bean.Orders; import org.apache.ibatis.annotations.*; import org.springframework.stereotype.Repository;

@Repository @Mapper public interface OrdersMapper {

@Insert("insert into orders(id,order_type,customer_id,amount) values(#{id},#{orderType},#{customerId},#{amount})")
public void insert(Orders orders);

@Select("select * from orders where id = #{id}")
@Results({
        @Result(property = "id",column = "id"),
        @Result(property = "orderType",column = "order_type"),
        @Result(property = "customerId",column = "customer_id"),
        @Result(property = "amount",column = "amount")
})
public Orders selectOne(Integer id);

}


6. 创建配置文件
```properties
#整合mybatis
mybatis.type-aliases-package=com.mashibing.mapper

#配置数据源的名称
spring.shardingsphere.datasource.names=ds1


#配置数据源的具体内容,
spring.shardingsphere.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds1.url=jdbc:mysql://172.16.13.147:3306/sharding_sphere?serverTimezone=UTC
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=123456

#指定orders表的分布情况,配置表在哪个数据库中,表名称是什么
spring.shardingsphere.sharding.tables.orders.actual-data-nodes=ds1.orders_$->{1..2}
#指定orders表里主键id生成策略
spring.shardingsphere.sharding.tables.orders.key-generator.column=id
spring.shardingsphere.sharding.tables.orders.key-generator.type=SNOWFLAKE

#指定分片策略。根据id的奇偶性来判断插入到哪个表
spring.shardingsphere.sharding.tables.orders.table-strategy.inline.sharding-column=id
spring.shardingsphere.sharding.tables.orders.table-strategy.inline.algorithm-expression=orders_${id%2+1}

#打开sql输出日志
spring.shardingsphere.props.sql.show=true
  1. 创建测试类 ```java package com.mashibing.shardingsphere_demo;

import com.mashibing.shardingsphere_demo.bean.Orders; import com.mashibing.shardingsphere_demo.mapper.OrdersMapper; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest class ShardingsphereDemoApplicationTests {

@Autowired
private OrdersMapper ordersMapper;
@Test
public void addOrders(){
    for (int i = 1; i <=10 ; i++) {
        Orders orders = new Orders();
        orders.setId(i);
        orders.setCustomerId(i);
        orders.setOrderType(i);
        orders.setAmount(1000.0*i);
        ordersMapper.insert(orders);
    }
}
@Test
public void queryOrders(){
    Orders orders = ordersMapper.selectOne(1);
    System.out.println(orders);
}

}

<a name="HE6pB"></a>
## �Sharding-jdbc实现水平分库

1. 在不同的数据节点node01,node02上创建不同名称的数据库:sharding_sphere_1,sharding_sphere_2
1. 在两个数据库上创建相同的表orders_1,orders_2
1. 分片规则,按照customer_id的奇偶性来进行分库,然后按照id的奇偶性进行分表
1. 修改配置文件
```properties
# 配置不同的数据源
spring.shardingsphere.datasource.names=ds1,ds2

#配置ds1数据源的基本信息
spring.shardingsphere.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds1.url=jdbc:mysql://172.16.13.147:3306/sharding_sphere_1?serverTimezone=UTC
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=123456

#配置ds2数据源的基本信息
spring.shardingsphere.datasource.ds2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds2.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds2.url=jdbc:mysql://172.16.13.148:3306/sharding_sphere_2?serverTimezone=UTC
spring.shardingsphere.datasource.ds2.username=root
spring.shardingsphere.datasource.ds2.password=123456

#指定数据库的分布情况
spring.shardingsphere.sharding.tables.orders.actual-data-nodes=ds$->{1..2}.orders_$->{1..2}

#指定orders表的主键生成策略
spring.shardingsphere.sharding.tables.orders.key-generator.column=id
spring.shardingsphere.sharding.tables.orders.key-generator.type=SNOWFLAKE

#指定表分片策略,根据id的奇偶性来添加到不同的表中
spring.shardingsphere.sharding.tables.orders.table-strategy.inline.sharding-column=id
spring.shardingsphere.sharding.tables.orders.table-strategy.inline.algorithm-expression=orders_$->{id%2+1}

#指定库分片策略,根据customer_id的奇偶性来添加到不同的库中
spring.shardingsphere.sharding.tables.orders.database-strategy.inline.sharding-column=customer_id
spring.shardingsphere.sharding.tables.orders.database-strategy.inline.algorithm-expression=ds$->{customer_id%2+1}

#打开sql输出日志
spring.shardingsphere.props.sql.show=true
  1. 修改mappers类 ```java package com.mashibing.shardingsphere_demo.mapper;

import com.mashibing.shardingsphere_demo.bean.Orders; import org.apache.ibatis.annotations.*; import org.springframework.stereotype.Repository;

@Repository @Mapper public interface OrdersMapper {

@Insert("insert into orders(id,order_type,customer_id,amount) values(#{id},#{orderType},#{customerId},#{amount})")
public void insert(Orders orders);

@Select("select * from orders where id = #{id}")
@Results({
        @Result(property = "id",column = "id"),
        @Result(property = "orderType",column = "order_type"),
        @Result(property = "customerId",column = "customer_id"),
        @Result(property = "amount",column = "amount")
})
public Orders selectOne(Integer id);

@Select("select * from orders where id = #{id} and customer_id=#{customerId}")
@Results({
        @Result(property = "id",column = "id"),
        @Result(property = "orderType",column = "order_type"),
        @Result(property = "customerId",column = "customer_id"),
        @Result(property = "amount",column = "amount")
})
public Orders selectOneDB(Orders orders);

}


6. 编写测试类
```java
package com.mashibing.shardingsphere_demo;

import com.mashibing.shardingsphere_demo.bean.Orders;
import com.mashibing.shardingsphere_demo.mapper.OrdersMapper;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Random;

@SpringBootTest
class ShardingsphereDemoApplicationTests {

    @Autowired
    private OrdersMapper ordersMapper;
    @Test
    public void addOrdersDB(){
        for (int i = 1; i <=10 ; i++) {
            Orders orders = new Orders();
            orders.setId(i);
            orders.setCustomerId(new Random().nextInt(10));
            orders.setOrderType(i);
            orders.setAmount(1000.0*i);
            ordersMapper.insert(orders);
        }
    }
    @Test
    public void queryOrdersDB(){
        Orders orders = new Orders();
        orders.setCustomerId(7);
        orders.setId(7);
        Orders o = ordersMapper.selectOneDB(orders);
        System.out.println(o);
    }
}

Sharding-jdbc实现垂直分库

  1. 在不同的数据节点node01,node02创建相同的库sharding_sphere�
  2. 在node01上创建orders表,在node02上创建customer表
  3. 分片规则:将不同的表插入到不同的库中
  4. 编写customer类 ```java package com.mashibing.shardingsphere_demo.bean;

public class Customer {

private Integer id;
private String name;

public Integer getId() {
    return id;
}

public void setId(Integer id) {
    this.id = id;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

@Override
public String toString() {
    return "Customer{" +
            "id=" + id +
            ", name='" + name + '\'' +
            '}';
}

}


5. 编写customerMapper类
```java
package com.mashibing.shardingsphere_demo.mapper;

import com.mashibing.shardingsphere_demo.bean.Customer;
import org.apache.ibatis.annotations.Insert;
import org.springframework.stereotype.Repository;

@Repository
public interface CustomerMapper {
    @Insert("insert into customer(id,name) values(#{id},#{name})")
    public void insertCustomer(Customer customer);
}
  1. 修改配置文件 ```properties

    配置数据源

    spring.shardingsphere.datasource.names=ds1,ds2

    配置第一个数据源

    spring.shardingsphere.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.ds1.url=jdbc:mysql://172.16.13.147:3306/sharding_sphere?serverTimezone=UTC spring.shardingsphere.datasource.ds1.username=root spring.shardingsphere.datasource.ds1.password=123456

配置第二个数据源

spring.shardingsphere.datasource.ds2.type=com.alibaba.druid.pool.DruidDataSource spring.shardingsphere.datasource.ds2.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.ds2.url=jdbc:mysql://172.16.13.148:3306/sharding_sphere?serverTimezone=UTC spring.shardingsphere.datasource.ds2.username=root spring.shardingsphere.datasource.ds2.password=123456

配置orders表所在的数据节点

spring.shardingsphere.sharding.tables.order.actual-data-nodes=ds1.orders

配置customer表所在的数据节点

spring.shardingsphere.sharding.tables.customer.actual-data-nodes=ds2.customer

customer表的主键生成策略

spring.shardingsphere.sharding.tables.customer.key-generator.column=id spring.shardingsphere.sharding.tables.customer.key-generator.type=SNOWFLAKE

指定分片的策略

spring.shardingsphere.sharding.tables.customer.table-strategy.inline.sharding-column=id spring.shardingsphere.sharding.tables.customer.table-strategy.inline.algorithm-expression=customer

显示sql

spring.shardingsphere.props.sql.show=true


7. 编写测试类
```java
package com.mashibing.shardingsphere_demo;

import com.mashibing.shardingsphere_demo.bean.Customer;
import com.mashibing.shardingsphere_demo.bean.Orders;
import com.mashibing.shardingsphere_demo.mapper.CustomerMapper;
import com.mashibing.shardingsphere_demo.mapper.OrdersMapper;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Random;

@SpringBootTest
class ShardingsphereDemoApplicationTests {

    @Autowired
    private CustomerMapper customerMapper;

    @Test
    public void insertCustomer(){
        for (int i = 1; i <= 10 ; i++) {
            Customer customer = new Customer();
            customer.setId(i);
            customer.setName("zs"+i);
            customerMapper.insertCustomer(customer);
        }
    }
}

sharding-jdbc公共表

之前我们在学习mycat的时候接触过字典表的概念,其实在shardingsphere中也有类似的概念,只不过名字叫做公共表,也就是需要在各个库中都存在的表,方便做某些关联查询。

  1. 在不同节点的库上创建相同的表
  2. 分片规则:公共表表示所有的库都具备相同的表
  3. 创建实体类 ```java package com.mashibing.shardingsphere_demo.bean;

public class DictOrderType {

private Integer id;
private String orderType;

public Integer getId() {
    return id;
}

public void setId(Integer id) {
    this.id = id;
}

public String getOrderType() {
    return orderType;
}

public void setOrderType(String orderType) {
    this.orderType = orderType;
}

@Override
public String toString() {
    return "DictOrderType{" +
            "id=" + id +
            ", orderType='" + orderType + '\'' +
            '}';
}

}


4. 创建DictOrderTypeMapper文件
```java
package com.mashibing.shardingsphere_demo.mapper;

import com.mashibing.shardingsphere_demo.bean.DictOrderType;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.springframework.stereotype.Repository;

@Repository
public interface DictOrderTypeMapper {

    @Insert("insert into dict_order_type(id,order_type) values(#{id},#{orderType})")
    public void insertDictOrderType(DictOrderType dictOrderType);

    @Delete("delete from dict_order_type where id = #{id}")
    public void DeleteDictOrderType(Integer id);
}
  1. 修改配置文件 ```properties

    配置数据源

    spring.shardingsphere.datasource.names=ds1,ds2

    配置第一个数据源

    spring.shardingsphere.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.ds1.url=jdbc:mysql://172.16.13.147:3306/sharding_sphere?serverTimezone=UTC spring.shardingsphere.datasource.ds1.username=root spring.shardingsphere.datasource.ds1.password=123456

配置第二个数据源

spring.shardingsphere.datasource.ds2.type=com.alibaba.druid.pool.DruidDataSource spring.shardingsphere.datasource.ds2.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.ds2.url=jdbc:mysql://172.16.13.148:3306/sharding_sphere?serverTimezone=UTC spring.shardingsphere.datasource.ds2.username=root spring.shardingsphere.datasource.ds2.password=123456

配置公共表

spring.shardingsphere.sharding.broadcast-tables=dict_order_type spring.shardingsphere.sharding.tables.dict_order_type.key-generator.column=id spring.shardingsphere.sharding.tables.dict_order_type.key-generator.type=SNOWFLAKE


6. 编写测试类
```java
package com.mashibing.shardingsphere_demo;

import com.mashibing.shardingsphere_demo.bean.Customer;
import com.mashibing.shardingsphere_demo.bean.DictOrderType;
import com.mashibing.shardingsphere_demo.bean.Orders;
import com.mashibing.shardingsphere_demo.mapper.CustomerMapper;
import com.mashibing.shardingsphere_demo.mapper.DictOrderTypeMapper;
import com.mashibing.shardingsphere_demo.mapper.OrdersMapper;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Random;

@SpringBootTest
class ShardingsphereDemoApplicationTests {

    @Autowired
    private DictOrderTypeMapper dictOrderTypeMapper;

    @Test
    public void insertDictOrderType(){
        for (int i = 1; i <= 10 ; i++) {
            DictOrderType dictOrderType = new DictOrderType();
            dictOrderType.setOrderType("orderType"+i);
            dictOrderTypeMapper.insertDictOrderType(dictOrderType);
        }
    }

    @Test
    public void deleteDictOrderType(){
        dictOrderTypeMapper.DeleteDictOrderType(1);
    }
}

sharding-jdbc实现读写分离�

�读写分离的概念大家应该已经很熟练了,此处不在赘述,下面我们通过sharding-jdbc来实现读写分离,其实大家应该已经发现了,所有的操作都是配置问题,下面我们来讲一下具体的配置,关于读写分离的原理,以及如何配置mysql的主从复制,我们就不在多聊了,直接看sharding-jdbc的配置。

  • 我们规定ds1为写库,ds2为读库
  • 创建person类 ```java package com.mashibing.shardingsphere_demo.bean;

public class Person {

private Long id;
private String name;

public Long getId() {
    return id;
}

public void setId(Long id) {
    this.id = id;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

@Override
public String toString() {
    return "Person{" +
            "id=" + id +
            ", name='" + name + '\'' +
            '}';
}

}


- 创建personMapper类
```java
package com.mashibing.shardingsphere_demo.mapper;

import com.mashibing.shardingsphere_demo.bean.Person;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

@Repository
@Mapper
public interface PersonMapper {

    @Insert("insert into person(id,name) values(#{id},#{name})")
    public void insertPerson(Person person);

    @Select("select * from person where id = #{id}")
    public Person queryPerson(Long id);
}
  • 修改配置文件

#配置数据源
spring.shardingsphere.datasource.names=ds1,ds2
#配置第一个数据源
spring.shardingsphere.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds1.url=jdbc:mysql://192.168.85.111:3306/sharding_sphere?serverTimezone=UTC
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=123456

#配置第二个数据源
spring.shardingsphere.datasource.ds2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds2.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds2.url=jdbc:mysql://192.168.85.112:3306/sharding_sphere?serverTimezone=UTC
spring.shardingsphere.datasource.ds2.username=root
spring.shardingsphere.datasource.ds2.password=123456

#主库从库逻辑定义
spring.shardingsphere.masterslave.name=ms
spring.shardingsphere.masterslave.master-data-source-name=ds1
spring.shardingsphere.masterslave.slave-data-source-names=ds2

#显示执行的sql
spring.shardingsphere.props.sql.show=true
  • 编写测试类 ```java package com.mashibing.shardingsphere_demo;

import com.mashibing.shardingsphere_demo.bean.Customer; import com.mashibing.shardingsphere_demo.bean.DictOrderType; import com.mashibing.shardingsphere_demo.bean.Orders; import com.mashibing.shardingsphere_demo.bean.Person; import com.mashibing.shardingsphere_demo.mapper.CustomerMapper; import com.mashibing.shardingsphere_demo.mapper.DictOrderTypeMapper; import com.mashibing.shardingsphere_demo.mapper.OrdersMapper; import com.mashibing.shardingsphere_demo.mapper.PersonMapper; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest;

import java.util.Random;

@SpringBootTest class ShardingsphereDemoApplicationTests {

@Autowired
private PersonMapper personMapper;

@Test
public void insertPerson(){
    Person person = new Person();
    person.setId(1l);
    person.setName("zhangsan");
    personMapper.insertPerson(person);
}

@Test
public void queryPerson(){
    Person person = personMapper.queryPerson(1l);
    System.out.println(person);
}

} ```