MySQL读写分离应用层实现

基于Spring的AOP

在应用层,实现读写分离
在请求进入Service之前,使用AOP来做出判断

判断方式
是使用写库还是读库,判断依据可以根据方法名判断
比如,以query、find、get等开头的就走读库,其他的走写库
这里写图片描述

DynamicDataSource

定义动态数据源

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 定义动态数据源,实现通过集成Spring提供的AbstractRoutingDataSource,只需要实现determineCurrentLookupKey方法即可
 * 
 * 由于DynamicDataSource是单例的,线程不安全的,所以采用ThreadLocal保证线程安全,由DynamicDataSourceHolder完成。
 *
 */
public class DynamicDataSource extends AbstractRoutingDataSource{

    @Override
    protected Object determineCurrentLookupKey() {
        // 使用DynamicDataSourceHolder保证线程安全,并且得到当前线程中的数据源key
        return DynamicDataSourceHolder.getDataSourceKey();
    }
}

DynamicDataSourceHolder

使用ThreadLocal记录数据源

/**
 * 
 * 使用ThreadLocal技术来记录当前线程中的数据源的key
 *
 */
public class DynamicDataSourceHolder {

    //写库对应的数据源key
    private static final String MASTER = "master";

    //读库对应的数据源key
    private static final String SLAVE = "slave";

    //使用ThreadLocal记录当前线程的数据源key
    private static final ThreadLocal<String> holder = new ThreadLocal<String>();

    /**
     * 设置数据源key
     * @param key
     */
    public static void putDataSourceKey(String key) {
        holder.set(key);
    }

    /**
     * 获取数据源key
     * @return
     */
    public static String getDataSourceKey() {
        return holder.get();
    }

    /**
     * 标记写库
     */
    public static void markMaster(){
        putDataSourceKey(MASTER);
    }

    /**
     * 标记读库
     */
    public static void markSlave(){
        putDataSourceKey(SLAVE);
    }
}

DataSourceAspect

定义数据源的AOP切面

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;

/**
 * 定义数据源的AOP切面,通过该Service的方法名判断是应该走读库还是写库
 *
 */
public class DataSourceAspect {

    /**
     * 在进入Service方法之前执行
     * 
     * @param point 切面对象
     */
    public void before(JoinPoint point) {
        // 获取到当前执行的方法名
        String methodName = point.getSignature().getName();
        if (isSlave(methodName)) {
            // 标记为读库
            DynamicDataSourceHolder.markSlave();
        } else {
            // 标记为写库
            DynamicDataSourceHolder.markMaster();
        }
    }

    /**
     * 判断是否为读库
     * 
     * @param methodName
     * @return
     */
    private Boolean isSlave(String methodName) {
        // 方法名以query、find、get开头的方法名走从库
        return StringUtils.startsWithAny(methodName, "query", "find", "get");
    }
}

数据源

配置2个数据源
jdbc.properties

jdbc.master.driver=com.mysql.jdbc.Driver
jdbc.master.url=jdbc:mysql://127.0.0.1:3306/mybatis_1128?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
jdbc.master.username=root
jdbc.master.password=123456

jdbc.slave01.driver=com.mysql.jdbc.Driver
jdbc.slave01.url=jdbc:mysql://127.0.0.1:3307/mybatis_1128?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
jdbc.slave01.username=root
jdbc.slave01.password=123456

定义连接池

applicationContext.xml

<!-- 配置连接池 -->
<bean id="masterDataSource" class="com.jolbox.bonecp.BoneCPDataSource"
    destroy-method="close">
    <!-- 数据库驱动 -->
    <property name="driverClass" value="${jdbc.master.driver}" />
    <!-- 相应驱动的jdbcUrl -->
    <property name="jdbcUrl" value="${jdbc.master.url}" />
    <!-- 数据库的用户名 -->
    <property name="username" value="${jdbc.master.username}" />
    <!-- 数据库的密码 -->
    <property name="password" value="${jdbc.master.password}" />
    <!-- 检查数据库连接池中空闲连接的间隔时间,单位是分,默认值:240,如果要取消则设置为0 -->
    <property name="idleConnectionTestPeriod" value="60" />
    <!-- 连接池中未使用的链接最大存活时间,单位是分,默认值:60,如果要永远存活设置为0 -->
    <property name="idleMaxAge" value="30" />
    <!-- 每个分区最大的连接数 -->
    <property name="maxConnectionsPerPartition" value="150" />
    <!-- 每个分区最小的连接数 -->
    <property name="minConnectionsPerPartition" value="5" />
</bean>

<!-- 配置连接池 -->
<bean id="slave01DataSource" class="com.jolbox.bonecp.BoneCPDataSource"
    destroy-method="close">
    <!-- 数据库驱动 -->
    <property name="driverClass" value="${jdbc.slave01.driver}" />
    <!-- 相应驱动的jdbcUrl -->
    <property name="jdbcUrl" value="${jdbc.slave01.url}" />
    <!-- 数据库的用户名 -->
    <property name="username" value="${jdbc.slave01.username}" />
    <!-- 数据库的密码 -->
    <property name="password" value="${jdbc.slave01.password}" />
    <!-- 检查数据库连接池中空闲连接的间隔时间,单位是分,默认值:240,如果要取消则设置为0 -->
    <property name="idleConnectionTestPeriod" value="60" />
    <!-- 连接池中未使用的链接最大存活时间,单位是分,默认值:60,如果要永远存活设置为0 -->
    <property name="idleMaxAge" value="30" />
    <!-- 每个分区最大的连接数 -->
    <property name="maxConnectionsPerPartition" value="150" />
    <!-- 每个分区最小的连接数 -->
    <property name="minConnectionsPerPartition" value="5" />
</bean>

DataSource

定义数据源

<!-- 定义数据源,使用自己实现的数据源 -->
<bean id="dataSource" class="cn.itcast.usermanage.spring.DynamicDataSource">
    <!-- 设置多个数据源 -->
    <property name="targetDataSources">
        <map key-type="java.lang.String">
            <!-- 这个key需要和程序中的key一致 -->
            <entry key="master" value-ref="masterDataSource" />
            <entry key="slave" value-ref="slave01DataSource" />
        </map>
    </property>
    <!-- 设置默认的数据源,这里默认走写库 -->
    <property name="defaultTargetDataSource" ref="masterDataSource" />
</bean>

事务管理

定义事务管理器

<!-- 定义事务管理器 -->
<bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

事务策略

定义事务策略

<!-- 定义事务策略 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!--定义查询方法都是只读的 -->
        <tx:method name="query*" read-only="true" />
        <tx:method name="find*" read-only="true" />
        <tx:method name="get*" read-only="true" />

        <!-- 主库执行操作,事务传播行为定义为默认行为 -->
        <tx:method name="save*" propagation="REQUIRED" />
        <tx:method name="update*" propagation="REQUIRED" />
        <tx:method name="delete*" propagation="REQUIRED" />

        <!--其他方法使用默认事务策略 -->
        <tx:method name="*" />
    </tx:attributes>
</tx:advice>

定义切面

自定义数据源在前面
先应该确定数据源,然后,开启事务
确保,该切面在最前面

<!-- 定义AOP切面处理器 -->
<bean class="cn.itcast.usermanage.spring.DataSourceAspect" id="dataSourceAspect" />
<aop:config>
    <!-- 定义切面,所有的service的所有方法 -->
    <aop:pointcut id="txPointcut"
        expression="execution(* xx.xxx.xxxxxxx.service.*.*(..))" />
    <!-- 应用事务策略到Service切面 -->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />

    <!-- 将切面应用到自定义的切面处理器上,-9999保证该切面优先级最高执行 -->
    <aop:aspect ref="dataSourceAspect" order="-9999">
        <aop:before method="before" pointcut-ref="txPointcut" />
    </aop:aspect>
</aop:config>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Node.js 的 MySQL 分表分库数据访问中间件,实现MySQL数据的分布式集群储存管理。在处理海量数据、高并发访问时,获得更加优越的性能及横向扩展能力。它包含以下主要特性: 可伸缩、高扩展的架构 自动路由分库,维护数据库连接池 支持数据表的“横向”和“纵向”分表 支持“一主多从”式读写分离 分布式并行处理,成倍提升性能 对应用层隐藏数据来源及技术细节 拥有以上特点意味着,可随时通过增加普通级别数据库服务器的方式,方便地扩展整体系统性能,而无需修改业务层架构和代码。理论上TribeDB的扩展能力上线在于主库单表插入性能和主从数据同步开销。通过合理设计“横向”和“纵向”分表和数据切分粒度,可轻松应对上亿级别的数据量和访问请求。 快速上手: var tribe = require('tribedb'); //载入配置文件,sync选项为true 表示同步读取解析配置文件 tribe.configure('/path/to/tribe.conf',{sync:true}); //通过数据库表名建立查询请求 var db = tribe.createQuery('my_table'); //插入封装 db.data({title:'标题'}).insert(function(err, data){   console.log(err);   console.log(data); }); //查询封装 db.where('title','标题').order_by('time','DESC').limit(1).select(function(err, data){   console.log(err);   console.log(data); }); //不使用封装的操作,直接执行sql tribe.query('SELECT * FROM user_0 WHERE id=1 LIMIT 1',function(err, data){   console.log(err);   console.log(data); }); TribeDB 通过全局唯一的表名,自动连接对应的数据库,并通过分表配置,将操作映射到涉及的分表,同时完成读写分离。 一切都由 TribeDB 自动完成,业务层不必关心数据的位置。当数据库负载过高需要添加服务器时,只需简单修改配置文件而不必修改业务代码,甚至将整个架构推倒重来。继续阅读文档详细了解如何使用。 标签:TribeDB
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值