ShardingSphere
Apache ShardingSphere 是一套开源的分布式数据库解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。 它们均提供标准化的数据水平扩展、分布式事务和分布式治理等功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。
以上是ShardingSphere官方的介绍,其实说得简单点就是ShardingSphere为我们提供了一整套数据库扩展解决方案,让我们可以很方便进行表的扩展、库的扩展。
在互联网场景下,我们关系型数据库中存储的数据量会很大,如果不进行表的拆分、库的拆分,会导致单表的数据量巨大,一方面单库的存储限制,另一方面单表数据量大会出现性能瓶颈,所以一般常见的解决方案就是进行分库、分库,那分库、分表会带来一些问题,比如
- 将一个表拆成多个表,写入一条数据该写到哪个表?
- 表拆分后,该如何去查询数据?
ShardingSphere提供了对应的解决方案,我们先来看看ShardingJDBC是如何解决的
ShardingJDBC
ShardingJDBC定位为轻量的Java框架,相当于是对JDBC功能的增强,以Jar包的形式提供服务,完全兼容 JDBC 和各种 ORM 框架
如上图所示,是整个ShardingJDBC的调用结构图,ShardingJDBC是嵌入到业务代码层,相当于接管了原来JDBC的功能,数据库相关的操作全部由ShardingJDBC来完成。
以上是ShardingJDBC简单的介绍,下面通过一个例子来看看如何使用ShardingJDBC
简单的示例
SpringBoot2.4.3 + mybatis-plus3.1.1 + ShardingJDBC4.1.1
表结构
CREATE TABLE `tb_user` (
`user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`user_name` varchar(20) NOT NULL COMMENT '用户名称',
`user_address` varchar(100) NOT NULL COMMENT '用户地址',
`age` int(11) NOT NULL COMMENT '年龄',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=230 DEFAULT CHARSET=utf8 COMMENT='用户信息表';
Maven依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.3</version>
<relativePath />
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--Mybatis-Plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.1</version>
</dependency>
<!-- for spring boot -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
<!-- for spring namespace -->
<!--<dependency>-->
<!--<groupId>org.apache.shardingsphere</groupId>-->
<!--<artifactId>sharding-jdbc-spring-namespace</artifactId>-->
<!--<version>4.1.1</version>-->
<!--</dependency>-->
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>3.0.6</version>
</dependency>
</dependencies>
User实体
@Data
@TableName("tb_user")
public class User {
@TableId("user_id")
private Integer userId;
@TableField("user_name")
private String userName;
@TableField("user_address")
private String userAddress;
@TableField("age")
private Integer age;
}
Mapper和Service
public interface UserMapper extends BaseMapper<User> {
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public void insert(User user){
int count = userMapper.insert(user);
System.out.println("insert count:" + count);
}
public User getUser(Long userId){
return userMapper.selectById(userId);
}
}
配置文件
server:
port: 8090
spring:
main:
allow-bean-definition-overriding: true
shardingsphere:
datasource:
names: testdb0,testdb1
testdb0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/testdb0?useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
testdb1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/testdb1?useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
# 分库配置
sharding:
tables:
tb_user: # 分库的表名
actual-data-nodes: testdb$->{0..1}.tb_user # 分片数据节点配置,Grovvy表达式,表示testdb0、testdb1
# 分库配置
database-strategy: # 分库策略配置
inline: # inline表示行表达式策略,还有其他分片策略
sharding-column: user_id # tb_user表的分片字段
algorithm-expression: testdb$->{user_id % 2} # 分片策略,表示user_id对2取模进行分片
props:
sql.show: true # 控制台打印SQL
在ShardingJDBC中分库、分表都是需要指定表名,因为我们只需要对数据量大的表进行扩展,不可能对所有的表进行拆分
5、Controller
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/user/add")
public String addUser(@RequestBody User user){
userService.insert(user);
return "ok";
}
@RequestMapping("/user/get")
@ResponseBody
public User getUser(Long userId){
return userService.getUser(userId);
}
}
6、启动类
@SpringBootApplication
@MapperScan("com.kxg.mapper")
public class ShardingSphereMain {
public static void main(String[] args) {
SpringApplication.run(ShardingSphereMain.class,args);
}
}
启动项目,会看到如下信息:
2021-08-23 20:16:24.920 INFO 8928 --- [ main] o.a.s.core.log.ConfigurationLogger : ShardingRuleConfiguration:
tables:
tb_user:
actualDataNodes: testdb$->{0..1}.tb_user
databaseStrategy:
inline:
algorithmExpression: testdb$->{user_id % 2}
shardingColumn: user_id
logicTable: tb_user
2021-08-23 20:16:24.922 INFO 8928 --- [ main] o.a.s.core.log.ConfigurationLogger : Properties:
sql.show: 'true'
表示你的配置已经生效了,下面我们来验证一下分库的效果
我们新增了两个用户,user_id分别为1和2,我们来看看实际插入数据库中的情况
可以看到,是按照user_id取模插入到不同的数据库中的,是我们预期的结果。再看看控制台打印的SQL
Logic SQL: INSERT INTO tb_user ( user_id,user_name,user_address,age ) VALUES ( ?,?,?,? )
/****************** 中间省略若干行 *************************************/
Actual SQL: testdb1 ::: INSERT INTO tb_user ( user_id,user_name,user_address,age ) VALUES (?, ?, ?, ?) ::: [1, 李四, 武汉市, 22]
---------------------------------------华丽的分隔线---------------------------------------------------------------
Logic SQL: INSERT INTO tb_user ( user_id,user_name,user_address,age ) VALUES ( ?,?,?,? )
Actual SQL: testdb0 ::: INSERT INTO tb_user ( user_id,user_name,user_address,age ) VALUES (?, ?, ?, ?) ::: [2, 张三, 武汉市, 18]
再来看看查询
后台打印的SQL如下:
Logic SQL: SELECT user_id,user_name,user_address,age FROM tb_user WHERE user_id=?
Actual SQL: testdb0 ::: SELECT user_id,user_name,user_address,age FROM tb_user WHERE user_id=? ::: [2]
从上面打印的SQL结果可以看出来,每次请求分成逻辑SQL(Logic SQL)和实际执行的SQL(Actual SQL),上面的例子中,我们实际只发送了两次新增用户的请求,控制台实际上打印了4条SQL
逻辑SQL是客户端发过来的真实SQL,实际执行的SQL是数据库实际执行的SQL,在以往普通项目中这两种SQL是同一个,但是在分库、分表场景下,这两个SQL却是不同的。因为当你新增一个用户时,客户端是不关心你到底数据存在哪个库哪张表的,它发过来的SQL就是逻辑SQL,服务端需要关心数据存储在哪里,所以这里使用ShardingJDBC来做了一次转换,将逻辑SQL转换成实际可以执行的SQL
以上就是简单的分库实例,当然实际的业务场景不可能这么简单,涉及到多张表的关联查询,在进行分库或分表之前一定要做好分片字段的设计,如果设计不合理不但达不到预期的效果,还可能会适得其反
如果感觉对你有些帮忙,请收藏好,你的关注和点赞是对我最大的鼓励!
如果想跟我一起学习,坚信技术改变世界,请关注【Java天堂】公众号,我会定期分享自己的学习成果,第一时间推送给您