如何在应用层通过spring特性解决数据库读写分离

如何在应用层通过spring特性解决数据库读写分离,第1张

两种方案

方案1:当只有读 *** 作的时候,直接 *** 作读库(从库);

当在写事务(即写主库)中读时,也是读主库(即参与到主库 *** 作),这样的优势是可以防止写完后可能读不到刚才写的数据;

此方案其实是使用事务传播行为为:SUPPORTS解决的。

方案2:当只有读 *** 作的时候,直接 *** 作读库(从库);

当在写事务(即写主库)中读时,强制走从库,即先暂停写事务,开启读(读从库),然后恢复写事务。

此方案其实是使用事务传播行为为:NOT_SUPPORTS解决的。

核心组件

cn.javass.common.datasource.ReadWriteDataSource:读写分离的动态数据源,类似于AbstractRoutingDataSource,具体参考javadoc;

cn.javass.common.datasource.ReadWriteDataSourceDecision:读写库选择的决策者,具体参考javadoc;

cn.javass.common.datasource.ReadWriteDataSourceProcessor:此类实现了两个职责(为了减少类的数量将两个功能合并到一起了):读/写动态数据库选择处理器、通过AOP切面实现读/写选择,具体参考javadoc。

具体配置

1、数据源配置

1.1、写库配置

Java代码

<bean id="writeDataSource" class="org.logicalcobwebs.proxool.ProxoolDataSource">

<property name="alias" value="writeDataSource"/>

<property name="driver" value="${write.connection.driver_class}" />

<property name="driverUrl" value="${write.connection.url}" />

<property name="user" value="${write.connection.username}" />

<property name="password" value="${write.connection.password}" />

<property name="maximumConnectionCount" value="${write.proxool.maximum.connection.count}"/>

<property name="minimumConnectionCount" value="${write.proxool.minimum.connection.count}" />

<property name="statistics" value="${write.proxool.statistics}" />

<property name="simultaneousBuildThrottle" value="${write.proxool.simultaneous.build.throttle}"/>

</bean>

1.2、读库配置

Java代码

<bean id="readDataSource1" class="org.logicalcobwebs.proxool.ProxoolDataSource">

<property name="alias" value="readDataSource"/>

<property name="driver" value="${read.connection.driver_class}" />

<property name="driverUrl" value="${read.connection.url}" />

<property name="user" value="${read.connection.username}" />

<property name="password" value="${read.connection.password}" />

<property name="maximumConnectionCount" value="${read.proxool.maximum.connection.count}"/>

<property name="minimumConnectionCount" value="${read.proxool.minimum.connection.count}" />

<property name="statistics" value="${read.proxool.statistics}" />

<property name="simultaneousBuildThrottle" value="${read.proxool.simultaneous.build.throttle}"/>

</bean>

1.3、读写动态库配置

通过writeDataSource指定写库,通过readDataSourceMap指定从库列表,从库列表默认通过顺序轮询来使用读库,具体参考javadoc;

Java代码

<bean id="readWriteDataSource" class="cn.javass.common.datasource.ReadWriteDataSource">

<property name="writeDataSource" ref="writeDataSource"/>

<property name="readDataSourceMap">

<map>

<entry key="readDataSource1" value-ref="readDataSource1"/>

<entry key="readDataSource2" value-ref="readDataSource1"/>

<entry key="readDataSource3" value-ref="readDataSource1"/>

<entry key="readDataSource4" value-ref="readDataSource1"/>

</map>

</property>

</bean>

2、XML事务属性配置

所以读方法必须是read-only(必须,以此来判断是否是读方法)。

Java代码

<tx:advice id="txAdvice" transaction-manager="txManager">

<tx:attributes>

<tx:method name="save*" propagation="REQUIRED" />

<tx:method name="add*" propagation="REQUIRED" />

<tx:method name="create*" propagation="REQUIRED" />

<tx:method name="insert*" propagation="REQUIRED" />

<tx:method name="update*" propagation="REQUIRED" />

<tx:method name="merge*" propagation="REQUIRED" />

<tx:method name="del*" propagation="REQUIRED" />

<tx:method name="remove*" propagation="REQUIRED" />

<tx:method name="put*" read-only="true"/>

<tx:method name="query*" read-only="true"/>

<tx:method name="use*" read-only="true"/>

<tx:method name="get*" read-only="true" />

<tx:method name="count*" read-only="true" />

<tx:method name="find*" read-only="true" />

<tx:method name="list*" read-only="true" />

<tx:method name="*" propagation="REQUIRED"/>

</tx:attributes>

</tx:advice>

3、事务管理器

事务管理器管理的是readWriteDataSource

Java代码

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<property name="dataSource" ref="readWriteDataSource"/>

</bean>

4、读/写动态数据库选择处理器

根据之前的txAdvice配置的事务属性决定是读/写,具体参考javadoc;

forceChoiceReadWhenWrite:用于确定在如果目前是写(即开启了事务),下一步如果是读,是直接参与到写库进行读,还是强制从读库读,具体参考javadoc;

Java代码

<bean id="readWriteDataSourceTransactionProcessor" class="cn.javass.common.datasource.ReadWriteDataSourceProcessor">

<property name="forceChoiceReadWhenWrite" value="false"/>

</bean>

5、事务切面和读/写库选择切面

Java代码

<aop:config expose-proxy="true">

<!-- 只对业务逻辑层实施事务 -->

<aop:pointcut id="txPointcut" expression="execution(* cn.javass..service..*.*(..))" />

<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>

<!-- 通过AOP切面实现读/写库选择 -->

<aop:aspect order="-2147483648" ref="readWriteDataSourceTransactionProcessor">

<aop:around pointcut-ref="txPointcut" method="determineReadOrWriteDB"/>

</aop:aspect>

</aop:config>

1、事务切面一般横切业务逻辑层;

2、此处我们使用readWriteDataSourceTransactionProcessor的通过AOP切面实现读/写库选择功能,order=Integer.MIN_VALUE(即最高的优先级),从而保证在 *** 作事务之前已经决定了使用读/写库。

6、测试用例

只要配置好事务属性(通过read-only=true指定读方法)即可,其他选择读/写库的 *** 作都交给readWriteDataSourceTransactionProcessor完成。

可以参考附件的:

cn.javass.readwrite.ReadWriteDBTestWithForceChoiceReadOnWriteFalse

cn.javass.readwrite.ReadWriteDBTestWithNoForceChoiceReadOnWriteTrue

可以下载附件的代码进行测试,具体选择主/从可以参考日志输出。

但我们仔细观察我们会发现,当我们的项目都是用的单体数据库时,那么就可能会存在如下问题:

为了解决上述提到的两个问题,我们可以准备两 (多) 台MySQL,一台主( Master )服务器,一台从( Slave )服务器,主库的 数据变更 (写、更新、删除这些 *** 作) ,需要 同步 到从库中 (主从复制) 。而用户在访问我们项目时,如果是 写 *** 作 (insert、update、delete),则直接 *** 作 主库 ;如果是 读 *** 作 (select) ,则直接 *** 作从库,这种结构就是 读写分离 啦。

在这种读写分离的结构中,从库是可以有多个的

MySQL主从复制是一个 异步 的复制过程,底层是基于Mysql数据库自带的 二进制日志 功能。就是一台或多台MySQL数据库(slave,即 从库 )从另一台MySQL数据库(master,即 主库 )进行日志的复制,然后再解析日志并应用到自身,最终实现 从库 的数据和 主库 的数据保持一致。MySQL主从复制是 MySQL数据库自带功能,无需借助第三方工具。

二进制日志(BINLOG)记录了所有的 DDL(数据定义语言)语句和 DML(数据 *** 纵语言)语句,但是不包括数据查询语句。此日志对于灾难时的数据恢复起着极其重要的作用,MySQL的主从复制, 就是通过该binlog实现的。默认MySQL是未开启该日志的。

在环境搭建之前,我们需要准备好两台服务器,如果生活富裕使用的是两台云服务器的时候记得要开放安全组,即防火墙;如果是比狗子我生活好点但也是用的虚拟机的话,记得别分这么多内存启动蓝屏了(别问怎么知道的)

这里就不给大家展示数据库的安装和防火墙的 *** 作了,这个我感觉网上好多资源都能够满足遇到的问题,在搭建主从库的时候有在网上见到过说MySQL版本要一致的,我也没太留意直接就在之前的MySQL上 *** 作了,大家可以自己去验证一下。

服务器:192.168.150.100(别试了黑不了的,这是虚拟机的ip)

这里有三个方法都能重启MySQL,最简单的无疑就是一关一开:

登录进去MySQL之后才能够执行下面的命令,因为这是SQL命令,Linux不认识这玩意是啥。

这个时候还 不用退出MySQL ,因为下面的命令还是SQL命令,执行下面的SQL,可以拿到我们后面需要的两个重要参数。

执行完这一句SQL之后,==不要再 *** 作主库!不要再 *** 作主库!不要再 *** 作主库!==重要的事情说三遍,因为再 *** 作主库之后可能会导致红框中的 两个属性值会发生变化 ,后面如果发生了错误可能就和这里有那么两毛钱关系了。

服务器:192.168.150.101(别试了黑不了的,这也是虚拟机的ip)

这里要注意server-id和主库以及其他从库都不能相同,否则后面将会配置不成功。

这里有三个方法都能重启MySQL,最简单的无疑就是一关一开:

登录进去MySQL之后才能够执行下面的命令,因为这是SQL命令

参数说明:

这个时候还 不用退出MySQL ,因为下面的命令还是SQL命令,执行下面的SQL,可以看到从库的状态信息。通过状态信息中的 Slave_IO_running Slave_SQL_running 可以看出主从同步是否就绪,如果这两个参数全为 Yes ,表示主从同步已经配置完成。

这可能是由于linux 是复制出来的,MySQL中还有一个 server_uuid 是一样的,我们也需要修改。 vim /var/lib/mysql/auto.cnf

这应该就是各位大牛设置server_id的时候不小心设置相同的id了,修改过来就行,步骤在上面的配置中。

这是狗子在 *** 作过程中搞出来的一个错误……

出错的原因是在主库中删除了用户信息,但是在从库中同步的时候失败导致同步停止,下面记录自己的 *** 作(是在进入MySQL的 *** 作且是从库)。

在数据库中 *** 作时,一定要注意当前所在的数据库是哪个,作为一个良好的实践:在SQL语句前加 USE dbname。

Sharding-JDBC定位为 轻量级Java框架 ,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以 jar包 形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动, 完全兼容JDBC和各种ORM框架

使用Sharding-JDBC可以在程序中轻松的实现数据库 读写分离

Sharding-JDBC具有以下几个特点:

下面我们将用ShardingJDBC在项目中实现MySQL的读写分离。

在pom.xml文件中导入ShardingJDBC的依赖坐标

在application.yml中增加数据源的配置

这时我们就可以对我们项目中的配置进行一个测试,下面分别调用一个更新接口和一个查询接口,通过查看日志中记录的数据源来判断是否能够按照我们预料中的跑。

搞定!!!程序正常按照我们预期的成功跑起来了,成功借助ShardingJDBC在我们项目中实现了数据库的读写分离。

读db数据的对象,只要被Spring加载了,都会读到ApplicationContext(应用环境)中,

然后任何控制器Controller可以实现ApplicationContextAware(打理应用环境)接口,可以自动加载ApplicationContext,也就可以在控制器的方法中 *** 作其中任一数据对象。

比如某个spring-beans.xml中写上

<bean id="myService" class="com.mypackage.MyDatabaseService" />

然后在控制器中

@Controller

class MyController implements ApplicationContextAware{

   public MyDatabaseService service

   @override public setApplicationContext(ApplicationContext ac){

       service=(MyDatabaseService) ac.getBean("myService")

   }

}

这样就自动加载了


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/sjk/10066677.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-04
下一篇 2023-05-04

发表评论

登录后才能评论

评论列表(0条)

保存