赞
踩
Apache ShardingSphere 是一套开源的分布式数据库解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。 它们均提供标准化的数据水平扩展、分布式事务和分布式治理等功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。
Apache ShardingSphere 旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。 关系型数据库当今依然占有巨大市场份额,是企业核心系统的基石,未来也难于撼动,我们更加注重在原有基础上提供增量,而非颠覆。
Apache ShardingSphere 5.x 版本开始致力于可插拔架构,项目的功能组件能够灵活的以可插拔的方式进行扩展。 目前,数据分片、读写分离、数据加密、影子库压测等功能,以及 MySQL、PostgreSQL、SQLServer、Oracle 等 SQL 与协议的支持,均通过插件的方式织入项目。 开发者能够像使用积木一样定制属于自己的独特系统。Apache ShardingSphere 目前已提供数十个 SPI 作为系统的扩展点,仍在不断增加中。
数据分⽚后,不同数据节点⽣成全局唯⼀主键是⾮常棘⼿的问题,同⼀个逻辑表(t_order)内的不同真实表(t_order_n)之间的⾃增键由于⽆法互相感知而产⽣重复主键。
尽管可通过设置⾃增主键 初始值 和 步⻓ 的⽅式避免ID碰撞,但这样会使维护成本加大,乏完整性和可扩展性。如果后去需要增加分片表的数量,要逐一修改分片表的步长,运维成本非常高,所以不建议这种方式。
建议使用分布式主键⽣成器等开源项目。
pom
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis驱动-->
<!-- mybatis plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--shardingsphere最新版本-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-core-common</artifactId>
<version>4.0.0-RC1</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-namespace</artifactId>
<version>4.0.0-RC1</version>
</dependency>
<!--lombok实体工具-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
application.yaml
server:
port: 10001
# mybatis-plus相关配置
mybatis-plus:
# xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)
mapper-locations: classpath:mapper/*.xml
typeAliasesPackage: com.whxd.sharding.web.entity
configuration:
# 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射
map-underscore-to-camel-case: true
# 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段
call-setters-on-nulls: true
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
spring:
main:
allow-bean-definition-overriding: true
shardingsphere:
datasource:
#数据源名称,多数据源以逗号分隔
names: logs0,logs1
#配置数据源具体内容,包含连接池,驱动,地址,用户名和密码
logs0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://xxx.xxx.xxx.xxx:3306/logs0?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=true&&serverTimezone=Asia/Shanghai
username: xxxxxx
password: xxxxxx
logs1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://xxx.xxx.xxx.xxx:3306/logs1?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=true&&serverTimezone=Asia/Shanghai
username: xxxxxx
password: xxxxxx
sharding:
tables:
operator_log:
actual-data-nodes: logs$->{0..1}.operator_log_$->{0..5}
table-strategy:
inline:
sharding-column: id
algorithm-expression: operator_log_$->{id % 5}
#分表策略,同分库策略
key-generator:
column: id
#自增列值生成器类型,缺省表示使用默认自增列值生成器。可使用用户自定义的列值生成器或选择内置类型:SNOWFLAKE/UUID
type: SNOWFLAKE
default-database-strategy:
inline:
sharding-column: id
algorithm-expression: logs$->{id % 2}
props:
sql:
show: true
启动类
@SpringBootApplication
@MapperScan("com.whxd.sharding.web.mapper")
public class ShardingSphereApplication {
public static void main(String[] args) {
SpringApplication.run(ShardingSphereApplication.class, args);
}
@Bean
@ConditionalOnMissingBean
public PaginationInterceptor paginationInterceptor() {
// 开启 count 的 join 优化,只针对 left join !!!
return new PaginationInterceptor();
}
}
controller层
/**
* <p>
* 前端控制器
* </p>
*
* @author KimWu
* @since 2021-08-09
*/
@RestController
@RequestMapping("/operatorLog")
public class OperatorLogController {
@Autowired
private IOperatorLogService operatorLogService;
@PostMapping("/insert")
public String insert(){
try {
for (int i = 1; i<100;i++){
operatorLogService.save(build());
}
return "成功";
} catch (Exception e) {
e.printStackTrace();
return "失败";
}
}
@PostMapping("/page")
public List<OperatorLog> page(){
List<OperatorLog> list = operatorLogService.list();
return list;
}
private OperatorLog build(){
return new OperatorLog(
null,System.currentTimeMillis()+"",null,null,null,
null,null, null,null, CreatName.getName(),null,null,
//随机生成姓名
CreatName.getName(),null,null,null,null,null,null
);
}
}
数据库表的sql
DROP TABLE IF EXISTS `operator_log`;
CREATE TABLE `operator_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`excute_time` mediumtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '接口執行耗時(毫秒)',
`remote_addr` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求IP',
`request_method` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'HTTP方法',
`system` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '所属系统',
`module` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '請求模塊',
`api` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '接口功能描述',
`uri` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求URL',
`params` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求参数',
`user_name` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作人名称',
`user_id` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作人id',
`session_id` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '会话ID',
`cur_user` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '当前用户(預留字段)',
`bean_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '类名',
`result` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求结果',
`time` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求时间',
`exc_name` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '异常名称',
`exc_message` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '异常内容',
`created` datetime(0) NULL DEFAULT NULL COMMENT '創建時間',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
创建两个数据库log0,log1,分别创建operator_log_0至4的表
调用新增的接口,看是否每张表都插入了数据,
而后调用查询接口,看是否能够都查询出来。
启动类上添加注解
@EnableTransactionManagement
@SpringBootApplication
@MapperScan("com.whxd.sharding.web.mapper")
@EnableTransactionManagement
public class ShardingSphereApplication {
public static void main(String[] args) {
SpringApplication.run(ShardingSphereApplication.class, args);
}
@Bean
@ConditionalOnMissingBean
public PaginationInterceptor paginationInterceptor() {
// 开启 count 的 join 优化,只针对 left join !!!
return new PaginationInterceptor();
}
}
执行方法上加上
@ShardingTransactionType(TransactionType.XA)
@Transactional(rollbackFor = Exception.class)
@PostMapping("/insert")
@ShardingTransactionType(TransactionType.XA)
@Transactional(rollbackFor = Exception.class)
public String insert() {
for (int i = 1; i < 100; i++) {
operatorLogService.save(build());
if (i == 90) {
throw new RuntimeException("系统异常");
}
}
return "成功";
}
测试执行完成后,已插入数据进行了事务回滚。保证了分库分表时的一致性。
测试案例使用环境:
①两个数据库 logs0和logs1
②分别5张数据表,共10张数据表。operator_log_0 - 4
③数据库未建立索引
插入一百万条数据
@PostMapping("/insert")
@ShardingTransactionType(TransactionType.XA)
@Transactional(rollbackFor = Exception.class)
public String insert() {
for (int i = 1; i <= 1000000; i++) {
operatorLogService.save(build());
log.info("正在执行数据库插入 {}", i);
}
return "成功";
}
方法执行时间耗时较长,建议空闲时段执行,避免cpu飙升。
查询方法
@PostMapping("/page")
public List<OperatorLog> page() {
long startTime = System.currentTimeMillis();
List<OperatorLog> list = operatorLogService.list(new QueryWrapper<OperatorLog>().gt("excute_time",1629774891192L));
log.info("耗时 : {} 秒 ",(System.currentTimeMillis()-startTime)/1000D);
return list;
}
当数据库的数据量过大,大到一定的程度,我们就可以进行分库分表。
分库分表在单表数据量很大的情况下,使用该种方法是一种不错的选择(常用的可以使用日志记录场景)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。