在之前的文章中,我们提到 MySQL 一主多从集群模式下,对上层业务系统的访问带来了一些问题。本编文章中我们将深入分析这个问题,并介绍如何对这个问题进行改进。mysql 一主多从集群对上层业务系统带来的主要问题是,上层业务系统需要自行控制本次 MySQL 数据操作需要访问 MySQL 集群中的哪个节点。产生这个问题的主要原因,是因为 MySQL 一主多从集群本身并没有提供现成功能,将集群中的节点打包成统一服务并向外提供。
在上图所示的 MySQL 集群中,有一个 Master 节点负责所有的写事务操作,还有两个 Salve 节点分别负责订单模块的读操作和用户模块的读操作,而这个架构方案中由于没有中间管理层,所以到底访问哪一个 MySQL 服务节点的判断工作全部需要由上层业务系统自行判断。那么解决这个问题的思路也就比较清晰了:我们需要通过一些手段自行为业务层的访问增加一个中间层,以减少业务开发人员的维护工作。
如果您的工程使用了 spring 组件,那这个问题可以使用 Spring 配置问题进行改善。但这个方式只能算是改善问题,不能算作完全解决了问题。这是因为虽然通过 Spring 配置后,业务开发人员不需要再为 "访问哪个数据库节点" 操碎了心,但是 Spring 的配置文件依然是存在于业务系统中,当下层 MySQL 集群节点发生变化时,业务系统就需要改变配置信息并且重新部署;当 MySQL 集群现有节点发生故障时,上层业务系统也需要变更配置信息并重新部署。这种配置的方法并不能实现数据访问逻辑的完全脱耦。
下面我们给出一个示例,在这个示例中我们使用 spring 3.X 版本 + hibernate 4.X 版本 + c3p0 + MySQL JDBC 实现在业务系统中访问数据库节点的规则配置。
如上图所示,我们在业务系统中建立了两个数据源:writeSessionFactory、readSessionFactory,分别负责业务数据的写操作和读操作。当下面的 MySQL 集群增加新的读节点或者集群中现有节点发生变化时,spring 的配置文件也要做相应的配置变化:
- <!-- 工程中和读写数据源分离无关的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" propagation="REQUIRED"
- />
- <tx:method name="remove*" rollback-for="java.lang.Exception" propagation="REQUIRED"
- />
- <tx:method name="*" read-only="true" />
- </tx:attributes>
- </tx:advice>
- ...... 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
- 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
- 49 50 51 52 53 54 55 56 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
- 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
- 44 45 46 47 48 49 50 51 52 53 54 55 56
- ...... < !--这个数据源连接到salve节点,作为读操作的数据源--><bean id = "readdataSource"class = "com.mchange.v2.c3p0.ComboPooledDataSource"destroy - method = "close" > <property name = "driverClass" > <value > $ {
- readjdbc.driver
- } < /value></property > <property name = "jdbcUrl" > <value > $ {
- readjdbc.url
- } < /value></property > <property name = "user" > <value > $ {
- readjdbc.username
- } < /value></property > <property name = "password" > <value > $ {
- readjdbc.password
- } < /value></property > <property name = "minPoolSize" > <value > $ {
- readc3p0.minPoolSize
- } < /value></property > <property name = "maxPoolSize" > <value > $ {
- readc3p0.maxPoolSize
- } < /value></property > <property name = "initialPoolSize" > <value > $ {
- readc3p0.initialPoolSize
- } < /value></property > <property name = "maxIdleTime" > <value > $ {
- readc3p0.maxIdleTime
- } < /value></property > <property name = "acquireIncrement" > <value > $ {
- readc3p0.acquireIncrement
- } < /value></property > </bean>
- <!-- 数据层会话工厂和数据源的映射关系,基本上和write的设置一致 -->
- <bean id="readSessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
- <property name="dataSource" ref="readdataSource" / > <property name = "namingStrategy" > <bean class = "org.hibernate.cfg.ImprovedNamingStrategy" / ></property>
- <property name="hibernateProperties">
- <props>
- <prop key="hibernate.dialect">${readhibernate.dialect}</prop > <prop key = "hibernate.show_sql" > $ {
- readhibernate.show_sql
- } < /prop>
- <prop key="hibernate.format_sql">${readhibernate.format_sql}</prop > <prop key = "hibernate.hbm2ddl.auto" > $ {
- readhibernate.hbm2ddl.auto
- } < /prop>
- <prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext</prop > </props>
- </property > </bean>
- <bean id="readtransactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
- <property name="sessionFactory" ref="readSessionFactory" / > </bean>
- <!--
- AOP设置,和写操作事务不同的点也在这里
- 这里不需要设置任何方法方法开启事务托管
- 甚至不需要配置这段AOP切面
- -->
- <aop:config>
- <aop:pointcut id="readdao" expression="execution(* templateSSHProject.dao.readop..*.* (..))" / > <aop: advisor advice - ref = "readtxAdvice"pointcut - ref = "readdao" / ></aop:config>
- <tx:advice id="readtxAdvice" transaction-manager="readtransactionManager">
- <tx:attributes>
- <tx:method name="*" read-only="true" / > </tx:attributes>
- </tx: advice > ......1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
- ......public abstract class AbstractReadRelationalDBDAO...... {
- // AbstractRelationalDBDAO类的其它部分都省略了
- ......@Autowired@Qualifier("readSessionFactory") private SessionFactory readSessionFactory;
- @Autowired@Qualifier("writeSessionFactory") private SessionFactory sessionFactory;......
- }......1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
那么有没有什么办法能够解决以上的问题呢?既突破以上 Spring 配置方式只适应一主一从 MySQL 集群的瓶颈,又不增加业务开发人员的维护难度,还能适应下层数据集群随时发生的节点故障。当然是有办法的,使用我们已经在负载均衡专题介绍过的 LVS,我们可以为 MySQL 集群中的多个读节点构造一个透明层,使得它们可以作为一个整体,并使用一个统一的访问地址向上层业务系统提供数据读取服务。
如果您还不清楚 LVS 配置方式,可以参见我另外一专题中,专门介绍 LVS 的几篇文章:《架构设计:负载均衡层设计方案(4)——LVS 原理》、《架构设计:负载均衡层设计方案(5)——LVS 单节点安装》。需要注意的是,这里选择的负载方案应该工作在网络协议的下层,例如 OSI 七层模型的链路层或者传输层。这是因为上层系统连接 MySQL 服务节点主要基于 TCP/IP 协议而不是基于 HTTP 协议,例如 MySQL 的多数客户端软件(MySQL-Front、Navicat 等)都使用 MySQL 原生连接协议,这个协议就是基于 TCP/IP 协议的,再例如绝大多数 Java 应用程序连接和调用 MySQL 操作所基于的 JDBC API,也是基于 TCP/IP 协议。所以这里使用的负载均衡方案不能使用 Nginx 这样只支持 Http 协议的组件,而 LVS 组件可以很好的适应技术需求。
以上的改进方案中,我们只对 MySQL 集群中的读操作节点进行了改进,但是整个集群还是没有足够的稳定保证。这是因为 MySQL 集群中写操作节点目前还只有单个节点承载工作,新加入的 LVS 负载节点也只有单个节点承载工作。如果在生产环境下,以上这些节点出现故障无法工作将导致整个 MySQL 集群崩溃。进一步的改进方式,就是为集群中的写操作节点和 LVS 负载节点增加热备方案,如下图所示:
百度搜索 "就爱阅读", 专业资料, 生活学习, 尽在就爱阅读网 92to.com, 您的在线图书馆!
来源: http://www.92to.com/bangong/2017/07-24/25640756.html