如何免費(fèi)自己做個(gè)網(wǎng)站seo入門(mén)基礎(chǔ)知識(shí)
一、ShardingSphere產(chǎn)品介紹
?????????Apache ShardingSphere 是一款分布式的數(shù)據(jù)庫(kù)生態(tài)系統(tǒng), 可以將任意數(shù)據(jù)庫(kù)轉(zhuǎn)換為分布式數(shù)據(jù)庫(kù),并通過(guò)數(shù)據(jù)分片、彈性伸縮、加密等能力對(duì)原有數(shù)據(jù)庫(kù)進(jìn)行增強(qiáng)。Apache ShardingSphere 設(shè)計(jì)哲學(xué)為 Database Plus,旨在構(gòu)建異構(gòu)數(shù)據(jù)庫(kù)上層的標(biāo)準(zhǔn)和生態(tài)。 它關(guān)注如何充分合理地利用數(shù)據(jù)庫(kù)的計(jì)算和存儲(chǔ)能力,而并非實(shí)現(xiàn)一個(gè)全新的數(shù)據(jù)庫(kù)。 它站在數(shù)據(jù)庫(kù)的上層視角,關(guān)注它們之間的協(xié)作多于數(shù)據(jù)庫(kù)自身。
????????另外需要注意的是ShardingSphere的版本演進(jìn)路線。
?????????當(dāng)前最新的版本是5.x??梢钥吹?#xff0c;對(duì)于當(dāng)前版本,ShardingSphere的核心是可插拔。其核心設(shè)計(jì)哲學(xué)就是連接、增強(qiáng)以及可拔插。這是官網(wǎng)對(duì)于其整個(gè)設(shè)計(jì)哲學(xué)的核心描述。
?????????你現(xiàn)在當(dāng)然不需要去了解各個(gè)細(xì)節(jié),但是你應(yīng)該要理解ShardingSphere是希望演進(jìn)成一個(gè)重要的分庫(kù)分表的功能核心。這個(gè)功能核心是構(gòu)建在現(xiàn)有的數(shù)據(jù)庫(kù)產(chǎn)品之上的,同時(shí)他可以支持大量的可插拔的上層應(yīng)用擴(kuò)展。這也意味著,在后面學(xué)習(xí)ShardingSphere時(shí),你一定需要花更多心思去理解如何對(duì)ShardingSphere的功能進(jìn)行擴(kuò)展,而不能僅僅是學(xué)會(huì)如何使用ShardingSphere已經(jīng)提供的功能。
二、客戶端分庫(kù)分表與服務(wù)端分庫(kù)分表
????????ShardingSphere最為核心的產(chǎn)品有兩個(gè):一個(gè)是ShardingJDBC,這是一個(gè)進(jìn)行客戶端分庫(kù)分表的框架。另一個(gè)是ShardingProxy,這是一個(gè)進(jìn)行服務(wù)端分庫(kù)分表的產(chǎn)品。他們代表了兩種不同的分庫(kù)分表的實(shí)現(xiàn)思路。
2.1 ShardingJDBC客戶端分庫(kù)分表
????????ShardingSphere最為核心的產(chǎn)品有兩個(gè):一個(gè)是ShardingJDBC,這是一個(gè)進(jìn)行客戶端分庫(kù)分表的框架。另一個(gè)是ShardingProxy,這是一個(gè)進(jìn)行服務(wù)端分庫(kù)分表的產(chǎn)品。他們代表了兩種不同的分庫(kù)分表的實(shí)現(xiàn)思路。
- 適用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC;
- 支持任何第三方的數(shù)據(jù)庫(kù)連接池,如:DBCP, C3P0, BoneCP, HikariCP 等;
- 支持任意實(shí)現(xiàn) JDBC 規(guī)范的數(shù)據(jù)庫(kù),目前支持 MySQL,PostgreSQL,Oracle,SQLServer 以及任何可使用 JDBC 訪問(wèn)的數(shù)據(jù)庫(kù)。
2.2 ShardingProxy服務(wù)端分庫(kù)分表?
?????????ShardingSphere-Proxy 定位為透明化的數(shù)據(jù)庫(kù)代理端,通過(guò)實(shí)現(xiàn)數(shù)據(jù)庫(kù)二進(jìn)制協(xié)議,對(duì)異構(gòu)語(yǔ)言提供支持。 目前提供 MySQL 和 PostgreSQL 協(xié)議,透明化數(shù)據(jù)庫(kù)操作,對(duì) DBA 更加友好。
- 向應(yīng)用程序完全透明,可直接當(dāng)做 MySQL/PostgreSQL 使用;
- 兼容 MariaDB 等基于 MySQL 協(xié)議的數(shù)據(jù)庫(kù),以及 openGauss 等基于 PostgreSQL 協(xié)議的數(shù)據(jù)庫(kù);
- 適用于任何兼容 MySQL/PostgreSQL 協(xié)議的的客戶端,如:MySQL Command Client, MySQL Workbench, Navicat 等。
2.3 ShardingSphere混合部署架構(gòu)?
?????????這兩個(gè)產(chǎn)品都各有優(yōu)勢(shì)。ShardingJDBC跟客戶端在一起,使用更加靈活。而ShardingProxy是一個(gè)獨(dú)立部署的服務(wù),所以他的功能相對(duì)就比較固定。他們的整體區(qū)別如下:
????????另外,在產(chǎn)品圖中,Governance Center也是其中重要的部分。他的作用有點(diǎn)類似于微服務(wù)架構(gòu)中的配置中心,可以使用第三方服務(wù)統(tǒng)一管理分庫(kù)分表的配置信息,當(dāng)前建議使用的第三方服務(wù)是Zookeeper,同時(shí)也支持Nacos,Etcd等其他第三方產(chǎn)品。
??????由于ShardingJDBC和ShardingProxy都支持通過(guò)Governance Center,將配置信息交個(gè)第三方服務(wù)管理,因此,也就自然支持了通過(guò)Governance Center進(jìn)行整合的混合部署架構(gòu)。
三、快速上手ShardingJDBC?
?????????? 我們預(yù)備將一批課程信息分別拆分到兩個(gè)庫(kù)中的兩個(gè)表里。
3.1 搭建基礎(chǔ)環(huán)境?
?????????接下來(lái)我們使用最常用的SpringBoot+MyBatis+MyBatis-plus快速搭建一個(gè)可以訪問(wèn)數(shù)據(jù)的簡(jiǎn)單應(yīng)用,以這個(gè)應(yīng)用作為我們分庫(kù)分表的基礎(chǔ)。
???????? step1: 在數(shù)據(jù)庫(kù)中創(chuàng)建course表,建表語(yǔ)句如下:
CREATE TABLE course (`cid` bigint(0) NOT NULL AUTO_INCREMENT,`cname` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,`user_id` bigint(0) NOT NULL,`cstatus` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,PRIMARY KEY (`cid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
? ????????step2: 搭建一個(gè)Maven項(xiàng)目,在pom.xml中加入依賴,其中就包含訪問(wèn)數(shù)據(jù)庫(kù)最為簡(jiǎn)單的幾個(gè)組件。
<dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.2.1.RELEASE</version><type>pom</type><scope>import</scope></dependency><!-- mybatisplus依賴 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.0.5</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.20</version></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><!-- 數(shù)據(jù)源連接池 --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.20</version></dependency><!-- mysql連接驅(qū)動(dòng) --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!-- mybatisplus依賴 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3.3</version></dependency></dependencies>
?????????step3: 使用MyBatis-plus的方式,直接聲明Entity和Mapper,映射數(shù)據(jù)庫(kù)中的course表。
public class Course {private Long cid;private String cname;private Long userId;private String cstatus;//省略。getter ... setter ....
}public interface CourseMapper extends BaseMapper<Course> {
}@SpringBootApplication
@MapperScan("com.roy.jdbcdemo.mapper")
public class App {public static void main(String[] args) {SpringApplication.run(App.class,args);}
}
?????????step5: 在springboot的配置文件application.properties中增加數(shù)據(jù)庫(kù)配置。
spring.datasource.druid.db-type=mysql
spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.url=jdbc:mysql://localhost:3306/coursedb?serverTimezone=UTC
spring.datasource.druid.username=root
spring.datasource.druid.password=root
?????????step6: 做一個(gè)單元測(cè)試,簡(jiǎn)單的把course課程信息插入到數(shù)據(jù)庫(kù),以及從數(shù)據(jù)庫(kù)中進(jìn)行查詢。
@SpringBootTest
@RunWith(SpringRunner.class)
public class JDBCTest {@Resourceprivate CourseMapper courseMapper;@Testpublic void addcourse() {for (int i = 0; i < 10; i++) {Course c = new Course();c.setCname("java");c.setUserId(1001L);c.setCstatus("1");courseMapper.insert(c);//insert into course values ....System.out.println(c);}}@Testpublic void queryCourse() {QueryWrapper<Course> wrapper = new QueryWrapper<Course>();wrapper.eq("cid",1L);List<Course> courses = courseMapper.selectList(wrapper);courses.forEach(course -> System.out.println(course));}
}
3.2 引入ShardingSphere分庫(kù)分表?
????????? 接下來(lái),我們將在這個(gè)簡(jiǎn)單案例上使用ShardingSphere快速Course表的分庫(kù)分表功能。
??????????step1:調(diào)整pom.xml中的依賴,引入ShardingSphere。
????????ShardingSphere的實(shí)現(xiàn)機(jī)制和我們之前章節(jié)中使用DynamicDataSource框架實(shí)現(xiàn)讀寫(xiě)分離很類似,也是在底層注入一個(gè)帶有分庫(kù)分表功能的DataSource數(shù)據(jù)源。因此,在調(diào)整依賴時(shí),需要注意不要直接使用druid-sprint-boot-starter依賴了。因?yàn)檫@個(gè)依賴會(huì)在Spring容器中注入一個(gè)DataSource,這樣再要使用ShardingSphere注入DataSource就會(huì)產(chǎn)生沖突了。
<dependencies><!-- shardingJDBC核心依賴 --><dependency><groupId>org.apache.shardingsphere</groupId><artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId><version>5.2.1</version><exclusions><exclusion><artifactId>snakeyaml</artifactId><groupId>org.yaml</groupId></exclusion></exclusions></dependency><!-- 坑爹的版本沖突 --><dependency><groupId>org.yaml</groupId><artifactId>snakeyaml</artifactId><version>1.33</version></dependency><!-- SpringBoot依賴 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><exclusions><exclusion><artifactId>snakeyaml</artifactId><groupId>org.yaml</groupId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><!-- 數(shù)據(jù)源連接池 --><!--注意不要用這個(gè)依賴,他會(huì)創(chuàng)建數(shù)據(jù)源,跟上面ShardingJDBC的SpringBoot集成依賴有沖突 --><!-- <dependency>--><!-- <groupId>com.alibaba</groupId>--><!-- <artifactId>druid-spring-boot-starter</artifactId>--><!-- <version>1.1.20</version>--><!-- </dependency>--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.20</version></dependency><!-- mysql連接驅(qū)動(dòng) --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!-- mybatisplus依賴 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3.3</version></dependency></dependencies>
?????????step2: 在對(duì)應(yīng)數(shù)據(jù)庫(kù)里創(chuàng)建分片表:按照我們之前的設(shè)計(jì),去對(duì)應(yīng)的數(shù)據(jù)庫(kù)中自行創(chuàng)建course_1和course_2表。但是這里要注意,在創(chuàng)建分片表時(shí),cid字段就不要用自增長(zhǎng)了。因?yàn)閿?shù)據(jù)分到四個(gè)表后,每個(gè)表都自增長(zhǎng),就沒(méi)辦法保證cid字段的唯一性了。
????????step3: 增加ShardingJDBC的分庫(kù)分表配置,然后,好玩的事情來(lái)了。應(yīng)用層代碼不需要做任何的修改,直接修改application.properties里的配置就可以完成我們之前設(shè)計(jì)的分庫(kù)分表的目標(biāo)。
# 打印SQL
spring.shardingsphere.props.sql-show = true
spring.main.allow-bean-definition-overriding = true# ----------------數(shù)據(jù)源配置
# 指定對(duì)應(yīng)的真實(shí)庫(kù)
spring.shardingsphere.datasource.names=m0,m1
# 配置真實(shí)庫(kù)
spring.shardingsphere.datasource.m0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m0.url=jdbc:mysql://localhost:3306/coursedb?serverTimezone=UTC
spring.shardingsphere.datasource.m0.username=root
spring.shardingsphere.datasource.m0.password=rootspring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://localhost:3306/coursedb2?serverTimezone=UTC
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=root
#------------------------分布式序列算法配置
# 雪花算法,生成Long類型主鍵。
spring.shardingsphere.rules.sharding.key-generators.alg_snowflake.type=SNOWFLAKE
spring.shardingsphere.rules.sharding.key-generators.alg_snowflake.props.worker.id=1
# 指定分布式主鍵生成策略
spring.shardingsphere.rules.sharding.tables.course.key-generate-strategy.column=cid
spring.shardingsphere.rules.sharding.tables.course.key-generate-strategy.key-generator-name=alg_snowflake
#-----------------------配置實(shí)際分片節(jié)點(diǎn)
spring.shardingsphere.rules.sharding.tables.course.actual-data-nodes=m$->{0..1}.course_$->{1..2}
#-----------------------配置分庫(kù)策略,按cid取模
spring.shardingsphere.rules.sharding.tables.course.database-strategy.standard.sharding-column=cid
spring.shardingsphere.rules.sharding.tables.course.database-strategy.standard.sharding-algorithm-name=course_db_algspring.shardingsphere.rules.sharding.sharding-algorithms.course_db_alg.type=MOD
spring.shardingsphere.rules.sharding.sharding-algorithms.course_db_alg.props.sharding-count=2
#給course表指定分表策略 standard-按單一分片鍵進(jìn)行精確或范圍分片
spring.shardingsphere.rules.sharding.tables.course.table-strategy.standard.sharding-column=cid
spring.shardingsphere.rules.sharding.tables.course.table-strategy.standard.sharding-algorithm-name=course_tbl_alg
# 分表策略-INLINE:按單一分片鍵分表
spring.shardingsphere.rules.sharding.sharding-algorithms.course_tbl_alg.type=INLINE
spring.shardingsphere.rules.sharding.sharding-algorithms.course_tbl_alg.props.algorithm-expression=course_$->{cid%2+1}
配置過(guò)程,剛開(kāi)始看會(huì)有點(diǎn)復(fù)雜,但是對(duì)應(yīng)之前的設(shè)計(jì)圖不難對(duì)應(yīng)上。而且后面也會(huì)詳細(xì)來(lái)解讀配置過(guò)程。
這里主要需要理解一下的是配置中用到的Groovy表達(dá)式。 比如 m$->${0..1}.course_$->{1..2} 和 course_$->{cid%2+1} 。這是ShardingSphere支持的Groovy表達(dá)式,在后面會(huì)大量接觸到這樣的表達(dá)式。這個(gè)表達(dá)式中,$->{}部分為動(dòng)態(tài)部分,大括號(hào)內(nèi)的就是Groovy語(yǔ)句。 兩個(gè)點(diǎn),表示一個(gè)數(shù)據(jù)組的起點(diǎn)和終點(diǎn)。m$->${0..1}表示m0和m1兩個(gè)字符串集合。course_$->{1..2}表示course_1和course_2集合。 course_$->{cid%2+1} 表示根據(jù)cid的值進(jìn)行計(jì)算,計(jì)算的結(jié)果再拼湊上course_前綴。
?????????在日志里也能看到實(shí)際的執(zhí)行情況。
這個(gè)示例中,course信息只能平均分到兩個(gè)表中,而無(wú)法均勻分到四個(gè)表中。這其實(shí)是根據(jù)cid進(jìn)行計(jì)算的結(jié)果。而將course_tbl_alg的計(jì)算表達(dá)式改成 course_$->{((cid+1)%4).intdiv(2)+1} 后,理論上,如果cid是連續(xù)遞增的,就可以將數(shù)據(jù)均勻分到四個(gè)表里。但是snowflake雪花算法生成的ID并不是連續(xù)的,所以有時(shí)候還是無(wú)法分到四個(gè)表。
四、理解ShardingSphere核心概念
?????????從這個(gè)簡(jiǎn)單示例中,我們可以接觸到分庫(kù)分表很多核心的概念。這些概念都是后面進(jìn)行更復(fù)雜的分庫(kù)分表時(shí),需要大量運(yùn)用的重要工具。
4.1 垂直分片與水平分片
?????????這是設(shè)計(jì)分庫(kù)分表方案時(shí)經(jīng)常會(huì)提到的概念。 其中垂直分片表示按照業(yè)務(wù)的緯度,將不同的表拆分到不同的庫(kù)當(dāng)中。這樣可以減少每個(gè)數(shù)據(jù)庫(kù)的數(shù)據(jù)量以及客戶端的連接數(shù),提高查詢效率。而水平分表表示按照數(shù)據(jù)的緯度,將原本存在同一張表中的數(shù)據(jù),拆分到多張子表當(dāng)中。每個(gè)子表只存儲(chǔ)一份的數(shù)據(jù)。這樣可以將數(shù)據(jù)量分散到多張表當(dāng)中,減少每一張表的數(shù)據(jù)量,提升查詢效率。
4.2 ShardingSphere實(shí)現(xiàn)分庫(kù)分表的核心概念?
?????????接下來(lái)我們依次解析一下剛才示例中配置的一些重要的概念,可以對(duì)照一下之前的配置信息進(jìn)行驗(yàn)證。
- 虛擬庫(kù): ShardingSphere的核心就是提供一個(gè)具備分庫(kù)分表功能的虛擬庫(kù),他是一個(gè)ShardingSphereDatasource實(shí)例。應(yīng)用程序只需要像操作單數(shù)據(jù)源一樣訪問(wèn)這個(gè)ShardingSphereDatasource即可。
- 真實(shí)庫(kù): 實(shí)際保存數(shù)據(jù)的數(shù)據(jù)庫(kù)。這些數(shù)據(jù)庫(kù)都被包含在ShardingSphereDatasource實(shí)例當(dāng)中,由ShardingSphere決定未來(lái)需要使用哪個(gè)真實(shí)庫(kù)。
- 邏輯表: 應(yīng)用程序直接操作的邏輯表。
- 真實(shí)表: 實(shí)際保存數(shù)據(jù)的表。這些真實(shí)表與邏輯表表名不需要一致,但是需要有相同的表結(jié)構(gòu),可以分布在不同的真實(shí)庫(kù)中。應(yīng)用可以維護(hù)一個(gè)邏輯表與真實(shí)表的對(duì)應(yīng)關(guān)系,所有的真實(shí)表默認(rèn)也會(huì)映射成為ShardingSphere的虛擬表。
- 分布式主鍵生成算法: 給邏輯表生成唯一主鍵。由于邏輯表的數(shù)據(jù)是分布在多個(gè)真實(shí)表當(dāng)中的,所有,單表的索引就無(wú)法保證邏輯表的ID唯一性。ShardingSphere集成了幾種常見(jiàn)的基于單機(jī)生成的分布式主鍵生成器。比如SNOWFLAKE,COSID_SNOWFLAKE雪花算法可以生成單調(diào)遞增的long類型的數(shù)字主鍵,還有UUID,NANOID可以生成字符串類型的主鍵。當(dāng)然,ShardingSphere也支持應(yīng)用自行擴(kuò)展主鍵生成算法。比如基于Redis,Zookeeper等第三方服務(wù),自行生成主鍵。
- 分片策略: 表示邏輯表要如何分配到真實(shí)庫(kù)和真實(shí)表當(dāng)中,分為分庫(kù)策略和分表策略兩個(gè)部分。分片策略由分片鍵和分片算法組成。分片鍵是進(jìn)行數(shù)據(jù)水平拆分的關(guān)鍵字段。如果沒(méi)有分片鍵,ShardingSphere將只能進(jìn)行全路由,SQL執(zhí)行的性能會(huì)非常差。分片算法則表示根據(jù)分片鍵如何尋找對(duì)應(yīng)的真實(shí)庫(kù)和真實(shí)表。簡(jiǎn)單的分片策略可以使用Groovy表達(dá)式直接配置,當(dāng)然,ShardingSphere也支持自行擴(kuò)展更為復(fù)雜的分片算法。
?????????示例當(dāng)中給course表分配配置了分庫(kù)策略course_db_alg ,和分表策略 course_tbl_alg。其中course_db_alg是使用的ShardingSphere內(nèi)置的簡(jiǎn)單算法MOD取模。如果對(duì)于字符串類型的主鍵,也提供了HASH_MOD進(jìn)行計(jì)算。這兩個(gè)算法都需要配置一個(gè)參數(shù)sharding‐count分片數(shù)量。這種內(nèi)置的算法雖然簡(jiǎn)單,但是不太靈活。 因?yàn)閷?duì)2取模的結(jié)果只能是0和1,而對(duì)于course表來(lái)說(shuō),他的真實(shí)表是course_1和course_2,后綴需要在取模的結(jié)果上加1,這種計(jì)算就沒(méi)法通過(guò)簡(jiǎn)單的取模算法實(shí)現(xiàn)了,所以需要通過(guò)Groovy表達(dá)式進(jìn)行定制。
五、ShardingSphere深入實(shí)戰(zhàn)
?????????理解這些基礎(chǔ)概念之后,我們就繼續(xù)深入更多的分庫(kù)分表場(chǎng)景。下面的過(guò)程會(huì)通過(guò)一系列的問(wèn)題來(lái)給你解釋ShardingSphere最常用的分片策略。這個(gè)過(guò)程強(qiáng)烈建議你自己動(dòng)手試試。因?yàn)椴还苣阒笆觳皇煜hardingSphere,你都需要一步步回顧總結(jié)一下分庫(kù)分表場(chǎng)景下需要解決的是哪些稀奇古怪的問(wèn)題。分庫(kù)分表的問(wèn)題非常非常多,你需要的是學(xué)會(huì)思考,而不是API。
4.1 簡(jiǎn)單INLINE分片算法?
?????????我們之前配置的簡(jiǎn)單分庫(kù)分表策略已經(jīng)可以根據(jù)自動(dòng)生成的cid,將數(shù)據(jù)插入到不同的真實(shí)庫(kù)當(dāng)中。那么當(dāng)然也支持按照cid進(jìn)行數(shù)據(jù)查詢。
@Testpublic void queryCourse() {QueryWrapper<Course> wrapper = new QueryWrapper<Course>();
// wrapper.eq("cid",851198095084486657L);wrapper.in("cid",851198095084486657L,851198095139012609L,851198095180955649L,4L);List<Course> courses = courseMapper.selectList(wrapper);courses.forEach(course -> System.out.println(course));}
?????????像= 和 in 這樣的操作,可以拿到cid的精確值,所以都可以直接通過(guò)表達(dá)式計(jì)算出可能的真實(shí)庫(kù)以及真實(shí)表,ShardingSphere就會(huì)將邏輯SQL轉(zhuǎn)去查詢對(duì)應(yīng)的真實(shí)庫(kù)和真實(shí)表。這些查詢的策略,只要配置了sql-show參數(shù),都會(huì)打印在日志當(dāng)中。
????????如果不使用分片鍵cid進(jìn)行查詢,那么SQL語(yǔ)句就只能根據(jù)actual-nodes到所有的真實(shí)庫(kù)和真實(shí)表里查詢。而這時(shí)ShardingSphere是怎么執(zhí)行的呢?例如,如果直接執(zhí)行select * from course,執(zhí)行情況是這樣的:
2023-04-12 15:55:02.958 INFO 12448 --- [ main] ShardingSphere-SQL : Logic SQL: SELECT cid,cname,user_id,cstatus FROM course2023-04-12 15:55:02.958 INFO 12448 --- [ main] ShardingSphere-SQL : Actual SQL: m0 ::: SELECT cid,cname,user_id,cstatus FROM course_1 UNION ALL SELECT cid,cname,user_id,cstatus FROM course_2
2023-04-12 15:55:02.958 INFO 12448 --- [ main] ShardingSphere-SQL : Actual SQL: m1 ::: SELECT cid,cname,user_id,cstatus FROM course_1 UNION ALL SELECT cid,cname,user_id,cstatus FROM course_2
?????????在之前4.x版本下,這種情況會(huì)拆分成四個(gè)SQL,查詢四次。而當(dāng)前版本下,會(huì)將每一個(gè)真實(shí)庫(kù)里的語(yǔ)句通過(guò)UNION合并成一個(gè)大SQL,一起進(jìn)行查詢。為什么要這樣呢?這其實(shí)是一個(gè)很大的優(yōu)化。因?yàn)槿绻枰獙?duì)一個(gè)真實(shí)庫(kù)進(jìn)行多個(gè)SQL查詢,那么就需要通過(guò)多線程進(jìn)行并發(fā)查詢,這種情況下,如果要進(jìn)行后續(xù)的結(jié)果歸并,比如sum,max這樣的結(jié)果歸并,那就只能將所有的結(jié)果都合并到一個(gè)大內(nèi)存,再進(jìn)行歸并。這種方式稱為內(nèi)存歸并。這種方式是比較消耗內(nèi)存的。而如果合并成了一個(gè)大的SQL,對(duì)一個(gè)真實(shí)庫(kù)只要進(jìn)行一次SQL查詢,這樣就可以通過(guò)一個(gè)線程進(jìn)行查詢。在進(jìn)行結(jié)果歸并時(shí),就可以拿一條數(shù)據(jù)歸并一次。這種方式稱為流式歸并。相比內(nèi)存歸并可以極大的節(jié)約內(nèi)存。
? ? ? ?在使用in進(jìn)行查詢時(shí),有可能計(jì)算出屬于多個(gè)不同的分片。在4.x版本當(dāng)中,如果出現(xiàn)了這種情況,由于ShardingSphere無(wú)法確定in算出來(lái)的分片有多少個(gè),所以遇到這種情況,他就不再去計(jì)算in中所有的分片結(jié)果了,直接改為全路由分片。這樣計(jì)算比較簡(jiǎn)單,但是查詢的效率肯定不好。而在當(dāng)前版本下,則優(yōu)化了這個(gè)問(wèn)題。比如示例當(dāng)中in操作的cid有奇數(shù)也有偶數(shù),新版本就能夠準(zhǔn)確的計(jì)算出m0.course_1和m1.course_2兩個(gè)分片。
4.2 STANDARD標(biāo)準(zhǔn)分片算法
? ????????應(yīng)用當(dāng)中我們可能對(duì)于主鍵信息不只是進(jìn)行精確查詢,還需要進(jìn)行范圍查詢。例如:
@Testpublic void queryCourseRange(){//select * from course where cid between xxx and xxxQueryWrapper<Course> wrapper = new QueryWrapper<>();wrapper.between("cid",799020475735871489L,799020475802980353L);List<Course> courses = courseMapper.selectList(wrapper);courses.forEach(course -> System.out.println(course));}
??????????這時(shí),如果直接執(zhí)行,那么由于ShardingSphere無(wú)法根據(jù)配置的表達(dá)式計(jì)算出可能的分片值,所以執(zhí)行時(shí)他就會(huì)拋出一個(gè)異常。
????????報(bào)錯(cuò)信息明確提到需要添加一個(gè)allow-range-query-with-inline-sharding參數(shù)。這時(shí),就需要給course_tbl_alg算法添加這個(gè)參數(shù)。
# 允許在inline策略中使用范圍查詢。
spring.shardingsphere.rules.sharding.sharding-algorithms.course_tbl_alg.props.allow-range-query-with-inline-sharding=true
?????????加上這個(gè)參數(shù)后,就可以進(jìn)行查詢了。但是這樣就可以了嗎?觀察一下Actual SQL的執(zhí)行方式,你會(huì)發(fā)現(xiàn)這時(shí)SQL還是按照全路由的方式執(zhí)行的。之前一直強(qiáng)調(diào)過(guò),這是效率最低的一種執(zhí)行方式。那么有沒(méi)有辦法通過(guò)查詢時(shí)的范圍下限和范圍上限自己計(jì)算出一個(gè)目標(biāo)真實(shí)庫(kù)和真實(shí)表呢?當(dāng)然是支持的。記住這個(gè)問(wèn)題,在后續(xù)章節(jié)會(huì)帶你解決
5.3??COMPLEX_INLINE復(fù)雜分片算法
???????現(xiàn)在既然可以根據(jù)cid進(jìn)行查詢,那么還可以增加其他的查詢條件嗎?像這樣:
@Testpublic void queryCourseComplexSimple(){QueryWrapper<Course> wrapper = new QueryWrapper<Course>();wrapper.orderByDesc("user_id");wrapper.in("cid",851198095084486657L,851198095139012609L);wrapper.eq("user_id",1001L);List<Course> course = courseMapper.selectList(wrapper);//select * from couse where cid in (xxx) and user_id =xxxSystem.out.println(course);}
?????????執(zhí)行一下,這當(dāng)然是可以的。但是有一個(gè)小問(wèn)題,user_id查詢條件只能參與數(shù)據(jù)查詢,但是并不能參與到分片算法當(dāng)中。例如在我們的示例當(dāng)中,所有的user_id都是1001L,這其實(shí)是數(shù)據(jù)一個(gè)非常明顯的分片規(guī)律。如果user_id的查詢條件不是1001L,那這時(shí)其實(shí)不需要到數(shù)據(jù)庫(kù)中去查,我們也能知道是不會(huì)有結(jié)果的。有沒(méi)有辦法讓user_id也參與到分片算法當(dāng)中呢?
????????當(dāng)然是可以的, 不過(guò)STANDARD策略就不夠用了。這時(shí)候就需要引入COMPLEX_INLINE策略。注釋掉之前給course表配置的分表策略,重新分配一個(gè)新的分表策略:
#給course表指定分表策略 complex-按多個(gè)分片鍵進(jìn)行組合分片
spring.shardingsphere.rules.sharding.tables.course.table-strategy.complex.sharding-columns=cid,user_id
spring.shardingsphere.rules.sharding.tables.course.table-strategy.complex.sharding-algorithm-name=course_tbl_alg
# 分表策略-COMPLEX:按多個(gè)分片鍵組合分表
spring.shardingsphere.rules.sharding.sharding-algorithms.course_tbl_alg.type=COMPLEX_INLINE
spring.shardingsphere.rules.sharding.sharding-algorithms.course_tbl_alg.props.algorithm-expression=course_$->{(cid+user_id+1)%2+1}
????????在這個(gè)配置當(dāng)中,就可以使用cid和user_id兩個(gè)字段聯(lián)合確定真實(shí)表。例如在查詢時(shí),將user_id條件設(shè)定為1002L,此時(shí)不管cid傳什么值,就總是會(huì)路由到錯(cuò)誤的表當(dāng)中,查不出數(shù)據(jù)了。?
5.4?CLASS_BASED 分片算法
????????雖然對(duì)于COMPLEX_INLINE策略,也支持添加allow-range-query-with-inline-sharding參數(shù)讓他能夠支持分片鍵的范圍查詢,但是這時(shí)這種復(fù)雜的分片策略就明顯不能再用一個(gè)簡(jiǎn)單的表達(dá)式來(lái)忽悠了。
????????這就需要一個(gè)Java類來(lái)實(shí)現(xiàn)這樣的規(guī)則。這個(gè)算法類也不用自己瞎設(shè)計(jì),只要實(shí)現(xiàn)ShardingSphere提供的ComplexKeysShardingAlgorithm接口就行了。
??
package com.roy.shardingDemo.algorithm;import com.google.common.collect.Range;
import org.apache.shardingsphere.sharding.api.sharding.complex.ComplexKeysShardingAlgorithm;
import org.apache.shardingsphere.sharding.api.sharding.complex.ComplexKeysShardingValue;
import org.apache.shardingsphere.sharding.exception.syntax.UnsupportedShardingOperationException;import java.util.*;/*** 實(shí)現(xiàn)自定義COMPLEX分片策略* 聲明算法時(shí),ComplexKeysShardingAlgorithm接口可傳入一個(gè)泛型,這個(gè)泛型就是分片鍵的數(shù)據(jù)類型。* 這個(gè)泛型只要實(shí)現(xiàn)了Comparable接口即可。* 但是官方不建議聲明一個(gè)明確的泛型出來(lái),建議是在doSharding中再做類型轉(zhuǎn)換。這樣是為了防止分片鍵類型與算法聲明的類型不符合。*/
public class MyComplexAlgorithm implements ComplexKeysShardingAlgorithm<Long> {private static final String SHARING_COLUMNS_KEY = "sharding-columns";private Properties props;//保留配置的分片鍵。在當(dāng)前算法中其實(shí)是沒(méi)有用的。private Collection<String> shardingColumns;@Overridepublic void init(Properties props) {this.props = props;this.shardingColumns = getShardingColumns(props);}/*** 實(shí)現(xiàn)自定義分片算法* @param availableTargetNames 在actual-nodes中配置了的所有數(shù)據(jù)分片* @param shardingValue 組合分片鍵* @return 目標(biāo)分片*/@Overridepublic Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<Long> shardingValue) {//select * from cid where cid in (xxx,xxx,xxx) and user_id between {lowerEndpoint} and {upperEndpoint};Collection<Long> cidCol = shardingValue.getColumnNameAndShardingValuesMap().get("cid");Range<Long> userIdRange = shardingValue.getColumnNameAndRangeValuesMap().get("user_id");//拿到user_id的查詢范圍Long lowerEndpoint = userIdRange.lowerEndpoint();Long upperEndpoint = userIdRange.upperEndpoint();//如果下限 》= 上限if(lowerEndpoint >= upperEndpoint){//拋出異常,終止去數(shù)據(jù)庫(kù)查詢的操作throw new UnsupportedShardingOperationException("empty record query","course");//如果查詢范圍明顯不包含1001}else if(upperEndpoint<1001L || lowerEndpoint>1001L){//拋出異常,終止去數(shù)據(jù)庫(kù)查詢的操作throw new UnsupportedShardingOperationException("error range query param","course");
// return result;}else{List<String> result = new ArrayList<>();//user_id范圍包含了1001后,就按照cid的奇偶分片String logicTableName = shardingValue.getLogicTableName();//操作的邏輯表 coursefor (Long cidVal : cidCol) {String targetTable = logicTableName+"_"+(cidVal%2+1);if(availableTargetNames.contains(targetTable)){result.add(targetTable);}}return result;}}private Collection<String> getShardingColumns(final Properties props) {String shardingColumns = props.getProperty(SHARING_COLUMNS_KEY, "");return shardingColumns.isEmpty() ? Collections.emptyList() : Arrays.asList(shardingColumns.split(","));}public void setProps(Properties props) {this.props = props;}@Overridepublic Properties getProps() {return this.props;}@Overridepublic String getType(){return "MYCOMPLEX";}
}
????????在核心的dosharding方法當(dāng)中,就可以按照我們之前的規(guī)則進(jìn)行判斷。不滿足規(guī)則,直接拋出UnsupportedShardingOperationException異常,就可以組織ShardingSphere把SQL分配到真實(shí)數(shù)據(jù)庫(kù)中執(zhí)行。
?????????接下來(lái),還是需要增加策略配置,讓course表按照這個(gè)規(guī)則進(jìn)行分片。
# 使用CLASS_BASED分片算法- 不用配置SPI擴(kuò)展文件
spring.shardingsphere.rules.sharding.sharding-algorithms.course_tbl_alg.type=CLASS_BASED
# 指定策略 STANDARD|COMPLEX|HINT
spring.shardingsphere.rules.sharding.sharding-algorithms.course_tbl_alg.props.strategy=COMPLEX
# 指定算法實(shí)現(xiàn)類。這個(gè)類必須是指定的策略對(duì)應(yīng)的算法接口的實(shí)現(xiàn)類。 STANDARD-> StandardShardingAlgorithm;COMPLEX->ComplexKeysShardingAlgorithm;HINT -> HintShardingAlgorithm
spring.shardingsphere.rules.sharding.sharding-algorithms.course_tbl_alg.props.algorithmClassName=com.roy.shardingDemo.algorithm.MyComplexAlgorithm
?5.5 HINT_INLINE強(qiáng)制分片算法
?????????接下來(lái)我們把查詢場(chǎng)景再進(jìn)一步,需要查詢所有cid為奇數(shù)的課程信息。這要怎么查呢?按照MyBatis-plus的機(jī)制,你應(yīng)該很快能想到在CourseMapper中實(shí)現(xiàn)一個(gè)自定義的SQL語(yǔ)句就行了。
public interface CourseMapper extends BaseMapper<Course> {@Select("select * from course where MOD(cid,2)=1")List<Long> unsupportSql();
}
?????????OK,拿過(guò)去試試。
@Testpublic void unsupportTest(){//select * from course where mod(cid,2)=1List<Long> res = courseMapper.unsupportSql();res.forEach(System.out::println);}
?????????執(zhí)行結(jié)果當(dāng)然是沒(méi)有問(wèn)題。但是你會(huì)發(fā)現(xiàn),分片的問(wèn)題又出來(lái)了。在我們當(dāng)前的這個(gè)場(chǎng)景下,course的信息就是按照cid的奇偶分片的,所以自然是希望只去查某一個(gè)真實(shí)表就可以了。這種基于虛擬列的查詢語(yǔ)句,對(duì)于ShardingSphere來(lái)說(shuō)實(shí)際上是一塊難啃的骨頭。因?yàn)樗茈y解析出你是按照cid分片鍵進(jìn)行查詢的,并且不知道怎么組織對(duì)應(yīng)的策略去進(jìn)行分庫(kù)分表。所以他的做法只能又是性能最低的全路由查詢。
實(shí)際上ShardingSphere無(wú)法正常解析的語(yǔ)句還有很多?;旧嫌蒙戏謳?kù)分表后,你的應(yīng)用就應(yīng)該要和各種多表關(guān)聯(lián)查詢、多層嵌套子查詢、distinct查詢等各種復(fù)雜查詢分手了。
?????????這個(gè)cid的奇偶關(guān)系并不能通過(guò)SQL語(yǔ)句正常體現(xiàn)出來(lái),這時(shí),就需要用上ShardingSphere提供的另外一種分片算法HINT強(qiáng)制路由。HINT強(qiáng)制路由可以用一種與SQL無(wú)關(guān)的方式進(jìn)行分庫(kù)分表。
????????注釋掉之前給course表分配的分表算法,重新分配一個(gè)HINT_INLINE類型的分表算法。
#給course表指定分表策略 hint-與SQL無(wú)關(guān)的方式進(jìn)行分片
spring.shardingsphere.rules.sharding.tables.course.table-strategy.hint.sharding-algorithm-name=course_tbl_alg
# 分表策略-HINT:用于SQL無(wú)關(guān)的方式分表,使用value關(guān)鍵字。
spring.shardingsphere.rules.sharding.sharding-algorithms.course_tbl_alg.type=HINT_INLINE
spring.shardingsphere.rules.sharding.sharding-algorithms.course_tbl_alg.props.algorithm-expression=course_$->{value}
????????? 然后,在應(yīng)用進(jìn)行查詢時(shí),使用HintManager給HINT策略指定value的值。
@Testpublic void queryCourseByHint(){//強(qiáng)制只查course_1表HintManager hintManager = HintManager.getInstance();// 強(qiáng)制查course_1表hintManager.addTableShardingValue("course","1");//select * from course;List<Course> courses = courseMapper.selectList(null);courses.forEach(course -> System.out.println(course));//線程安全,所有用完要注意關(guān)閉。hintManager.close();//hintManager關(guān)閉的主要作用是清除ThreadLocal,釋放內(nèi)存。HintManager實(shí)現(xiàn)了AutoCloseable接口,所以建議使用try-resource的方式,用完自動(dòng)關(guān)閉。//try(HintManager hintManager = HintManager.getInstance()){ xxxx }}
?????????這樣就可以讓SQL語(yǔ)句只查詢course_1表,在當(dāng)前場(chǎng)景下,也就相當(dāng)于是實(shí)現(xiàn)了只查cid為奇數(shù)的需求。
5.6 常用策略總結(jié)
?????????在之前的示例中就介紹了ShardingSphere提供的MOD、HASH-MOD這樣的簡(jiǎn)單內(nèi)置分片策略,standard、complex、hint三種典型的分片策略以及CLASS_BASED這種擴(kuò)展分片策略的方法。為什么要有這么多的分片策略,其實(shí)就是以為分庫(kù)分表面臨的業(yè)務(wù)場(chǎng)景其實(shí)是很復(fù)雜的。即便是ShardingSphere,也無(wú)法真的像MySQL、Oracle這樣的數(shù)據(jù)庫(kù)產(chǎn)品一樣,完美的兼容所有的SQL語(yǔ)句。因此,一旦開(kāi)始決定用分庫(kù)分表,那么后續(xù)業(yè)務(wù)中的每一個(gè)SQL語(yǔ)句就都需要結(jié)合分片策略進(jìn)行思考,不能像操作真正數(shù)據(jù)庫(kù)那樣隨心所欲了。