MySQL主从读写分离

今天我们来说一说主从库的读写分离的问题

主从结构

一般情况下主从的结构是由一个Master和多个Slave来组成的, Slave的个数和数据库的业务访问量有密切的关系

MySQL读写分离配置的主要流程

  1. 在进行数据的更改之前Master会通知存储引擎开启事务,Master会把数据库的操作记录在一个Binary Log中,在写入完成之后,Master通知存储引擎提交事务,这样的一次操作称为一次二进制日志事件;
  2. Slave开启一个IO线程,把Binary Log中的日志拷贝到自己的Relay log中,如果读取完成之后,Slave的这个线程就会睡眠,等待Master的Binary产生新的数据;
  3. Slave会把Delay Log中的数据重新执行,使得日志中的数据重现在Slave的数据库中;

一、主从读写分离的配置

  1. 准备两台服务器并分别安装MySQL软件
  2. 配置主服务器,编辑主服务器的配置文件
    • 在配置文件的mysqld节点下面添加mysql-id和log-bin以及log-bin-index,如下
      server-id=1
      log-bin=master-bin
      log-bin-index=master-bin.index
      
    • 配置完成后重启:service mysqld restart
    • 查看Master的状态:show master status
    • 创建用户并赋予从服务器的访问权限:
      create user '<user-name>';
      grant replication slave on *.* to '<user-name>'@'<host>' identified by '<access-pwd>';
      flush privileges;
      
  3. 配置从服务器,编辑配置文件
    • 在配置文件的mysqld节点下面添加如下的内容,需要注意的是干掉其中server-id=1,在mysqld中重新添加如下内容:
      server-id=2
      relay-log=slave-relay-bin
      relay-log-index=slave-relay-bin.index
      
    • 配置完成后重启:service mysqld restart
    • 把从服务器挂到主服务器之下,连接到从服务器上,执行如下指令
      change master to master_host='<master-ip>',master_port=<master-ip>,master_user='<user-name>',master_password='<access-pwd>',master_log_file='<master_status-file-name>',master-log-pos=0;
      
    • 开始监听主服务器:start slave
    • 查看从库的状态:show slave status \G
  4. 测试主从:在主库上进行操作,后查看从库的数据变化

二、MySQL读写分离代码编写

  1. 编写一个读写分离的类DynamicDataSource,这个类继承自AbstractRoutingDataSource,实现其中的方法,如:

    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    public class DynamicDataSource extends AbstractRoutingDataSource {
        @Override
        protected Object determineCurrentLookupKey() {
            return DynamicDataSourceHolder.getDataSourceType();
        }
    }
    
  2. 编写一个用来维护连接类型的类DynamicDataSourceHolder,为了线程安全可以使用ThreadLocal来存放两个类型:MasterSlave

    import org.springframework.util.Assert;
    
    public class DynamicDataSourceHolder {
        
        public static final String DATASOURCE_TYPE_MASTER = "master";
        public static final String DATASOURCE_TYPE_SLAVE = "slave";
        
        private static ThreadLocal<String> typeHolder = new ThreadLocal<>();
        
        public static String getDataSourceType() {
            String type = typeHolder.get();
            if (type == null) {
                type = DATASOURCE_TYPE_MASTER;
            }
            return type;
        }
        
        public static void setDataSourceType(String type) {
            Assert.notNull(type, "type not allowed to be null");
            typeHolder.set(type);
        }
        
        public static void clearDataSourceType() {
            typeHolder.remove();
        }
    
    }
    
  3. 编写一个用来根据请求自动返回数据库连接类型的拦截器的类DynamicDataSourceInteceptor,该类实现MyBatis的拦截器接口:org.apache.ibatis.plugin.Interceptor

    import java.util.Locale;
    import java.util.Properties;
    import org.apache.ibatis.executor.Executor;
    import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
    import org.apache.ibatis.mapping.BoundSql;
    import org.apache.ibatis.mapping.MappedStatement;
    import org.apache.ibatis.mapping.SqlCommandType;
    import org.apache.ibatis.plugin.Interceptor;
    import org.apache.ibatis.plugin.Intercepts;
    import org.apache.ibatis.plugin.Invocation;
    import org.apache.ibatis.plugin.Plugin;
    import org.apache.ibatis.plugin.Signature;
    import org.apache.ibatis.session.ResultHandler;
    import org.apache.ibatis.session.RowBounds;
    import org.springframework.transaction.support.TransactionSynchronizationManager;
    
    @Intercepts({
        @Signature(type=Executor.class, method="update", args={MappedStatement.class, Object.class}),
        @Signature(type=Executor.class, method="query", args={MappedStatement.class, RowBounds.class, ResultHandler.class, Object.class})
    })
    public class DynamicDataSourceInteceptor implements Interceptor {
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            boolean txActive = TransactionSynchronizationManager.isActualTransactionActive();
            String type = DynamicDataSourceHolder.DATASOURCE_TYPE_MASTER;
            if (txActive == false) {
                Object[] args = invocation.getArgs();
                MappedStatement statement = (MappedStatement) args[0];
                if (statement.getSqlCommandType().equals(SqlCommandType.SELECT)) {
                    if (statement.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
                        type = DynamicDataSourceHolder.DATASOURCE_TYPE_MASTER;
                    } else {
                        BoundSql boundSql = statement.getSqlSource().getBoundSql(args[1]);
                        String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\n\\r\\t]", " ");
                        if (sql.matches(".*insert\\u0020.*|.*update\\u0020.*|.*delete\\u0020.*")) {
                            type = DynamicDataSourceHolder.DATASOURCE_TYPE_MASTER;
                        } else {
                            type = DynamicDataSourceHolder.DATASOURCE_TYPE_SLAVE;
                        }
                    }
                }
            }
            DynamicDataSourceHolder.setDataSourceType(type);
            return invocation.proceed();
        }
        @Override
        public Object plugin(Object target) {
            if (target instanceof Executor) {
                return Plugin.wrap(target, this);
            }
            return target;
        }
        @Override
        public void setProperties(Properties properties) {
        }
    }
    
  4. 在Mybatis配置中配置拦截器

    <plugins>
        <plugin interceptor="org.sherekr.dfo.core.dao.datasource.DynamicDataSourceInteceptor" />
    </plugins>
    
  5. Spring配置文件中配置两个数据源,一个用来执行DML的数据源,一个用来执行DQL的数据源,同时配置动态数据源和懒连接数据源代理

    <bean id="abstractDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" abstract="true" destroy-method="close">
        <property name="driverClass" value="${jdbc.driver}" />
        <property name="user" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        
        <property name="maxPoolSize" value="30" />
        <property name="minPoolSize" value="10" />
        <property name="autoCommitOnClose" value="false" />
        <property name="checkoutTimeout" value="10000" />
        <property name="acquireRetryAttempts" value="2" />
    </bean>
    <bean id="masterDataSource" parent="abstractDataSource">
        <property name="jdbcUrl" value="${jdbc.master.url}" />
    </bean>
    <bean id="slaveDataSource" parent="abstractDataSource">
        <property name="jdbcUrl" value="${jdbc.slave.url}" />
    </bean>
    
    <bean id="dynamicDataSource" class="org.sherekr.dfo.core.dao.datasource.DynamicDataSource">
        <property name="targetDataSources">
            <map>
                <entry key="master" value-ref="masterDataSource" />
                <entry key="slave" value-ref="slaveDataSource" />
            </map>
        </property>
    </bean>
    <bean id="dataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
        <property name="targetDataSource" ref="dynamicDataSource" />
    </bean>
    

这个代码已经是几年前写的代码了,运行时可能会遇到一些问题,大家就见招拆招吧,如果遇到问题欢迎吐我

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,293评论 18 399
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,355评论 6 343
  • 一. Java基础部分.................................................
    wy_sure阅读 3,731评论 0 11
  • 我到了最近才读三毛和张爱玲的作品,想来为这样的迟到觉得惭愧。 三毛和张爱玲都是忽然流行起来的,真的跟雨后春笋一样,...
    舟和1995阅读 513评论 1 3