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 實現。

相關文章
相關標籤/搜索