mycat注解及高可用(三)


一、mycat注解

1.1、注解原理

概念

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 表所在的所有分片上去執行去,這樣造成的后果若是插入語句,則在多個分片上都存在重復記錄,同樣查詢、更新、刪除操作也會得到錯誤的結果!

解決問題

1. MySql 不支持的語法結構,如 insert …select…;
2. 同一個實例內的跨庫關聯查詢,如用戶庫和平台庫內的表關聯;
3. 存儲過程調用;
4. 表,存儲過程創建。

注解規范

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.2、 注解使用示例

注解支持的'!'不被 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.1、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.3、二階段提交

所謂的兩個階段是指准備 prepare 階段和提交 commit 階段。

第一階段分為兩個步驟:

1、事務管理器通知參與該事務的各個資源管理器,通知他們開始准備事務。

2、資源管理器接收到消息后開始准備階段,寫好事務日志(redo undo)並執行事務,但不提交,然后將是否就緒的消息返回給事務管理器(此時已經將事務的大部分事情做完,以后的操作耗時極小)。

第二階段也分為兩個步驟:

1、事務管理器在接受各個消息后,開始分析,如果有任意數據庫失敗,則發送回滾命令,否則發送提交命令。

2、各個資源管理器接收到命令后,執行(耗時很少),並將提交消息返回給事務管理器。

 

 

 

事務管理器接受消息后,事務結束,應用程序繼續執行。

至於為什么要分兩步執行,一是因為分兩步,就有了事務管理器統一管理的機會;二盡可能晚地提交事務,讓事務在提交前盡可能地完成所有能完成的工作,這樣,最后的提交階段將是耗時極短,耗時極短意味着操作失敗的可能性也就降低。

 2.4、 XA 規范

XA 的分布式事務處理模型里面涉及到三個角色 AP(應用程序)、RM(數據庫)、TM(事務管理器)。AP 定義事務的開始和結束,訪問事務內的資源。RM 除了數據庫之外,還可以是其他的系統資源,比如文件系統,打印機服務器。 TM 負責管理全局事務,分配事務唯一標識,監控事務的執行進度,並負責事務的提交、回滾、失敗恢復等,是一個協調者的角色,可能是程序或者中間件。XA 協議主要規定了了 TM 與 RM 之間的交互。注意:通過實現 XA 的接口,只是提供了對 XA 分布式事務的支持,並不是說數據庫本身有分布式事務的能力。

2.5、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.6、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.7、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.8、保證 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.9、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>

三、核心流程總結

官網的架構圖:

 

啟動

1、MycatServer 啟動,解析配置文件,包括服務器、分片規則等

2、創建工作線程,建立前端連接和后端連接

執行 SQL

1、前端連接接收 MySQL 命令

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

3、獲取路由

4、改寫 MySQL,例如兩個條件在兩個節點上,則變成兩條單獨的 SQL

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

改寫成:

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

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

又比如多表關聯查詢,先到各個分片上去獲取結果,然后在內存中計算

5、與后端數據庫建立連接

6、發送 SQL 語句到 MySQL 執行

7、獲取返回結果

8、處理返回結果,例如排序、計算等等

9、返回給客戶端

四、Mycat 高可用

目前 Mycat 沒有實現對多 Mycat 集群的支持。集群之前最麻煩的是數據同步和選舉。可以暫時使用 HAProxy+Keepalived 實現。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM