如何利用一個數據庫中間件擴展MySQL集羣——kingshard使用指南

上次寫了一篇有關kingshard(https://github.com/flike/king... ) 架構設計的文章,獲得了不少熱心網友的關注。其中有網友提到:但願再寫一篇關於如何利用kingshard搭建一個可擴展的MySQL集羣的文檔。利用假期時間,寫了一篇kingshard使用指南,在這篇文章中,我將結合本身對MySQL Proxy的理解,爲你們講述如何正確使用kingshard數據庫中間件。前端

1. kingshard的應用場景

如今不少互聯網公司仍是在大量使用MySQL來存儲各類類型的關係型數據。隨着訪問量和數據量的增加,開發者不得不考慮一些MySQL相關的新問題:node

  1. 讀寫分離問題。因爲前端應用訪問量增長,單臺MySQL不足以支撐整個系統的寫入和查詢操做。這時候,咱們不得不將一些耗時的查詢操做分散到多個slave上。
  2. 單表容量問題。若是在系統設計之初,沒有考慮到分表問題。隨着數據量的增加,單表容量愈來愈大。做者見過單表容量5億條記錄,而後一個簡單的delete操做都會引發系統慢日誌,並且有可能致使MySQL IO瞬發性的飆升。不少同窗可能會想到,在查詢的字段上加上索引,但當數據量增加到這麼大的時候,即便加上索引效果也不明顯了。歸根結底,就是單表數據量太大,致使MySQL即便經過索引定位數據,仍然須要掃描不少記錄。
  3. 數據庫的運維問題。若是在代碼中配置主庫和從庫host,系統運行固然也是沒問題的。但這樣大大增長了運維工做的壓力,好比:MySQL數據庫IO壓力因爲訪問量的增長居高不下,DBA須要添加一臺slave,這時候就不得不修改代碼,而後打包並上線。還有不少很是實際的例子,在這就不一一列舉。
  4. 鏈接池。前端應用頻繁鏈接MySQL,由此給MySQL帶來的額外性能消耗也是不容忽視的。若是經過增長一個鏈接池,每一個DB緩存必定數量的MySQL鏈接,當有應用須要鏈接後端的MySQL,直接從鏈接池裏取出一個已建好的鏈接來發送SQL請求,這樣會大大加快數據查詢速度。並且能夠下降MySQL的性能消耗。
  5. SQL日誌。在程序出現問題時,咱們但願獲得一些SQL日誌,好比,什麼時刻哪條SQL發送到哪一臺DB上了。經過查看這種日誌可以幫助咱們快速定位問題。

面對這些問題,咱們能夠在客戶端代碼中逐一實現。但這樣也會使得客戶端愈來愈重,不那麼靈活。做者一直從事數據庫相關工做的開發,正是基於數據庫開發的痛點,設計和實現了kingshard數據庫中間件。kingshard對上述5類問題都有比較合適的解決方案。下面對kingshard的主要功能,逐個介紹並演示一下。mysql

2. 安裝和啓動說明

(1). 設置配置文件

下面給出一個配置文件範例,用戶能夠自行按照本身的需求逐項配置:git

# kingshard的地址和端口
addr : 127.0.0.1:9696

# 鏈接kingshard的用戶名和密碼
user :  kingshard
password : kingshard

# log級別,[debug|info|warn|error],默認是error
log_level : debug
#日誌文件路徑,若是不配置則會輸出到終端。
log_path : /Users/flike/log
# 只容許下面的IP列表鏈接kingshard,若是不配置則對鏈接kingshard的IP不作限制。
allow_ips: 127.0.0.1

# 一個node節點表示mysql集羣的一個數據分片,包括一主多從(能夠不配置從庫)
nodes :
    #node節點名字
    name : node1 

    # 鏈接池中默認的空閒鏈接數
    idle_conns : 16

    # kingshard鏈接該node中mysql的用戶名和密碼,master和slave的用戶名和密碼必須一致
    user :  kingshard 
    password : kingshard

    # master的地址和端口 
    master : 127.0.0.1:3306

    # slave的地址和端口,可不配置
    #slave : 192.168.0.12@2,192.168.0.13@3
    #kingshard在300秒內都鏈接不上mysql,kingshard則會下線該mysql
    down_after_noalive : 300
- 
    name : node2 
    idle_conns : 16
    rw_split: true
    user :  kingshard 
    password : kingshard

    master : 192.168.59.103:3307
    slave : 
    down_after_noalive: 100

# 分表規則
schemas :
-
    #分表使用的db,全部的分表必須都在這個db中。
    db : kingshard
    #分表分佈的node名字
    nodes: [node1,node2]
    rules:
            #全部未分表的SQL,都會發往默認node。
        default: node1
        shard:
        -   
            #分表名字
            table: test_shard_hash
            #分表字段
            key: id
            #分表分佈的node
            nodes: [node1, node2]
            #分表類型
            type: hash
            #子表個數分佈,表示node1有4個子表,                  #node2有4個子表。
            locations: [4,4]

        -   
            table: test_shard_range
            key: id
            type: range
            nodes: [node1, node2]
            locations: [4,4]
            #表示每一個子表包含的最大記錄數,也就是說每                  #個子表最多包好10000條記錄。
            table_row_limit: 10000

這裏着重說一下分表的配置規則:github

  • kingshard支持兩種類型的分表規則:hash和range。
  • kingshard分表涉及到的子表,須要用戶在各個db手動建立好,而且格式是:table_name_%4d,也就是說子表下標由4位數組成。例如:table_name_0000,table_name_0102
  • 全部操做未分表的SQL語句都將發送到默認節點。

(2). 安裝和啓動kingshard

  1. 安裝Go語言環境,具體步驟請Google。
  2. git clone https://github.com/flike/king... src/github.com/flike/kingshard
  3. cd src/github.com/flike/kingshard
  4. source ./dev.sh
  5. make
  6. 設置配置文件
  7. 運行kingshard。./bin/kingshard -config=etc/ks.yaml

3. 跨節點分表

因爲做者的只有兩臺MySQL,因此搭建了兩個節點,這兩個節點都只有一臺Master 角色的MySQL數據庫,具體的拓撲圖以下所示:
拓撲圖web

3.1. 分表操做演示

分表操做有hash和range兩種類型,在這裏只演示hash類型的分表操做,range類型的分表相似,就再也不贅述了。sql

3.1.1. 手動建立子表

在node1和node2上各建立4張子表,下面只給出在node1上test_shard_hash_0000的建表SQL語句,其餘子表的建表SQL語句相似。node1包含:test_shard_hash_0000, test_shard_hash_0001, test_shard_hash_0002, test_shard_hash_0003。node2包含:test_shard_hash_0004, test_shard_hash_0005, test_shard_hash_0006, test_shard_hash_0007數據庫

CREATE TABLE `test_shard_hash_0000` (
  `id` bigint(64) unsigned NOT NULL,
  `str` varchar(256) DEFAULT NULL,
  `f` double DEFAULT NULL,
  `e` enum('test1','test2') DEFAULT NULL,
  `u` tinyint(3) unsigned DEFAULT NULL,
  `i` tinyint(4) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

3.1.2. 分表的插入和查詢

執行下面SQL語句,根據查詢的結果能夠看出SQL語句根據分表規則落到不一樣的子表。查詢操做(select)能夠跨多個node,當更新操做涉及到多個node時,kingshard會返回錯誤。爲了保證數據一致性,kingshard不容許同時更新多個node上的子表(由於kingshard還未實現分佈式事務)。但能夠更新單個node上的多個子表,由單node上的事務保證。後端

mysql> insert into test_shard_hash(id,str,f,e,u,i) values(15,"flike",3.14,'test2',2,3);
Query OK, 1 row affected (0.01 sec)

mysql> mysql> insert into test_shard_hash(id,str,f,e,u,i) values(7,"chen",2.1,'test1',32,3);
Query OK, 1 row affected (0.01 sec)

mysql> insert into test_shard_hash(id,str,f,e,u,i) values(17,"github",2.5,'test1',32,3);
Query OK, 1 row affected (0.00 sec)

mysql> insert into test_shard_hash(id,str,f,e,u,i) values(18,"kingshard",7.3,'test1',32,3);
Query OK, 1 row affected (0.01 sec)

對應的SQL日誌以下所示:數組

2015/09/02 18:48:24 - INFO - 127.0.0.1:55003->192.168.59.103:3307:insert into test_shard_hash_0007(id, str, f, e, u, i) values (15, 'flike', 3.14, 'test2', 2, 3)
2015/09/02 18:49:05 - INFO - 127.0.0.1:55003->192.168.59.103:3307:insert into test_shard_hash_0007(id, str, f, e, u, i) values (7, 'chen', 2.1, 'test1', 32, 3)
2015/09/02 18:49:51 - INFO - 127.0.0.1:55003->127.0.0.1:3306:insert into test_shard_hash_0001(id, str, f, e, u, i) values (17, 'github', 2.5, 'test1', 32, 3)
2015/09/02 18:50:21 - INFO - 127.0.0.1:55003->127.0.0.1:3306:insert into test_shard_hash_0002(id, str, f, e, u, i) values (18, 'kingshard', 7.3, 'test1', 32, 3)

能夠看到前兩條SQL發送到了node2的master上了,後兩條SQL發送到node1上的master了。

而後咱們能夠用select語句查看數據,且select支持跨node查詢。

mysql> select * from test_shard_hash where id < 18;
+----+--------+------+-------+------+------+
| id | str    | f    | e     | u    | i    |
+----+--------+------+-------+------+------+
| 17 | github |  2.5 | test1 |   32 |    3 |
|  7 | chen   |  2.1 | test1 |   32 |    3 |
| 15 | flike  | 3.14 | test2 |    2 |    3 |
+----+--------+------+-------+------+------+
3 rows in set (0.02 sec)

由於是hash類型的分表,因此對於select範圍類型的查詢,必須查詢每個子表。對應的SQL日誌以下所示:

2015/09/02 18:55:01 - INFO - 127.0.0.1:55003->127.0.0.1:3306:select * from test_shard_hash_0000 where id < 18
2015/09/02 18:55:01 - INFO - 127.0.0.1:55003->127.0.0.1:3306:select * from test_shard_hash_0001 where id < 18
2015/09/02 18:55:01 - INFO - 127.0.0.1:55003->127.0.0.1:3306:select * from test_shard_hash_0002 where id < 18
2015/09/02 18:55:01 - INFO - 127.0.0.1:55003->127.0.0.1:3306:select * from test_shard_hash_0003 where id < 18
2015/09/02 18:55:01 - INFO - 127.0.0.1:55003->192.168.59.103:3307:select * from test_shard_hash_0004 where id < 18
2015/09/02 18:55:01 - INFO - 127.0.0.1:55003->192.168.59.103:3307:select * from test_shard_hash_0005 where id < 18
2015/09/02 18:55:01 - INFO - 127.0.0.1:55003->192.168.59.103:3307:select * from test_shard_hash_0006 where id < 18
2015/09/02 18:55:01 - INFO - 127.0.0.1:55003->192.168.59.103:3307:select * from test_shard_hash_0007 where id < 18

對應等值的select查詢,kingshard會計算出具體命中的子表,而後只會在相應的子表中查詢。對應的SQL以下所示:

mysql> select * from test_shard_hash where id = 18;
+----+-----------+------+-------+------+------+
| id | str       | f    | e     | u    | i    |
+----+-----------+------+-------+------+------+
| 18 | kingshard |  7.3 | test1 |   32 |    3 |
+----+-----------+------+-------+------+------+
1 row in set (0.00 sec)

對應的SQL日誌以下所示:

2015/09/02 18:59:37 - INFO - 127.0.0.1:55003->127.0.0.1:3306:select * from test_shard_hash_0002 where id = 18

3.1.3. 分表的更新

當更新的記錄落在同一個子表時,kingshard支持這類操做。在上面插入的記錄中,id爲7和15的記錄都落在test_shard_hash_0007中,因此能夠成功地執行下面的SQL:

mysql> update test_shard_hash set u=123 where id = 15 or id = 7;
Query OK, 2 rows affected (0.01 sec)

對應的SQL日誌是:

2015/09/02 19:17:27 - INFO - 127.0.0.1:55003->192.168.59.103:3307:update test_shard_hash_0007 set u = 123 where id = 15 or id = 7

當更新的記錄落在不一樣的子表,只有當這些子表在同一個node中,kingshard才支持。kingshard是經過單node事務實現的,也就是說將發往同一個node的SQL都放在一個事務中執行,這些操做正確性由MySQL保證。在上述記錄中,咱們能夠看出id爲17和18的記錄都在node1中,因此kingshard是能夠執行下列SQL:

mysql> update test_shard_hash set i=23 where id = 17 or id = 18;
Query OK, 2 rows affected (0.00 sec)

對應的SQL日誌是:

2015/09/02 19:24:46 - INFO - 127.0.0.1:55003->127.0.0.1:3306:update test_shard_hash_0001 set i = 23 where id = 17 or id = 18
2015/09/02 19:24:46 - INFO - 127.0.0.1:55003->127.0.0.1:3306:update test_shard_hash_0002 set i = 23 where id = 17 or id = 18

可是若是更新的記錄落在不一樣的node時,kingshard則會報告錯誤:
例如:

mysql> update test_shard_hash set i=23 where id = 15 or id = 18;
ERROR 1105 (HY000): no route node

對應的SQL日誌是:

2015/09/02 19:24:24 - ERROR - router.go:[483] - [Router] "generateUpdateSql" "update in multi node" "RouteNodeIndexs=[0 1]" conn_id=0

3.2. 指定發送的node

有時候咱們須要操做的表,不在default node中。在kingshard中容許用戶將特定的sql路由到指定的node上。只須要在sql語句前面加上包含node名稱的註釋。

mysql> /*node2*/show tables;
+-----------------------+
| Tables_in_kingshard   |
+-----------------------+
| kingshard_test_conn   |
| test_shard_hash_0004  |
| test_shard_hash_0005  |
| test_shard_hash_0006  |
| test_shard_hash_0007  |
| test_shard_range_0004 |
| test_shard_range_0005 |
| test_shard_range_0006 |
| test_shard_range_0007 |
+-----------------------+
9 rows in set (0.03 sec)

mysql> /*node2*/select * from kingshard_test_conn;
Empty set (0.01 sec)

mysql> /*node2*/desc kingshard_test_conn;
+-------+-----------------------+------+-----+---------+-------+
| Field | Type                  | Null | Key | Default | Extra |
+-------+-----------------------+------+-----+---------+-------+
| id    | bigint(20) unsigned   | NO   | PRI | NULL    |       |
| str   | varchar(256)          | YES  |     | NULL    |       |
| f     | double                | YES  |     | NULL    |       |
| e     | enum('test1','test2') | YES  |     | NULL    |       |
| u     | tinyint(3) unsigned   | YES  |     | NULL    |       |
| i     | tinyint(4)            | YES  |     | NULL    |       |
+-------+-----------------------+------+-----+---------+-------+
6 rows in set (0.00 sec)

mysql> /*node2*/insert into kingshard_test_conn values(10,"hello",10.2,'test1',1,1);
Query OK, 1 row affected (0.00 sec)

mysql> /*node2*/select * from kingshard_test_conn;
+----+-------+------+-------+------+------+
| id | str   | f    | e     | u    | i    |
+----+-------+------+-------+------+------+
| 10 | hello | 10.2 | test1 |    1 |    1 |
+----+-------+------+-------+------+------+
1 row in set (0.00 sec)

3.3. 強制讀主庫

有時候在主庫中插入數據後,但願當即從主庫讀出來。在kingshard中因爲讀寫分離的緣由,select默認會發送到相應node的從庫上。可是隻須要在select語句中加入相應的註釋項(/*master*/),就能夠將select語句發送到主庫。

mysql> select/*master*/ * from kingshard_test_conn;
+----+----------+------+-------+------+------+
| id | str      | f    | e     | u    | i    |
+----+----------+------+-------+------+------+
|  1 | a        | 3.14 | test1 | NULL | NULL |
|  5 | ""''\abc | NULL | NULL  | NULL | NULL |
|  6 | 中國     | NULL | NULL  | NULL | NULL |
+----+----------+------+-------+------+------+
3 rows in set (0.01 sec)

3.4. 跨node的sum和count函數

在kingshard中,支持sum和count函數,kingshard會將相應的SQL發生到正確的DB,並將結果合併起來再返回給客戶的。例如:

mysql> select count(id) from test_shard_hash where id > 1;
+-----------+
| count(id) |
+-----------+
|         4 |
+-----------+
1 row in set (0.02 sec)

mysql> select sum(id) from test_shard_hash where id > 1;
+---------+
| sum(id) |
+---------+
|      57 |
+---------+
1 row in set (0.02 sec)

相應的SQL日誌以下所示:

2015/09/03 14:49:01 - INFO - 127.0.0.1:55768->127.0.0.1:3306:select count(id) from test_shard_hash_0000 where id > 1
2015/09/03 14:49:01 - INFO - 127.0.0.1:55768->127.0.0.1:3306:select count(id) from test_shard_hash_0001 where id > 1
2015/09/03 14:49:01 - INFO - 127.0.0.1:55768->127.0.0.1:3306:select count(id) from test_shard_hash_0002 where id > 1
2015/09/03 14:49:01 - INFO - 127.0.0.1:55768->127.0.0.1:3306:select count(id) from test_shard_hash_0003 where id > 1
2015/09/03 14:49:01 - INFO - 127.0.0.1:55768->192.168.59.103:3307:select count(id) from test_shard_hash_0004 where id > 1
2015/09/03 14:49:01 - INFO - 127.0.0.1:55768->192.168.59.103:3307:select count(id) from test_shard_hash_0005 where id > 1
2015/09/03 14:49:01 - INFO - 127.0.0.1:55768->192.168.59.103:3307:select count(id) from test_shard_hash_0006 where id > 1
2015/09/03 14:49:01 - INFO - 127.0.0.1:55768->192.168.59.103:3307:select count(id) from test_shard_hash_0007 where id > 1
2015/09/03 14:49:14 - INFO - 127.0.0.1:55768->127.0.0.1:3306:select sum(id) from test_shard_hash_0000 where id > 1
2015/09/03 14:49:14 - INFO - 127.0.0.1:55768->127.0.0.1:3306:select sum(id) from test_shard_hash_0001 where id > 1
2015/09/03 14:49:14 - INFO - 127.0.0.1:55768->127.0.0.1:3306:select sum(id) from test_shard_hash_0002 where id > 1
2015/09/03 14:49:14 - INFO - 127.0.0.1:55768->127.0.0.1:3306:select sum(id) from test_shard_hash_0003 where id > 1
2015/09/03 14:49:14 - INFO - 127.0.0.1:55768->192.168.59.103:3307:select sum(id) from test_shard_hash_0004 where id > 1
2015/09/03 14:49:14 - INFO - 127.0.0.1:55768->192.168.59.103:3307:select sum(id) from test_shard_hash_0005 where id > 1
2015/09/03 14:49:14 - INFO - 127.0.0.1:55768->192.168.59.103:3307:select sum(id) from test_shard_hash_0006 where id > 1
2015/09/03 14:49:14 - INFO - 127.0.0.1:55768->192.168.59.103:3307:select sum(id) from test_shard_hash_0007 where id > 1

3.5. 跨node的order by

kingshard支持跨node的select操做使用order by,kingshard先將合適的SQL發生到對應的node,而後將結果集在內存中排序,從而實現select的order by操做。示例以下所示:

mysql> select * from test_shard_hash where id > 1 order by id;
+----+-----------+------+-------+------+------+
| id | str       | f    | e     | u    | i    |
+----+-----------+------+-------+------+------+
|  7 | chen      |  2.1 | test1 |  123 |    3 |
| 15 | flike     | 3.14 | test2 |  123 |    3 |
| 17 | github    |  2.5 | test1 |   32 |   23 |
| 18 | kingshard |  7.3 | test1 |   32 |   23 |
+----+-----------+------+-------+------+------+
4 rows in set (0.02 sec)

對應的SQL日誌爲:

2015/09/03 14:54:11 - INFO - 127.0.0.1:55768->127.0.0.1:3306:select * from test_shard_hash_0000 where id > 1 order by id asc
2015/09/03 14:54:11 - INFO - 127.0.0.1:55768->127.0.0.1:3306:select * from test_shard_hash_0001 where id > 1 order by id asc
2015/09/03 14:54:11 - INFO - 127.0.0.1:55768->127.0.0.1:3306:select * from test_shard_hash_0002 where id > 1 order by id asc
2015/09/03 14:54:11 - INFO - 127.0.0.1:55768->127.0.0.1:3306:select * from test_shard_hash_0003 where id > 1 order by id asc
2015/09/03 14:54:11 - INFO - 127.0.0.1:55768->192.168.59.103:3307:select * from test_shard_hash_0004 where id > 1 order by id asc
2015/09/03 14:54:11 - INFO - 127.0.0.1:55768->192.168.59.103:3307:select * from test_shard_hash_0005 where id > 1 order by id asc
2015/09/03 14:54:11 - INFO - 127.0.0.1:55768->192.168.59.103:3307:select * from test_shard_hash_0006 where id > 1 order by id asc
2015/09/03 14:54:11 - INFO - 127.0.0.1:55768->192.168.59.103:3307:select * from test_shard_hash_0007 where id > 1 order by id asc

4. 單node的事務

kingshard支持在單個node上執行事務,也就是說同一個事務不能跨多個node,當出現跨node的狀況時,kingshard會返回錯誤給客戶端。能夠跨同node上的不一樣子表。示例以下所示:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into test_shard_hash(id,str,f,e,u,i) values(23,'proxy',9.2,'test1',12,3);
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.01 sec)

當在一個事務中,出現跨node的SQL語句時,kingshard會返回錯誤:

#SQL語句在node2中執行
mysql> insert into test_shard_hash(id,str,f,e,u,i) values(31,'proxy',9.2,'test1',12,3);
Query OK, 1 row affected (0.01 sec)
#SQL語句在須要在node1執行,跨node了。
mysql> insert into test_shard_hash(id,str,f,e,u,i) values(40,'proxy',9.2,'test1',12,3);
ERROR 1105 (HY000): transaction in multi node

6. kingshard的管理端操做

kingshard的管理接口,目前仍是命令行的方式。後續有時間打算將其改爲web方式。管理端具體的命令能夠參考文檔。管理端的命令格式,能夠分爲兩類:

  • admin server(opt,k,v) values(action,k1,v1)。這種命令是操做整個kingshard的,其中opt表示這個操做的動做;k表示操做的對象,v表示給對象的賦值。
  • admin node(opt,node,k,v) values(action,nodeName,k1,v1),這類命令表示操做node。其中opt表示這個操做的動做;node表示操做哪一個node;k表示操做的對象,v表示給對象的賦值。

7. 總結

kingshard開源兩個月以來,獲得了不少開發者的關注。這足以證實,你們對數據庫中間件是有需求的,但願出現一款簡單好用的MySQL Proxy。kingshard通過這兩個月的迭代開發,也比較穩定了。據瞭解,有幾個公司正在對其進行嘗試。後續做者的主要精力會放在優化kingshard的性能上,同時完善kingshard已有的功能。若是你們對kingshard有什麼想法或建議,能夠發郵件聯繫我(flikecn#126.com),很是樂意和你們交流。

8. 開源網址

github: https://github.com/flike/king...

9. 公衆號

歡迎關注後端技術快訊公衆號,有關kingshard的最新消息與後端架構設計類的文章,都會在這個公衆號分享。圖片描述

相關文章
相關標籤/搜索