mycat注解及高可用(三)

1、mycat注解

1.一、注解原理

概念html

MyCat 对自身不支持的 Sql 语句提供了一种解决方案——在要执行的 SQL 语句前添加额外的一段由注解SQL 组织的代码,这样 Sql 就能正确执行,这段代码称之为“注解”。注解的使用至关于对 mycat 不支持的 sql语句作了一层透明代理转发,直接交给目标的数据节点进行 sql 语句执行,其中注解 SQL 用于肯定最终执行 SQL的数据节点。注解的形式是:
/*!mycat: sql=注解 Sql 语句*/
注解的使用方式是:
/*!mycat: sql=注解 Sql 语句*/真正执行 Sql
使用时将=号后的“注解 Sql 语句”替换为须要的 Sql 语句便可,后面会提到具体的用法。

原理前端

MyCat 执行 SQL 语句的流程是先进行 SQL 解析处理,解析出分片信息(路由信息)后,而后到该分片对应的物理库上去执行;若传入的 SQL 语句 MyCat 没法解析,则 MyCat 不会去执行;而注解则是告诉 MyCat 按照注解内的 SQL(称之为注解 SQL)去进行解析处理,解析出分片信息后,将注解后真正要执行的 SQL 语句(称之为原始 SQL)发送到该分片对应的物理库上去执行。从上面的原理能够看到,注解只是告诉 MyCat 到何处去执行原始 SQL;于是使用注解前,要清楚的知道该原始 SQL 去哪一个分片执行,而后在注解 SQL 中也指向该分片,这样才能使用!例如sharding_id=10010 便是指明分片信息的。须要说明的是,若注解 SQL 没有能明确到具体某个分片,譬如例子中的注解 SQL 没有添加sharding_id=10010 这个条件,则 MyCat 会将原始 SQL 发送到 persons 表所在的全部分片上去执行去,这样形成的后果如果插入语句,则在多个分片上都存在重复记录,一样查询、更新、删除操做也会获得错误的结果!

解决问题java

1. MySql 不支持的语法结构,如 insert …select…;
2. 同一个实例内的跨库关联查询,如用户库和平台库内的表关联;
3. 存储过程调用;
4. 表,存储过程建立。

注解规范mysql

1. 注解 SQL 使用 select 语句,不容许使用 delete/update/insert 等语句;虽然 delete/update/insert 等语句也能用在注解中,但这些语句在 Sql 处理中有额外的逻辑判断,从性能考虑,请使用 select 语句
2. 注解 SQL 禁用表关联语句;
3. 注解 SQL 尽可能用最简单的 SQL 语句,如 select id from tab_a where id=’10000’;
4. 不管是原始 SQL 仍是注解 SQL,禁止 DDL 语句;
5. 能不用注解的尽可能不用;
6. 详细要求见下表。
补充说明:
使用注解并不额外增长 MyCat 的执行时间;从解析复杂度以及性能考虑,注解 SQL 应尽可能简单。至于一个SQL 使用注解和不使用注解的性能对比,不存在参考意义,由于前提是 MyCat 不支持的 SQL 才使用注解。

1.二、 注解使用示例

注解支持的'!'不被 mysql 单库兼容,
注解支持的'#'不被 mybatis 兼容
新增长 mycat 字符前缀标志 Hintsql:"/** mycat: */"
从 1.6 开始支持三种注解方式:
/*#mycat:db_type=master*/ select * from travelrecord
/*!mycat:db_type=slave*/ select * from travelrecord
/**mycat:db_type=master*/ select * from travelrecord
1. Mycat 端执行存储建立表或存储过程为:
 存储过程:
/*!mycat: sql=select 1 from test */ CREATE PROCEDURE `test_proc`() BEGIN END ;
 表:
/*!mycat: sql=select 1 from test */create table test2(id int);
注意注解中语句是节点的表请替换成本身表如 select 1 from 表 ,注解内语句查出来的数据在哪一个分片,数据在那个节点往哪一个节点建.
2. 特殊语句自定义分片:
/*!mycat: sql=select 1 from test */insert into t_user(id,name) select id,name from t_user2;
3. 读写分离
配置了 Mycat 读写分离后,默认查询都会从读节点获取数据,可是有些场景须要获取实时数据,若是从读节点获取数据可能因延时而没法实现实时,Mycat 支持经过注解/*balance*/来强制从写节点查询数据:
a. 事务内的 SQL,默认走写节点,以注解/*balance*/开头,则会根据 schema.xml 的 dataHost 标签属性的
balance=“1”或“2”去获取节点
b. 非事务内的 SQL,开启读写分离默认根据 balance=“1”或“2”去获取,以注解/*balance*/开头则会走写节
点解决部分已经开启读写分离,可是须要强一致性数据实时获取的场景走写节点
/*balance*/ select a.* from customer a where a.company_id=1;
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;
5.读写分离数据源选择
/*!mycat:db_type=master*/ select * from travelrecord
/*!mycat:db_type=slave*/ select * from travelrecord
/*#mycat:db_type=master*/ select * from travelrecord
/*#mycat:db_type=slave*/ select * from travelrecord
6. 多租户支持
经过注解方式在配置多个 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、 事务支持

 2.一、Mycat 里的数据库事务

Mycat 目前没有出来跨分片的事务强一致性支持,目前单库内部能够保证事务的完整性,若是跨库事务,在执行的时候任何分片出错,能够保证全部分片回滚,可是一旦应用发起 commit 指令,没法保证全部分片都成功考虑到某个分片挂的可能性不大因此称为弱 xa。

2.2 XA 事务原理

分布式事务处理( Distributed Transaction Processing , DTP )指一个程序或程序段,在一个或多个资源如数据库或文件上为完成某些功能的执行过程的集合,分布式事务处理的关键是必须有一种方法能够知道事务在任何地方所作的全部动做,提交或回滚事务的决定必须产生统一的结果(所有提交或所有回滚)。X/Open 组织(即如今的 Open Group )定义了分布式事务处理模型。 X/Open DTP 模型( 1994 )包括应用程序( AP )、事务管理器( TM )、资源管理器( RM )、通讯资源管理器( CRM )四部分。通常,常见的事务管理器( TM )是交易中间件,常见的资源管理器( RM )是数据库,常见的通讯资源管理器( CRM )是消息中间件,下图是 X/Open DTP 模型

 

 

通常的编程方式是这样的:
• 配置 TM,经过 TM 或者 RM 提供的方式,把 RM 注册到 TM。能够理解为给 TM 注册 RM 做为数据源。一个 TM 能够注册多个 RM。
• AP 从 TM 获取资源管理器的代理(例如:使用 JTA 接口,从 TM 管理的上下文中,获取出这个 TM 所管理的 RM 的 JDBC 链接或 JMS 链接)
• AP 向 TM 发起一个全局事务。这时,TM 会通知各个 RM。XID(全局事务 ID)会通知到各个 RM。
• AP 经过 1 中获取的链接,直接操做 RM 进行业务操做。这时,AP 在每次操做时把 XID(包括所属分支的信息)传递给 RM,RM 正是经过这个 XID 与 2 步中的 XID 关联来知道操做和事务的关系的。
• AP 结束全局事务。此时 TM 会通知 RM 全局事务结束。
• 开始二段提交,也就是 prepare - commit 的过程。
• XA 协议(XA Specification),指的是 TM 和 RM 之间的接口,其实这个协议只是定义了 xa_和 ax_系列的函数原型以及功能描述、约束和实施规范等。至于 RM 和 TM 之间经过什么协议通讯,则没有说起,目前知名的数据库,如 Oracle, DB2 等,都是实现了 XA 接口的,均可以做为 RM。Tuxedo、TXseries 等事务中间件能够经过 XA 协议跟这些数据源进行对接。JTA(Java Transaction API)是符合 X/Open DTP 的一个编程模型,事务管理和资源管理器支架也是用了 XA 协议。

 2.三、二阶段提交

所谓的两个阶段是指准备 prepare 阶段和提交 commit 阶段。web

第一阶段分为两个步骤:sql

一、事务管理器通知参与该事务的各个资源管理器,通知他们开始准备事务。数据库

二、资源管理器接收到消息后开始准备阶段,写好事务日志(redo undo)并执行事务,但不提交,而后将是否就绪的消息返回给事务管理器(此时已经将事务的大部分事情作完,之后的操做耗时极小)。编程

第二阶段也分为两个步骤:后端

一、事务管理器在接受各个消息后,开始分析,若是有任意数据库失败,则发送回滚命令,不然发送提交命令。性能优化

二、各个资源管理器接收到命令后,执行(耗时不多),并将提交消息返回给事务管理器。

 

 

 

事务管理器接受消息后,事务结束,应用程序继续执行。

至于为何要分两步执行,一是由于分两步,就有了事务管理器统一管理的机会;二尽量晚地提交事务,让事务在提交前尽量地完成全部能完成的工做,这样,最后的提交阶段将是耗时极短,耗时极短意味着操做失败的可能性也就下降。

 2.四、 XA 规范

XA 的分布式事务处理模型里面涉及到三个角色 AP(应用程序)、RM(数据库)、TM(事务管理器)。AP 定义事务的开始和结束,访问事务内的资源。RM 除了数据库以外,还能够是其余的系统资源,好比文件系统,打印机服务器。 TM 负责管理全局事务,分配事务惟一标识,监控事务的执行进度,并负责事务的提交、回滚、失败恢复等,是一个协调者的角色,多是程序或者中间件。XA 协议主要规定了了 TM 与 RM 之间的交互。注意:经过实现 XA 的接口,只是提供了对 XA 分布式事务的支持,并非说数据库自己有分布式事务的能力。

2.五、MySQL 对 XA 的支持

  XA 是一种两阶段提交的实现。数据库自己必需要提供被协调的接口,好比事务开启,准备,事务结束,事务提交,事务回滚。https://dev.mysql.com/doc/refman/5.7/en/xa.html

 

 MySQL 单节点运行 XA 事务演示:

 

 

use ljxmycat;
--开启 XA 事务 xa start 'xid';
--插入数据
INSERT INTO `delivery_mod` (id,name) VALUES (22222, '张三');

5结束一个 XA 事务
 xa end 'xid'; 
6准备提交
xa prepare
'xid';
--列出全部处于 PREPARE 阶段的 XA 事务
xa recover;

提交

xa commit 'xid';

 
                    

2.六、XA 事务的问题和 MySQL 的局限

       XA 事务的明显问题是 timeout 问题,好比当一个 RM 出问题了,那么整个事务只能处于等待状态。这样能够会连锁反应,致使整个系统都很慢,最终不可用,另外 2 阶段提交也大大增长了 XA 事务的时间,使得 XA 事务没法支持高并发请求。
      避免使用 XA 事务的方法一般是最终一致性。
      举个例子,好比一个业务逻辑中,最后一步是用户帐号增长 300 元,为了减小 DB 的压力,先把这个放到消息队列里,而后后端再从消息队列里取出消息,更新 DB。那么如何保证,这条消息不会被重复消费?或者重复消费后,仍能保证结果是正确的?在消息里带上用户账号在数据库里的版本,在更新时比较数据的版本,若是相同则加上 300;好比用户原本有 500 元,那么消息是更新用户的钱数为 800,而不是加上 300;
       另一个方式是,建一个消息是否被消费的表,记录消息 ID,在事务里,先判断消息是否已经消息过,若是没有,则更新数据库,加上 300,不然说明已经消费过了,丢弃。
       前面两种方法都必须从流程上保证是单方向的。
       其实严格意义上,用消息队列来实现最终一致性仍然有漏洞,由于消息队列跟当前操做的数据库是两个不一样的资源,仍然存在消息队列失败致使这个帐号增长 300 元的消息没有被存储起来(固然复杂的高级的消息队列产品能够避免这种现象,但仍然存在风险),而第二种方式则因为新的表跟以前的事务操做的表示在一个 Database中,所以不存在上述的可能性
       MySQL 的 XA 事务,长期以来都存在一个缺陷:
       MySQL 数据库的主备数据库的同步,经过 Binlog 的复制完成。而 Binlog 是 MySQL 数据库内部 XA 事务的协调者,而且 MySQL 数据库为 binlog 作了优化——binlog 不写 prepare 日志,只写 commit 日志。全部的参与节点 prepare 完成,在进行 xa commit 前 crash。crash recover 若是选择 commit 此事务。因为binlog 在 prepare 阶段未写,所以主库中看来,此分布式事务最终提交了,可是此事务的操做并未写到 binlog中,所以也就未能成功复制到备库,从而致使主备库数据不一致的状况出现。

2.七、XA 事务使用指南

Mycat 从1.6.5 版本开始支持标准 XA 分布式事务,考虑到 mysql5.7 以前版本 xa 的2 个bug,因此推荐最佳搭配 XA 功能使用 mysql 5.7 版本。Mycat 实现 XA 标准分布式事务,mycat 做为 xa 事务协调者角色,即便事务过程当中 mycat 宕
机挂掉,因为 mycat 会记录事务日志,因此 mycat 恢复后会进行事务的恢复善后处理工做。考虑到分布式事务的性能开销比较大,因此只推荐在全局表的事务以及其余一些对一致性要求比较高的场景。
使用示例:
XA 操做说明
1. set autocommit=0;
XA 事务 须要设置手动提交
2. set xa=on;
使用该命令开启 XA 事务
3. insert into travelrecord(id,name)
values(1,'N'),(6000000,'A'),(321,'D'),(13400000,'C'),(59,'E');
执行相应的 SQL 语句部分
4.commit;
对事务进行提交,事务结束

2.八、保证 repeatable read

mycat 有一个特性,就是开事务以后,若是不运行 update/delete/select for update 等更新类语句 SQL 的话,不会将当前链接与当前 session 绑定。以下图所示:
这样作的好处是能够保证链接能够最大限度的复用,提高性能。可是,这就会致使两次 select 中若是有其它的在提交的话,会出现两次一样的 select 不一致的现象,即不能 repeatable read,这会让人直连 mysql 的人很困惑,可能会在依赖
repeatable read 的场景出现问题。因此作了一个开关,当 server.xml 的system 配置了strictTxIsolation=true 的时候(true),会关掉这个特性,以保证 repeatable read,加了开关后以下图所示:

 

2.九、Mycat SQL 拦截机制

SQL 拦截是一个比较有用的高级技巧,用户能够写一个 java 类,将传入 MyCAT 的 SQL 进行改写而后交给Mycat 去执行,此技巧能够完成以下一些特殊功能:
• 捕获和记录某些特殊的 SQL;
• 记录 sql 查找异常;
• 出于性能优化的考虑,改写 SQL,好比改变查询条件的顺序或增长分页限制;
• 将某些 Select SQL 强制设置为 Read 模式,走读写分离(不少事务框架很难剥离事务中的 Select SQL;
• 后期 Mycat 智能优化,拦截全部 sql 作智能分析,自动监控节点负载,自动优化路由,提供数据库优化建议
SQL 拦截的原理是在路由以前拦截 SQL,而后作其余处理,完了以后再作路由,执行,以下图所示:
默认的拦截器实现了 Mysql 转义字符的过滤转换,非默认拦截器只有一个拦截记录 sql 的拦截器。
a. 默认 SQL 拦截器:
配置:
<system>
<property name="sqlInterceptor">io.mycat.interceptor.impl.DefaultSqlInterceptor</property>
</system>
源码:
/**
* escape mysql escape letter
*/
@Override
public String interceptSQL(String sql, int sqlType) {
if (sqlType == ServerParse.UPDATE || sqlType == ServerParse.INSERT||sqlType == 
ServerParse.SELECT||sqlType == ServerParse.DELETE) {
return sql.replace("\\'", "''");
} else {
return sql;
} }
b. 捕获记录 sql 拦截器配置:
<system>
<property name="sqlInterceptor">io.mycat.interceptor.impl.StatisticsSqlInterceptor</property>
<property name="sqlInterceptorType">select,update,insert,delete</property>
<property name="sqlInterceptorFile">E:/mycat/sql.txt</property>
</system>
sqlInterceptorType : 拦截 sql 类型
sqlInterceptorFile : sql 保存文件路径
注意:捕获记录 sql 拦截器的配置只有 1.4 及其之后可用,1.3 无本拦截。
若是须要实现本身的 sql 拦截,只须要将配置类改成本身配置便可:
1.定义自定义类 implements SQLInterceptor ,而后改写 sql 后返回。
2.将本身实现的类放入 catlet 目录,能够为 class 或 jar。
3.配置配置文件:
<system>
<property name="sqlInterceptor">io.mycat.interceptor.impl.自定义 class</property>
<!--其余配置-->
</system>

3、核心流程总结

官网的架构图:

 

启动

一、MycatServer 启动,解析配置文件,包括服务器、分片规则等

二、建立工做线程,创建前端链接和后端链接

执行 SQL

一、前端链接接收 MySQL 命令

二、解析 MySQL,Mycat 用的是 Druid 的 DruidParser

三、获取路由

四、改写 MySQL,例如两个条件在两个节点上,则变成两条单独的 SQL

    例如 select * from text where id in(5000001, 10000001);

改写成:

    select * from text where id = 5000001;(dn2 执行)

    select * from text where id = 10000001;(dn3 执行)

又好比多表关联查询,先到各个分片上去获取结果,而后在内存中计算

五、与后端数据库创建链接

六、发送 SQL 语句到 MySQL 执行

七、获取返回结果

八、处理返回结果,例如排序、计算等等

九、返回给客户端

4、Mycat 高可用

目前 Mycat 没有实现对多 Mycat 集群的支持。集群以前最麻烦的是数据同步和选举。能够暂时使用 HAProxy+Keepalived 实现。

相关文章
相关标签/搜索