实现myql数据库的读写分离

数据库层面的主从配置实现

这里写图片描述

  • 第一步 master主数据库将对数据的操作记录在binary log日志中,在每个数据进行事务更新之前,master会将这些事务串行写入在二进制日志中,在写入完成后通知存储引擎提交事务。
  • Slave将master的二进制日志拷贝到Relay log日志中。I/Othread在master上打开一个普通的连接,将master中的二进制日志读取,然后写入到Relay log日志中。
  • sql thread从Relay log读取事件,并存放其中的事件,更新Slave中的数据使其和master数据一致。

设计模式(生产者消费者)

Relay log相当于生产者消费者的管道,用来缓存master(生产者)生成的二进制日志,sql thread(消费者)从Relay log中读取事件并执行。通过生产者消费者达到了不相互影响的目的,使得Slave还可以去做别的事。

注意事项

1.当数据太多的时候,将会分出一个文件,position置为0,然后继续递增。
2.主库能写数据和读数据,从库只能读数据(从库不能写数据,从库的数据不会同步到主库,当主库再写入相同的数据后可能会引起冲突)。
3.主库和从库的版本可以 不一样,但是从库的版本一定要比主库的高
mysql是向后兼容的。低版本的语句高版本支持,高版本的语句低版本不支持。

mysql配置(基于2台服务器)

1.配置主库的mysqld信息,创建唯一id和开启二进制日志。

通过vim编辑器进去mysqld

vim /etc/my.cnf

添加如下配置

server-id=1
log-bin=master-bin
log-bin-index=master-bin.index

这里写图片描述
配置完成后重启mysql

service mysqld restart 

2.进入主库查看数据库相关信息,检验配置是否正确

mysql登录

mysql -root -p

查看信息

show databases;
SHOW MASTER STATUS;

这里写图片描述

3.配置从库的mysqld信息

通过vim编辑器进去mysqld

vim /etc/my.cnf

添加如下配置

server-id=2
relay-log-index=slave-relay-bin.index
relay-log=slave-relay-bin

(如果只有一台服务器配置主从同步时,这里从库的port要修改一下,只要改成和主库的端口不一样即可)
这里写图片描述
配置完成后重启mysql
这里我们换一种方式重启mysql

/etc/init.d/mysql stop 
/etc/init.d/mysql start

4.主库创建一个账号并授权

创建账号

create user repl;

这里写图片描述
授权
赋予repl这个账号可以对主库的任意数据库的任意表进行查看(115.28.159.6,从库所在服务器的ip)

GRANT REPLICATION SLAVE ON *.* TO 'repl'@'115.28.159.6' IDENTIFIED BY 'mysql';

这里写图片描述
刷新一下权限

flush privileges

这里写图片描述

5.从库连接主库,master_host为主库的ip,master_port为主库的端口,master_user为主库的账号,master_password为主库的密码。

master_log_file和master_log_pos对应主库的
这里写图片描述
在从库中执行一下下面的命令
这里写图片描述

6.开启主从跟踪

这里写图片描述

7.查看从库的数据库相关信息

这里写图片描述

8.这里显示报错,不要慌,接下来解决这个报错

这里写图片描述

9.解决方案

先停止主从同步
这里写图片描述
用vim编辑器进入mysqld

vim /etc/my.cnf

进入mysqld后
先按Esc后输入 :/server-id查找server-id,找到后将server-id=1这行删除掉。
这里写图片描述

10.重启从库,登录从库,开启主从同步

这里写图片描述

11.查看是否成功(到了这其实已经配置完成了)

\G是将内容竖形显示。

show slave status \G;

这里写图片描述

12.在主库和从库配置远程访问(这样在其他服务器也可以访问)

GRANT select,insert,update,delete ON . TO ‘账号’@’%’ IDENTIFIED BY ‘密码’ WITH GRANT OPTION;

GRANT select,insert,update,delete ON *.* TO 'work'@'%' IDENTIFIED BY '230230' WITH GRANT OPTION;

这里写图片描述

代码层面的读写分离实现(无需改动现有的代码)

这里以ssm为例,配置读写分离

  • 创建多数据源
  • 配置动态数据源
  • 配置mybatis拦截器

配置动态数据源

public class DynamicDataSource extends AbstractRoutingDataSource{

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDbType();
    }

}
public class DynamicDataSourceHolder {

    private static Logger logger=(Logger) LoggerFactory.getLogger(DynamicDataSourceHolder.class);
    private static ThreadLocal<String> contextHolder=new ThreadLocal<String>();
    public static final String DB_MASTER="master";
    public static final String DB_SLAVE="slave";

    public static String getDbType(){
        String db=contextHolder.get();
        if(db==null){
            db=DB_MASTER;
        }
        return db;
    }

    /**
     * 设置线程的dbType
     * @param str
     */
    public static void setDbType(String str){
        logger.debug("所使用的数据源为:"+str);
        contextHolder.set(str);
    }

    /**
     * 清理连接类型
     */
    public static void clearDbType(){
        contextHolder.remove();
    }
}

mybatis拦截器

//mybatis将增删改封装在了update里
@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 Logger logger=(Logger) LoggerFactory.getLogger(DynamicDataSourceInterceptor.class);

    private static final String REGEX=".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";
    //选择数据源
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        boolean synchronizationActive = TransactionSynchronizationManager.isActualTransactionActive();
        Object[] objects=invocation.getArgs();
        //判断CRUD
        MappedStatement ms=(MappedStatement) objects[0];
        String lookupKey = DynamicDataSourceHolder.DB_MASTER;
        if(synchronizationActive !=true){
            //读方法
            if(ms.getSqlCommandType().equals(SqlCommandType.SELECT)){
                //selectKey 为自增id查询主键 (SELECT LAST_INSERT_ID())方法,使用主库
                if(ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)){
                    lookupKey=DynamicDataSourceHolder.DB_MASTER;
                }else{
                    BoundSql boundSql= ms.getSqlSource().getBoundSql(objects[1]);
                    String sql=boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", " ");
                    if(sql.matches(REGEX)){
                        lookupKey=DynamicDataSourceHolder.DB_MASTER;
                    }else{
                        lookupKey=DynamicDataSourceHolder.DB_SLAVE;
                    }
                }
            }
        }else{
            //事务管理的一般都是写的,用主库
            lookupKey = DynamicDataSourceHolder.DB_MASTER;
        }
        logger.debug("设置方法[{}]user[{}]Strategy,SqlCommanType[{}]..",ms.getId(),lookupKey,
            ms.getSqlCommandType().name());
        DynamicDataSourceHolder.setDbType(lookupKey);
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        //Executor是用来支持一系列的CRUD操作的,只要对象里有CRUD就将它拦截下来
        if(target instanceof Executor){
            //返回代理
            return Plugin.wrap(target, this);
        }else{
            //返回本体
            return target;
        }
    }

    @Override
    public void setProperties(Properties arg0) {
        // TODO Auto-generated method stub

    }

}

创建多数据源(在xml里进行一些配置)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 1.数据库连接池 -->
    <bean id="abstractDataSource" abstract="true" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">

        <!-- c3p0连接池的私有属性 -->
        <property name="maxPoolSize" value="30" />
        <property name="minPoolSize" value="10" />
        <!-- 关闭连接后不自动commit -->
        <property name="autoCommitOnClose" value="false" />
        <!-- 获取连接超时时间 -->
        <property name="checkoutTimeout" value="10000" />
        <!-- 当获取连接失败重试次数 -->
        <property name="acquireRetryAttempts" value="2" />
    </bean>

    <!--主库数据源  -->
    <bean id="master" parent="abstractDataSource">
        <property name="driverClass" value="${jdbc.driver}" />
        <property name="jdbcUrl" value="${jdbc.master.url}" />
        <property name="user" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>

    <!--从库数据源  -->
    <bean id="slave" parent="abstractDataSource">
        <property name="driverClass" value="${jdbc.driver}" />
        <property name="jdbcUrl" value="${jdbc.slave.url}" />
        <property name="user" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>

    <!--配置动态数据源,这儿targetDataSources就是路由数据对应的名称  -->
    <bean id="dynamicDataSource" class="com.imooc.myo2o.dao.split.DynamicDataSource">
        <property name="targetDataSources">
            <map>
                <entry value-ref="master" key="master"></entry>
                <entry value-ref="slave" key="slave"></entry>
            </map>
        </property>
    </bean>

    <!--mybatis在生成sql后才决定数据源,所以在这懒加载,在运行时决定具体的数据源  -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
        <property name="targetDataSource">
            <ref bean="dynamicDataSource"/>
        </property>
    </bean>

    <!-- 3.配置SqlSessionFactory对象 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 注入数据库连接池 -->
        <property name="dataSource" ref="dataSource" />
        <!-- 配置MyBaties全局配置文件:mybatis-config.xml -->
        <property name="configLocation" value="classpath:mybatis-config.xml" />
        <!-- 扫描entity包 使用别名 -->
        <property name="typeAliasesPackage" value="com.imooc.myo2o.entity" />
        <!-- 扫描sql配置文件:mapper需要的xml文件 -->
        <property name="mapperLocations" value="classpath:mapper/*.xml" />
    </bean>

    <!-- 4.配置扫描Dao接口包,动态实现Dao接口,注入到spring容器中 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 注入sqlSessionFactory -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <!-- 给出需要扫描Dao接口包 -->
        <property name="basePackage" value="com.ypf.o2o.dao" />
    </bean>
</beans>

猜你喜欢

转载自blog.csdn.net/a1102325298/article/details/80560855