狂创客圈 经典图书 : 《Netty Zookeeper Redis 高并发实战》 面试必备 + 面试必备 + 面试必备 【博客园总入口 】html
疯狂创客圈 经典图书 : 《SpringCloud、Nginx高并发核心编程》 大厂必备 + 大厂必备 + 大厂必备 【博客园总入口 】java
入大厂+涨工资必备: 高并发【 亿级流量IM实战】 实战系列 【 SpringCloud Nginx秒杀】 实战系列 【博客园总入口 】mysql
组件 | 连接地址 |
---|---|
windows centos 虚拟机 安装&排坑 | vagrant+java+springcloud+redis+zookeeper镜像下载(&制做详解)) |
centos mysql 安装&排坑 | centos mysql 笔记(内含vagrant mysql 镜像) |
linux kafka安装&排坑 | kafka springboot (或 springcloud ) 整合 |
Linux openresty 安装 | Linux openresty 安装 |
【必须】Linux Redis 安装(带视频) | Linux Redis 安装(带视频) |
【必须】Linux Zookeeper 安装(带视频) | Linux Zookeeper 安装, 带视频 |
Windows Redis 安装(带视频) | Windows Redis 安装(带视频) |
RabbitMQ 离线安装(带视频) | RabbitMQ 离线安装(带视频) |
ElasticSearch 安装, 带视频 | ElasticSearch 安装, 带视频 |
Nacos 安装(带视频) | Nacos 安装(带视频) |
【必须】Eureka | Eureka 入门,带视频 |
【必须】springcloud Config 入门,带视频 | springcloud Config 入门,带视频 |
【必须】SpringCloud 脚手架打包与启动 | SpringCloud脚手架打包与启动 |
Linux 自启动 假死自启动 定时自启 | Linux 自启动 假死启动 |
组件 | 连接地址 |
---|---|
准备一: 在window安装虚拟机集群 | vagrant+java+springcloud+redis+zookeeper镜像下载(&制做详解)) |
准备二:在虚拟机上安装 mysql ,至少须要两个mysql节点 | centos mysql 笔记(内含vagrant mysql 镜像) |
Sharding-JDBC 从入门到精通之一 | 入门实战 |
Sharding-JDBC 从入门到精通之二 | 基本原理 |
Sharding-JDBC 从入门到精通之源码 | git |
有关Sharding-JDBC介绍这里就不在多说,以前Sharding-JDBC是当当网自研的关系型数据库的水平扩展框架,如今已经捐献给Apache,其原理请参见后面的博客。linux
shardingsphere文档地址是:https://shardingsphere.apache.org/document/current/cn/overview/。nginx
在深刻了解以前,先实战一把,增长印象, 激发兴趣。git
通常状况下,你们都会使用水平切分库和表:将一张表水平切分红多张表,还能够放到多个库中。这就涉及到数据分片的规则,比较常见的有:Hash取模分表、数值Range分表、一致性Hash算法分表。面试
概念 通常采用Hash取模的切分方式,例如:假设按goods_id分4张表。(goods_id%4 取整肯定表)redis
后期分片集群扩容时,须要迁移旧的数据很难。算法
容易面临跨分片查询的复杂问题。好比上例中,若是频繁用到的查询条件中不带goods_id时,将会致使没法定位数据库,从而须要同时向4个库发起查询,
再在内存中合并数据,取最小集返回给应用,分库反而成为拖累。spring
概念 按照时间区间或ID区间来切分。例如:将goods_id为11000的记录分到第一个表,10012000的分到第二个表,以此类推。
一致性Hash算法能很好的解决由于Hash取模而产生的分片集群扩容时,须要迁移旧的数据的难题。至于具体原理这里就不详细说,
能够参考一篇博客:一致性哈希算法(分库分表,负载均衡等)
假设一个订单表的user_id和order_id 分布较为均匀,按照1000W的数据规模,可使用以下的分库、分表结构来保存:
db0 ├── t_order0 └── t_order1 db1 ├── t_order0 └── t_order1
简单的进行分库分表: 按照user_id %2 的规则进行分库,按照 order_id %2 的规则进行分表
逻辑订单表的结构以下:
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`));
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两个表
本文分库样例比较简单,根据数据库表中字段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
本文使用SpringBoot2,SpringData-JPA,Druid链接池,和当当的sharding-jdbc 5。
新建项目,加入当当的sharding-jdbc-core依赖和druid链接池。请参见源码工程。
使用@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" + "----------------------------------------------------------"); } }
就是简单的实体和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(); } }
更多详细内容请参见源码工程。
接下来建立一个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); } }
启动应用。
而后,在浏览器或HTTP请求工具访问http://localhost:7700/sharding-jdbc-provider/swagger-ui.html,如图所示
使用插入订单的接口,能够插入订单, 注意 userid %2 ==0 进入 db1, 注意 userid %2 ==1进入 db2, 具体在哪一个表呢?
由于 orderid是经过雪花算法生成的,若是orderid%2==0 ,则进入t_order_0,不然使用t_order_1。
插入以后,能够经过数据库,看结果。具体以下图:
使用程序的查询所有的方法,shardingjdbc ,会查出全部的订单。
使用shardingjdbc ,除了数据源的配置有些特殊的规则外, 持久层程序和普通的 JPA代码,区别并不大。
固然,若是要实现特殊的分库分表逻辑,仍是须要动代码的,请看后续分解。
疯狂创客圈 - Java高并发研习社群,为你们开启大厂之门