有使用注解,或者使用代理的.
首先是代码层面的
由于简单,使用了注解的方式来做读写分离
后面会讲mysql层面主从的配置
Spring为我们提供了一个类org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
核心思想就是通过AOP拿到注解上的数据源,然后在AbstractRoutingDataSource 的实现类中将数据源返回对应的数据源
public class CustomerRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
LocalDS ds = CustomerContextHolder.getLocalDS();
logger.info("返回数据源对象【" + ds + "】");
if(ds != null){
return ds.getCustomerType();
}
return null;
}
}
public enum CustomerType {
/**
* 主库
*/
MASTER,
/**
* 从库
*/
SLAVE;
}
AOP拦截
@Aspect
@Order(-10000)
@Component
public class AopDatasource {
private Logger logger = LoggerFactory.getLogger(getClass());
/**
* 添加业务逻辑方法切入点
*/
@Pointcut("execution(* com.njq.nongfadai.service..*.*(..))")
public void dynamicDS() {
}
/**
*
* Description: 请添加方法说明: 处理事务之前动态切换数据源
* @param point
* @throws Throwable 参数
*/
@Before("dynamicDS()")
public void dynamicDSSwitchBefore(JoinPoint point) throws Throwable {
Object target = point.getTarget();
String method = point.getSignature().getName();
Class<?> classz = target.getClass(); // 获取目标类
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
.getMethod().getParameterTypes();
LocalDS ds = CustomerContextHolder.getLocalDS();
if(ds == null){
ds = new LocalDS(CustomerType.MASTER, 0);
logger.info("默认主数据源");
}
try {
Method m = classz.getMethod(method, parameterTypes);
if (m != null && m.isAnnotationPresent(DynDatasource.class)) {
DynDatasource data = m.getAnnotation(DynDatasource.class);
logger.info("用户选择数据库库类型:" + data.value());
ds.setCustomerType(data.value());// 数据源放到当前线程中
}
} catch (Exception e) {
logger.error(classz.getName() + "." + method + " 动态切换数据源异常:", e);
} finally{
// 设置数据源
logger.info("设置数据源【{}】", ds);
CustomerContextHolder.setLocalDS(ds); // 数据源放到当前线程中
}
}
/**
*
* Description: 请添加方法说明: 处理事务之后清除动态数据源
* 不论正常执行还是异常退出都会执行
*/
@After("dynamicDS()")
public void dynamicDSSwitchAfter(){
LocalDS ds = CustomerContextHolder.getLocalDS();
logger.info("清空当前线程数据源【{}】",ds);
CustomerContextHolder.clearLocalDS();
}
}
DynDatasource 注解如下
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface DynDatasource {
/**
* 默认走主库
* Description: 请添加方法说明:
* @return 参数
*/
CustomerType value() default CustomerType.MASTER;
}
关于数据源的配置
<bean id="masterDataSource" name="masterDataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 指定连接数据库的驱动 -->
<property name="driverClass" value="${jdbc.driverClassName}" />
<!-- 指定连接数据库的URL -->
<property name="jdbcUrl" value="${jdbc.url}" />
<!-- 指定连接数据库的用户名 -->
<property name="user" value="${jdbc.username}" />
<!-- 指定连接数据库的密码 -->
<property name="password" value="${jdbc.password}" />
<!-- 指定连接池中保留的最大连接数. Default:15 -->
<property name="maxPoolSize" value="${jdbc.maxPoolSize}" />
<!-- 指定连接池中保留的最小连接数 -->
<property name="minPoolSize" value="${jdbc.minPoolSize}" />
<!-- 指定连接池的初始化连接数 取值应在minPoolSize 与 maxPoolSize 之间.Default:3 -->
<property name="initialPoolSize" value="${jdbc.initialPoolSize}" />
<!-- 最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。 Default:0 -->
<property name="maxIdleTime" value="${jdbc.maxIdleTime}" />
<!-- 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数. Default:3 -->
<property name="acquireIncrement" value="${jdbc.acquireIncrement}" />
<!-- JDBC的标准,用以控制数据源内加载的PreparedStatements数量。 但由于预缓存的statements属于单个connection而不是整个连接池所以设置这个参数需要考虑到多方面的因数.如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default:0 -->
<property name="maxStatements" value="${jdbc.maxStatements}" />
<!-- 每60秒检查所有连接池中的空闲连接.Default:0 -->
<property name="idleConnectionTestPeriod" value="${jdbc.idleConnectionTestPeriod}" />
</bean>
<bean id="slaveDataSource" name="slaveDataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 指定连接数据库的驱动 -->
<property name="driverClass" value="${jdbc.slave.driverClassName}" />
<!-- 指定连接数据库的URL -->
<property name="jdbcUrl" value="${jdbc.slave.url}" />
<!-- 指定连接数据库的用户名 -->
<property name="user" value="${jdbc.slave.username}" />
<!-- 指定连接数据库的密码 -->
<property name="password" value="${jdbc.slave.password}" />
<!-- 指定连接池中保留的最大连接数. Default:15 -->
<property name="maxPoolSize" value="${jdbc.slave.maxPoolSize}" />
<!-- 指定连接池中保留的最小连接数 -->
<property name="minPoolSize" value="${jdbc.slave.minPoolSize}" />
<!-- 指定连接池的初始化连接数 取值应在minPoolSize 与 maxPoolSize 之间.Default:3 -->
<property name="initialPoolSize" value="${jdbc.slave.initialPoolSize}" />
<!-- 最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。 Default:0 -->
<property name="maxIdleTime" value="${jdbc.slave.maxIdleTime}" />
<!-- 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数. Default:3 -->
<property name="acquireIncrement" value="${jdbc.slave.acquireIncrement}" />
<!-- JDBC的标准,用以控制数据源内加载的PreparedStatements数量。 但由于预缓存的statements属于单个connection而不是整个连接池所以设置这个参数需要考虑到多方面的因数.如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default:0 -->
<property name="maxStatements" value="${jdbc.slave.maxStatements}" />
<!-- 每60秒检查所有连接池中的空闲连接.Default:0 -->
<property name="idleConnectionTestPeriod" value="${jdbc.slave.idleConnectionTestPeriod}" />
</bean>
<bean id="dataSource" class="com.yjm.datasource.CustomerRoutingDataSource">
<property name="targetDataSources">
<map key-type="com.yjm.datasource.CustomerType">
<entry key="MASTER" value-ref="masterDataSource" />
<entry key="SLAVE" value-ref="slaveDataSource" />
</map>
</property>
<property name="defaultTargetDataSource" ref="masterDataSource" />
</bean>
Mysql层面的搭建
首先得介绍Mysql的集群
Mysql主主互备架构图如下
使用Keepalived来监控DB1和DB2的运行状态,同时维护一个VIP,此IP用来对外提供连续服务.详细的可见对Keepalived的介绍
MMM架构
这里是使用了MMM套件来进行管理,需要5个IP地址,两个Master节点各有一个固定不变的物理IP地址,另外还有两个只读IP和一个可写IP,这三个虚拟IP(两个读IP和一个写IP)不会固定在任何一个节点上,相反,它会在两个Master节点之间来回切换,如何切换取决于可用性
在双Master节点的基础上,增加多个Slave节点,即可实现双主多从节点应用架构
MMM的优势就是不仅可以监控两个Master节点的运行状态,还可以监控多个Slave节点的运行状态,整个切换过程完全不需要手工更改同步复制的配置,如果使用Keepalived的话,还需要手工写脚本监控每个节点的运行状态
Amoeba是一个分布式数据库的前端代理,主要是在应用层访问Mysql的时候充当SQL路由的功能,具有负载均衡,高可用,SQL过滤,读写分离等功能.
当然该架构比较复杂,后面有时间再实践一下吧