mysql spring主从库_002-使用Spring实现读写分离(MySQL实现主从复制)

24d1a0418eb0ae28f7d7f2da50353a922f2.jpg

761d7f25ca6b737f4027aeb4803bb668cea.jpg

View Code

4.2. 改进后的实现

b82470dc4853cc339e339c64392fe8e9df6.jpg

07f34aa35029649a1862efa98cc008aa001.jpg

import org.apache.commons.lang3.StringUtils;

import org.aspectj.lang.JoinPoint;

import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;

import org.springframework.transaction.interceptor.TransactionAttribute;

import org.springframework.transaction.interceptor.TransactionAttributeSource;

import org.springframework.transaction.interceptor.TransactionInterceptor;

import org.springframework.util.PatternMatchUtils;

import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;

import java.util.ArrayList;

import java.util.List;

import java.util.Map;

/**

* @since 2018/1/15 18:03

*/

public class DataSourceAspectTx {

private List slaveMethodPattern = new ArrayList();

private static final String[] defaultSlaveMethodStart = new String[]{ "query", "find", "get" };

private String[] slaveMethodStart;

/**

* 读取事务管理中的策略

*

* @param txAdvice

* @throws Exception

*/

@SuppressWarnings("unchecked")

public void setTxAdvice(TransactionInterceptor txAdvice) throws Exception {

if (txAdvice == null) {

// 没有配置事务管理策略

return;

}

//从txAdvice获取到策略配置信息

TransactionAttributeSource transactionAttributeSource = txAdvice.getTransactionAttributeSource();

if (!(transactionAttributeSource instanceof NameMatchTransactionAttributeSource)) {

return;

}

//使用反射技术获取到NameMatchTransactionAttributeSource对象中的nameMap属性值

NameMatchTransactionAttributeSource matchTransactionAttributeSource = (NameMatchTransactionAttributeSource) transactionAttributeSource;

Field nameMapField = ReflectionUtils.findField(NameMatchTransactionAttributeSource.class, "nameMap");

nameMapField.setAccessible(true); //设置该字段可访问

//获取nameMap的值

Map map = (Map) nameMapField.get(matchTransactionAttributeSource);

//遍历nameMap

for (Map.Entryentry : map.entrySet()) {

if (!entry.getValue().isReadOnly()) {//判断之后定义了ReadOnly的策略才加入到slaveMethodPattern

continue;

}

slaveMethodPattern.add(entry.getKey());

}

}

/**

* 在进入Service方法之前执行

*

* @param point 切面对象

*/

public void before(JoinPoint point) {

// 获取到当前执行的方法名

String methodName = point.getSignature().getName();

boolean isSlave = false;

if (slaveMethodPattern.isEmpty()) {

// 当前Spring容器中没有配置事务策略,采用方法名匹配方式

isSlave = isSlave(methodName);

} else {

// 使用策略规则匹配

for (String mappedName : slaveMethodPattern) {

if (isMatch(methodName, mappedName)) {

isSlave = true;

break;

}

}

}

if (isSlave) {

// 标记为读库

DynamicDataSourceHolder.markSlave();

} else {

// 标记为写库

DynamicDataSourceHolder.markMaster();

}

}

/**

* 判断是否为读库

*

* @param methodName

* @return

*/

private Boolean isSlave(String methodName) {

// 方法名以query、find、get开头的方法名走从库

return StringUtils.startsWithAny(methodName, getSlaveMethodStart());

}

/**

* 通配符匹配

*

* Return if the given method name matches the mapped name.

*

* The default implementation checks for "xxx*", "*xxx" and "*xxx*" matches, as well as direct

* equality. Can be overridden in subclasses.

*

* @param methodName the method name of the class

* @param mappedName the name in the descriptor

* @return if the names match

* @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String)

*/

protected boolean isMatch(String methodName, String mappedName) {

return PatternMatchUtils.simpleMatch(mappedName, methodName);

}

/**

* 用户指定slave的方法名前缀

* @param slaveMethodStart

*/

public void setSlaveMethodStart(String[] slaveMethodStart) {

this.slaveMethodStart = slaveMethodStart;

}

public String[] getSlaveMethodStart() {

if(this.slaveMethodStart == null){

// 没有指定,使用默认

return defaultSlaveMethodStart;

}

return slaveMethodStart;

}

}

View Code

5. 一主多从的实现

很多实际使用场景下都是采用“一主多从”的架构的,所有我们现在对这种架构做支持,目前只需要修改DynamicDataSource即可。

29282db3c859e97d3d7dc957dbee599fdce.jpg

实现没有校验:

d455d5c8c0fe0a471c2ce435fd6c9fe3c45.jpg

006576d3fa22820ec74624d747aa2db6b40.jpg

packagecom.jd.ofc.trace.common.datasource;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;importorg.springframework.util.ReflectionUtils;importjavax.sql.DataSource;importjava.lang.reflect.Field;importjava.util.ArrayList;importjava.util.List;importjava.util.Map;importjava.util.concurrent.atomic.AtomicInteger;/***@authorlihongxu6

*@since2018/1/16 15:07*/

public class DynamicDataSourceMutliSlave extendsAbstractRoutingDataSource {private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class);privateInteger slaveCount;//轮询计数,初始为-1,AtomicInteger是线程安全的

private AtomicInteger counter = new AtomicInteger(-1);//记录读库的key

private List slaveDataSources = new ArrayList(0);

@OverrideprotectedObject determineCurrentLookupKey() {//使用DynamicDataSourceHolder保证线程安全,并且得到当前线程中的数据源key

if(DynamicDataSourceHolder.isMaster()) {

Object key=DynamicDataSourceHolder.getDataSourceKey();if(LOGGER.isDebugEnabled()) {

LOGGER.debug("当前DataSource的key为: " +key);

}returnkey;

}

Object key=getSlaveKey();if(LOGGER.isDebugEnabled()) {

LOGGER.debug("当前DataSource的key为: " +key);

}returnkey;

}

@SuppressWarnings("unchecked")

@Overridepublic voidafterPropertiesSet() {super.afterPropertiesSet();//由于父类的resolvedDataSources属性是私有的子类获取不到,需要使用反射获取

Field field = ReflectionUtils.findField(AbstractRoutingDataSource.class, "resolvedDataSources");

field.setAccessible(true); //设置可访问

try{

Map resolvedDataSources = (Map) field.get(this);//读库的数据量等于数据源总数减去写库的数量

this.slaveCount = resolvedDataSources.size() - 1;for (Map.Entryentry : resolvedDataSources.entrySet()) {if(DynamicDataSourceHolder.MASTER.equals(entry.getKey())) {continue;

}

slaveDataSources.add(entry.getKey());

}

}catch(Exception e) {

LOGGER.error("afterPropertiesSet error! ", e);

}

}/*** 轮询算法实现

*

*@return

*/

publicObject getSlaveKey() {//得到的下标为:0、1、2、3……

Integer index = counter.incrementAndGet() %slaveCount;if (counter.get() > 9999) { //以免超出Integer范围

counter.set(-1); //还原

}returnslaveDataSources.get(index);

}

}

View Code

四、MySQL主从复制

4.1、原理

cae69df22bd67be5a56b486b10ae5c18212.jpg

mysql主(称master)从(称slave)复制的原理:

1、master将数据改变记录到二进制日志(binary log)中,也即是配置文件log-bin指定的文件(这些记录叫做二进制日志事件,binary log events)

2、slave将master的binary log events拷贝到它的中继日志(relay log)

3、slave重做中继日志中的事件,将改变反映它自己的数据(数据重演)

4.2、主从配置需要注意的地方

1、主DB server和从DB server数据库的版本一致

2、主DB server和从DB server数据库数据一致[ 这里就会可以把主的备份在从上还原,也可以直接将主的数据目录拷贝到从的相应数据目录]

3、主DB server开启二进制日志,主DB server和从DB server的server_id都必须唯一

4.3、主库配置(windows,Linux下也类似)

在my.ini修改:

f2e9e7910e6ec77ceffd2b4ef536c249b18.jpg

20d04ed1892b8efb239c929f15430ae4ff3.jpg

#开启主从复制,主库的配置

log-bin = mysql3306-bin

#指定主库serverid

server-id=101

#指定同步的数据库,如果不指定则同步全部数据库

binlog-do-db=mybatis_1128

View Code

执行SQL语句查询状态:

SHOW MASTER STATUS

c764858198f53baa65a50205d347ce185f5.jpg

需要记录下Position值,需要在从库中设置同步起始值。

4.4、在主库创建同步用户

#授权用户slave01使用123456密码登录mysql

grant replication slave on *.* to 'slave01'@'127.0.0.1' identified by '123456';

flush privileges;

4.5、从库配置

在my.ini修改:

#指定serverid,只要不重复即可,从库也只有这一个配置,其他都在SQL语句中操作

server-id=102

以下执行SQL:

CHANGE MASTER TO

master_host='127.0.0.1',

master_user='slave01',

master_password='123456',

master_port=3306,

master_log_file='mysql3306-bin.000006',

master_log_pos=1120;

#启动slave同步

START SLAVE;

#查看同步状态

SHOW SLAVE STATUS;

20e04df4edc7c3ffcb400d152fcbb80772a.jpg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值