【已测可⾏】配置mysql数据库⼀主两从多数据源+数据库主从同步的⼀种⽅案⽂章⽬录
⼀、配置mysql数据库⼀主两从多数据源
系统环境:
mysql 5.7
oracle jdk 11
linux:centos 7
框架⽣态:
springboot 2.3.0 + mybatis 3.3.2
1.1 什么是读写分离?
读写分离其实就是将数据库分为了主从库,⼀个主库⽤于写数据,多个从库完成读数据的操作,主从库之间通过某种机制进⾏数据的同步,是⼀种常见的数据库架构。
⼀个组从同步集,通常被称为是⼀个“分组”。
1.2 数据库读写分离解决什么问题?
⼤多数互联⽹业务,往往读多写少,这时候,数据库的读会⾸先称为数据库的瓶颈,这时,如果我们希望能够线性的提升数据库的读性能,消除读写锁冲突从⽽提升数据库的写性能,那么就可以使⽤“分组架构”(读写分离架构)。
⽤⼀句话概括,读写分离是⽤来解决数据库的读性能瓶颈的。
在互联⽹的应⽤场景中,常常数据量⼤、并发量⾼、⾼可⽤要求⾼、⼀致性要求⾼,如果使⽤“读写分离”,就需要注意这些问题:数据库连接池要进⾏区分,哪些是读连接池,哪个是写连接池,研发的难度会增加;
为了保证⾼可⽤,读连接池要能够实现故障⾃动转移;
主从的⼀致性问题需要考虑。
可以看到要考究的还是有很多的,所以相⽐于做缓存的复杂度,读写分离很多时候是作为缓存的⼀种补充⽅案。
1.3 读写分离的⽅案
读写分离有2种主流实现⽅案:
其⼀是构造⼀个Interceptor,在SqlSessionFactory⽣成statement前,拦截prestatement预编译的sql请求,根据 add / update / delete / select 不同类型的sql语句动态调⽤不同的数据源来实现;
其⼆是在业务层(service)构造⼀个aop切⾯,对不同的 ⽅法 / 类 添加不同的aop注解来拦截请求,改变sql调⽤时的数据源;
本⽂采⽤了第⼀种思路。
最后在⽂末安利⼀款⾮常好⽤的插件,同时集成了2种⽅案的功能,可以快速上⼿。
1.4 引⼊依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
</dependencies>
1.5 yml数据源配置
spring:
datasource:
贴隔热膜druid:
master:
driverClassName: sql.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.122.130:3306/qa?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=false username: root
password:123456
type: com.alibaba.druid.pool.DruidDataSource
slave1:
driverClassName: sql.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.122.140:3306/qa?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=false username: root
password:123456
type: com.alibaba.druid.pool.DruidDataSource
slave2:
driverClassName: sql.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.122.141:3306/qa?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=false username: root
password:123456
type: com.alibaba.druid.pool.DruidDataSource
# 连接池设置
initial-size:5
min-idle:5
max-active:20
# 配置获取连接等待超时的时间
max-wait:60000
# 配置间隔多久才进⾏⼀次检测,检测需要关闭的空闲连接,单位是毫秒
time-between-eviction-runs-millis:60000
# 配置⼀个连接在池中最⼩⽣存的时间,单位是毫秒
min-evictable-idle-time-millis:300000
# Oracle请使⽤select 1 from dual
validation-query: SELECT 'x'
test-while-idle:true
test-on-borrow:false
test-on-return:false
# 打开PSCache,并且指定每个连接上PSCache的⼤⼩
pool-prepared-statements:true
max-pool-prepared-statement-per-connection-size:20
# 配置监控统计拦截的filters,去掉后监控界⾯sql⽆法统计,'wall'⽤于防⽕墙
filters: stat,wall,slf4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connection-properties: Sql=true;druid.stat.slowSqlMillis=5000
烈士名言# 合并多个DruidDataSource的监控数据
use-global-data-source-stat:true
1.6 动态配置
注意事项:
数据库连接地址需要重⽤jdbc-url (这个属性才能重写,url不⾏)
数据库驱动⽤驼峰命名法:driverClassName(有些⽂档⽤driver-class-name,本⽂环境下亲测不⾏)1.7 dynamic-datasource-select
该类继承了springframework的AbstractRoutingDataSource类,⽤来从ThreadLocal中动态选择数据源。public class DynamicDataSource extends AbstractRoutingDataSource {
@Nullable
@Override
protected Object determineCurrentLookupKey(){
DBType();
}
}
1.8 datasource-holder
这个类⽤来存放相应的datasource
@Slf4j
public class DynamicDataSourceHolder {
/**
* 创建⼀个本地线程
*/
private static ThreadLocal<String> contextHolder =new ThreadLocal<>();
public static final String DB_MASTER ="master";
public static final String DB_SLAVE_1 ="slave_1";
public static final String DB_SLAVE_2 ="slave_2";
public static String getDBType(){
String db = ();
if(db == null){
db = DB_MASTER;
}
return db;
}
public static void setDBType(String str){
log.info("数据源为"+ str);
contextHolder.set(str);
}
public static void clearDBType(){
}
}
1.9 Interceptor
:指定拦截哪些⽅法,拦截策略中update包括增删改,query等于查询
@Slf4j
@Intercepts({@Signature(type = Executor.class, method ="update", args ={ MappedStatement.class,
Object.class}), @Signature(type = Executor.class, method ="query", args ={ MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class})})
public class DynamicDataSourceInterceptor implements Interceptor {
private static final String REGEX =".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";
@Override
public Object intercept(Invocation invocation)throws Throwable {
boolean synchronizationActive = TransactionSynchronizationManager.isActualTransactionActive();
String lookupKey = DynamicDataSourceHolder.DB_MASTER;
if(!synchronizationActive){
Object[] objects = Args();
MappedStatement ms =(MappedStatement) objects[0];
SqlCommandType().equals(SqlCommandType.SELECT)){
//如果selectKey为⾃增id查询主键,使⽤主库
Id().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)){
lookupKey = DynamicDataSourceHolder.DB_MASTER;
}else{
BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]);
String sql = Sql().toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]"," ");
if(sql.matches(REGEX)){
lookupKey = DynamicDataSourceHolder.DB_MASTER;
}else{
//添加轮询算法
WeightedRoundRobinScheduling obj =new WeightedRoundRobinScheduling();
obj.init();
lookupKey = Server().getSlaveId();
}
}
}
}else{
lookupKey = DynamicDataSourceHolder.DB_MASTER;
}
DynamicDataSourceHolder.setDBType(lookupKey);
何猷佳怎么了return invocation.proceed();
}
@Override
public Object plugin(Object target){
//增删改查的拦截, 然后交由intercept处理
if(target instanceof Executor){
return Plugin.wrap(target,this);
}else{
return target;
}
}
@Override
public void setProperties(Properties properties){
}
}
篮球8种基本步法1.10 WeightedRound - RobinScheduling
权重轮询调度算法
public class WeightedRoundRobinScheduling {
/**
* 上⼀次选择的服务器
张婧仪为什么叫周迅姑*/
private int currentIndex =-1;
/**
/
**
* 当前调度的权值
*/
private int currentWeight =0;
/**
* 最⼤权重
*/
private int maxWeight =0;
/**
* 所有服务器权重的最⼤公约数
*/
private int gcdWeight =0;
/**
* 服务器数量
*/
private int serverCount =2;
/**
* 服务器集合
*/
private List<Server> serverList;
/**
* 返回最⼤公约数
*
* @param a
* @param b
* @return
*/
private static int gcd(int a,int b){
BigInteger b1 =new BigInteger(String.valueOf(a));
BigInteger b2 =new BigInteger(String.valueOf(b));
BigInteger gcd = b1.gcd(b2);
return gcd.intValue();
}
/
**世界通用语言
* 返回所有服务器权重的最⼤公约数
*
* @param serverList
* @return
*/
private static int getGCDForServers(List<Server> serverList){
int w =0;
for(int i =0, len = serverList.size(); i < len -1; i++){
if(w ==0){
w =(i).weight, (i +1).weight);
}else{
w =gcd(w, (i +1).weight);
}
}
return w;
}
/**
* 返回所有服务器中的最⼤权重
*
* @param serverList
* @return
*/
public static int getMaxWeightForServers(List<Server> serverList){
int w =0;
for(int i =0, len = serverList.size(); i < len -1; i++){
if(w ==0){
w = Math.(i).weight, (i +1).weight);
}else{
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论