手把手教你使用 Spring Boot 3 開發上線一個前後端分離的生產級系統(十)

2022-06-09 15:03:46

手把手教你使用 Spring Boot 3 開發上線一個前後端分離的生產級系統(一) - 介紹
手把手教你使用 Spring Boot 3 開發上線一個前後端分離的生產級系統(二) - 資料庫設計
手把手教你使用 Spring Boot 3 開發上線一個前後端分離的生產級系統(三) - 專案初始化
手把手教你使用 Spring Boot 3 開發上線一個前後端分離的生產級系統(四) - 紀錄檔 & 跨域設定
手把手教你使用 Spring Boot 3 開發上線一個前後端分離的生產級系統(五) - MyBatis-Plus & 程式碼生成器整合與設定
手把手教你使用 Spring Boot 3 開發上線一個前後端分離的生產級系統(六) - 本地快取 Caffeine 和 分散式快取 Redis 整合與設定
手把手教你使用 Spring Boot 3 開發上線一個前後端分離的生產級系統(七) - Elasticsearch 8.2 整合與設定
手把手教你使用 Spring Boot 3 開發上線一個前後端分離的生產級系統(八) - XXL-JOB 整合與設定
手把手教你使用 Spring Boot 3 開發上線一個前後端分離的生產級系統(九) - Spring AMQP 整合與設定

背景

傳統的將資料集中儲存至單一節點的解決方案,在效能、可用性和運維成本這三方面已經難於滿足海量資料的場景。

從效能方面來說,由於關係型資料庫大多采用 B+ 樹型別的索引,在資料量超過閾值的情況下,索引深度的增加也將使得磁碟存取的 IO 次數增加,進而導致查詢效能的下降; 同時,高並行存取請求也使得集中式資料庫成為系統的最大瓶頸。

從可用性的方面來講,服務化的無狀態性,能夠達到較小成本的隨意擴容,這必然導致系統的最終壓力都落在資料庫之上。 而單一的資料節點,或者簡單的主從架構,已經越來越難以承擔。資料庫的可用性,已成為整個系統的關鍵。

從運維成本方面考慮,當一個資料庫範例中的資料達到閾值以上,對於 DBA 的運維壓力就會增大。 資料備份和恢復的時間成本都將隨著資料量的大小而愈發不可控。一般來講,單一資料庫範例的資料的閾值在 1TB 之內,是比較合理的範圍。

資料分片指按照某個維度將存放在單一資料庫中的資料分散地存放至多個資料庫或表中以達到提升效能瓶頸以及可用性的效果。通過分庫和分表進行資料的拆分來使得各個表的資料量保持在閾值以下,以及對流量進行疏導應對高存取量,是應對高並行和海量資料系統的有效手段。分庫和分表均可以有效的避免由資料量超過可承受閾值而產生的查詢瓶頸。

小說資料有著內容多、增長速度快的特點,一本主流的完結小說一般所需儲存空間大概在 5MB 以上。一個主流的小說網站在發展中後期,資料量是遠遠超過單一資料庫範例的閾值的,所以我們對小說內容進行分庫分表儲存是非常有必要的。在發展初期,我們的資料量還不是很大,可以先將小說內容分表儲存以減輕資料庫單表壓力以及為後期的資料庫分庫做準備。等資料量即將超過閾值時,再遷移到不同的資料庫範例上。

注:資料分片分為按照業務將表進行歸類,分佈到不同的資料庫中的垂直分片和通過某個欄位(或某幾個欄位)按照某種規則將資料分散至多個庫或表中的水平分片。

Apache ShardingSphere 介紹

Apache ShardingSphere 產品定位為 Database Plus,它關注如何充分合理地利用資料庫的計算和儲存能力,而並非實現一個全新的資料庫。ShardingSphere 站在資料庫的上層視角,關注他們之間的共同作業多於資料庫自身,由 JDBC、Proxy 和 Sidecar(規劃中)這 3 款既能夠獨立部署,又支援混合部署配合使用的產品組成。 它們均提供標準化的基於資料庫作為儲存節點的增量功能,可適用於如 Java 同構、異構語言、雲原生等各種多樣化的應用場景。

ShardingSphere-JDBC 定位為輕量級 Java 框架,在 Java 的 JDBC 層提供額外服務。 它使用使用者端直連資料庫,以 jar 包形式提供服務,無需額外部署和依賴,可理解為增強版的 JDBC 驅動,完全相容 JDBC 和各種 ORM 框架。

ShardingSphere-Proxy 定位為透明化的資料庫代理端,提供封裝了資料庫二進位制協定的伺服器端版本,用於完成對異構語言的支援。

ShardingSphere-Sidecar 定位為 Kubernetes 的雲原生資料庫代理,以 Sidecar 的形式代理所有對資料庫的存取。 通過無中心、零侵入的方案提供與資料庫互動的齧合層,即 Database Mesh,又可稱資料庫網格。

連線、增量 和 可插拔 是 Apache ShardingSphere 的核心概念:

  • 連線:通過對資料庫協定、SQL 方言以及資料庫儲存的靈活適配,快速的連線應用與多模式的異構資料庫;

  • 增量:獲取資料庫的存取流量,並提供流量重定向(資料分片、讀寫分離、影子庫)、流量變形(資料加密、資料脫敏)、流量鑑權(安全、審計、許可權)、流量治理(熔斷、限流)以及流量分析(服務質量分析、可觀察性)等透明化增量功能;

  • 可插拔:專案採用微核心 + 三層可插拔模型,使核心、功能元件以及生態對接完全能夠靈活的方式進行插拔式擴充套件,開發者能夠像使用積木一樣客製化屬於自己的獨特系統。

Apache ShardingSphere 的資料分片模組透明化了分庫分表所帶來的影響,讓使用方儘量像使用一個資料庫一樣使用水平分片之後的資料庫叢集。

整合步驟

  1. MySQL 執行以下的資料遷移指令碼:
DROP PROCEDURE
IF
	EXISTS createBookChapterTable;
-- 建立小說章節表的儲存過程
CREATE PROCEDURE createBookChapterTable ( ) BEGIN
	-- 定義變數
	DECLARE
		i INT DEFAULT 0;
	DECLARE
		tableName CHAR ( 13 ) DEFAULT NULL;
	WHILE
			i < 10 DO
			
			SET tableName = concat( 'book_chapter', i );
		
			SET @stmt = concat( 'create table ', tableName, '(
				`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
				`book_id` bigint(20) unsigned NOT NULL COMMENT \'小說ID\',
				`chapter_num` smallint(5) unsigned NOT NULL COMMENT \'章節號\',
				`chapter_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT \'章節名\',
				`word_count` int(10) unsigned NOT NULL COMMENT \'章節字數\',
				`is_vip` tinyint(3) unsigned NOT NULL DEFAULT \'0\' COMMENT \'是否收費;1-收費 0-免費\',
				`create_time` datetime DEFAULT NULL,
				`update_time` datetime DEFAULT NULL,
				PRIMARY KEY (`id`) USING BTREE,
				UNIQUE KEY `uk_bookId_chapterNum` (`book_id`,`chapter_num`) USING BTREE,
				UNIQUE KEY `pk_id` (`id`) USING BTREE,
				KEY `idx_bookId` (`book_id`) USING BTREE
			) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT=\'小說章節\'' );
			PREPARE stmt 
			FROM
				@stmt;
			EXECUTE stmt;
			DEALLOCATE PREPARE stmt;
			
			SET i = i + 1;
		
	END WHILE;
END;
CALL createBookChapterTable ( );

DROP PROCEDURE
IF
	EXISTS createBookContentTable;
-- 建立小說內容表的儲存過程
CREATE PROCEDURE createBookContentTable ( ) BEGIN
	-- 定義變數
	DECLARE
		i INT DEFAULT 0;
	DECLARE
		tableName CHAR ( 13 ) DEFAULT NULL;
	WHILE
			i < 10 DO
			
			SET tableName = concat( 'book_content', i );
		
			SET @stmt = concat( 'create table ', tableName, '(
				`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT \'主鍵\',
				`chapter_id` bigint(20) unsigned NOT NULL COMMENT \'章節ID\',
				`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT \'小說章節內容\',
				`create_time` datetime DEFAULT NULL,
				`update_time` datetime DEFAULT NULL,
				PRIMARY KEY (`id`) USING BTREE,
				UNIQUE KEY `uk_chapterId` (`chapter_id`) USING BTREE,
				UNIQUE KEY `pk_id` (`id`) USING BTREE
			) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT=\'小說內容\'' );
			PREPARE stmt 
			FROM
				@stmt;
			EXECUTE stmt;
			DEALLOCATE PREPARE stmt;
			
			SET i = i + 1;
		
	END WHILE;
END;
CALL createBookContentTable ( );

DROP PROCEDURE
IF
	EXISTS copyBookChapterData;
-- 遷移小說章節資料的儲存過程
CREATE PROCEDURE copyBookChapterData ( ) BEGIN
	-- 定義變數
	DECLARE
		s INT DEFAULT 0;
	DECLARE
		chapterId BIGINT;
	DECLARE
		bookId BIGINT;
	DECLARE
		chapterNum SMALLINT;
	DECLARE
		chapterName VARCHAR ( 100 );
	DECLARE
		wordCount INT DEFAULT 0;
	DECLARE
		isVip TINYINT ( 64 ) DEFAULT 0;
	DECLARE
		createTime datetime DEFAULT NULL;
	DECLARE
		updateTime datetime DEFAULT NULL;
	DECLARE
		tableNumber INT DEFAULT 0;
	DECLARE
		tableName CHAR ( 13 ) DEFAULT NULL;
	-- 定義遊標
	DECLARE
		report CURSOR FOR SELECT
		id,
		book_id,
		chapter_num,
		chapter_name,
		word_count,
		is_vip,
		create_time,
		update_time 
	FROM
		book_chapter;
	-- 宣告當遊標遍歷完後將標誌變數置成某個值
	DECLARE
		CONTINUE HANDLER FOR NOT FOUND 
		SET s = 1;
	-- 開啟遊標
	OPEN report;
	-- 將遊標中的值賦值給變數,注意:變數名不要和返回的列名同名,變數順序要和sql結果列的順序一致
	FETCH report INTO chapterId,
	bookId,
	chapterNum,
	chapterName,
	wordCount,
	isVip,
	createTime,
	updateTime;
	-- 迴圈遍歷
	WHILE
			s <> 1 DO
			-- 執行業務邏輯
			
			SET tableNumber = bookId % 10;
		
			SET tableName = concat( 'book_chapter', tableNumber );
			
			SET @stmt = concat(
				'insert into ',
				tableName,
				'(`id`, `book_id`, `chapter_num`, `chapter_name`, `word_count`, `is_vip`, `create_time`, `update_time`) VALUES (',
				chapterId,
				', ',
				bookId,
				', ',
				chapterNum,
				', \'',
				chapterName,
				'\', ',
				wordCount,
				', ',
				isVip,
				', \'',
				createTime,
				'\', \'',
				updateTime,
				'\')' 
			);
			PREPARE stmt 
			FROM
				@stmt;
			EXECUTE stmt;
			DEALLOCATE PREPARE stmt;
			FETCH report INTO chapterId,
			bookId,
			chapterNum,
			chapterName,
			wordCount,
			isVip,
			createTime,
			updateTime;
		
	END WHILE;
	-- 關閉遊標
	CLOSE report;
END;
CALL copyBookChapterData ( );

DROP PROCEDURE
IF
	EXISTS copyBookContentData;
-- 遷移小說內容資料的儲存過程
CREATE PROCEDURE copyBookContentData ( ) BEGIN
	-- 定義變數
	DECLARE
		s INT DEFAULT 0;
	DECLARE
		contentId BIGINT;
	DECLARE
		chapterId BIGINT;
	DECLARE
		bookContent MEDIUMTEXT;
	DECLARE
		createTime datetime DEFAULT NULL;
	DECLARE
		updateTime datetime DEFAULT NULL;
	DECLARE
		tableNumber INT DEFAULT 0;
	DECLARE
		tableName CHAR ( 13 ) DEFAULT NULL;
	-- 定義遊標
	DECLARE
		report CURSOR FOR SELECT
		id,
		chapter_id,
		content,
		create_time,
		update_time 
	FROM
		book_content;
	-- 宣告當遊標遍歷完後將標誌變數置成某個值
	DECLARE
		CONTINUE HANDLER FOR NOT FOUND 
		SET s = 1;
	-- 開啟遊標
	OPEN report;
	-- 將遊標中的值賦值給變數,注意:變數名不要和返回的列名同名,變數順序要和sql結果列的順序一致
	FETCH report INTO contentId,
	chapterId,
	bookContent,
	createTime,
	updateTime;
	-- 迴圈遍歷
	WHILE
			s <> 1 DO
			-- 執行業務邏輯
			
			SET tableNumber = chapterId % 10;
		
			SET tableName = concat( 'book_content', tableNumber );
			
			SET bookContent = REPLACE ( bookContent, '\'', "\\'" );
			
			SET @stmt = concat(
				'insert into ',
				tableName,
				'(`id`, `chapter_id`, `content`) VALUES (',
				contentId,
				', ',
				chapterId,
				',\'',
				bookContent,
				'\')' 
			);
			PREPARE stmt 
			FROM
				@stmt;
			EXECUTE stmt;
			DEALLOCATE PREPARE stmt;
			FETCH report INTO contentId,
			chapterId,
			bookContent,
			createTime,
			updateTime;
		
	END WHILE;
	-- 關閉遊標
	CLOSE report;
END;
CALL copyBookContentData ( );
  1. 引入 ShardingSphere-JDBC 官方提供的 Spring Boot Starter 依賴:
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
    <version>5.1.1</version>
</dependency>
  1. application.yml 中新增 ShardingSphere-JDBC 的設定:
spring:
  shardingsphere:
    # 是否開啟 shardingsphere
    enabled: false
    props:
      # 是否在紀錄檔中列印 SQL
      sql-show: true
    # 模式設定
    mode:
      # 單機模式
      type: Standalone
      repository:
        # 檔案持久化
        type: File
        props:
          # 後設資料儲存路徑
          path: .shardingsphere
      # 使用本地設定覆蓋持久化設定
      overwrite: true
    # 資料來源設定
    datasource:
      names: ds_0
      ds_0:
        type: com.zaxxer.hikari.HikariDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        jdbcUrl: jdbc:mysql://localhost:3306/novel_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
        username: root
        password: test123456
    # 規則設定
    rules:
      # 資料分片
      sharding:
        tables:
          # book_content 表
          book_content:
            # 資料節點
            actual-data-nodes: ds_$->{0}.book_content$->{0..9}
            # 分表策略
            table-strategy:
              standard:
                # 分片列名稱
                sharding-column: chapter_id
                # 分片演演算法名稱
                sharding-algorithm-name: bookContentSharding
        sharding-algorithms:
          bookContentSharding:
            # 行表示式分片演演算法,使用 Groovy 的表示式,提供對 SQL 語句中的 = 和 IN 的分片操作支援
            type: INLINE
            props:
              # 分片演演算法的行表示式
              algorithm-expression: book_content$->{chapter_id % 10}

設定是 ShardingSphere-JDBC 中唯一與應用開發者互動的模組,通過它可以快速清晰的理解 ShardingSphere-JDBC 所提供的功能。

  • 模式設定: Apache ShardingSphere 提供的 3 種執行模式分別是適用於整合測試的環境啟動,方便開發人員在整合功能測試中整合 Apache ShardingSphere 而無需清理執行痕跡記憶體模式、能夠將資料來源和規則等後設資料資訊持久化,但無法將後設資料同步至多個 Apache ShardingSphere 範例,無法在叢集環境中相互感知的單機模式和提供了多個 Apache ShardingSphere 範例之間的後設資料共用和分散式場景下狀態協調能力的叢集模式

  • 資料來源設定:包括使用本地資料來源設定(本專案中)和使用 JNDI 資料來源的設定。如果計劃使用 JNDI 設定資料庫,在應用容器(如 Tomcat)中使用 ShardingSphere-JDBC 時, 可使用 spring.shardingsphere.datasource.${datasourceName}.jndiName 來代替資料來源的一系列設定。

  • 規則設定:規則是 Apache ShardingSphere 面向可插拔的一部分,包括資料分片、讀寫分離、高可用、資料加密、影子庫、SQL 解析、混合規則等。

以下是資料分片的設定項說明:

# 標準分片表設定
spring.shardingsphere.rules.sharding.tables.<table-name>.actual-data-nodes= # 由資料來源名 + 表名組成,以小數點分隔。多個表以逗號分隔,支援 inline 表示式。預設表示使用已知資料來源與邏輯表名稱生成資料節點,用於廣播表(即每個庫中都需要一個同樣的表用於關聯查詢,多為字典表)或只分庫不分表且所有庫的表結構完全一致的情況

# 分庫策略,預設表示使用預設分庫策略,以下的分片策略只能選其一

# 用於單分片鍵的標準分片場景
spring.shardingsphere.rules.sharding.tables.<table-name>.database-strategy.standard.sharding-column= # 分片列名稱
spring.shardingsphere.rules.sharding.tables.<table-name>.database-strategy.standard.sharding-algorithm-name= # 分片演演算法名稱

# 用於多分片鍵的複合分片場景
spring.shardingsphere.rules.sharding.tables.<table-name>.database-strategy.complex.sharding-columns= # 分片列名稱,多個列以逗號分隔
spring.shardingsphere.rules.sharding.tables.<table-name>.database-strategy.complex.sharding-algorithm-name= # 分片演演算法名稱

# 用於 Hint 的分片策略
spring.shardingsphere.rules.sharding.tables.<table-name>.database-strategy.hint.sharding-algorithm-name= # 分片演演算法名稱

# 分表策略,同分庫策略
spring.shardingsphere.rules.sharding.tables.<table-name>.table-strategy.xxx= # 省略

# 自動分片表設定
spring.shardingsphere.rules.sharding.auto-tables.<auto-table-name>.actual-data-sources= # 資料來源名

spring.shardingsphere.rules.sharding.auto-tables.<auto-table-name>.sharding-strategy.standard.sharding-column= # 分片列名稱
spring.shardingsphere.rules.sharding.auto-tables.<auto-table-name>.sharding-strategy.standard.sharding-algorithm-name= # 自動分片演演算法名稱

# 分散式序列策略設定
spring.shardingsphere.rules.sharding.tables.<table-name>.key-generate-strategy.column= # 分散式序列列名稱
spring.shardingsphere.rules.sharding.tables.<table-name>.key-generate-strategy.key-generator-name= # 分散式序列演演算法名稱

spring.shardingsphere.rules.sharding.binding-tables[0]= # 繫結表規則列表
spring.shardingsphere.rules.sharding.binding-tables[1]= # 繫結表規則列表
spring.shardingsphere.rules.sharding.binding-tables[x]= # 繫結表規則列表

spring.shardingsphere.rules.sharding.broadcast-tables[0]= # 廣播表規則列表
spring.shardingsphere.rules.sharding.broadcast-tables[1]= # 廣播表規則列表
spring.shardingsphere.rules.sharding.broadcast-tables[x]= # 廣播表規則列表

spring.shardingsphere.sharding.default-database-strategy.xxx= # 預設資料庫分片策略
spring.shardingsphere.sharding.default-table-strategy.xxx= # 預設表分片策略
spring.shardingsphere.sharding.default-key-generate-strategy.xxx= # 預設分散式序列策略
spring.shardingsphere.sharding.default-sharding-column= # 預設分片列名稱

# 分片演演算法設定
spring.shardingsphere.rules.sharding.sharding-algorithms.<sharding-algorithm-name>.type= # 分片演演算法型別
spring.shardingsphere.rules.sharding.sharding-algorithms.<sharding-algorithm-name>.props.xxx= # 分片演演算法屬性設定

# 分散式序列演演算法設定
spring.shardingsphere.rules.sharding.key-generators.<key-generate-algorithm-name>.type= # 分散式序列演演算法型別
spring.shardingsphere.rules.sharding.key-generators.<key-generate-algorithm-name>.props.xxx= # 分散式序列演演算法屬性設定

其中,分片演演算法分為包含取模分片、雜湊取模分片、基於分片容量的範圍分片、基於分片邊界的範圍分片、自動時間段分片在內的自動分片演演算法和包含行表示式分片、時間範圍分片在內的標準分片演演算法以及複合分片演演算法Hint 分片演演算法。我們還可以自定義類分片演演算法,通過設定分片策略型別和演演算法類名,實現自定義擴充套件。

分散式序列演演算法包括雪花演演算法和 UUID。