1.为什么要做读写分离
Sharding-JDBC官方说法:面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈。 对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库,主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善。
通过一主多从的配置方式,可以将查询请求均匀的分散到多个数据副本,能够进一步的提升系统的处理能力。 使用多主多从的方式,不但能够提升系统的吞吐量,还能够提升系统的可用性,可以达到在任何一个数据库宕机,甚至磁盘物理损坏的情况下仍然不影响系统的正常运行。
Sharding-JDBC读写分离是根据SQL语义的分析,将读操作和写操作分别路由至主库与从库。
那么如何才能够将业务中的读写请求路由到不同的读写库呢?想起刚出道那几年,我为了处理这个问题,自己基于SpringJDBC提供的AbstractRoutingDataSource,使用AOP+ThreadLocal技术自己封装到一套框架来实现读写分离,后来发现业界有更好的的框架已经解决了这个问题,如今能够实现读写分离的组件挺多,业界比较热门的介绍两款:MyCat , Sharding-sphere,今天主要是介绍如何使用Sharding-sphere来实现读写分离。
Apache ShardingSphere( 官网:Apache ShardingSphere) 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款相互独立,却又能够混合部署配合使用的产品组成。 它们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。
我们可以认为Sharding-sphere是一个关系型数据的分布式中间件。有三个部分组成,sharding-jdbc(JAVA),sharding-proxy(异构系统),Sharding-Sidecar(云原生)。

Sharding-jdbc 是一个开源的适用于微服务的分布式数据访问基础类库(jar),它始终以云原生的基础开发套件为目标。只支持java语言。sharding-jdbc完整的实现了分库分表/读写分离/分布式主键功能,并实现了柔性事务

ShardingProxy :定位为透明化的数据库代理端,提供封装了数据库二进制协议的服务端版本,用于完成对异构语言的支持,也就是支持多做开发语言的异构系统
Sharding-Sidecar :定位为Kubernetes(k8s)或Mesos的云原生数据库代理,以DaemonSet的形式代理所有对数据库的访问。 通过无中心、零侵入的方案提供与数据库交互的的啮合层,即Database Mesh,又可称数据网格。
2.主从复制存在的问题
在主从复制中数据在主节点写入从节点读出,主从节点数据同步可能存在一定的延时,这导致主节点和从节点的数据不一致。对于时效性比较高的查询,一般都会强制走主节点查询。
3.读写分离实现效果
配置一个主节点,两个从节点。增/删/改操作交由主节点完成(对于时效性高的查询也可强制路由到主节点),查询操作交由从节点完成
4.读写分离配置(读写分离 :: ShardingSphere)
4.1.Maven依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
</parent>
<dependencies>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- sharding-jdbc -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-core-api</artifactId>
<version>4.0.0-RC2</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC2</version>
</dependency>
<!-- Mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Mybatis plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
<exclusions>
<!-- 排除默认的 HikariCP 数据源,使用druid的数据源 -->
<exclusion>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
4.2.application.yml
spring:
# sharding-jdbc配置
shardingsphere:
# 是否开启SQL显示
props:
sql:
show: true
# 数据源配置
datasource:
names: ds-master,ds-slave1,ds-slave2 # 主从节点名称
# 主节点配置
ds-master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3307/student?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
username: root
password: 123456
# 从节点一配置
ds-slave1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3308/student?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
username: root
password: 123456
# 从节点二配置
ds-slave2:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3309/student?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
username: root
password: 123456
# 读写分离配置
sharding:
master-slave-rules:
ds-master:
# 主库
masterDataSourceName: ds-master
# 从库
slaveDataSourceNames:
- ds-slave1
- ds-slave2
# 从库查询数据的负载均衡算法 目前有2种算法 round_robin(轮询)和 random(随机)
loadBalanceAlgorithmType: round_robin
# Mybatis Plus 配置
mybatis-plus:
configuration:
# 控制台打印完整带参数SQL语句
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 设置当查询结果值为null时,同样映射该查询字段给实体(Mybatis-Plus默认会忽略查询为空的实体字段返回)。
call-setters-on-nulls: true
4.3.Mapper
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.Map;
@Mapper
public interface StudentMapper {
/** 查询 */
@Select("SELECT * FROM stu LIMIT 1")
Map loadOneStu();
/** 新增 */
@Insert("INSERT INTO stu(id,name) VALUES (2,'zs')")
void saveOneStu();
}
4.4.测试
写

读

5.读操作强制路由到主节点原因
主从复制,主节点写入从节点读取,由于主节点写入从节点复制有一定延迟。对于一些时效性要求很高的查询,比如我写入了立马就要去查,假如此时从库还未复制主库这条数据从库就不会查询到。而解决此类问题一般都是对时效性比较高的查询强制路由到主节点进行查询(主节点写入当然主节点也能立马查询出来)。当然Sharding-JDBC也支持“强制路由(强制路由 :: ShardingSphere)”这个功能。
强制走主库代码
HintManager.clear();
try (HintManager hintManager = HintManager.getInstance()) {
//设置走主库
hintManager.setMasterRouteOnly();
return xxMapper.selectList();
}
测试,然后测试观察控制台,看得出查询是走了主库 master 了
自定义强制走主库注解
一:定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface DataSourceMaster {
}
二:定义AOP切面,获取方法上的DataSourceMaster 来设置走主库
@Slf4j
@Component
@Aspect
public class DataSourceMasterAop {
@Around("execution(* cn.itsource.service.impl.*.*(..))")
public Object master(ProceedingJoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
Object ret = null;
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
DataSourceMaster shardingJdbcMaster = method.getAnnotation(DataSourceMaster.class);
HintManager hintManager = null;
try {
if (Objects.nonNull(shardingJdbcMaster)) {
HintManager.clear();
hintManager = HintManager.getInstance();
hintManager.setMasterRouteOnly();
}
ret = joinPoint.proceed(args);
}catch (Exception ex){
log.error("exception error",ex);
}finally {
if (Objects.nonNull(shardingJdbcMaster) && Objects.nonNull(hintManager)) {
hintManager.close();
}
}
return ret;
}
}
三:在方法上贴注解即可
@DataSourceMaster
public List<Xxx> selectList(){
return XxxMapper.selectList();
}

3121

被折叠的 条评论
为什么被折叠?



