MySQL主从方案业务连接透明化
1、MySQL主从方案业务层的问题
在之前的文章中,我们提到MySQL一主多从集群模式下,对上层业务系统的访问带来了一些问题。本编文章中我们将深入分析这个问题,并介绍如何对这个问题进行改进。MySQL一主多从集群对上层业务系统带来的主要问题是,上层业务系统需要自行控制本次MySQL数据操作需要访问MySQL集群中的哪个节点。产生这个问题的主要原因,是因为MySQL一主多从集群本身并没有提供现成功能,将集群中的节点打包成统一服务并向外提供。
在上图所示的MySQL集群中,有一个Master节点负责所有的写事务操作,还有两个Salve节点分别负责订单模块的读操作和用户模块的读操作,而这个架构方案中由于没有中间管理层,所以到底访问哪一个MySQL服务节点的判断工作全部需要由上层业务系统自行判断。那么解决这个问题的思路也就比较清晰了:我们需要通过一些手段自行为业务层的访问增加一个中间层,以减少业务开发人员的维护工作。
2、改进方式一:使用Spring套件屏蔽细节
如果您的工程使用了Spring组件,那这个问题可以使用Spring配置问题进行改善。但这个方式只能算是改善问题,不能算作完全解决了问题。这是因为虽然通过Spring配置后,业务开发人员不需要再为“访问哪个数据库节点”操碎了心,但是Spring的配置文件依然是存在于业务系统中,当下层MySQL集群节点发生变化时,业务系统就需要改变配置信息并且重新部署;当MySQL集群现有节点发生故障时,上层业务系统也需要变更配置信息并重新部署。这种配置的方法并不能实现数据访问逻辑的完全脱耦。
下面我们给出一个示例,在这个示例中我们使用spring 3.X 版本 + hibernate 4.X 版本 + c3p0 + MySQL JDBC 实现在业务系统中访问数据库节点的规则配置。
如上图所示,我们在业务系统中建立了两个数据源:writeSessionFactory、readSessionFactory,分别负责业务数据的写操作和读操作。当下面的MySQL集群增加新的读节点或者集群中现有节点发生变化时,spring的配置文件也要做相应的配置变化:
- 写操作涉及的数据源和AOP点配置
<!-- 工程中和读写数据源分离无关的Spring配置信息,在这里就不进行赘述了 -->
......
<!-- 这个数据源连接到maseter节点, 作为写操作的数据源-->
<bean id="writedataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass"><value>${writejdbc.driver}</value></property>
<property name="jdbcUrl"><value>${writejdbc.url}</value></property>
<property name="user"><value>${writejdbc.username}</value></property>
<property name="password"><value>${writejdbc.password}</value></property>
<property name="minPoolSize"><value>${writec3p0.minPoolSize}</value></property>
<property name="maxPoolSize"><value>${writec3p0.maxPoolSize}</value></property>
<property name="initialPoolSize"><value>${writec3p0.initialPoolSize}</value></property>
<property name="maxIdleTime"><value>${writec3p0.maxIdleTime}</value></property>
<property name="acquireIncrement"><value>${writec3p0.acquireIncrement}</value></property>
</bean>
<!-- 数据层会话工厂和数据源的映射关系 -->
<bean id="writeSessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="writedataSource" />
<property name="namingStrategy">
<bean class="org.hibernate.cfg.ImprovedNamingStrategy" />
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${writehibernate.dialect}</prop>
<prop key="hibernate.show_sql">${writehibernate.show_sql}</prop>
<prop key="hibernate.format_sql">${writehibernate.format_sql}</prop>
<prop key="hibernate.hbm2ddl.auto">${writehibernate.hbm2ddl.auto}</prop>
<prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext</prop>
</props>
</property>
</bean>
<bean id="writetransactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="writeSessionFactory" />
</bean>
<!--
AOP设置,在templateSSHProject.dao.writeop包或者子包中
涉及以下名字的方法,都要开启事务托管。
并且在抛出任何异常的情况下,spring都要回滚事务
-->
<aop:config>
<aop:pointcut id="writedao" expression="execution(* templateSSHProject.dao.writeop..*.* (..))" />
<aop:advisor advice-ref="writetxAdvice" pointcut-ref="writedao" />
</aop:config>
<tx:advice id="writetxAdvice" transaction-manager="writetransactionManager">
<tx:attributes>
<tx:method name="save*" rollback-for="java.lang.Exception" propagation="REQUIRED" />
<tx:method name="update*" rollback-for="java.lang.Exception" propagation="REQUIRED" />
<tx:method name="delete*" rollback-for="java.lang.Exception" propagation="REQUIRED" />
<tx:method name="modify*" rollback-for="java.lang.Exception" propagation="REQUIRED" />
<tx:method name="create*" rollback-for="java.lang.Exception"