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

    一.责任链模式 责任链模式是一种行为模式,为请求创建一个接收者的对象链.这样就避免,一个请求链接多个接收者的情况.进行外部解耦.类似于单向链表结构. 优点: 1. 降低耦合度.它将请求的发送者和接收者 ...

  2. Java之GUI编程

    GUI编程 组建 窗口 弹窗 面板 文本框 列表框 按钮 图片 监听事件 鼠标 键盘事件 破解工具 1.简介 GUI的核心技术:Swing AWT 为什么不流行? 界面不美观. 需要jre环境.(没必 ...

  3. Centos7内核版安装nginx环境问题及解决方法

    错误信息:./configure: error: C compiler cc is not found解决方案:yum -y install gcc gcc-c++ autoconf automake ...

  4. 关于PHP连接上MySQL但不能插入数据

    出现这种情况,有三种可能 1.SQL语句有问题 insert into table_name(field1,field2...) values(value1,value2...); 先在MySQL中粘 ...

  5. MySQL 的一条语句是怎么执行的

    该文为< MySQL 实战 45 讲>的学习笔记,感谢查看,如有错误,欢迎指正 一.MySQL 的基础架构 以下就是 MySQL 的基础架构图. 在 Linux 中安装 MySQL 时,最 ...

  6. 关于非旋转Treap

    刚刚跟着EM-LGH大佬学了非旋转Treap 非常庆幸不用再写万恶的rotate了(来自高级数据结构的恶意) 来记一下 Treap 概念 简单来说,\(Tree_{二叉搜索树} * Heap_堆 = ...

  7. Eclipse jee最新版国内镜像点下载方式

    Eclipse jee是Eclipse的JavaEE版本,官网服务器在国外,无条件下载的可以使用国内镜像点 清华大学网站镜像网站资料,可用于下载其它版本的Eclipsehttps://mirrors. ...

  8. MySQL 8 InnoDB 集群生产部署

    生产部署InnoDB集群 1.先决条件 InnoDB集群使用组复制技术,所以InnoDB中的实例需要满足组复制要求.可以查看MySQL文档中组复制相关的部分,也可以通过AdminAPI提供的dba.c ...

  9. checkbox 样式重写

    css样式 .me-checkbox:checked { background: #1673ff } .me-checkbox { outline: none;/*轮廓*/ width: 25px; ...

  10. opencv —— imread、namedWindow & imshow、cvtColor、imwrite 加载、显示、修改、保存图像

    加载图像:imread 函数 Mat imread(const string& filename, int flags = 1): filename:需要载入的图像的路径名. flags:加载 ...