百尺竿頭更進一步 – Amazon Aurora 的讀寫能力擴充套件之 ShardingSphere-Proxy 篇

亞馬遜雲開發者發表於2022-04-25

1.前言

Amazon Aurora 是亞馬遜雲科技自研的一項關聯式資料庫服務,它在提供和開源資料庫MySQL、PostgreSQL的完好相容性同時,也能夠提供和商業資料庫媲美的效能和可用性。效能方面,Aurora MySQL能夠支援到與開源標準MySQL同等配置下五倍的吞吐量,Aurora PostgreSQL能夠支援與開源標準PostgreSQL同等配置下三倍的吞吐量的提升。在擴充套件性的角度,Aurora在儲存與計算、橫向與縱向方面都進行了功能的增強和創新。Aurora支援多達128TB的儲存容量,而且支援10GB為單位的儲存層動態收縮。計算方面,Aurora提供多個讀副本的可擴充套件性配置支援一個區域內多達15個讀副本的擴充套件,提供多主的架構來支援同一個區域內4個寫節點的擴充套件,提供Serverless無伺服器化的架構例項級別的秒級縱向擴充套件,提供全球資料庫來實現資料庫的低延遲跨區域擴充套件。

隨著使用者資料量的增長,Aurora已經提供了很好的擴充套件性,那是否可以進一步處理更多的資料量、支援更多的併發訪問呢?您可以考慮利用分庫分表的方式,來支援底層多個Aurora叢集的配置。基於此,包含這篇部落格在內的系列部落格會進行相應的介紹,旨在為您進行分庫分表時選擇使用代理或者JDBC提供參考。

本篇部落格會聚焦如何使用ShardingSphere-Proxy,一個開源的分庫分表中介軟體工具,來進行資料庫叢集的構建,會涵蓋分庫分表、讀寫分離、動態配置等方面。

2.ShardingSphere-Proxy介紹

Apache ShardingSphere 是一套開源的分散式資料庫解決方案組成的生態圈,它由 JDBC、Proxy 和 Sidecar(規劃中)這 3 款既能夠獨立部署,又支援混合部署配合使用的產品組成。

作為中介軟體,ShardingSphere-Proxy的定位是透明化的資料庫代理端。它採用Apache2.0協議,持續迭代版本,最新版本為5.1.0,目前支援MySQL和PostgreSQL版本。它對應用程式透明,相容MySQL/PostgreSQL協議的客戶端。MySQL命令列mysql,MySQL workbench等都可以直接訪問ShardingSphere-Proxy。

ShardingSphere-Proxy下層可以連線不同的資料庫,這些資料庫可以是同構也可以是異構的。使用者可以有兩種方式指定底層資料庫的分庫分表或者讀寫分離規則:1)根據yaml配置檔案靜態指定2)利用ShardingSphere提供的增強性的DistSQL語言來指定。因為DistSQL支援動態建立規則不需要重啟Proxy本身,它成為ShardingSphere-Proxy未來發展的重點。

作為資料庫代理,是否能夠提供連線池增強使用者併發訪問的連線處理是需要考量的一方面,ShardingSphere-Proxy在新增資料來源並進行初始化時,會支援為每個資料庫配置一個Hikari連線池。Hikari是業界廣泛使用的連線池,對效能損耗較小,而且被SpringBoot採用為預設連線池。ShardingSphere-Proxy的連線池可以支援使用者配置最大連線數、最大空閒時間以及快取相關的資訊等。除Hikari連線池外,ShardingSphere-Proxy也支援其它連線池的配置。

和現有SQL的語法相容性也是使用者衡量資料庫代理的關鍵因素,因為這涉及到是否更改應用程式碼。以MySQL為例,ShardingSphere支援大部分的MySQL語法,但也有少量不支援的語法,比如optimize表、資源組的管理、使用者的建立和GRANT許可權管理等。具體可以查閱ShardingSphere的最新文件

下面會分享我對ShardingSphereProxy連線Aurora的幾個維度的實驗測試:1)分庫分表 2)動態擴充套件 3)讀寫分離 4)多表join 5)故障恢復。

3.環境構建

3.1 Aurora叢集搭建

首先根據Aurora叢集建立指南建立三套Aurora MySQL叢集,機型為db.r5.2xlarge,每套叢集有一個寫節點一個讀節點。

3.2 ShardingSphere-Proxy搭建

在與Aurora相同的可用區下啟動一臺EC2節點,機型為r5.8xlarge. 然後在上面安裝ShardingSphere-Proxy。

3.2.1 下載安裝包

直接下載二進位制安裝包,進行解壓。下載最新版本5.1.0,它對DistSQL支援較好。

wget https://dlcdn.apache.org/shardingsphere/5.1.0/apache-shardingsphere-5.1.0-shardingsphere-proxy-bin.tar.gz
tar -xvf apache-shardingsphere-5.1.0-shardingsphere-proxy-bin.tar.gz

SharingSphereProxy自帶的庫裡包含對PostgreSQL的JDBC driver,但不包含MySQL的driver。因為建立的叢集是MySQL,需要將MySQL的JDBC driver拷貝到lib目錄。

wget https://repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.47/mysql-connector-java-5.1.47.jar
cp mysql-connector-java-5.1.47.jar apache-shardingsphere-5.1.0-shardingsphere-proxy-bin/lib/

3.2.2 配置Proxy的服務端

在ShardingSphere-Proxy的根目錄下,有個配置檔案目錄為conf,裡面有一個檔案是server.yaml,用來配置ShardingSphere-Proxy自己作為代理對外提供服務的資訊以及元資訊存放等。下面是一個配置示例,裡面配置了使用者許可權資訊,特定屬性資訊,以及元資訊以叢集模式存放在zookeeper裡。

rules:
  - !AUTHORITY
    users:  //訪問Proxy的使用者名稱和密碼資訊
      - root@%:root
      - sharding@:sharding
    provider:  //控制使用者對schema的登陸許可權
      type: ALL_PRIVILEGES_PERMITTED
  - !TRANSACTION  //事務型別配置,支援本地事務、XA兩階段事務、BASE柔性事務
    defaultType: XA
    providerType: Atomikos

props:  //特定屬性配置
  max-connections-size-per-query: 1
  proxy-hint-enabled: true //為強制路由使用,預設值為false

mode: //元資訊存放的配置,shardingsphereProxy支援三種模式:記憶體、單機和叢集
  type: Cluster
  repository:
    type: ZooKeeper //可以設定為zookeeper、etcd等
    props:
      namespace: shardingproxy
      server-lists: localhost:2181
      retryIntervalMilliseconds: 500
      timeToLiveSeconds: 60
      maxRetries: 3
      operationTimeoutMilliseconds: 500
  overwrite: false

3.3 啟動Proxy

直接在ShardingSphereProxy根目錄下的bin對應著啟動和停止指令碼。執行時的日誌在目錄logs下。啟動Proxy

bin/start.sh
bin/stop.sh

3.4 驗證連線

如無特殊配置,ShardingSphereProxy預設使用3307埠。使用3.2.2中配置的使用者名稱和密碼登入proxy。在EC2上執行mysql命令列工具進行連線,連線成功。注意這裡沒有任何資料庫,因為我們沒有使用YAML配置檔案預先配置資料來源。

[ec2-user@ip-111-22-3-123 bin]$ mysql -h 127.0.0.1 -uroot --port 3307 -proot
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.7.22-ShardingSphere-Proxy 5.1.0 

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)]> show databases;
Empty set (0.01 sec)

4.功能測試

4.1 DistSQL建立分片規則和資料分片測試

本節來驗證ShardingSphere的基本的分庫分表能力。ShardingSphereProxy支援兩種方式建立分片規則和讀寫分離規則,YAML和DistSQL。DistSQL擴充套件了SQL語法,可以支援線上建立資料來源、建立和更改建表規則,較為靈活,本文只介紹DistSQL的用例。

4.1.1 建立資料庫

連線到ShardingSphere-Proxy,去建立資料庫,作為邏輯的分散式資料庫。

MySQL [(none)]> create database distsql_sharding_db;
Query OK, 0 rows affected (0.90 sec)

在各個Aurora叢集上建立資料庫,作為資料庫源進行連線。其中,rshard1,rshard2,rshard3是我自己定義的連線Aurora資料庫的alias。

alias rshard1=’mysql -h $dbname -u$username -p$password’
[ec2-user@ ip-111-22-3-123 bin]$ rshard1 -e "create database dist_ds";
[ec2-user@ ip-111-22-3-123 bin]$ rshard2 -e "create database dist_ds;"
[ec2-user@ ip-111-22-3-123 bin]$ rshard3 -e "create database dist_ds;"

4.1.2 建立資料來源

在ShadingSphereProxy中執行下面DistSQL語句建立3個資料來源,分別指向3個不同Aurora叢集

MySQL [distsql_sharding_db]> add resource ds_0(url="jdbc:mysql://aurora-2-07-7-shard1.cluster-12345678.us-east-1.rds.amazonaws.com:3306/dist_ds?serverTimezone=UTC&useSSL=false",user=admin,password=12345678);
Query OK, 0 rows affected (0.03 sec)

MySQL [distsql_sharding_db]> add resource ds_1(url="jdbc:mysql://aurora-2-07-7-shard2.cluster-12345678.us-east-1.rds.amazonaws.com:3306/dist_ds?serverTimezone=UTC&useSSL=false",user=admin,password=12345678);
Query OK, 0 rows affected (0.06 sec)

MySQL [distsql_sharding_db]> add resource ds_2(url="jdbc:mysql://aurora-2-07-7-shard3.cluster-12345678.us-east-1.rds.amazonaws.com:3306/dist_ds?serverTimezone=UTC&useSSL=false",user=admin,password=12345678);
Query OK, 0 rows affected (0.05 sec)

4.1.3 建立分片規則

這裡指明t_order表的分片規則,注意分片規則的表名和後續要建立的表表名一致。具體規則為:對底層的3個資料來源(Aurora叢集)按照order_id對錶進行hash分片,分成6份。另外,對order_id採用值自動生成的策略,採用策略為snowflake演算法。ShardingSphere支援兩種分散式主鍵生成策略:UUID和雪花演算法SNOWFLAKE。使用雪花演算法生成的主鍵,二進位制表示形式包含4部分,從高位到低位分表為:1bit符號位、41bit時間戳位、10bit工作程式位以及12bit序列號位。在ShardingSphereProxy中執行下面DistSQL語句建立分片規則:

MySQL [distsql_sharding_db]> CREATE SHARDING TABLE RULE t_order(
→ RESOURCES(ds_0,ds_1, ds_2),
→ SHARDING_COLUMN=order_id,
→ TYPE(NAME=hash_mod,PROPERTIES("sharding-count"=6)),
→ KEY_GENERATE_STRATEGY(COLUMN=order_id,TYPE(NAME=snowflake))
→ );
Query OK, 0 rows affected (0.02 sec)

4.1.4 建表

建表語句和普通MySQL建表語句一致。在ShardingSphereProxy中執行下面語句建表:

MySQL [distsql_sharding_db]> CREATE TABLE `t_order` ( `order_id` bigint NOT NULL, `user_id` int NOT NULL, `status` varchar(45) DEFAULT NULL, PRIMARY KEY (`order_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
    -> ;
Query OK, 0 rows affected (0.22 sec)

在ShardingSphereProxy上檢視錶的狀態。

MySQL [distsql_sharding_db]> show tables;
+-------------------------------+------------+
| Tables_in_distsql_sharding_db | Table_type |
+-------------------------------+------------+
| t_order                       | BASE TABLE |
+-------------------------------+------------+
1 row in set (0.00 sec)

分別連線到3個Aurora叢集上檢視錶是否自動建立。可以看到每個底層資料庫叢集上都建立了兩張表,一共是6張表。而且表名是以“t_oder_”數字排序的

[ec2-user@ ip-111-22-3-123 bin]$ rshard1 -Ddist_ds -e "show tables;"
+-------------------+
| Tables_in_dist_ds |
+-------------------+
| t_order_0         |
| t_order_3         |
+-------------------+
[ec2-user@ ip-111-22-3-123 bin ]$ rshard2 -Ddist_ds -e "show tables;"
+-------------------+
| Tables_in_dist_ds |
+-------------------+
| t_order_1         |
| t_order_4         |
+-------------------+
[ec2-user@ ip-111-22-3-123 bin]$ rshard3 -Ddist_ds -e "show tables;"
+-------------------+
| Tables_in_dist_ds |
+-------------------+
| t_order_2         |
| t_order_5         |
+-------------------+

4.1.5 插入和查詢資料

在ShardingSphere-Proxy中插入並查詢資料,資料可以正常插入和查詢成功。在ShardingSphere-Proxy中執行:

MySQL [distsql_sharding_db]> insert into t_order(user_id, status) values (1, 'ok');
insert into t_order(user_id, status) values (2, 'abc');
Query OK, 1 row affected (0.01 sec)

MySQL [distsql_sharding_db]> insert into t_order(user_id, status) values (2, 'abc');
insert into t_order(user_id, status) values (3, 'abc');
Query OK, 1 row affected (0.00 sec)

MySQL [distsql_sharding_db]> insert into t_order(user_id, status) values (3, 'abc');
insert into t_order(user_id, status) values (4, 'abc');
Query OK, 1 row affected (0.01 sec)

MySQL [distsql_sharding_db]> insert into t_order(user_id, status) values (4, 'abc');
insert into t_order(user_id, status) values (5, 'abc');
Query OK, 1 row affected (0.00 sec)

MySQL [distsql_sharding_db]> insert into t_order(user_id, status) values (5, 'abc');
insert into t_order(user_id, status) values (6, 'abc');
Query OK, 1 row affected (0.01 sec)

MySQL [distsql_sharding_db]> insert into t_order(user_id, status) values (6, 'abc');
insert into t_order(user_id, status) values (7, 'abc');
Query OK, 1 row affected (0.00 sec)

MySQL [distsql_sharding_db]> insert into t_order(user_id, status) values (7, 'abc');
insert into t_order(user_id, status) values (8, 'abc');
Query OK, 1 row affected (0.01 sec)

MySQL [distsql_sharding_db]> insert into t_order(user_id, status) values (8, 'abc');
Query OK, 1 row affected (0.00 sec)

MySQL [distsql_sharding_db]> insert into t_order(user_id, status) values (9, 'abc');
Query OK, 1 row affected (0.00 sec)

MySQL [distsql_sharding_db]> select * from t_order;
+--------------------+---------+--------+
| order_id           | user_id | status |
+--------------------+---------+--------+
| 708700161915748353 |       2 | abc    |
| 708700161995440128 |       5 | abc    |
| 708700169725542400 |       9 | abc    |
| 708700161877999616 |       1 | ok     |
| 708700161936719872 |       3 | abc    |
| 708700162041577472 |       7 | abc    |
| 708700161970274305 |       4 | abc    |
| 708700162016411649 |       6 | abc    |
| 708700162058354689 |       8 | abc    |
+--------------------+---------+--------+
9 rows in set (0.01 sec)

去各個Aurora叢集中查詢子表插入的資料,可以看到在Proxy插入的9條記錄被打散到底層的6張表中。因為order_id為snowflake演算法生成而資料量比較小,這裡的資料並不均勻。

[ec2-user@ip-111-22-3-123 bin]$ rshard1 -Ddist_ds -e "select * from t_order_0;"
[ec2-user@ip-111-22-3-123 bin]$ rshard1 -Ddist_ds -e "select * from t_order_3;"
+--------------------+---------+--------+
| order_id           | user_id | status |
+--------------------+---------+--------+
| 708700161915748353 |       2 | abc    |
+--------------------+---------+--------+
[ec2-user@ip-111-22-3-123 bin]$ rshard2 -Ddist_ds -e "select * from t_order_1;"
[ec2-user@ip-111-22-3-123 bin]$ rshard2 -Ddist_ds -e "select * from t_order_4;"
+--------------------+---------+--------+
| order_id           | user_id | status |
+--------------------+---------+--------+
| 708700161995440128 |       5 | abc    |
| 708700169725542400 |       9 | abc    |
+--------------------+---------+--------+
[ec2-user@111-22-3-123 bin]$ rshard3 -Ddist_ds -e "select * from t_order_2;"
+--------------------+---------+--------+
| order_id           | user_id | status |
+--------------------+---------+--------+
| 708700161877999616 |       1 | ok     |
| 708700161936719872 |       3 | abc    |
| 708700162041577472 |       7 | abc    |
+--------------------+---------+--------+
[ec2-user@ip-111-22-3-123 bin]$ rshard3 -Ddist_ds -e "select * from t_order_5;"
+--------------------+---------+--------+
| order_id           | user_id | status |
+--------------------+---------+--------+
| 708700161970274305 |       4 | abc    |
| 708700162016411649 |       6 | abc    |
| 708700162058354689 |       8 | abc    |
+--------------------+---------+--------+</code></pre></div>

上述實驗驗證了ShardingSphere-Proxy具有建立邏輯庫、連線資料來源、建立分片規則、建立邏輯表時會自動在底層資料庫上建立子表、能夠執行查詢的分發以及聚合能力。

4.2 動態伸縮驗證(線上擴充套件分片)

本節來驗證ShardingSphere-Proxy是否具有動態更改表的分片規則的能力。

ShardingSphere-Proxy提供線上更改分片規則的能力,但是如果子表已經按照之前的規則建立成功,則不會有新的子表隨著分片數目的增多被建立出來,也不會有原來的子表隨著分片數目的減少而被刪除。所以需要手動在底層分片資料庫上建立表名和遷移資料。

將4.1節裡的表的分片數從6調高到9,修改分片規則本身能夠成功,但是後續查詢會出錯,因為沒有新的子表建立出來。在ShardingSphere-Proxy上執行下面DistSQL:

MySQL [distsql_sharding_db]> alter SHARDING TABLE RULE t_order(
    -> RESOURCES(ds_0,ds_1, ds_2),
    -> SHARDING_COLUMN=order_id,
    -> TYPE(NAME=hash_mod,PROPERTIES("sharding-count"=9)),
    -> KEY_GENERATE_STRATEGY(COLUMN=order_id,TYPE(NAME=snowflake))
    -> );
Query OK, 0 rows affected (0.01 sec)

MySQL [distsql_sharding_db]> select * from t_order;
ERROR 1146 (42S02): Table 'dist_ds.t_order_6' doesn't exist

如果此時在子叢集上分別建立好對應的子表,再在ShardingSphere-Proxy上查詢就不會再出錯。連線到3個Aurora叢集,手動建立子表

[ec2-user@ip-111-22-3-123 bin]$ rshard1 -Ddist_ds -e "create table t_order_6(order_id bigint not null, user_id int not null, status varchar(45) default null, primary key(order_id)) engine=innodb default charset=utf8mb4; "
[ec2-user@ ip-111-22-3-123 bin]$ rshard2 -Ddist_ds -e "create table t_order_7(order_id bigint not null, user_id int not null, status varchar(45) default null, primary key(order_id)) engine=innodb default charset=utf8mb4; "
[ec2-user@ip-111-22-3-123 bin]$ rshard3 -Ddist_ds -e "create table t_order_8(order_id bigint not null, user_id int not null, status varchar(45) default null, primary key(order_id)) engine=innodb default charset=utf8mb4; "

Proxy查詢整個邏輯表不再報錯。在ShardingSphere-Proxy上執行下面SQL:

MySQL [distsql_sharding_db]> select * from t_order;
+--------------------+---------+--------+
| order_id           | user_id | status |
+--------------------+---------+--------+
| 708700161915748353 |       2 | abc    |
| 708700161995440128 |       5 | abc    |
| 708700169725542400 |       9 | abc    |
| 708700161877999616 |       1 | ok     |
| 708700161936719872 |       3 | abc    |
| 708700162041577472 |       7 | abc    |
| 708700161970274305 |       4 | abc    |
| 708700162016411649 |       6 | abc    |
| 708700162058354689 |       8 | abc    |
+--------------------+---------+--------+
9 rows in set (0.01 sec)

如果有新的資料插入,會按照新的分片規則進行到子表的對映。在ShardingSphere-Proxy上檢視SQL語句的查詢計劃:

MySQL [distsql_sharding_db]> preview insert into t_order values(7, 100, 'new');
+------------------+---------------------------------------------+
| data_source_name | sql                                         |
+------------------+---------------------------------------------+
| ds_1             | insert into t_order_7 values(7, 100, 'new') |
+------------------+---------------------------------------------+
1 row in set (0.00 sec)

MySQL [distsql_sharding_db]> insert into t_order values(7, 100, 'new');
Query OK, 1 row affected (0.00 sec)

登入到Aurora子叢集上檢視子表,可以看到資料已經成功插入。

[ec2-user@ip-111-22-3-123 bin]$ rshard2 -Ddist_ds -e "select * from t_order_7;"
+----------+---------+--------+
| order_id | user_id | status |
+----------+---------+--------+
|        7 |     100 | new    |
+----------+---------+--------+

再來看下線上減少分片的情況。如果將分片數目調小,比如調到3,表裡的已有資料不會被遷移,查詢整張表時只能拿到部分資料。在ShardingSphere-Proxy上執行下面DistSQL和SQL語句:

MySQL [distsql_sharding_db]> alter SHARDING TABLE RULE t_order(
    ->     RESOURCES(ds_0,ds_1, ds_2),
    ->     SHARDING_COLUMN=order_id,
    ->     TYPE(NAME=hash_mod,PROPERTIES("sharding-count"=3)),
    ->     KEY_GENERATE_STRATEGY(COLUMN=order_id,TYPE(NAME=snowflake))
    ->     );
Query OK, 0 rows affected (0.02 sec)
MySQL [distsql_sharding_db]> select * from t_order;
+--------------------+---------+--------+
| order_id           | user_id | status |
+--------------------+---------+--------+
| 708700161877999616 |       1 | ok     |
| 708700161936719872 |       3 | abc    |
| 708700162041577472 |       7 | abc    |
+--------------------+---------+--------+
3 rows in set (0.00 sec)

經過上面驗證,我們的結論是ShardingSphereProxy的分片規則是可以線上更改的,但子表的建立和資料的重新分佈需要手動去完成。

4.3 繫結表和廣播表的測試

本節來驗證ShardingSphere-Proxy對於多表join的支援。儘管OLTP的資料庫中的操作通常較為簡單,但也有可能會涉及到多表join的情況。ShardingSphereProxy針對多表join的優化有支援繫結表和廣播表。如果兩張表是繫結表而且join時採用的是shard key,可以進行兩張表的join。廣播表通過把小表複製到各個節點,可以實現大表和小表的快速join。

4.3.1 繫結表

ShardingSphereProxy的繫結表可以通過DistSQL裡的CREATE SHARDING BINDING TABLE RULES 來繫結兩張表。這裡以4.1節中提到的t_order表和新建立的一張表t_order_item為例進行展開。

連線到ShardingSphere-Proxy上執行下面DistSQL和SQL語句。

MySQL [distsql_sharding_db]> CREATE SHARDING TABLE RULE t_order_item(
    ->  RESOURCES(ds_0,ds_1, ds_2),
    ->  SHARDING_COLUMN=order_id,
    -> TYPE(NAME=hash_mod,PROPERTIES("sharding-count"=6)));
Query OK, 0 rows affected (0.04 sec)

MySQL [distsql_sharding_db]> CREATE TABLE `t_order_item` ( `order_id` bigint NOT NULL, `item_id` int NOT NULL, `name` varchar(45) DEFAULT NULL, PRIMARY KEY (`item_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ;
Query OK, 0 rows affected (0.08 sec)

建立了binding rule以後,檢視join計劃,我們看到join下推到對應子表和子表的join上。在ShardingSphere-Proxy上執行:

MySQL [distsql_sharding_db]>  CREATE SHARDING BINDING TABLE RULES (t_order,t_order_item);
Query OK, 0 rows affected (0.04 sec)

MySQL [distsql_sharding_db]> preview select * from t_order, t_order_item where t_order.order_id=t_order_item.order_id;
+------------------+------------------------------------------------------------------------------------------+
| data_source_name | sql                                                                                      |
+------------------+------------------------------------------------------------------------------------------+
| ds_0             | select * from t_order_0, t_order_item_0 where t_order_0.order_id=t_order_item_0.order_id |
| ds_0             | select * from t_order_3, t_order_item_3 where t_order_3.order_id=t_order_item_3.order_id |
| ds_1             | select * from t_order_1, t_order_item_1 where t_order_1.order_id=t_order_item_1.order_id |
| ds_1             | select * from t_order_4, t_order_item_4 where t_order_4.order_id=t_order_item_4.order_id |
| ds_2             | select * from t_order_2, t_order_item_2 where t_order_2.order_id=t_order_item_2.order_id |
| ds_2             | select * from t_order_5, t_order_item_5 where t_order_5.order_id=t_order_item_5.order_id |
+------------------+------------------------------------------------------------------------------------------+
6 rows in set (0.01 sec)

4.3.2 廣播表

廣播表是指每張表在每個庫裡都有一個完整的備份,可以通過CREATE SHARDING BROADCAST TABLE RULES來指定。

MySQL [distsql_sharding_db]> CREATE SHARDING BROADCAST TABLE RULES (t_user);
Query OK, 0 rows affected (0.03 sec)

MySQL [distsql_sharding_db]> create table t_user (user_id int, name varchar(100));
Query OK, 0 rows affected (0.04 sec)

登入到各個shard Aurora叢集檢視建立的表。可以看到與分片表的子表名末尾有數字序號不同的是 ,廣播表對應的每個庫上的名字是相同的,就是邏輯表名本身。

[ec2-user@ip-111-22-3-123 bin]$ rshard1 -D dist_ds -e "show tables like '%user%';"
+----------------------------+
| Tables_in_dist_ds (%user%) |
+----------------------------+
| t_user                     |
+----------------------------+
[ec2-user@ip-111-22-3-123 bin]$ rshard2 -D dist_ds -e "show tables like '%user%';"
+----------------------------+
| Tables_in_dist_ds (%user%) |
+----------------------------+
| t_user                     |
+----------------------------+
[ec2-user@ip-111-22-3-123 bin]$ rshard3 -D dist_ds -e "show tables like '%user%';"
+----------------------------+
| Tables_in_dist_ds (%user%) |
+----------------------------+
| t_user                     |
+----------------------------+

在ShardingSphereProxy中執行廣播表和其它表的join,採用的是本地join的方式。

MySQL [distsql_sharding_db]> preview select * from t_order, t_user where t_order.user_id=t_user.user_id;
+------------------+------------------------------------------------------------------------+
| data_source_name | sql                                                                    |
+------------------+------------------------------------------------------------------------+
| ds_0             | select * from t_order_0, t_user where t_order_0.user_id=t_user.user_id |
| ds_0             | select * from t_order_3, t_user where t_order_3.user_id=t_user.user_id |
| ds_1             | select * from t_order_1, t_user where t_order_1.user_id=t_user.user_id |
| ds_1             | select * from t_order_4, t_user where t_order_4.user_id=t_user.user_id |
| ds_2             | select * from t_order_2, t_user where t_order_2.user_id=t_user.user_id |
| ds_2             | select * from t_order_5, t_user where t_order_5.user_id=t_user.user_id |
+------------------+--------

上面實驗驗證了ShardingSphere-Proxy是可以支援兩張繫結表的join,以及廣播表和分片表的join的。對於非繫結的兩張分片表的join,ShardingSphere-Proxy有一個Federation的功能是在支援的,但還不是很成熟,建議後續持續關注。

4.4 讀寫分離功能驗證

本節來驗證ShardingSphere-Proxy對於讀寫分離的支援。隨著業務增長,寫和讀的負載分別在不同的資料庫節點上能夠有效提供整個資料庫叢集的處理能力。Aurora通過讀/寫的endpoint可以滿足使用者寫和強一致性讀的需求,只讀的endpoint可以滿足使用者非強一致性讀的需求。Aurora的讀寫延遲在毫秒級別,比MySQL基於binlog的邏輯複製要低得多,所以有很多負載是可以直接打到只讀endpoint的。

ShardingSphereProxy提供的讀寫分離的特性可以進一步可以封裝Aurora的讀/寫端點和只讀端點。使用者可以直接連線到Proxy的端點,即可進行自動的讀寫分離。ShardingSphereProxy對特殊情況的處理邏輯是:1)同一執行緒且同一資料庫連線內,如果有寫入操作,則後續的讀操作均從主庫讀取 2)可以通過Hint的機制強制把讀請求發到寫節點(主庫)。下面會以Aurora3個叢集中的第一個叢集來驗證ShardingSphere-Proxy讀寫分離的能力。

4.4.1 檢視Aurora叢集讀/寫端點和只讀端點

Aurora叢集有兩個端點,寫的端點和讀的端點。

4.4.2 在Aurora叢集中建立資料庫

連線到Aurora叢集中執行:

[ec2-user@ip-111-22-3-123 ~]$ rdbw -e "create database wr_ds;"

4.4.3 資料來源配置

在ShardingSphere-Proxy上建立資料來源,寫的資料來源指向Aurora的讀寫endpoint,讀的資料來源指向Aurora的只讀endpoint。注意:對域名的情況,ShardingSphereProxy只支援通過url的方式建立資料來源,尚未支援通過HOST、Port的方式。連線到ShardingSphere-Proxy上建立邏輯資料庫distsql_rwsplit_db並在改資料庫中新增資料來源:

MySQL [(none)]> create database distsql_rwsplit_db;
Query OK, 0 rows affected (0.02 sec)
MySQL [(none)]> use distsql_rwsplit_db;
Database changed
MySQL [distsql_rwsplit_db]> add resource write_ds(url="jdbc:mysql://aurora-2-07-7-shard1.cluster-12345678.us-east-1.rds.amazonaws.com:3306/wr_ds?serverTimezone=UTC&useSSL=false",user=admin,password=12345678), read_ds(url="jdbc:mysql://aurora-2-07-7-shard1.cluster-ro-12345678.us-east-1.rds.amazonaws.com:3306/wr_ds?serverTimezone=UTC&useSSL=false",user=admin,password=12345678);
Query OK, 0 rows affected (0.08 sec)

4.4.4 讀寫分離規則配置

建立讀寫分離規則,寫請求發到寫的資料來源,讀請求發到讀的資料來源。與分庫分表規則要求RULE後面必須是表名不同的是,這裡的RULE後面跟的是資料來源的名字,適用於在這個資料庫裡建立的所有的表。在ShardingSphere-Proxy上執行下面DistSQL語句:

MySQL [distsql_ rwsplit_db]> CREATE READWRITE_SPLITTING RULE wr_ds (
    -> WRITE_RESOURCE=write_ds,
    -> READ_RESOURCES(read_ds),
    -> TYPE(NAME=random)
    -> );
Query OK, 0 rows affected (0.36 sec)

4.4.5 建表

建立一張普通表,建表語句和MySQL建表語句一致。在ShardingSphere-Proxy上執行下面SQL語句:

MySQL [distsql_ rwsplit_db]> create table wr_table (a int, b int, c varchar(20));
Query OK, 0 rows affected (0.17 sec)

4.4.6 檢查讀寫分離是否實現

在ShardingSphere-Proxy上執行下面語句檢視查詢計劃,檢視語句是傳送到底層哪個資料來源。可以看到:寫請求傳送到寫節點,讀請求會傳送到讀寫點。

MySQL [distsql_rwsplit_db]> preview insert into wr_table values(1,1,'ab');
+------------------+---------------------------------------+
| data_source_name | sql                                   |
+------------------+---------------------------------------+
| write_ds         | insert into wr_table values(1,1,'ab') |
+------------------+---------------------------------------+
1 row in set (0.10 sec)
MySQL [distsql_rwsplit_db]> preview select * from wr_table;
+------------------+------------------------+
| data_source_name | sql                    |
+------------------+------------------------+
| read_ds          | select * from wr_table |
+------------------+------------------------+
1 row in set (0.02 sec)

執行一個指令碼來多次操作,再去Aurora叢集指標監控中去驗證。該指令碼是一個迴圈,執行1000次,每次會插入一條記錄,並查詢表的記錄總條數。

[ec2-user@ip-111-22-3-123 shardingproxy]$ cat testReadWrite.sh 
#!/bin/bash
n=1
while [ $n -le 1000 ]
do
    mysql -h 127.0.0.1 -uroot --port 3307 -proot -Ddistsql_rwsplit_db -e "insert into wr_table values($n,$n,'ok');"
    mysql -h 127.0.0.1 -uroot --port 3307 -proot -Ddistsql_rwsplit_db -e "select count(*) from wr_table;"
    let n++
done  

檢視Aurora叢集的寫節點和讀節點的讀寫延遲,可以看到寫延遲只在寫節點上發生,讀延遲只在讀節點上發生。說明讀寫分離規則生效。

儘管Aurora的寫和讀節點之間的複製延遲很低在毫秒級別,但某些應用還是會有強一致性的需求,即要求寫後立刻可以讀。這時候,可以採用強制將讀請求傳送到寫節點的方式。ShardingSphereProxy通過hint的方式來支援。首先需要在前面提到的conf/server.yaml裡新增一個屬性proxy-hint-enabled: true。然後在連線中顯式設定 readwrite_splitting hint source 值為write來開啟強制路由到寫節點通過設定值為auto或者clear hint可以採用預設的規則。readwrite_splitting hint source可以在session級別生效。

在ShardingSphere-Proxy上依次執行下面語句。可以看到預設的讀請求是傳送到讀節點,將readwrite_splitting hint source設定為write以後,會傳送到寫節點,再設成auto,可以發回至讀寫點。

MySQL [distsql_rwsplit_db]> preview select count(*) from wr_table;
+------------------+-------------------------------+
| data_source_name | sql                           |
+------------------+-------------------------------+
| read_ds          | select count(*) from wr_table |
+------------------+-------------------------------+
1 row in set (0.01 sec)

MySQL [distsql_rwsplit_db]> set readwrite_splitting hint source = write;
Query OK, 0 rows affected (0.00 sec)

MySQL [distsql_rwsplit_db]> preview select count(*) from wr_table;
+------------------+-------------------------------+
| data_source_name | sql                           |
+------------------+-------------------------------+
| write_ds         | select count(*) from wr_table |
+------------------+-------------------------------+
1 row in set (0.01 sec)
MySQL [distsql_rwsplit_db]> set readwrite_splitting hint source = auto;
Query OK, 0 rows affected (0.00 sec)

MySQL [distsql_rwsplit_db]> preview select count(*) from wr_table;
+------------------+-------------------------------+
| data_source_name | sql                           |
+------------------+-------------------------------+
| read_ds          | select count(*) from wr_table |
+------------------+-------------------------------+
1 row in set (0.00 sec)

另外不使用YAML檔案更改的方式是直接在DistSQL裡先後設定兩個變數proxy_hint_enabled和readwrite_splitting hint source。

MySQL [distsql_rwsplit_db]> set variable proxy_hint_enabled=true;
Query OK, 0 rows affected (0.01 sec)
MySQL [distsql_rwsplit_db]> set readwrite_splitting hint source = write;
Query OK, 0 rows affected (0.01 sec)
MySQL [distsql_rwsplit_db]> preview select * from wr_table;
+------------------+------------------------+
| data_source_name | sql                    |
+------------------+------------------------+
| write_ds         | select * from wr_table |
+------------------+------------------------+
1 row in set (0.00 sec)

以上實驗驗證了ShardingSphere-Proxy有良好的讀寫分離的能力。它驗證了底下連線單個Aurora叢集進行讀寫分離的場景。如果既需要分庫分表又需要讀寫分離,ShardingSphere-Proxy也是支援的。比如先分到3個Aurora叢集,然後每個叢集需要提供讀寫分離的能力,我們可以直接將讀寫分離規則後面定義的資料來源名稱(4.4.4裡的wr_ds)放在分庫分表規則對每張表指定的資料來源裡(4.1.3裡的ds_0,ds_1,ds_2)。

4.5 故障恢復驗證

本節來驗證ShardingSphere-Proxy對於Aurora叢集故障切換的感知能力。在Aurora叢集發生主備切換時,如果Proxy能夠動態檢測到主備切換並連線到新的主資料庫是比較理想的。本節實驗仍然是驗證第一個Aurora叢集。

測試指令碼如下,它會持續連線到寫節點併傳送update請求,每次請求間隔1秒鐘。

[ec2-user@ip-111-22-3-123 shardingproxy]$ cat testFailover.sh 
#!/bin/bash
while true
do
    mysql -h 127.0.0.1 -uroot --port 3307 -proot -Ddistsql_rwsplit_db -e "update wr_table set c='failover' where a = 1;"
    now=$(date +"%T")
    echo "update done: $now"
    sleep 1
done   

執行指令碼,然後在Aurora叢集的寫節點上點選Action->Failover。會啟動Aurora寫節點和讀節點的自動切換。在切換過程中,整個叢集的讀/寫endpoint和只讀endpoint維持不變,只是底層對映的節點發生變化。

通過觀測Aurora的Event(事件),可以看到整個故障切換在30秒左右完成。

遺憾的是,應用程式直接連線ShardingSphereProxy也就是前面的執行指令碼不能自動監測到底層的IP變化。執行指令碼一直拋錯:

ERROR 1290 (HY000) at line 1: The MySQL server is running with the --read-only option so it cannot execute this statement
update done: 15:04:04
ERROR 1290 (HY000) at line 1: The MySQL server is running with the --read-only option so it cannot execute this statement
update done: 15:04:05
ERROR 1290 (HY000) at line 1: The MySQL server is running with the --read-only option so it cannot execute this statement
update done: 15:04:06

直接在MySQL命令列連線到Proxy也是會有一樣的錯誤。

MySQL [distsql_rwsplit_db]> update wr_table set c="failover" where a =2;
ERROR 1290 (HY000): The MySQL server is running with the --read-only option so it cannot execute this statement

分析原因在於Aurora發生故障切換的時候,讀寫endpoint和IP的對映會發生變化,而ShardingSphere的連線池在連線Aurora的時候,沒有更新到新的IP上。我們可以採用下面的workaround可以使ShardingSphereProxy指向新的寫節點,即重新建立資料來源。儘管資料來源本身定義沒有發生變化,但是通過重建資料來源alter resource的操作, ShardingSphereProxy會重新拿取一遍endpoint到IP的對映,所以能夠成功執行。

MySQL [distsql_rwsplit_db]> alter resource write_ds(url="jdbc:mysql://aurora-2-07-7-shard1.cluster-12345678.us-east-1.rds.amazonaws.com:3306/wr_ds?serverTimezone=UTC&useSSL=false",user=admin,password=12345678), read_ds(url="jdbc:mysql://aurora-2-07-7-shard1.cluster-ro-12345678.us-east-1.rds.amazonaws.com:3306/wr_ds?serverTimezone=UTC&useSSL=false",user=admin,password=12345678);
Query OK, 0 rows affected (0.05 sec)

MySQL [distsql_rwsplit_db]> update wr_table set c="failover" where a =2;
Query OK, 1 row affected (0.01 sec)

每次Aurora故障切換時,我們可以檢測故障切換的event,或者是在應用收到read-only報錯時顯式呼叫上面語句。為了降低對應用的影響,我們可以採用Lambda的方式將failover重置資料來源的操作自動化。因為Aurora的failover的事件是可以被監測到的,我們可以寫一個Lambda函式,在監測到failover成功以後,顯示呼叫更改resource的操作。

總體的思路是: RDS通過Event Subscription將Event的通知資訊傳遞給SNS topic,再由SNS topic傳遞給Lambda方法,然後在Lambda方法裡顯式連線ShardingProxy呼叫alter resource 的DistSQL語句。

具體步驟如下:

4.5.1 建立SNS

按照SNS建立指南建立SNS。開啟SNS的dashboard,點選建立SNS topic,選擇Standard標準型別。其它選擇預設或者根據需要調整。

4.5.2 對要進行的Aurora叢集建立Event Subscription

在RDS的Event Subscriptions上,點選“Create Event Subscription”,在彈出的選項卡中選擇Target為上一步驟建立的SNS,Source type選擇為Cluster,cluster裡面選中我們需要關注的Aurora的叢集,事件選擇Failover事件。

4.5.3 建立Lamdba方法

因為Lambda要呼叫VPC裡的EC2上部署的ShardingProxy,應該給它繫結一個專門的Role,這個Role有許可權在VPC裡執行Lambda方法: AWSLambdaVPCAccessExecutionRole 按照IAM Role建立文件建立Role和Policy,使failoverlambda的role有AWSLambdaVPCAccessExecutionRole的許可權。

接下來按照Lambda文件建立Lambda方法

建立好Lambda方法以後,點選Trigger,指明為SNS,並指明在4.5.1裡建立的SNS topic。

4.5.4 編寫Lambda方法

import os
import json
import pymysql

# connect to ShardingProxy to reset the data source    
def resetDataSource():
    db = pymysql.connect(host='111.22.3.123', user='root', password='root', port=3307, database='distsql_rwsplit_db')
    
    cur = db.cursor()
    SQL = "alter resource write_ds(url=\"jdbc:mysql://aurora-2-07-7-shard1.cluster-12345678.us-east-1.rds.amazonaws.com:3306/wr_ds?serverTimezone=UTC&useSSL=false\",user=admin,password=12345678), read_ds(url=\"jdbc:mysql://aurora-2-07-7-shard1.cluster-ro-12345678.us-east-1.rds.amazonaws.com:3306/wr_ds?serverTimezone=UTC&useSSL=false\",user=admin,password=12345678);"
    print (SQL)
    cur.execute(SQL)
    result = cur.fetchall()
    for x in result:
        print(x)
    db.close()

def lambda_handler(event, context):
    wholeMessage = event['Records'][0]['Sns']['Message']
    print ("whole message" + wholeMessage)
    
    wholeMessageInJson = json.loads(wholeMessage)
    eventMessage = wholeMessageInJson['Event Message']
    print ("event message: " + eventMessage)
    
    isFailover = eventMessage.startswith('Completed failover to DB instance')
    if isFailover == True:
        print ("Failover completed! " + eventMessage)
        resetDataSource()

    return {
        'statusCode': 200,
        'body': Lambda Invocation Successful!'
    }

Lambda方法是Python語言書寫的,在訪問ShardingSphereProxy時以MySQL方式訪問,所以需要引入pymysql的lib庫。具體方法為:

1)在Linux上安裝pymysql,以Amazon-Linux虛擬機器為例,會預設安裝到目錄./home/ec2-user/.local/lib/python3.7/site-packages/pymysql下

2)將pymysql目錄拷貝到臨時目錄/tmp

3)寫Lambda方法,儲存到lambda_function.py檔案中

4)打包 zip -r lambda.zip pymysql lambda_function.py

5) 在控制檯通過S3 或者本地上傳。

4.5.5 設定 ShardingSphereProxy所在EC2的security group

因為Lambda要在VPC裡訪問ShardingSphereProxy,而ShardingSphereProxy以3307埠執行,應該配置相應secuity group,開啟3307埠給同一個VPC內部的訪問。依據安全組配置文件配置成的security group如下:

4.5.6 驗證failover

重複本小節開始的操作,執行testFailover.sh,然後手動在RDS console頁failover Aurora節點,會發現testFailover.sh持續穩定輸出, 不會再出現read-only的錯誤。

update done: 13:44:44
…
update done: 13:44:47
update done: 13:45:00
update done: 13:45:01
…
update done: 13:45:17

去cloudwatch裡檢視Lambda function的日誌,會發現Lambda被成功呼叫。

以上實驗驗證了ShardingSphere-Proxy對於Aurora叢集故障切換的感知能力。儘管ShardingSphere-Proxy自己沒有提供良好的匹配性,通過監測Aurora叢集事件觸發Lamdba方法來顯式重置ShardingSphere-Proxy資料來源的方式,我們可以實現ShardingSphere-Proxy與Aurora結合的故障切換能力。

5.結語

本篇文章通過資料庫中介軟體ShardingSphere-Proxy擴充了Aurora的分庫分表能力和讀寫分離的能力。

ShardingSphere-Proxy內建連線池,對MySQL語法支援較強,在分庫分表和讀寫分離上表現出色。它對多表join上,可以支援分片規則相同的表的join,以及小表和大表的join,基本能滿足OLTP場景的需求。在動態分片上,ShardingSphere-Proxy提供線上更改分片規則的能力,但需要使用者在底層Aurora叢集手動操作子表建立及資料遷移,需要一定工作量。故障切換維度,ShardingSphere-Proxy與Aurora的融合不是很好,但是可以通過本文提供的Aurora故障切換Event呼叫Lambda方法來顯式重置資料來源的方式,實現ShardingSphere-Proxy對Aurora叢集故障切換對感知。

總體而言,ShardingSphere-Proxy這個中介軟體產品還是能與Aurora叢集進行一個良好匹配,進一步提升Aurora叢集的讀寫能力的。它有良好的文件,也是比較受關注的開源產品,建議讀者在考慮Aurora分庫分表實現時,評估下這個產品。後續我們會繼續推出對其他中介軟體以及JDBC方面的擴充和研究系列部落格。

本篇作者

馬麗麗
亞馬遜雲科技資料庫解決方案架構師,十餘年資料庫行業經驗,先後涉獵NoSQL 資料庫Hadoop/Hive、企業級資料庫DB2、分散式數倉Greenplum/Apache HAWQ以及亞馬遜雲原生資料庫的開發和研究。

點選連結,瞭解更多內容:

相關文章