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

    参考自:解决Android Studio无法下载sdk的问题 国内网站无法登进google,android sdk无法下载. 尝试使用FQ,重装软件都没有成功. 最后找到了解决办法:http://pi ...

  2. 共同战“疫”,CODING 帮助研发团队高效协同

    新冠疫情下,家里蹲的日子继续延长.部分企业虽然受困于不能回公司办公,但都陆续开启了远程协作办公,远程协作领域被推上了风口.但「远程协同」看不见摸不着工作伙伴,个人的自律能力也无法保证,难免出现沟通响应 ...

  3. MySQL表名大小写敏感性

    Linux版MySQL 库名与表名是严格区分大小写的: 表的别名是严格区分大小写的: 列名与列的别名在所有的情况下均是忽略大小写的: 变量名也是严格区分大小写的: 修改步骤如下: 1. 编辑[/etc ...

  4. Mysql基础04-查询

    关联查询 1.内连接:实现A∩B select 字段列表 from A表 inner join B表 on 关联条件 where 等其他子句; 2.左外连接 #实现查询结果是A select 字段列表 ...

  5. Jenkins+robotframework持续集成环境(二)

    配置Jenkins上的robotframework环境 一.添加robot插件 需要导一个robot framework 的包,导包方式如下: 1.进入插件管理页面,选择“可选插件”,在右侧搜索栏搜索 ...

  6. C语言程序设计(实验一)

    实验项目:1.3.2,1.3.3,1.3.4,2.3.1,2.3.2 姓名:邹琼   实验地点:家 实验时间:2020年2月28日 一.实验目的与要求 1.实验目的 掌握DEVC++的安装方法,并实现 ...

  7. Python安装和配置环境变量(简明教程)

    声明:借鉴Python 简明教程 安装我们在本书中提到的「Python 3」指的是 Python 版本大于或等于 Python 3.6.0. 针对Python3.6.版本:注意数据的缓存机制 # ## ...

  8. 开发FTP服务接口,对外提供接口服务

    注意:本文只适合小文本文件的上传下载,因为post请求是有大小限制的.默认大小是2m,虽然具体数值可以调节,但不适合做大文件的传输 最近公司有这么个需求:以后所有的项目开发中需要使用ftp服务器的地方 ...

  9. C#效率优化(4)-- 编译器对数组遍历的优化

    在平时开发过程中,数组是我们使用频率最高的类型之一,在使用定长列表时,数组可以说是最佳方案,这也是我们最熟悉的数据结构之一. 在C#中使用数组,可以获取在内存上连续的相同类型的一组变量,在连续访问时可 ...

  10. JavaScript-跨浏览器事件处理程序(EventUtil)

    事件操作对象: var EventUtil= { //添加事件 addHandler: function (element, type, handler) { if (element.addEvent ...