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 。。。。。。。。。。。。。。。。。。
SSM 配合 Mysql 数据库和代码数据源主从分离的更多相关文章
- Linux自动备份MySQL数据库脚本代码
下面这段Linux的Shell脚本用于每日自动备份MySQL数据库,可通过Linux的crontab每天定时执行 在脚本中可设置需要备份的数据库表清单,并且会将备份文件通过gzip压缩.需要注意的是, ...
- 操作MySQL数据库相关代码
注意事项: 1.导入驱动包,如我导的是mysql-connector-java-5.1.26-bin.jar 2.修改下面代码中的用户名和密码 3.其中URL为"jdbc:mysql://数 ...
- MySql数据库实现分布式的主从结构
最近学习了关于使用MySql数据的实现主动结构的原理,在以前的并发访问低的场景一下,一般一台性能高的服务器作为一个MySql数据,就可以满足业务的增删改查场景,但是随着网络用户的增加 当出现高并发,高 ...
- Java连接MySQl数据库实现代码
1. 获取数据库连接和查询代码 package connectionmysql; import java.sql.Connection; import java.sql.DriverManager; ...
- SSM实现mysql数据库账号密码加密连接
引言 咱们公司从事的是信息安全涉密应用的一些项目研发一共有分为三步,相比较于一般公司和一般的项目,对于信息安全要求更加严格,领导要求数据量和用户的用户名及密码信息都必需是要密文配置和存储的,这就涉及到 ...
- Mysql数据库-多实例主从复制-主从故障详解
一.mysql结构 1.实例 1.什么是单实例 一个进程 + 多个线程 + 一个预分配的内存空间 2.多实例 多个进程 + 多个线程 + 多个预分配的内存空间 ](http://shelldon.51 ...
- Yii Framework2.0开发教程(10)配合mysql数据库实现用户登录
1.首先在mysql创建一个存用户的表格 create table test_user ( user_id bigint(20) unsigned not null auto_increment co ...
- (详细)JAVA使用JDBC连接MySQL数据库(3)-代码部分
欢迎任何形式的转载,但请务必注明出处. 本节主要内容 项目建立 数据库连接 数据库操作 主函数 点击进入推荐博客(必看) 一.项目建立 如图所示:新建Java Project.Package.Clas ...
- Mysql 数据库 基础代码
-- 创建数据库 CREATE DATABASE book; -- 创建作者表 CREATE TABLE authors( Id int not NULL, -- 作者编号 Fname VARCHAR ...
随机推荐
- iOS在类内部怎么访问实例变量比较好?
OC在类文件的内部访问实例变量,有直接访问和使用getter/setter方法访问两种方式,它们的区别有: 1.直接访问不经过OC的方法分发(method dispatch),所以访问速度比较快,在这 ...
- ZOJ2345Gold Coins 简单分块
昨天做过一样的题: 平方和公式:n*(n+1)*(2n+1) #include<cstdio> #include<cstdlib> #include<iostream&g ...
- WebApi实现验证授权Token,WebApi生成文档等
using System; using System.Linq; using System.Web; using System.Web.Http; using System.Web.Security; ...
- 项目常见函数封装,基于Jquery
/// <reference path="jquery-1.8.0.min.js" /> /* * DIV或元素居中 * @return */ jQuery.fn.mC ...
- nodejs+express-实现文件上传下载管理的网站
Nodejs+Express-实现文件上传下载管理的网站 项目Github地址(对你有帮助记得给星哟):https://github.com/qcer/updo 后端:基于nodejs的express ...
- (图文实例)用VB.net操作SQLite数据库
转载请注明出处:HateMath归来(http://www.cnblogs.com/hatemath/) 环境: Visual Studio 社区版 64位 Windows 7系统 新建的解决方案基于 ...
- Vue中之nextTick函数源码分析
Vue中之nextTick函数源码分析 1. 什么是Vue.nextTick()?官方文档解释如下:在下次DOM更新循环结束之后执行的延迟回调.在修改数据之后立即使用这个方法,获取更新后的DOM. 2 ...
- 【转载】小tips: PC端传统网页试试使用Zepto.js进行开发
Zepto.js设计之初专为移动端,不对一些古董浏览器支持.所以,尺寸很小,压缩后20K多一点,但是,jQuery压缩后,3.*版本要80多K,1.*版本则要90多K,4倍差距. 由于每个页面都会使用 ...
- UVa11212,Editing a Book
正如书上所说,本题需要用IDA*算法求解 启发函数是3d+h>3maxd(d为当前操作步骤数,h为当前逆序对数,maxd为当前枚举的最大步骤数) 可见迭代递归的核心思想是枚举ans去dfs是否可 ...
- struts2使用模型传值
用户bean package userBeans; public class User { private String username; public String getUsername() { ...