什么是Mycat?为什么要使用MyCat?

什么是Mycat?

Mycat是数据库中间件

 因为放在数据库和应用程序中间,所以叫中间件。是不是觉得有点多余,直接连接数据库不香么?

为什么要使用MyCat?

1.可以应用程序连接到多个数据库

2.缓解单个数据库压力,提升了数据库的访问量与并发量

3.实现了应用程序的高可用,数据库的高可用。

 当然可能会有疑问,各个数据库的数据不一致了怎么办,主要mysql,oracle都有主从复制机制,不做详细介绍。

MyCat能干什么呢?

1.读写分离

 

 配置mycat 的schema.xml 文件,大致如下

<dataNode name="dn1" dataHost="localhost1" database="db03" /> 
	<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> 
		<heartbeat>select user()</heartbeat> 
		<writeHost host="hostM1" url="127.0.0.1:3306" user="root" password="pwd"> 
			<readHost host="hostS1" url="127.0.0.2:3306" user="root" password="pwd" /> 
		</writeHost> 
			
		<writeHost host="hostM2" url="127.0.0.3:3306" user="root" password="pwd"> 
			<readHost host="hostS2" url="127.0.0.4:3306" user="root" password="pwd" /> 
		</writeHost> 
	</dataHost> 

balance :
    负载均衡类型, 目前取值有4种: 
    
    balance="0" : 不开启读写分离机制 , 所有读操作都发送到当前可用的writeHost上. 
    
    balance="1" : 全部的readHost 与 stand by writeHost (备用的writeHost) 都参与 select 语句的负载均衡,
    简而言之,就是采用双主双从模式(M1 --> S1 , M2 --> S2, 正常情况下, M2,S1,S2 都参与 select 语句的负载均衡。); 
    
    balance="2" : 所有的读写操作都随机在writeHost , readHost上分发 
    
    balance="3" : 所有的读请求随机分发到writeHost对应的readHost上执行, writeHost不负担 读压力 ;balance=3 只在MyCat1.4 之后生效 .

writeType 

        一般配置0

writeType=“0”, 所有写操作都发送到可用的writeHost上。

writeType=“1”,所有写操作都随机的发送到readHost。

writeType=“2”,所有写操作都随机的在writeHost、readhost分上发。

switchType

表示不自动切换: -1

默认值,自动切换: 1  如果第一个主节点宕机后,Mycat会进行3次心跳检测,如果3次都没有响应,则会自动切换到第二个主节点,并且会更新/conf/dnindex.properties文件的主节点信息 localhost1=0 表示第一个节点.该文件不要随意修改否则会出现大问题

switchType=2,基于MySQL主从同步的状态决定是否切换,心跳语句为show slave status。此时设置slaveThreshold=100表示从节点落后主库100秒就会剔除这个从节点

使用中遇到的问题

        1.主从复制延迟高,因为数据库设置了触发器,导致更新数据慢,同时也引起了主从复制延时高的问题

        2.刚插入数据,立刻查询查询不到,特别是mysql设置了自增长主键,必须要查一次的时候

解决方案

        1.强制走主库查询,可通过注解的形式     /*#mycat:db_type=master*/

注解支持的’!‘不被 mysql 单库兼容,
注解支持的’#'不被 mybatis 兼容

/*#mycat:db_type=master*/ select * from travelrecord
/*!mycat:db_type=slave*/ select * from travelrecord

其他注解

1. Mycat 端执行存储创建表或存储过程为:
 存储过程:
/*!mycat: sql=select 1 from user*/  存储过程sql ;
 表:
/*!mycat: sql= select 1 from user */ 建表sql;
插入数据:
/*!mycat: sql= select 1 from user */插入sql;
注意注解中语句是节点的表请替换成自己表如 select 1 from user ,注解内语句查出来的数据在哪个分片,数据在那个节点往哪个节点执行后面的sql

2. 读写分离
配置了 Mycat 读写分离后,默认查询都会从读节点获取数据,但是有些场景需要获取实时数据,如果从读节点获取数据可能因延时而无法实现实时,Mycat 支持通过注解/*balance*/来强制从写节点查询数据:
a. 事务内的 SQL,默认走写节点,以注解/*balance*/开头,则会根据 schema.xml 的 dataHost 标签属性的
balance=“1”或“2”去获取节点
b. 非事务内的 SQL,开启读写分离默认根据 balance=“1”或“2”去获取,以注解/*balance*/开头则会走写节
点解决部分已经开启读写分离,但是需要强一致性数据实时获取的场景走写节点
/*balance*/ select  *  from user;
4. 多表 ShareJoin(分库分表的时候用,实际我也没有用过)
/*!mycat:catlet=demo.catlets.ShareJoin */ select a.*,b.id, b.name as tit from customer a,company b on
a.company_id=b.id;
4.读写分离数据源选择
/*!mycat:db_type=master*/ select * from user
/*!mycat:db_type=slave*/ select * from user
/*#mycat:db_type=master*/ select * from user
/*#mycat:db_type=slave*/ select * from user
5. 多租户支持
通过注解方式在配置多个 schema 情况下,指定走哪个配置的 schema。
web 部分修改:
a.在用户登录时,在线程变量(ThreadLocal)中记录租户的 id
b.修改 jdbc 的实现:在提交 sql 时,从 ThreadLocal 中获取租户 id, 添加 sql 注释,把租户的 schema放到注释中。例如:/*!mycat : schema = test_01 */ sql ;
 在 db 前面建立 proxy 层,代理所有 web 过来的数据库请求。proxy 层是用 mycat 实现的,web 提交的 sql 过来时在注释中指定 schema, proxy 层根据指定的 schema 转发 sql 请求。
/*!mycat : schema = test_01 */ sql ;

2.数据分片

两个典型例子

        垂直拆分——分库

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

分库的原则:有紧密关联关系的表应该在一个库里,相互没有关联关系的表可以分到不同的库里。

垂直拆分的优点:

  1. 拆分后业务清晰,拆分规则明确
  2. 系统之间进行整合或扩展很容易
  3. 按照成本、应用的等级、应用的类型等将表放到不同的机器上便于管理数据
  4. 维护相对简单

垂直拆分的缺点:

  1. 部分业务表无法Join,只能单表查询或通过接口方式解决,提高了系统的复杂度
  2. 受每种业务的不同限制,存在单库性能瓶颈,不易进行数据扩展和提升性能
  3. 事务处理变复杂

mycat具体如何分库?

编辑myca/conf/schema.xml文件

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

        <schema name="agencyDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
	<!-- 指定表名到对应dn2节点,其他默认dn1 -->
            <table name="user_info" dataNode="dn2" ></table>
	        <table name="mobile_info" dataNode="dn2" ></table>
        </schema>
	<!-- 配置了两个节点  -->
        <dataNode name="dn1" dataHost="host1" database="testdb" />
        <dataNode name="dn2" dataHost="host2" database="testdb" />
        <dataHost name="host1" maxCon="1000" minCon="10" balance="0"
                          writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
                <!-- can have multi write hosts -->
                <writeHost host="hostM1" url="127.0.0.1:3306" user="root"
                                   password="123456">
                </writeHost>
        </dataHost>
        <dataHost name="host2" maxCon="1000" minCon="10" balance="0"
                          writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
                <!-- can have multi write hosts -->
                <writeHost host="hostM2" url="127.0.0.2:3306" user="root"
                                   password="123456">
                </writeHost>
        </dataHost>
</mycat:schema>

所有需要拆分的表都需要在table标签中定义

基于以上拆分的user_info和mobile_info 两个表则可以拆分至host2中

另外一个思路,根据微服务拆分成不同的schema,每个微服务对应一个schema则可

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

        <schema name="agencyDB1" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
        </schema>
        <schema name="agencyDB2" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn2">
        </schema>
	<!-- 配置了两个节点  -->
        <dataNode name="dn1" dataHost="host1" database="userdb" />
        <dataNode name="dn2" dataHost="host2" database="orderdb" />
        <dataHost name="host1" maxCon="1000" minCon="10" balance="0"
                          writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
                <!-- can have multi write hosts -->
                <writeHost host="hostM1" url="127.0.0.1:3306" user="root"
                                   password="123456">
                </writeHost>
        </dataHost>
        <dataHost name="host2" maxCon="1000" minCon="10" balance="0"
                          writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
                <!-- can have multi write hosts -->
                <writeHost host="hostM2" url="127.0.0.2:3306" user="root"
                                   password="123456">
                </writeHost>
        </dataHost>
</mycat:schema>

 思考,如果分库后单个表的数据量仍然到达千万级

水平拆分——分表

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

 水平拆分的优点:

  1. 单库单表的数据能保持在一定的量级,有助于性能的提高。
  2. 切分的表结构相同,应用层改造较少,只需要增加路由规则即可。
  3. 提高了系统的稳定性和负载能力。

水平拆分的缺点:

  1. 切分后,数据是分散的,跨库join操作难和性能差
  2. 拆分规则难以抽象
  3. 分片事务的一致性难以解决
  4. 数据扩容的难度和维护量极大

mycat实现分表

假设test表已经有1000万数据了。单表达到1000万条数据就达到了瓶颈,会影响查询效率, 需要进行水平拆分(分表)进行优化。

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

        <schema name="agencyDB1" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
         <table name="test" dataNode="dn1,dn2" rule="mod_rule" ></table>
        </schema>
      
	<!-- 配置了两个节点  -->
        <dataNode name="dn1" dataHost="host1" database="userdb" />
        <dataNode name="dn2" dataHost="host2" database="userdb" />
        <dataHost name="host1" maxCon="1000" minCon="10" balance="0"
                          writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
                <!-- can have multi write hosts -->
                <writeHost host="hostM1" url="127.0.0.1:3306" user="root"
                                   password="123456">
                </writeHost>
        </dataHost>
        <dataHost name="host2" maxCon="1000" minCon="10" balance="0"
                          writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
                <heartbeat>select user()</heartbeat>
                <!-- can have multi write hosts -->
                <writeHost host="hostM2" url="127.0.0.2:3306" user="root"
                                   password="123456">
                </writeHost>
        </dataHost>
</mycat:schema>

核心配置是table标签,指定分片规则为mod_rule

配置rule.xml

新加tableRule配置

<tableRule name="mod_rule">
<rule>
<columns>id</columns>
<algorithm>od-long</algorithm>
</rule>
</tableRule>

name:指定唯一的名字,用于标识不同的表规则。
columns:指定要拆分的列名字。
algorithm:使用function标签中的name属性,连接表规则和具体路由算法

mod-long规则为配置文件自带的,修改count属性。代表意思为,根据count数取模

<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
        <!-- how many data nodes -->
        <property name="count">3</property>
</function>

根据以上规则假设数据id是1-10,那么id是 1,4,7,10 的数据在一个节点,id是2,5,8 的数据在一个节点,id是3,6,9的数据在一个节点

这是最常用的取模运算分片规则

枚举规则

通过在配置文件中配置可能的枚举id,自己配置分片,本规则适用于特定的场景,比如有些业务需要按照国内国际类型不同保存订单信息

修改schema.xml文件

<table name="orders" dataNode="dn1,dn2" rule="sharding_by_type" ></table>

修改rule.xml文件

  <tableRule name="sharding_by_type">
                <rule>
                        <columns>type</columns>
                        <algorithm>hash-int</algorithm>
                </rule>
        </tableRule>
  • columns:分片字段
  • algorithm:分片函数
     <function name="hash-int" class="io.mycat.route.function.PartitionByFileMap">
                <property name="mapFile">hash-int.txt</property>
                <property name="type">1</property>
                <property name="defaultNode">0</property>
        </function>
  • mapFile:标识配置文件名称
  • type:0为int型、非0为String
  • defaultNode:默认节点。小于 0 表示不设置默认节点,大于等于 0 表示设置默认节点,设置默认节点如果碰到不识别的枚举值,就让它路由到默认节点,如不设置不识别就报错。

修改配置文件hash-int.txt文件

CHN=0
INT=1

范围分片

此分片适用于提前规划好分片字段某个范围属于哪个分片。

 修改schema.xml文件

<table name="orders" dataNode="dn1,dn2" rule="sharding_by_range" ></table>

修改rule.xml文件

  <tableRule name="sharding_by_range">
                <rule>
                        <columns>id</columns>
                        <algorithm>range-long</algorithm>
                </rule>
        </tableRule>
  • columns:分片字段
  • algorithm:分片函数
     <function name="ranage-long"  class="io.mycat.route.function.AutoPartitionByLong">
                <property name="mapFile">range-long.txt</property>
                <property name="defaultNode">0</property>
        </function>
  • mapFile:标识配置文件名称
  • defaultNode:默认节点。小于 0 表示不设置默认节点,大于等于 0 表示设置默认节点,设置默认节点如果碰到不识别的枚举值,就让它路由到默认节点,如不设置不识别就报错。

修改配置文件range-long.txt文件

0-100=0
101-200=1

以上将id是0-100 的数据分到一个节点,101-200的数据分到一个节点

日期分片

配置步骤:

修改配置文件schema.xml

<table name="login_info" dataNode="dn1,dn2" rule="sharding_by_date" ></table>


修改配置文件rule.xml

        <tableRule name="sharding_by_date">
                <rule>
                        <columns>create_at</columns>
                        <algorithm>shardingByDate</algorithm>
                </rule>
        </tableRule>


columns:分片字段
algorithm:分片函数
 

      <function name="shardingByDate" class="io.mycat.route.function.PartitionByDate">
                <property name="dateFormat">yyyy-MM-dd</property>
                <property name="sBeginDate">2022-01-01</property>
                <property name="sEndDate">2025-01-01</property>
                <property name="sPartionDay">2</property>
        </function>


dateFormat :日期格式
sBeginDate :开始日期
sEndDate:结束日期,则代表数据达到了这个日期的分片后循环从开始分片插入,也可以不设置
sPartionDay :分区天数,即默认从开始日期算起,分隔2天一个分区
 

还有一些其他方法,未深入研究

何时分库分表

非必要不分库,非必要不分表

IO瓶颈

第一种:磁盘读IO瓶颈,热点数据太多,数据库缓存放不下,每次查询会产生大量的IO,降低查询速度->分库和垂直分表
第二种:网络IO瓶颈,请求的数据太多,网络带宽不够 ->分库

CPU瓶颈

第一种:SQl问题:如SQL中包含join,group by, order by,非索引字段条件查询等,增加CPU运算的操作->SQL优化,建立合适的索引,在业务Service层进行业务计算。
第二种:单表数据量太大,查询时扫描的行太多,SQl效率低,增加CPU运算的操作。->水平分表。

其他分库分表中间件

sharding-jdbc(当当)
TSharding(蘑菇街)
Atlas(奇虎360)
Cobar(阿里巴巴)
Oceanus(58同城)
Vitess(谷歌)

我都没用过,不了解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值