背景:
我們實際開發中,總有幾張和業務相關的大表,這裏的大表是指數據量巨大。如用戶表、訂單表,又或者公司業務中的主表,可能很快這種表的數據就達到了百萬、千萬、億級別的規模,並且增長規模一直很快。這種情況下,單表已經滿足不了了存儲需求了,同時,這麼大的數據量,即使搭配合理的索引,數據庫查詢也是很慢的。這時就需要對這些大表進行分庫、分表。
例如:
user表現在數據增長很快,這時對user庫、表。通過部署多個MySQL實例來進行拓展。每個MySQL實例上都會有user庫,每個user庫裏又有user_N(N爲0,1...),N張數據結構一致,只不過表名不同的user表,來存儲數據。
分庫、分表的基本原則爲:
建議每個表的數據量不超過1000萬行數據
分表的數目選擇:
N =(未來3到5年內總共的記錄行數) / 單張表建議記錄行數 (單張表建議記錄行數 = 1000萬)
N就是每個庫的分表數。建議一次分夠,如果因爲分不夠造成的擴容,是很麻煩的,特別是分表的鍵是採用hash算法分的
分庫的數目選擇:
按照存儲容量來計算 = (3到5年內的存儲容量)/ 單個庫建議存儲容量 (單個庫建議存儲容量 <300G以內)
使用ShardingSphere的原因是,原來很多的數據庫中間件都已不再維護,如mycat、tddl,只有ShardingSphere一直維護,並且今年成功從Apache畢業,成爲頂級項目。美團開源的Zebra的配置和ShardingSphere相比,太過複雜.
對數據庫中間件設計的文章感興趣的同學,可以看下美團這篇文章
數據庫中間件有兩種代理方案,客戶端代理(DataSource)、服務端代理(數據庫代理),以下內容引用自美團文章
-
服務端代理(proxy:代理數據庫)中: 我們獨立部署一個代理服務,這個代理服務背後管理多個數據庫實例。而在應用中,我們通過一個普通的數據源(c3p0、druid、dbcp等)與代理服務器建立連接,所有的sql操作語句都是發送給這個代理,由這個代理去操作底層數據庫,得到結果並返回給應用。在這種方案下,分庫分表和讀寫分離的邏輯對開發人員是完全透明的。
-
客戶端代理(datasource:代理數據源): 應用程序需要使用一個特定的數據源,其作用是代理,內部管理了多個普通的數據源(c3p0、druid、dbcp等),每個普通數據源各自與不同的庫建立連接。應用程序產生的sql交給數據源代理進行處理,數據源內部對sql進行必要的操作,如sql改寫等,然後交給各個普通的數據源去執行,將得到的結果進行合併,返回給應用。數據源代理通常也實現了JDBC規範定義的API,因此能夠直接與orm框架整合。在這種方案下,用戶的代碼需要修改,使用這個代理的數據源,而不是直接使用c3p0、druid、dbcp這樣的連接池
ShardingSphere-jdbc屬於客戶端(數據源代理)
ShardingSpehere將分庫、分表統稱爲數據分片
一、添加依賴
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.0</version>
</dependency>
二、配置
server:
port: 8999
spring:
application:
name: mybatis-demo
shardingsphere:
datasource:
# 數據庫的別名
names: ds0,ds1
ds0:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/dhb?serverTimezone=UTC
password: 12345
username: root
ds1:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3310/dhb?serverTimezone=UTC
password: 12345
username: root
sharding:
# 默認分庫策略
default-database-strategy:
inline:
sharding-column: id
algorithm-expression: ds$->{id % 2}
# 默認分表策略
default-table-strategy:
inline:
sharding-column: age
algorithm-expression: user_$->{age % 2}
# 數據節點
tables:
user:
actual-data-nodes: ds$->{0..1}.user_$->{0..1}
# 默認數據庫
default-data-source-name: ds0
props:
# 打印SQL
sql.show: true
check:
table:
metadata:
# 是否在啓動時檢查分表元數據一致性
enabled: true
# 因爲Druid數據源和默認的數據源衝突,添加此配置
main:
allow-bean-definition-overriding: true
配置說明:
本地有兩個MySQL實例,分別爲3306、3310,每個數據庫下都有dhb數據庫,每個數據庫都有user_0、user_1兩張user表,如下圖:
配置文件說明:
配置文件中的分庫策略是將根據id %2 ,結果如果是0,則走ds0,實例3306的數據庫;結果是1,則走ds1,實例3310的數據庫。
選擇了數據庫之後,怎麼選擇入哪一張表呢?根據age % 2,如果爲0,則走user_0,如果是1,則走user_1。
關於邏輯表、真實表、數據節點概念參見官方文檔
配置文件中分片,屬於行表達式分片,實際業務中,可以自己實現官方的接口,實現自己業務需要的分庫、分表算法,具體實現的4個接口參見分片。StandardShardingStrategy、ComplexShardingStrategy、HintShardingStrategy
關於行表達式的寫法可見官方example
特別注意:
如果使用了阿里的Druid數據源,啓動的時候會報數據源衝突,則使用上述配置文件的最後一行配置即可。
三、實體類,使用、Lambok和Mybatis-plus簡化操作,這裏的table名字是邏輯表名字-user,實際對應的表有ds0.user_0、ds0.user_1、ds1.user_0、ds1.user_1四張表
@Data
@TableName(value = "user")
public class UserItem {
@TableId
private Long id;
private String name;
private Integer age;
private Integer del;
}
四、使用。插入一條數據
控制檯打印SQL入下
最終shardingsphere-jdbc最終根據配置的分庫、分表策略將數據寫入ds0-3306實例庫中的user_1表中。
當查詢的時候,如果條件是根據分片鍵查詢,那麼最終定位到某一個庫的某一個表,如果條件中沒有分片鍵,則會進行全路由,也就是四個庫都查
ShardingSphere-jdbc爲我們做了,根據配置的分片策略,進行 SQL解析 => 執行器優化 => SQL路由 => SQL改寫 => SQL執行 => 結果歸併 工作。具體文檔見內核剖析
以上僅僅是一個使用小例子,實際業務場景比這更復雜,如主子表的分片、分片表和爲分片的表join查詢等等