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

    1.真机配置包路径 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport 2.配置文 ...

  2. Android中实现照片滑动时左右进出的动画的xml代码

    场景 Android中通过ImageSwitcher实现相册滑动查看照片功能(附代码下载): https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/det ...

  3. 「Flink」RocksDB介绍以及Flink对RocksDB的支持

    RocksDB介绍 RocksDB简介 RocksDB是基于C++语言编写的嵌入式KV存储引擎,它不是一个分布式的DB,而是一个高效.高性能.单点的数据库引擎.它是由Facebook基于Google开 ...

  4. 静态存储SRAM设计

    SRAM即静态随机存取存储器.它是具有静止存取功能的内存,不需要刷新电路便能保存它内部存储的数据.在工业与科学用的很多子系统,汽车电子等等都用到了SRAM.现代设备中很多都嵌入了几千字节的SRAM.实 ...

  5. leetcode-简单-栈-有效的括号

    给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效. 有效字符串需满足:  左括号必须用相同类型的右括号闭合. 左括号必须以正确的顺序闭合. 注意空字符串可被 ...

  6. nginx配置访问https[自签版]

    通过openssl生成证书 (1)设置server.key,这里需要设置两遍密码: openssl genrsa -des3 -out server.key 1024 (2)参数设置,首先这里需要输入 ...

  7. Html介绍,了解html代码的注释

    什么是代码注释?在代码编辑器中的注释代码是不会在浏览器窗口展示的!html代码注释的作用是帮助程序员标注代码的用途,过一段时间后再看你之前编写的代码,很快可以想起这个代码的用途.代码注释不仅方便程序员 ...

  8. java 虚拟机原理

    什么是JVM JVM是Java Virtual Machine(Java虚拟机)的缩写,是一个虚构出来的计算机,它屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目 ...

  9. 全面了解Java中的15种锁概念及机制!

    在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类.介绍的内容如下: 1.公平锁 / 非公平锁 2.可重入锁 / 不可重入锁 3.独享锁 / 共享锁 4.互斥锁 / 读 ...

  10. SV 类继承的多态性问题(NVDIA2019笔试)

    1.原题 class class_a; virtual function void print_name(); $display("this is class_a"); endfu ...