-
狂創客圈 經典圖書 : 《Netty Zookeeper Redis 高並發實戰》 面試必備 + 面試必備 + 面試必備 【博客園總入口 】
-
瘋狂創客圈 經典圖書 : 《SpringCloud、Nginx高並發核心編程》 大廠必備 + 大廠必備 + 大廠必備 【博客園總入口 】
-
入大廠+漲工資必備: 高並發【 億級流量IM實戰】 實戰系列 【 SpringCloud Nginx秒殺】 實戰系列 【博客園總入口 】
目錄:分庫分表 Sharding-JDBC從入門到精通
主題 | 鏈接地址 |
---|---|
准備1: 在window安裝虛擬機集群 | 分布式 虛擬機 linux 環境制作 GO |
准備2:在虛擬機的各個節點有 mysql | centos mysql 筆記(內含vagrant mysql 鏡像)GO |
分庫分表 -Sharding-JDBC- 從入門到精通 1 | Sharding-JDBC 分庫、分表(入門實戰) GO |
分庫分表 -Sharding-JDBC- 從入門到精通 2 | Sharding-JDBC 基礎知識 GO |
分庫分表 Sharding-JDBC 從入門到精通之 3 | 自定義主鍵、分布式雪花主鍵,原理與實戰 GO |
分庫分表 -Sharding-JDBC- 從入門到精通 4 | MYSQL集群主從復制,原理與實戰 GO |
分庫分表 Sharding-JDBC 從入門到精通之 5 | 讀寫分離 實戰 GO |
分庫分表 Sharding-JDBC 從入門到精通之 6 | Sharding-JDBC執行原理 GO |
分庫分表 Sharding-JDBC 從入門到精通之源碼 | git倉庫地址GO |
1.有關Sharding-JDBC
有關Sharding-JDBC介紹這里就不在多說,之前Sharding-JDBC是當當網自研的關系型數據庫的水平擴展框架,現在已經捐獻給Apache,其原理請參見后面的博客。
shardingsphere文檔地址是:https://shardingsphere.apache.org/document/current/cn/overview/。
2 Sharding-JDBC 實戰的場景
在深入了解之前,先實戰一把,增加印象, 激發興趣。
一般情況下,大家都會使用水平切分庫和表:將一張表水平切分成多張表,還可以放到多個庫中。這就涉及到數據分片的規則,比較常見的有:Hash取模分表、數值Range分表、一致性Hash算法分表。
1、Hash取模分表
概念 一般采用Hash取模的切分方式,例如:假設按goods_id分4張表。(goods_id%4 取整確定表)
優點
- 數據分片相對比較均勻,不容易出現熱點和並發訪問的瓶頸。
缺點
-
后期分片集群擴容時,需要遷移舊的數據很難。
-
容易面臨跨分片查詢的復雜問題。比如上例中,如果頻繁用到的查詢條件中不帶goods_id時,將會導致無法定位數據庫,從而需要同時向4個庫發起查詢,
再在內存中合並數據,取最小集返回給應用,分庫反而成為拖累。
2、數值Range分表
概念 按照時間區間或ID區間來切分。例如:將goods_id為11000的記錄分到第一個表,10012000的分到第二個表,以此類推。
優點
- 單表大小可控
- 天然便於水平擴展,后期如果想對整個分片集群擴容時,只需要添加節點即可,無需對其他分片的數據進行遷移
- 使用分片字段進行范圍查找時,連續分片可快速定位分片進行快速查詢,有效避免跨分片查詢的問題。
缺點
- 熱點數據成為性能瓶頸。
例如按時間字段分片,有些分片存儲最近時間段內的數據,可能會被頻繁的讀寫,而有些分片存儲的歷史數據,則很少被查詢
3、一致性Hash算法
一致性Hash算法能很好的解決因為Hash取模而產生的分片集群擴容時,需要遷移舊的數據的難題。至於具體原理這里就不詳細說,
可以參考一篇博客:一致性哈希算法(分庫分表,負載均衡等)
4、實戰:簡單的Hash取模分表
假設一個訂單表的user_id和order_id 分布較為均勻,按照1000W的數據規模,可以使用如下的分庫、分表結構來保存:
db0
├── t_order0
└── t_order1
db1
├── t_order0
└── t_order1
簡單的進行分庫分表: 按照user_id %2 的規則進行分庫,按照 order_id %2 的規則進行分表
3 庫表的結構設計:
3.1 邏輯訂單表
邏輯訂單表的結構如下:
3.2 節點1 (cdh1)上的訂單庫
DROP TABLE IF EXISTS `t_order_0`;
DROP TABLE IF EXISTS `t_order_1`;
DROP TABLE IF EXISTS `t_config`;
CREATE TABLE `t_order_0` (`order_id` bigInt NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`));
CREATE TABLE `t_order_1` (`order_id` bigInt NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`));
3.3 節點2 (cdh2)上的訂單庫
DROP TABLE IF EXISTS `t_order_0`;
DROP TABLE IF EXISTS `t_order_1`;
DROP TABLE IF EXISTS `t_config`;
CREATE TABLE `t_order_0` (`order_id` bigInt NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`));
CREATE TABLE `t_order_1` (`order_id` bigInt NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`));
兩個db上,都有t_order_0,和t_order_1兩個表
4 Sharding-JDBC 分庫分表配置
- 分庫
本文分庫樣例比較簡單,根據數據庫表中字段user_id%2進行判斷,如果user_id%2==0則使用ds0,否則使用ds1。
- 分表
分樣例比較簡單,根據數據庫表中字段order_id%2進行判斷,如果order_id%2==0則使用t_order_0,否則使用t_order_1。
對 t_order 表進行的如下圖所示的數據表水平 分庫和分表,具體如下圖所示:
(對 t_order_item 表也要進行類似的水平分片,但是這部分配置省略了):
在 yml 配置文件中,可以使用 Groovy 表達式,進行分庫分表的規則配置,具體的 Groovy 表達式如下:
表達式一: 例如 ds0.t_order_0
ds$->{0..1}.t_order_$->{0..1}
表達式一:db 維度的拆分, 例如 ds_0、ds_1
ds_${user_id % 2}
表達式一:table 維度的拆分, 例如 t_order_1
t_order_${order_id % 2}
這些表達式被稱為 Groovy 表達式,它們的含義很容易識別:
1)對 t_order 進行兩種維度的拆分:db 維度和 table 維度;
2)在db 維度,user_id % 2 == 0 的記錄全部落到 ds0,user_id % 2 == 1 的記錄全部落到 ds1;(有人稱這一過程為水平分庫,其實它的本質還是在水平地分表,只不過依據表中 user_id 的不同把拆分的后的表放入兩個數據庫實例。)
3)在表維度,order_id% 2 == 0 的記錄全部落到 t_order0,order_id% 2 == 1 的記錄全部落到 t_order1。
4)對記錄的讀和寫都按照這種方向進行,“方向”,就是分片方式,就是路由。
使用這種簡潔的 Groovy 表達式, 可以設置的分片策略和分片算法。但是這種方式所能表達的含義是有限的。因此,官方提供了分片策略接口和分片算法接口,讓你們利用 Java 代碼盡情表達更為復雜的分片策略和分片算法。
實際上,分片算法是分片策略的組成部分,分片策略設置=分片鍵設置+分片算法設置。上述配置里使用的策略是 Inline 類型的分片策略,使用的算法是 Inline 類型的行表達式算法。
具體的配置如下:
spring:
application:
name: sharding-jdbc-provider
jpa: #配置自動建表:updata:沒有表新建,有表更新操作,控制台顯示建表語句
hibernate:
ddl-auto: none
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
show-sql: true
freemarker:
allow-request-override: false
allow-session-override: false
cache: false
charset: UTF-8
check-template-location: true
content-type: text/html
enabled: true
expose-request-attributes: false
expose-session-attributes: false
expose-spring-macro-helpers: true
prefer-file-system-access: true
settings:
classic_compatible: true
default_encoding: UTF-8
template_update_delay: 0
suffix: .ftl
template-loader-path: classpath:/templates/
shardingsphere:
props:
sql:
show: true
# 配置真實數據源
datasource:
common:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
validationQuery: SELECT 1 FROM DUAL
names: ds0,ds1
ds0:
url: jdbc:mysql://cdh1:3306/store?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=true&serverTimezone=UTC
username: root
password: 123456
# 配置第 2 個數據源 org.apache.commons.dbcp2
ds1:
url: jdbc:mysql://cdh2:3306/store?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=true&serverTimezone=UTC
username: root
password: 123456
# 配置分片規則和分片算法
rules:
# 配置分片規則
sharding:
tables:
# 配置 t_order 表規則
t_order:
actualDataNodes: ds$->{0..1}.t_order_$->{0..1}
# 配置分庫策略
databaseStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: database-inline
# 配置分表策略
tableStrategy:
standard:
shardingColumn: order_id
shardingAlgorithmName: table-inline
keyGenerateStrategy:
column: order_id
keyGeneratorName: snowflake
# 配置分片算法
bindingTables: t_order
sharding-algorithms:
database-inline:
type: INLINE
props:
algorithm-expression: ds$->{user_id % 2}
table-inline:
type: INLINE
props:
algorithm-expression: t_order_$->{order_id % 2}
keyGenerators:
snowflake:
type: SNOWFLAKE
props:
workerId: 123
5.代碼實現
本文使用SpringBoot2,SpringData-JPA,Druid連接池,和當當的sharding-jdbc 5。
5.1 依賴文件
新建項目,加入當當的sharding-jdbc-core依賴和druid連接池。請參見源碼工程。
5.2 啟動類
使用@EnableTransactionManagement開啟事務,
使用@EnableConfigurationProperties注解加入配置實體,啟動類完整代碼請入所示。
package com.crazymaker.springcloud.sharding.jdbc.demo.start;
@EnableConfigurationProperties
@SpringBootApplication(scanBasePackages =
{"com.crazymaker.springcloud.sharding.jdbc.demo",
// "com.crazymaker.springcloud.base",
// "com.crazymaker.springcloud.standard"
}, exclude = {
DataSourceAutoConfiguration.class,
SecurityAutoConfiguration.class,
DruidDataSourceAutoConfigure.class})
@EnableScheduling
@EnableSwagger2
@EnableJpaRepositories(basePackages = {
"com.crazymaker.springcloud.sharding.jdbc.demo.dao.impl",
// "com.crazymaker.springcloud.base.dao"
})
@EnableTransactionManagement(proxyTargetClass = true)
@EntityScan(basePackages = {
// "com.crazymaker.springcloud.user.*.dao.po",
"com.crazymaker.springcloud.sharding.jdbc.demo.entity.jpa",
// "com.crazymaker.springcloud.standard.*.dao.po"
})
/**
* 啟用 Hystrix
*/
@EnableHystrix
@EnableFeignClients(
basePackages = "com.crazymaker.springcloud.user.info.remote.client",
defaultConfiguration = FeignConfiguration.class)
@Slf4j
@EnableEurekaClient
public class ShardingJdbcDemoCloudApplication
{
public static void main(String[] args)
{
ConfigurableApplicationContext applicationContext = SpringApplication.run(ShardingJdbcDemoCloudApplication.class, args);
Environment env = applicationContext.getEnvironment();
String port = env.getProperty("server.port");
String path = env.getProperty("server.servlet.context-path");
System.out.println("\n----------------------------------------------------------\n\t" +
"Application is running! Access URLs:\n\t" +
"Local: \t\thttp://localhost:" + port + path + "/index.html\n\t" +
"swagger-ui: \thttp://localhost:" + port + path + "/swagger-ui.html\n\t" +
"----------------------------------------------------------");
}
}
5.3實體類和數據庫操作層
就是簡單的實體和Repository,更多詳細內容請參見源碼工程。
/*
* Copyright 2016-2018 shardingsphere.io.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* </p>
*/
package com.crazymaker.springcloud.sharding.jdbc.demo.entity.jpa;
import com.crazymaker.springcloud.sharding.jdbc.demo.entity.Order;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "t_order")
public final class OrderEntity extends Order
{
@Id
@Column(name = "order_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Override
public long getOrderId() {
return super.getOrderId();
}
@Column(name = "user_id")
@Override
public int getUserId() {
return super.getUserId();
}
@Column(name = "status")
public String getStatus() {
return super.getStatus();
}
}
5.4 服務層
更多詳細內容請參見源碼工程。
5.4 Controller
接下來創建一個Controller進行測試,保存方法使用了插入數據和查看數據,根據我們的規則,會每個庫插入數據,同時我這里還創建了一個查詢方法,查詢全部訂單。
package com.crazymaker.springcloud.sharding.jdbc.demo.controller;
@RestController
@RequestMapping("/api/sharding/")
@Api(tags = "sharding jdbc 演示")
public class ShardingJdbcController
{
@Resource
JpaEntityService jpaEntityService;
@PostMapping("/order/add/v1")
@ApiOperation(value = "插入訂單")
public RestOut<Order> orderAdd(@RequestBody Order dto)
{
jpaEntityService.addOrder(dto);
return RestOut.success(dto);
}
@PostMapping("/order/list/v1")
@ApiOperation(value = "查詢訂單")
public RestOut<List<Order>> listAll()
{
List<Order> list = jpaEntityService.selectAll();
return RestOut.success(list);
}
}
6 執行測試
6.1 打開swagger
啟動應用。
然后,在瀏覽器或HTTP請求工具訪問http://localhost:7700/sharding-jdbc-provider/swagger-ui.html,如圖所示
6.2 加入兩條數據
使用插入訂單的接口,可以插入訂單, 注意 userid %2 ==0 進入 db1, 注意 userid %2 ==1進入 db2, 具體在哪個表呢?
因為 orderid是通過雪花算法生成的,如果orderid%2==0 ,則進入t_order_0,否則使用t_order_1。
插入之后,可以通過數據庫,看結果。具體如下圖:
6.3 查看數據
使用程序的查詢全部的方法,shardingjdbc ,會查出所有的訂單。
7 總結
使用shardingjdbc ,除了數據源的配置有些特殊的規則外, 持久層程序和普通的 JPA代碼,區別並不大。
當然,如果要實現特殊的分庫分表邏輯,還是需要動代碼的,請看后續分解。
回到◀瘋狂創客圈▶
瘋狂創客圈 - Java高並發研習社群,為大家開啟大廠之門