• 主从分离之SSM与Mysql


      大型网站为了软解大量的并发访问,除了在网站实现分布式负载均衡,远远不够。到了数据业务层、数据访问层,如果还是传统的数据结构,或者只是单单靠一台服务器扛,如此多的数据库连接操作,数据库必然会崩溃,数据丢失的话,后果更是 不堪设想。这时候,我们会考虑如何减少数据库的联接,一方面采用优秀的代码框架,进行代码的优化,采用优秀的数据缓存技术如:redis,如果资金丰厚的话,必然会想到假设服务器群,来分担主数据库的压力。Ok切入今天微博主题,利用MySQL主从配置,实现读写分离,减轻数据库压力。这种方式,在如今很多网站里都有使用,也不是什么新鲜事情,今天总结一下,方便大家学习参考一下。

      原理:主服务器(master)负责写操作(包括增删改),有且仅有一台;从服务器(slave)负责读操作,可以配置n台,写入数据的时候,首先master会把数据写在本地 Binary Log 文件中,然后通过I/O 写入 slave 的 relayLog 日志文件中,之后才同步数据库中,实现主从同步

      在这里我使用的是两台CenterOS 6.5 的虚拟机进行配置,master IP :192.168.1.111 , Slave IP :192.168.1.112,开始了:

    一: Mysql 配置主从分离

    1.下载安装 Mysql 数据库

     1.1  # yum list | grep mysql     --我们通过命令可以查看yum上提供下载的mysql的版本信息

      

     1.2  # yum install -y mysql-server mysql mysql-deve  --运行命令开始安装直到安装完成

     

    2. 配置Mysql数据库的 master 和 slave

      2.1  首先配置 master: 

        # vi /etc/my.cnf  -- Mysql 的配置一般都是在这,直接运行命令进行修改

        在【mysqld】添加以下:

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

        随后开启数据库

        # service mysqld start;

        登录进去数据库:

        # mysql -uroot -p;

        进去之后创建一个用户用来主从数据库的通信

        # create user manager;

        授予 REPLICATION SLAVE 权限就够了

        # grant replication slave on *.* to 'manager'@'192.168.1.112' identified by '123456';

        # flush privileges;

        之后查看一下master日志

        # show master status;

        +-------------------+----------+--------------+------------------+
        | File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
        +-------------------+----------+--------------+------------------+
        | master-bin.000001 | 1285 | | |
        +-------------------+----------+--------------+------------------+
        1 row in set (0.00 sec)

        好了 ,master 配置完成了。

      2,2 配置slave

        和 master 一样,首先修改配置文件

        # vi /etc/my.cnf 

        在【mysqld】添加以下: 

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

        然后开启 mysql 服务,登录进去之后,连接master

       # change master to master_host='192.168.1.111', //Master 服务器Ip
        master_port=3306,
        master_user='manager',
        master_password='123456', 
        master_log_file='master-bin.000001',//Master服务器产生的日志
        master_log_pos=0;

        启动 slave

        # start slave 

        查看一下slave运行有没有错误,如果没有就说明已经配置好了,主和从已经正常工作了

        # show slave status G;

    二: 配置Spring 和 Mybatis

      首先修改一下 jdbc.properties ,配置两条连接数据库URL

    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.slave.url=jdbc:mysql://192.168.237.111/test?useUnicode=true&characterEncoding=utf8
    jdbc.master.url=jdbc:mysql://192.168.237.112/test?useUnicode=true&characterEncoding=utf8
    jdbc.username=xxxxx
    jdbc.password=xxxxx

     

       定义一个拦截器,实现 import org.apache.ibatis.plugin.Interceptor 接口

    package com.smy.dao.split;
    
    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.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    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, Object.class,
                RowBounds.class, ResultHandler.class }) })
    public class DynamicDataSourceInterceptor implements Interceptor {
        
        // 数据库操作字符串的匹配,insert,update,delete
        private static final String REGEX = ".*insert\u0020.*|.*delete\u0020.*|.*update\u0020.*";
        private static Logger log = LoggerFactory.getLogger(DynamicDataSourceInterceptor.class);
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            // 判断是否被事务管理
            boolean synchronization = TransactionSynchronizationManager.isActualTransactionActive();
            Object[] objects = invocation.getArgs();
            MappedStatement ms = (MappedStatement) objects[0];
            String lookupKey = DynamicDataSourceHolder.DB_MASTER;;
            // 判断是否被事务管理
            if (!synchronization) {
                // 读操作
                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 {
                        //  如果执行到了这里说明就是没有被事务管理也没有指定主键Key,只能对sql
                        // 语句进行匹配规则 
                        BoundSql boundSql = ms.getBoundSql(objects[1]);
                        String sql = boundSql.getSql().toLowerCase().replaceAll("\t\n\r", " ");
                        if (sql.matches(REGEX)) {
                            lookupKey = DynamicDataSourceHolder.DB_MASTER;
                        } else {
                            lookupKey = DynamicDataSourceHolder.DB_SLAVE;
                        }
                    }
                }
            } else {
                // 如果被事务管理说明 就是增删改,需要在 master 中操作
                lookupKey = DynamicDataSourceHolder.DB_MASTER;
            }
            log.debug("设置方法[{}] use [{}] Strategy, SqlCommanType [{}]..", ms.getId(), lookupKey,
                    ms.getSqlCommandType().name());
            //设置访问数据库类型 master 或者 slave
            DynamicDataSourceHolder.setDbType(lookupKey);
            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) {
    
        }
    
    }

      

      定义一个类 DynamicDataSourceHolder 来管理 我们的master 和 slave 常量,也就是管理我们的数据源

    package com.smy.dao.split;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class DynamicDataSourceHolder {
    
        private static Logger log = 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";
        
        /**
         * 获取数据源
         * @return
         */
        public static String getDbType() {
            String db = contextHolder.get();
            if(db==null) {
                db = DB_MASTER;
            }
            return db;
        }
        
        /**
         * 设置数据源
         * @param dbType
         */
        public static void setDbType(String dbType) {
            log.debug("所使用的数据源"+dbType);
            contextHolder.set(dbType);
        }
    
        /**
         * 清理数据源
         */
        public static void clearDbType() {
            contextHolder.remove();
        }
        
    }

      接下来 定义一个类 继承org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 类,因为这个类能够动态路由到数据源

    package com.smy.dao.split;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    public class DynamicDataSource extends AbstractRoutingDataSource {
    
        @Override
        protected Object determineCurrentLookupKey() {
            return DynamicDataSourceHolder.getDbType();
        }
    }

       实现这个类的一个抽象方法,查看AbstractRoutingDataSource 类源码有这么一个方法:

    /**
         * Retrieve the current target DataSource. Determines the
         * {@link #determineCurrentLookupKey() current lookup key}, performs
         * a lookup in the {@link #setTargetDataSources targetDataSources} map,
         * falls back to the specified
         * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
         * @see #determineCurrentLookupKey()
         */
        protected DataSource determineTargetDataSource() {
            Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
            Object lookupKey = determineCurrentLookupKey(); // 这个方法就确定了要使用哪个数据源,然而AbstractRoutingDataSource 类中,这个方法是抽象的,所以我们要实现这个类并实现该方法
            DataSource dataSource = this.resolvedDataSources.get(lookupKey);
            if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
                dataSource = this.resolvedDefaultDataSource;
            }
            if (dataSource == null) {
                throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
            }
            return dataSource;
        }

      OK ,开始我们在Spring 和 Mybatis 配置文件中配置我们的拦截器 和 动态数据源Bean 

      Mybatis.xml 中配置添加plugin:

    <plugins>
            <plugin interceptor="com.smy.dao.split.DynamicDataSourceInterceptor" />
    </plugins>

      Spring-dao.xml 中:

    <context:property-placeholder location="classpath:jdbc.properties"/>
        <!-- 2.数据库连接池 -->
        <!--  定义抽象数据源,使其它数据源 Bean 继承该数据源 -->
        <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" />
            <property name="maxStatements" value="0" />
        </bean>
        
        <!-- 主数据源 master -->
        <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>
        
        <!-- 从数据源 slave -->
        <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.smy.dao.split.DynamicDataSource">
            <property name="targetDataSources">
                <map>
                    <entry value-ref="master" key="master"></entry>
                    <entry value-ref="slave" key="slave"></entry>
                </map>
            </property>
        </bean>
        <bean id="dataSource"
            class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
            <property name="targetDataSource">
                <ref bean="dynamicDataSource" />
            </property>
        </bean>

      至于为什么这么配置, 相信大家看过源码之后就会很清楚了。。

      

            The   End 。。。。。。。。。。。。。。。。。。

  • 相关阅读:
    [珠玑之椟]字符串和序列:左移、哈希、最长重复子序列的后缀数组解法、最大连续子序列
    [珠玑之椟]二分思想与分治法、排序思想
    [珠玑之椟]浅谈代码正确性:循环不变式、断言、debug
    [珠玑之椟]随机数函数取样与概率
    逆序对:从插入排序到归并排序
    [珠玑之椟]估算的应用与Little定律
    [珠玑之椟]位向量/位图的定义和应用
    lua----------------使用VS2015搭建lua开发环境的一些侥幸成功经验,
    Lua--------------------unity3D与Slua融合使用
    lua------------------Unity3D研究院编辑器之打开unity不可识别的文件(十三)
  • 原文地址:https://www.cnblogs.com/gudu1/p/7860714.html
Copyright © 2020-2023  润新知