ShardingSphere結(jié)合MySQL實(shí)現(xiàn)分庫分表的項(xiàng)目實(shí)踐
ShardingSphere介紹
Apache ShardingSphere 是一套開源的分布式數(shù)據(jù)庫中間件解決方案組成的生態(tài)圈,它由JDBC、Proxy和Sidecar(規(guī)劃中)這3款相互獨(dú)立,卻又能夠混合部署配合使用的產(chǎn)品組成。 它們均提供標(biāo)準(zhǔn)化的數(shù)據(jù)分片、分布式事務(wù)和數(shù)據(jù)庫治理功能,可適用于如 Java 同構(gòu)、異構(gòu)語言、云原生等各種多樣化的應(yīng)用場景。
- 一套開源的分布式數(shù)據(jù)庫中間件解決方案
- 有3個產(chǎn)品:JDBC、Proxy、Sidecar
分庫分表
當(dāng)我們使用讀寫分離、索引、緩存后,數(shù)據(jù)庫的壓力還是很大的時候,這就需要使用到數(shù)據(jù)庫拆分了。
- 一般來說, MySQL推薦的單表數(shù)據(jù)量在500w ~ 800w, 超過800w則建議分表.
- 或是在系統(tǒng)接口響應(yīng)時間明顯變慢, 并且通過代碼優(yōu)化, 改寫sql等形式無法獲得明顯提升, 且明確了性能瓶頸在數(shù)據(jù)庫時, 建議分表
- 分庫則是在分表后單庫性能到達(dá)瓶頸后進(jìn)行, 如果個人或項(xiàng)目有錢任性的除外.分庫主要解決的是并發(fā)量大的問題。
垂直拆分
分表
表中的字段較多,一般將不常用的、 數(shù)據(jù)較大、長度較長的拆分到“擴(kuò)展表“。一般情況加表的字段可能有幾百列,此時是按照字段進(jìn)行數(shù)豎直切。注意垂直分是列多的情況。
分庫
一個數(shù)據(jù)庫的表太多。此時就會按照一定業(yè)務(wù)邏輯進(jìn)行垂直切,比如用戶相關(guān)的表放在一個數(shù)據(jù)庫里,訂單相關(guān)的表放在一個數(shù)據(jù)庫里。注意此時不同的數(shù)據(jù)庫應(yīng)該存放在不同的服務(wù)器上,此時磁盤空間、內(nèi)存、TPS等等都會得到解決。
優(yōu)點(diǎn):
- 拆分后業(yè)務(wù)清晰,拆分規(guī)則明確。
- 系統(tǒng)之間整合或擴(kuò)展容易。
- 數(shù)據(jù)維護(hù)簡單。
缺點(diǎn):
- 部分業(yè)務(wù)表無法 join,只能通過接口方式解決,提高了系統(tǒng)復(fù)雜度。
- 受每種業(yè)務(wù)不同的限制存在單庫性能瓶頸,不易數(shù)據(jù)擴(kuò)展跟性能提高。
- 事務(wù)處理復(fù)雜。
水平拆分
分表
單表的數(shù)據(jù)量太大。按照某種規(guī)則(RANGE,HASH取模等),切分到多張表里面去。 但是這些表還是在同一個庫中,所以庫級別的數(shù)據(jù)庫操作還是有IO瓶頸。這種情況是不建議使用的,因?yàn)閿?shù)據(jù)量是逐漸增加的,當(dāng)數(shù)據(jù)量增加到一定的程度還需要再進(jìn)行切分。比較麻煩。
分庫
水平分庫理論上切分起來是比較麻煩的,它是指將單張表的數(shù)據(jù)切分到多個服務(wù)器上去,每個服務(wù)器具有相應(yīng)的庫與表,只是表中數(shù)據(jù)集合不同。 水平分庫分表能夠有效的緩解單機(jī)和單庫的性能瓶頸和壓力,突破IO、連接數(shù)、硬件資源等的瓶頸。
優(yōu)點(diǎn):
- 不存在單庫大數(shù)據(jù),高并發(fā)的性能瓶頸。
- 對應(yīng)用透明,應(yīng)用端改造較少。
- 按照合理拆分規(guī)則拆分,join 操作基本避免跨庫。
- 提高了系統(tǒng)的穩(wěn)定性跟負(fù)載能力。
缺點(diǎn):
- 拆分規(guī)則難以抽象。
- 分片事務(wù)一致性難以解決。
- 數(shù)據(jù)多次擴(kuò)展難度跟維護(hù)量極大。
- 跨庫 join 性能較差。
分片后的常見問題
數(shù)據(jù)傾斜
這個幾乎是無法避免的, 即使是id取模, 也會因?yàn)閿?shù)據(jù)的刪除導(dǎo)致每張分表的數(shù)據(jù)不一樣, 或者id是UUID, 取模也會導(dǎo)致數(shù)據(jù)發(fā)生傾斜. 但是一般來說傾斜只要不是太離譜, 都在我們的接受范圍以內(nèi).
id生成策略
如果分片之前你的id是遞增的, 那么分片后你就無法保證id的全局唯一性, 這時比較常見的業(yè)內(nèi)方案就是UUID或者SnowFlake.
當(dāng)然如果想要排序和分頁, 就需要有個id生成器去統(tǒng)一集中生成連續(xù)的id(參考下文).
全路由
這個是最糟糕的情況, 這種情況會讓我們的查詢比分片之前還要慢, 可以在自定義的分片算法中校驗(yàn)這種情況直接拋出異常, 然后coder們根據(jù)日志中的報(bào)錯來統(tǒng)計(jì)這部分sql加以改寫.
jpa級聯(lián)
如果jpa級聯(lián)中包含分表, 則需要拆除這種級聯(lián)關(guān)系, 以免導(dǎo)致上述全路由情況發(fā)生.
排序&分頁
如果只是單獨(dú)分頁, Sharding Sphere會剔除數(shù)據(jù)不寫入內(nèi)存, 實(shí)際上不會導(dǎo)致內(nèi)存的大量占用, 但如果加上排序, 那情況就不容樂觀了, 官方建議通過可以保證連續(xù)性的id去加以限制.
四種分片算法&五種分片策略
4種分片算法
精確分片算法
對應(yīng)PreciseShardingAlgorithm,用于處理使用單一鍵作為分片鍵的=與IN進(jìn)行分片的場景。需要配合StandardShardingStrategy使用。
范圍分片算法
對應(yīng)RangeShardingAlgorithm,用于處理使用單一鍵作為分片鍵的BETWEEN AND、>、<、>=、<=進(jìn)行分片的場景。需要配合StandardShardingStrategy使用。
復(fù)合分片算法
對應(yīng)ComplexKeysShardingAlgorithm,用于處理使用多鍵作為分片鍵進(jìn)行分片的場景,包含多個分片鍵的邏輯較復(fù)雜,需要應(yīng)用開發(fā)者自行處理其中的復(fù)雜度。需要配合ComplexShardingStrategy使用。
Hint分片算法
對應(yīng)HintShardingAlgorithm,用于處理使用Hint行分片的場景。需要配合HintShardingStrategy使用。
5種分片策略
標(biāo)準(zhǔn)分片策略
對應(yīng)StandardShardingStrategy。提供對SQL語句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持單分片鍵,提供PreciseShardingAlgorithm和RangeShardingAlgorithm兩個分片算法。PreciseShardingAlgorithm是必選的,用于處理=和IN的分片。RangeShardingAlgorithm是可選的,用于處理BETWEEN AND, >, <, >=, <=分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND將按照全庫路由處理。
復(fù)合分片策略
對應(yīng)ComplexShardingStrategy。復(fù)合分片策略。提供對SQL語句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片鍵,由于多分片鍵之間的關(guān)系復(fù)雜,因此并未進(jìn)行過多的封裝,而是直接將分片鍵值組合以及分片操作符透傳至分片算法,完全由應(yīng)用開發(fā)者實(shí)現(xiàn),提供最大的靈活度。
行表達(dá)式分片策略
對應(yīng)InlineShardingStrategy。使用Groovy的表達(dá)式,提供對SQL語句中的=和IN的分片操作支持,只支持單分片鍵。對于簡單的分片算法,可以通過簡單的配置使用,從而避免繁瑣的Java代碼開發(fā),如: t_user_$->{u_id % 8}
表示t_user表根據(jù)u_id模8,而分成8張表,表名稱為t_user_0
到t_user_7
。
Hint分片策略
對應(yīng)HintShardingStrategy。通過Hint指定分片值而非從SQL中提取分片值的方式進(jìn)行分片的策略。
不分片策略
對應(yīng)NoneShardingStrategy。不分片的策略。
ShardingSphere-JDBC
定位為輕量級 Java 框架,在 Java 的 JDBC 層提供的額外服務(wù)。 它使用客戶端直連數(shù)據(jù)庫,以 jar 包形式提供服務(wù),無需額外部署和依賴,可理解為增強(qiáng)版的 JDBC 驅(qū)動,完全兼容 JDBC 和各種 ORM 框架。
需要注意的是,分庫分表并不是由 ShardingSphere-JDBC 來做,它是用來負(fù)責(zé)操作已經(jīng)分完之后的 CRUD 操作。
水平分表實(shí)操(單分片鍵,分片類型:HASH_MODE)
環(huán)境使用:SpringBoot 2.7.12 + MybatisPlus + ShardingSphere-jdbc 5.2.0 + Druid連接池
本示例為單庫,庫內(nèi)有6個分表,并且按照order_id的hash值進(jìn)行取模計(jì)算得到實(shí)際表
添加Maven依賴
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.1.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency>
按照水平分表創(chuàng)建數(shù)據(jù)庫
CREATE TABLE `t_order_5` ( `order_id` bigint NOT NULL AUTO_INCREMENT, `price` double(12,2), `user_id` int NOT NULL, `address_id` bigint NOT NULL, `city` varchar(32) NULL DEFAULT NULL, `status` tinyint NULL DEFAULT NULL, `interval_time` datetime NULL DEFAULT NULL, `creator` varchar(32) NULL DEFAULT NULL, `create_time` datetime NULL DEFAULT NULL, `updater` varchar(32) NULL DEFAULT NULL, `update_time` datetime NULL DEFAULT NULL, PRIMARY KEY (`order_id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic
配置Sharding-jdbc分片策略
application.yml內(nèi)容:
spring: main: banner-mode: off shardingsphere: # 配置數(shù)據(jù)源,給數(shù)據(jù)源起別名ds datasource: ds: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8 username: root password: 1234 names: ds rules: sharding: binding-tables: - t_order,t_order_item broadcast-tables: t_address # 分片算法 sharding-algorithms: t-order-algorithm: type: HASH_MOD props: sharding-count: '6' t-order-item-inline: type: INLINE props: algorithm-expression: t_order_item_$->{order_id % 2} tables: # 分表 t_order: actual-data-nodes: ds.t_order_$->{0..5} # 真實(shí)表名 table-strategy: standard: sharding-algorithm-name: t-order-algorithm sharding-column: order_id # 分片鍵 t_order_item: actual-data-nodes: ds.t_order_item_$->{0..1} table-strategy: standard: sharding-algorithm-name: t-order-item-inline sharding-column: order_id props: sql-show: true #mybatis-plus: # global-config: # banner: false # db-config: # id-type: assign_id #使用雪花算法開啟數(shù)據(jù)入庫時ID自增 # logic-delete-field: deleted # logic-delete-value: 1 # logic-not-delete-value: 0 # #開啟mp的日志(控制臺輸出) # configuration: # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl mybatis: mapper-locations: classpath*:/mappers/*-mapper.xml type-aliases-package: com.panliu.domain configuration: default-fetch-size: 20 default-statement-timeout: 30 map-underscore-to-camel-case: true use-generated-keys: true
測試代碼運(yùn)行
@Slf4j @SpringBootTest public class OrderMapperTests { private final static String[] CITIES = {"shanghai", "beijing"}; /** * -120000, 0, 18, 20000, 40000, 50000, 60000, 64000, 80000, 99000 */ private final static long[] PRICES = {-120000, 0, 18, 20000, 40000, 50000, 60000, 64000, 80000, 99000}; @Autowired private OrderMapper orderMapper; @Test //@Disabled void save() { ThreadLocalRandom random = ThreadLocalRandom.current(); Date[] dates = create(); IntStream.range(0, 20).forEach(i -> { Order order = new Order(); order.setOrderId(System.nanoTime() + i); order.setPrice(PRICES[i % PRICES.length]); order.setAddressId(i); order.setCity(CITIES[i % 2]); order.setUserId(Math.abs(random.nextInt())); order.setCreator("user.0" + i); order.setIntervalTime(dates[i % 5]); order.setUpdater(order.getCreator()); log.info("====>{}", order); orderMapper.save(order); }); } @Test // @Disabled void findAll() { List<Order> list = orderMapper.findAllAtPrice(60000); log.info("===>{}", list); } private Date[] create() { Date[] dates = new Date[6]; try { Date date = DateUtils.parseDate("2023-08-14 00:00:00", Locale.CHINA, "yyyy-MM-dd HH:mm:ss"); IntStream.range(0, 6).forEach(i -> dates[i] = DateUtils.addDays(date, i)); } catch (ParseException e) { log.error("date parse fail: ", e); } return dates; } }
運(yùn)行結(jié)果
復(fù)合分片(根據(jù)城市和order_id分片)
application.yml配置
rules: sharding: sharding-algorithms: t-order-algorithm: type: COMPLEX_INLINE props: algorithm-expression: t_order_$->{city}_$->{order_id % 2} sharding-columns: city,order_id tables: t_order: actual-data-nodes: ds.t_order_$->{['shanghai','beijing']}_$->{0..1} table-strategy: complex: sharding-algorithm-name: t-order-algorithm sharding-columns: city,order_id
分片結(jié)果
到此這篇關(guān)于使用ShardingSphere實(shí)現(xiàn)MySQL分庫分表的文章就介紹到這了,更多相關(guān)ShardingSphere MySQL分庫分表內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot整合FTPClient線程池的實(shí)現(xiàn)示例
這篇文章主要介紹了Spring Boot整合FTPClient線程池的實(shí)現(xiàn)示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12關(guān)于maven環(huán)境的安裝及maven集成idea環(huán)境的問題
Maven 是一個基于 Java 的工具,所以要做的第一件事情就是安裝 JDK。本文重點(diǎn)給大家介紹關(guān)于maven環(huán)境的安裝及和idea環(huán)境的集成問題,感興趣的朋友一起看看吧2021-09-09Java+MySQL實(shí)現(xiàn)設(shè)計(jì)優(yōu)惠券系統(tǒng)
這篇文章主要介紹了Java+MySQL實(shí)現(xiàn)設(shè)計(jì)優(yōu)惠券系統(tǒng),文章基于Java與MySQL的相關(guān)資料展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-05-05因不會遠(yuǎn)程debug調(diào)試我被項(xiàng)目經(jīng)理嘲笑了
這篇文章主要介紹了遠(yuǎn)程debug調(diào)試的相關(guān)內(nèi)容,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08自定義@RequestBody注解如何獲取JSON數(shù)據(jù)
這篇文章主要介紹了自定義@RequestBody注解如何獲取JSON數(shù)據(jù)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04解決idea創(chuàng)建版本時只有Java21和Java17選項(xiàng)
你是否在使用IntelliJ?IDEA創(chuàng)建新項(xiàng)目時遇到了只有Java?21和Java?17的選項(xiàng)?別擔(dān)心,我們的指南將為你提供解決方案,通過簡單的步驟,你將能夠選擇你需要的任何Java版本,繼續(xù)閱讀,讓我們開始吧!2024-03-03