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 。。。。。。。。。。。。。。。。。。

时间: 2024-10-10 11:05:28

SSM 配合 Mysql 数据库和代码数据源主从分离的相关文章

php 获取mysql数据库信息代码

有时候我们需要知道mysql数据库中的一些情况,好在php提供了一些内置方法与函数,大家了解下了. 代码如下: <?php @mysql_connect("localhost", "root","1981427") //选择数据库之前需要先连接数据库服务器 or die("数据库服务器连接失败"); $dbs = mysql_list_dbs(); //调用mysql_list_dbs函数 while ($array =

mysql数据库多种备份及读写分离搭建

数据库的版本 1.社区版 2.企业版 3.集群版 数据库的安装 1.     专用软件包管理器(二进制) deb .rpm等 mysql                 MySQL客户端程序和共享库 mysql-server            MySQL服务器需要的相关程序 2.     源代码软件包(编译安装) configure.cmake 数据库常用的配置选项 -DCMAKE_INSTALL_PREFIX=/usr/local/mysql    ----指定残可安装路径(默认的就是/u

MySQL 数据库的主从复制与读写分离

在实际生产环境中,如果对数据库的读和写都在同一个数据库服务器中操作,无论是安全性.高可用性,还是高并发等各个方面都是完全不能满足实际需求的,因此,一般来说都是通过主从复制(Master-Slave)的方式来同步数据,再通过读写分离来提升数据库的并发负载能力这样的方案来进行部署与实施. MySQL 的主从复制和读写分离两者有着紧密关联,首先要部署主从复制,才能在此基础上进行数据的读写分离. MySQL 主从复制的复制类型1) 基于语句的数据.在主服务器上执行的 SQL 语句,在从服务器上执行同样的

tomcat服务器连接MySQL数据库的JNDI数据源配置以及获得连接的Java代码

->首先将mysql的jar包导入到tomcat/lib文件夹下 ->然后在tomcat/conf/context.xml文件中配置以下内容 <Resource name="jdbc/mysql" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000&quo

solr(四)---将MYSQL数据库做成索引数据源

前面几篇关于solr的文章在导入数据进行分词.索引,都是通过导入本地的XML或者直接在页面上填写XML.但是现实中,很多情况下数据源是来自于数据库的.所以,本文就以mysql为例进行一个较详细的介绍.其使用到的是"dataimport". 1.在conf\solrconfig.xml中添加,增加导入数据功能  <requestHandler name="/dataimport" class="org.apache.solr.handler.datai

Java连接MySQl数据库实现代码

1. 获取数据库连接和查询代码 package connectionmysql; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class ConnectionMysql { //数据库连接用户名 private String userName

Yii Framework2.0开发教程(10)配合mysql数据库实现用户登录

1.首先在mysql创建一个存用户的表格 create table test_user ( user_id bigint(20) unsigned not null auto_increment comment 'ID', user_email varchar(100) not null comment '电子邮件', user_password varchar(100) not null comment '密码', user_access_token varchar(200) comment

JAVA使用JDBC连接MySQL数据库(3)-代码部分

1 /**************连接数据库部分********************/ 2 public static Connection conn(){ 3 Connection conn = null; 4 String driver = "com.mysql.jdbc.Driver"; 5 String url = "jdbc:mysql://localhost:3306/demo?characterEncoding=utf-8"; 6 String u

Mysql数据库添加从库,主从同步

环境准备 更改my.cnf 开启slave服务 查看已有mysql的版本 mysql -uroot -p >select version(); 下载相对应的mysql版本: 下载地址:http://dev.mysql.com/downloads/mysql 1)解压 #解压 tar-zxvf mysql-5.6.35-linux-glibc2.5-x86_64.tar.gz #复制解压后的mysql目录 cp-r mysql-5.6.35-linux-glibc2.5-x86_64 /usr/l