大型网站为了软解大量的并发访问,除了在网站实现分布式负载均衡,远远不够。到了数据业务层、数据访问层,如果还是传统的数据结构,或者只是单单靠一台服务器扛,如此多的数据库连接操作,数据库必然会崩溃,数据丢失的话,后果更是 不堪设想。这时候,我们会考虑如何减少数据库的联接,一方面采用优秀的代码框架,进行代码的优化,采用优秀的数据缓存技术如: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 。。。。。。。。。。。。。。。。。。

主从分离之SSM与Mysql的更多相关文章

  1. SSM 配合 Mysql 数据库和代码数据源主从分离

    大型网站为了软解大量的并发访问,除了在网站实现分布式负载均衡,远远不够.到了数据业务层.数据访问层,如果还是传统的数据结构,或者只是单单靠一台服务器扛,如此多的数据库连接操作,数据库必然会崩溃,数据丢 ...

  2. MySQL数据库主从分离的配置方法

    1.介绍 MySQL数据库设置读写分离,可以使对数据库的写操作和读操作在不同服务器上执行,提高并发量和响应速度.现在的网站一般大点的,都采用有数据库主从分离.读写分离,既起到备份作用也可以减轻数据库的 ...

  3. MySQL的主从分离基本配置

    1.介绍 MySQL数据库设置读写分离,可以使对数据库的写操作和读操作在不同服务器上执行,提高并发量和响应速度.现在的网站一般大点的,都采用有数据库主从分离.读写分离,既起到备份作用也可以减轻数据库的 ...

  4. Mysql数据库进阶之(分表分库,主从分离)

    前言:数据库的优化是一个程序员的分水岭,作为小白我也得去提前学习这方面的数据的 (一)  三范式和逆范式 听起范式这个迟非常专业我来举个简单的栗子: 第一范式就是:  把能够关联的每条数据都拆分成一个 ...

  5. centos MySQL主从配置 ntsysv chkconfig setup命令 配置MySQL 主从 子shell MySQL备份 kill命令 pid文件 discuz!论坛数据库读写分离 双主搭建 mysql.history 第二十九节课

    centos  MySQL主从配置 ntsysv   chkconfig  setup命令  配置MySQL 主从 子shell  MySQL备份  kill命令  pid文件  discuz!论坛数 ...

  6. Atlas实现mysql主从分离

     可以接受失败,无法接受放弃!加油! 一.介绍Atlas及架构图 Atlas源代码用C语言编写,它对于Web Server相当于是DB,相对于DB相当于是Client,如果把Atlas的逻辑放到Web ...

  7. Mysql主从分离介绍及实现

    参考: http://www.cnblogs.com/panxuejun/p/5887118.html https://www.cnblogs.com/alvin_xp/p/4162249.html ...

  8. 基于MySql主从分离的代码层实现

    前言   该文是基于上篇<MySQL主从分离的实现>的代码层实现,所以本文配置的主数据库和从数据库的数据源都是在上篇博文中已经介绍了的. 动态选择数据源的配置   由于我们在写数据的时候需 ...

  9. Mysql主从分离与双机热备超详细配置

    一.概述 本例是在Windows环境,基于一台已经安装好的Mysql57,在本机安装第二台Mysql57服务. 读完本篇内容,你可以了解到Mysql的主从分离与双机热备的知识,以及配置期间问题的解决方 ...

随机推荐

  1. 吴裕雄--天生自然轻量级JAVA EE企业应用开发Struts2Sping4Hibernate整合开发学习笔记:Spring_autowire

    <?xml version="1.0" encoding="GBK"?> <beans xmlns:xsi="http://www. ...

  2. badboy录制,出现弹框提示脚本错误解决方法

    录制的时候经常出现如下问题: 结合网上一些资料,发现如下设置可以解决,具体原理不太清楚,但能达到效果(后期探究一下是为什么,如有知道的朋友,请赐教)

  3. opencv —— erode、dilate 腐蚀与膨胀

    腐蚀与膨胀是形态学滤波.其中,腐蚀是最小值滤波,膨胀是最大值滤波,即分别选取内核中的最小值与最大值赋值给锚点.若内核为 N×1 或 1×N 形状,可用于横纵方向直线检测. 膨胀:dilate 函数 v ...

  4. JAVA类变量、类方法

    类变量(static) 类变量是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量. public class C { ...

  5. JN_0010:谷歌浏览器启动安全模式,直接打开H5项目

    1,找到桌面chrome 2,复制粘贴一份新的 3,右键属性 4,在目标输入框最末端加上这句(注意空格) --disable-web-security --user-data-dir=D:\chrom ...

  6. Pikachu-SSRF(服务器端请求伪造)

    SSRF(Server-Side Request Forgery:服务器端请求伪造) 其形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能,但又没有对目标地址做严格过滤与限制 导致攻击者可 ...

  7. “石家庄铁道大学软件工程系学生学籍管理系统2019版”java小程序制作分享

    首先附上完整代码: import java.util.Scanner; public class SocreInformation { public SocreInformation(){}; pub ...

  8. js限制按钮每隔一段时间才能再次点击

    设置属性 disabled 可以限制交互,单击按钮时添加disabled=“disabled”属性,再为按钮添加定时器,一定时间后删除定时器和disabled属性 <!DOCTYPE html& ...

  9. BZOJ 4238: 电压 DFS树

    分类讨论一下奇环和偶环的情况. code: #include <bits/stdc++.h> #define N 200006 #define setIO(s) freopen(s&quo ...

  10. xadmin使用

    xadmin使用 官方 使用参考