分庫分表之第四篇

4.水平分表

水平分表是在同一個數據庫內,把同一個表的數據按照一定的規則拆到多個表中。前面以及介紹過來,這裏不再重複介紹。

5.水平分庫

水平分庫是把同一個表的數據按一定規則拆到不同的數據庫中,每個庫可以放在不同的服務器上。
(1)將原來的order_db庫拆分爲order_db_1、order_db_2
在這裏插入圖片描述
(2)分片規則修改
由於數據庫拆分來兩個,這裏需要配置兩個數據源。
分庫需要配置分庫的策略,和分表策略的意義類似,通過分庫策略實現數據操作針對分庫的數據庫進行操作。
在這裏插入圖片描述
分庫策略定義方式如下 :

#分庫策略,如何將一個邏輯表映射到多個數據源 spring.shardingsphere.sharding.tables.<邏輯表名稱>.database‐strategy.<分片策略>.<分片策略屬性名>= # 分片策略屬性值
#分表策略,如何將一個邏輯表映射爲多個實際表 spring.shardingsphere.sharding.tables.<邏輯表名稱>.table‐strategy.<分片策略>.<分片策略屬性名>= #分 片策略屬性值

Sharding-JDBC支持以下幾種分片策略 :
不管分庫還是分表,策略基本一樣。

  • standard : 標準分片策略,對應StandardShardingStrategy。提供對SQL語句中的=,IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持單分片鍵,提供PreciseShardingAlgorithm和RangeShardingAlgorithm兩個分片算法。PreciseShardingAlgorithm是必選的,用於處理=和IN的分片。RangeShardingAlgorithm是可選的,用於處理BETWEEN AND分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND將按照全庫路由處理。
  • complex :複合分片策略,對應ComplexShardingStrategy。複合分片策略。提供對SQL語句中=,IN和BWTWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片鍵,由於多分片鍵之間的關係複雜,因此並未進行過多的封裝,而是直接將分片鍵值組合以及分片操作符透傳至分片算法,完全由應用開發者實現,提供最大的靈活度。
  • inline :行表達式分片策略,對應InlineShardingStrategy。使用Groovy的表達式,提供對SQL語句中的=和IN的分片操作支持,只支持單分片鍵。對於簡單的分片算法,可以通過簡單的配置使用,從而避免繁瑣的Java代碼開發,如 :t_user_$->{u_id % 8} 表示t_user表根據u_id模8,而分成8張表,表名稱爲t_user_0到t_user_7。
  • hint :Hint分片策略,對應HintShardingStrategy。通過Hint而非SQL解析的方式分片的策略。對於分片字段非SQL決定,而由其他外置條件決定的場景,可使用SQL Hint靈活的注入分片字段。例如 :內部系統,按照員工登錄主鍵分庫,而數據庫中並無此字段。SQL Hint支持通過Java API和SQL註釋(待實現)兩種方式使用。
  • none :不分片策略,對應NoneShardingStrategy。不分片的策略。
    目前例子中都使用inline分片策略,若對其他分片策略細節若感興趣,請查閱官方文檔 :
    https://shardingsphere.apache.org
    (3)插入測試
    修改testInsertOrder方法,插入數據中包含不同的user_id

@Test
public void testInsertOrder(){ 
	for (int i = 0 ; i<10; i++){
		orderDao.insertOrder(new BigDecimal((i+1)*5),1L,"WAIT_PAY"); 
	}
	for (int i = 0 ; i<10; i++){
		orderDao.insertOrder(new BigDecimal((i+1)*10),2L,"WAIT_PAY");
	} 
}

執行testInsertOrder :
在這裏插入圖片描述
通過日誌可以看出,根據user_id的奇偶不同,數據分別落在了不同數據源,達到目標。
(4)查詢測試
調用快速入門的查詢接口進行測試 :

List<Map> selectOrderbyIds(@Param("orderIds")List<Long> orderIds);

通過日誌發現,sharding-jdbc將sql路由到m1和m2 :
在這裏插入圖片描述
問題分析 :
由於查詢語句中沒有使用分片鍵user_id,所以sharding-jdbc將廣播路由到每個數據結點。
下邊我們在sql中添加分片鍵進行查詢。
在OrderDao中定義接口 :

@Select({"<script>", " select",
" * ",
" from t_order t ",
"where t.order_id in",
"<foreach collection='orderIds' item='id' open='(' separator=',' close=')'>", "#{id}",
"</foreach>",
" and t.user_id = #{userId} ",
"</script>"
})
List<Map> selectOrderbyUserAndIds(@Param("userId") Integer userId,@Param("orderIds")List<Long> orderIds);

編寫測試方法 :

 @Test
public void testSelectOrderbyUserAndIds(){ 
	List<Long> orderIds = new ArrayList<>();
	orderIds.add(373422416644276224L); 
	orderIds.add(373422415830581248L); 
	//查詢條件中包括分庫的鍵user_id
	int user_id = 1;
	List<Map> orders = orderDao.selectOrderbyUserAndIds(user_id,orderIds);
	JSONArray jsonOrders = new JSONArray(orders); 
	System.out.println(jsonOrders);
}

執行testSelectOrderByUserAndIds :
在這裏插入圖片描述
查詢條件user_id爲1,根據分片策略m$ -> {user_id % 2 + 1}計算得出m2,此sharding-jdbc將sql路由到m2,見上圖日誌。

6.垂直分庫

垂直分庫是指按照業務將表進行分類,分佈到不同的數據庫上面,每個庫可以放在不同的服務器上,它的核心理念是專庫專用。接下來看一下如何使用Sharding-JDBC實現垂直分庫。
(1)創建數據庫
創建數據庫user_db

CREATE DATABASE `user_db` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';

在user_db中創建t_user表

DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` (
`user_id` bigint(20) NOT NULL COMMENT '用戶id',
`fullname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用戶姓名', `user_type` char(1) DEFAULT NULL COMMENT '用戶類型',
PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

(2)在Sharding-JDBC規則中修改

# 新增m0數據源,對應user_db
spring.shardingsphere.datasource.names = m0,m1,m2
...
spring.shardingsphere.datasource.m0.type = com.alibaba.druid.pool.DruidDataSource
     spring.shardingsphere.datasource.m0.driver‐class‐name = com.mysql.jdbc.Driver
 spring.shardingsphere.datasource.m0.url = jdbc:mysql://localhost:3306/user_db?useUnicode=true spring.shardingsphere.datasource.m0.username = root spring.shardingsphere.datasource.m0.password = root
....
# t_user分表策略,固定分配至m0的t_user真實表 spring.shardingsphere.sharding.tables.t_user.actual‐data‐nodes = m$‐>{0}.t_user spring.shardingsphere.sharding.tables.t_user.table‐strategy.inline.sharding‐column = user_id spring.shardingsphere.sharding.tables.t_user.table‐strategy.inline.algorithm‐expression = t_user

(3)數據操作
新增UserDao :

@Mapper
@Component
public interface UserDao {
	/**
	* 新增用戶
	* @param userId 用戶id
	* @param fullname 用戶姓名 * @return
	*/
	@Insert("insert into t_user(user_id, fullname) value(#{userId},#{fullname})") int insertUser(@Param("userId")Long userId,@Param("fullname")String fullname);
	/**
	* 根據id列表查詢多個用戶
	* @param userIds 用戶id列表 * @return
	*/
	@Select({"<script>", " select",
	" * ",
	" from t_user t ",
	" where t.user_id in",
	"<foreach collection='userIds' item='id' open='(' separator=',' close=')'>", "#{id}",
	"</foreach>",
	"</script>"
	})
	List<Map> selectUserbyIds(@Param("userIds")List<Long> userIds);
}

(4)測試
新增單元測試方法 :

 @Test
  public void testInsertUser(){ 
	  for (int i = 0 ; i<10; i++){
	Long id = i + 1L;
	userDao.insertUser(id,"姓名"+ id ); 
		}
}
@Test
public void testSelectUserbyIds(){
	List<Long> userIds = new ArrayList<>(); userIds.add(1L);
	userIds.add(2L);
	List<Map> users = userDao.selectUserbyIds(userIds); System.out.println(users);
}

執行testInsertUser:
在這裏插入圖片描述
通過日誌可以看出t_user表的數據被落在了m0數據源,達到目標。 執行testSelectUserbyIds:
在這裏插入圖片描述
通過日誌可以看出t_user表的查詢操作被落在了m0數據源,達到目標。

7.公共表

公共表屬於系統中數據量較小,變動少,而且屬於高頻聯合查詢的依賴表。參數表、數據字典表等屬於此類型。可以將這類表在每個數據庫都保存一份,所有更新操作都同時發送到所有分庫執行。接下來看一下如何使用Sharding-JDBC實現公共表。
(1)創建數據庫
分別在user_db、order_db_1、order_db_2中創建t_dict表 :

 CREATE TABLE `t_dict` (
`dict_id` bigint(20) NOT NULL COMMENT '字典id',
`type` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典類型', `code` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典編碼', `value` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典值', PRIMARY KEY (`dict_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

(2)在Sharding-JDBC規則中修改

# 指定t_dict爲公共表
spring.shardingsphere.sharding.broadcast‐tables=t_dict

(3)數據操作
新增DictDao :

@Mapper
@Component
public interface DictDao {
	/**
	* 新增字典
	* @param type 字典類型 * @param code 字典編碼 * @param value 字典值 * @return
	*/
	@Insert("insert into t_dict(dict_id,type,code,value) value(#{dictId},#{type},#{code},# {value})")
	int insertDict(@Param("dictId") Long dictId,@Param("type") String type, @Param("code")String code, @Param("value")String value);
	/**
	* 刪除字典
	* @param dictId 字典id * @return
	*/
	@Delete("delete from t_dict where dict_id = #{dictId}") 
	int deleteDict(@Param("dictId") Long dictId);
}

(4)字典操作測試
新增單元測試方法 :

@Test
public void testInsertDict(){
	dictDao.insertDict(1L,"user_type","0","管理員");
	dictDao.insertDict(2L,"user_type","1","操作員"); 
}
@Test
public void testDeleteDict(){
	dictDao.deleteDict(1L);
	dictDao.deleteDict(2L); 
}

執行testInsertDict :
在這裏插入圖片描述
通過日誌可以看出,對t_dict的表的操作被廣播至所有數據源。
測試刪除字典,觀察是否把所有數據源中該公共表的記錄刪除。
(5)字典關聯查詢測試
字典表已在各各分庫存在,各業務表即可和字典表關聯查詢。
定義用戶關聯查詢dao :
在UserDao中定義 :

/**
* 根據id列表查詢多個用戶,關聯查詢字典表 * @param userIds 用戶id列表
* @return
*/
@Select({"<script>", " select",
" * ",
" from t_user t ,t_dict b",
" where t.user_type = b.code and t.user_id in",
"<foreach collection='userIds' item='id' open='(' separator=',' close=')'>", "#{id}",
"</foreach>",
"</script>"
})
List<Map> selectUserInfobyIds(@Param("userIds")List<Long> userIds);

定義測試方法 :

 @Test
public void testSelectUserInfobyIds(){ 
	List<Long> userIds = new ArrayList<>(); 
	userIds.add(1L);
	userIds.add(2L);
	List<Map> users = userDao.selectUserInfobyIds(userIds); 
	JSONArray jsonUsers = new JSONArray(users); 
	System.out.println(jsonUsers);
}

執行測試方法,查看日誌,成功關聯查詢字典表 :
在這裏插入圖片描述

8.讀寫分離

8.1 理解讀寫分離

面對日益增加的系統訪問量,數據庫的吞吐量面臨着巨大瓶頸。對於同一時刻有大量併發讀操作和較少寫操作類型的應用系統來說,將數據庫拆分爲主庫和從庫,主庫負責處理事務性的增刪改操作,從庫負責處理查詢操作,能夠有效的避免由數據更新導致的行鎖,使得整個系統的查詢性能得到極大的改善。
在這裏插入圖片描述
通過一主多從的配置方式,可以將查詢請求均勻的分散到多個數據副本,能夠進行一步的提升系統的處理能力。使用多主多從的方式,不但能夠提升系統的吞吐量,還能夠提升系統的可用性,可以達到在任何一個數據庫宕機,甚至磁盤物理損壞的情況下仍然不影響系統的正常運行。
在這裏插入圖片描述
讀寫分離的數據節點中的數據內容是一致的,而水平分片的每個數據節點的數據內容卻並不相同。將水平分片和讀寫分離聯合使用,能夠更加有效的提升系統的性能。
Sharding-JDBC讀寫分離則是根據SQL語義的分析,將讀操作和寫操作分別路由至主庫與從庫。它提供透明化讀寫分離,讓使用方儘量像使用一個數據庫一個使用主從數據庫集羣。
在這裏插入圖片描述
Sharding-JDBC提供一主多從的讀寫分離配置,可獨立使用,也可配合分庫分表使用,同一線程且同一數據庫連接內,如有寫操作,以後的讀操作均從主庫讀取,用於保證數據一致性。Sharding-JDBC不提供主從數據庫的數據同步功能,需要採用其他機制支持。
在這裏插入圖片描述
接下來,對上面例子中user_db進行讀寫分離實現。爲了實現Sharding-JDBC的讀寫分離,首先,要進行mysql的主從同步配置。

8.2.mysql主從同步(windows)

一,新增mysql實例
複製原有mysql如 :D:\mysql-5.7.25(作爲主庫)-> D:\mysql-5.7.25-s1(作爲從庫),並修改以下從庫的my.ini:

[mysqld]
#設置3307端口
port = 3307
# 設置mysql的安裝目錄 basedir=D:\mysql‐5.7.25‐s1
# 設置mysql數據庫的數據的存放目錄 datadir=D:\mysql‐5.7.25‐s1\data

然後將從庫安裝爲windows服務,注意配置文件位置 :

D:\mysql‐5.7.25‐s1\bin>mysqld install mysqls1 ‐‐defaults‐file="D:\mysql‐5.7.25‐s1\my.ini"

由於從庫是從主庫複製過來的,因此裏面的數據完全一致,可使用原來的賬號、密碼登錄。
二、修改主、從庫的配置文件(my.ini),新增內容如下 :
主庫 :

[mysqld]
#開啓日誌
log‐bin = mysql‐bin #設置服務id,主從不能一致
server‐id = 1
#設置需要同步的數據庫 binlog‐do‐db=user_db
#屏蔽系統庫同步 
binlog‐ignore‐db=mysql 
binlog‐ignore‐db=information_schema 
binlog‐ignore‐db=performance_schema

從庫 :

[mysqld]
#開啓日誌
log‐bin = mysql‐bin
#設置服務id,主從不能一致
server‐id = 2
#設置需要同步的數據庫 
replicate_wild_do_table=user_db.%
#屏蔽系統庫同步 
replicate_wild_ignore_table=mysql.% replicate_wild_ignore_table=information_schema.% replicate_wild_ignore_table=performance_schema.%

重啓主庫和從庫 :

net start [主庫服務名]
net start [從庫服務名mysqls1]

請注意,主從MySQL下的數據(data)目錄下有個文件auto.cnf,文件中定義了uuid,要保證主從數據庫實例的uuid不一樣,建議直接刪除掉,重啓服務後將會重新生成。
三、授權主從複製專用賬號

#切換至主庫bin目錄,登錄主庫
mysql ‐h localhost ‐uroot ‐p
#授權主備複製專用賬號
GRANT REPLICATION SLAVE ON *.* TO 'db_sync'@'%' IDENTIFIED BY 'db_sync'; 
#刷新權限
FLUSH PRIVILEGES;
#確認位點 記錄下文件名以及位點 
show master status;

在這裏插入圖片描述
四、設置從庫向主庫同步數據、並檢查鏈路

#切換至從庫bin目錄,登錄從庫
mysql ‐h localhost ‐P3307 ‐uroot ‐p #先停止同步
STOP SLAVE;
#修改從庫指向到主庫,使用上一步記錄的文件名以及位點 CHANGE MASTER TO
master_host = 'localhost', master_user = 'db_sync', master_password = 'db_sync', master_log_file = 'mysql‐bin.000002', master_log_pos = 154;
#啓動同步
START SLAVE; #查看從庫狀態Slave_IO_Runing和Slave_SQL_Runing都爲Yes說明同步成功,如果不爲Yes,請檢查error_log,然後 排查相關異常。
show slave status\G
#注意 如果之前此備庫已有主庫指向 需要先執行以下命令清空 STOP SLAVE IO_THREAD FOR CHANNEL '';
reset slave all;

最後測試在主庫修改數據庫,看從庫是否能夠同步成功。

8.3.實現sharding-jdbc讀寫分離

(1)在Sharding-JDBC規則中修改

# 增加數據源s0,使用上面主從同步配置的從庫。
spring.shardingsphere.datasource.names = m0,m1,m2,s0
...
spring.shardingsphere.datasource.s0.type = com.alibaba.druid.pool.DruidDataSource spring.shardingsphere.datasource.s0.driver‐class‐name = com.mysql.jdbc.Driver spring.shardingsphere.datasource.s0.url = jdbc:mysql://localhost:3307/user_db?useUnicode=true spring.shardingsphere.datasource.s0.username = root spring.shardingsphere.datasource.s0.password = root
....
# 主庫從庫邏輯數據源定義 ds0爲user_db 
spring.shardingsphere.sharding.master‐slave‐rules.ds0.master‐data‐source‐name=m0 
spring.shardingsphere.sharding.master‐slave‐rules.ds0.slave‐data‐source‐names=s0
# t_user分表策略,固定分配至ds0的t_user真實表 spring.shardingsphere.sharding.tables.t_user.actual‐data‐nodes = ds0.t_user ....

(2)測試
執行testInsertUser單元測試 :
在這裏插入圖片描述
通過日誌可以看出,所有寫操作落入m0數據源。
執行testSelectUserByIds單元測試 :
在這裏插入圖片描述
通過日誌可以看出,所有寫操作落入s0數據源,達到目標。

發佈了136 篇原創文章 · 獲贊 22 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章