在Spring/J2EE应用中分离只读和读写操作

4
我们的项目中使用了Spring,Spring-Data和JPA。
对于生产服务器,我们希望设置数据库集群,以便所有读取查询都指向一个服务器,而所有写入查询都指向另一个服务器。
显然,这将需要在DAO构建方式上进行一些更改。
如果迄今为止一直在使用Spring-Data/JPA创建cookbook式DAO实现,其中一个DAO实现负责读取和写入操作,那么有谁知道如何实现这一点呢?需要对体系结构进行哪些更改才能分离这两种类型的调用呢?

你使用的是哪个数据库? - manish
我正在使用MySQL 5.6。 - Wand Maker
在这种情况下,ReplicationDriver 对你是否有用?我以前试过它,效果很好。ReplicationDriver 类就像常规的 MySQL Driver 类一样,只是它允许您在 JDBC URL 中传递多个服务器主机。如果您在代码中使用 @Transactional(readOnly = true) 注释只读方法,ReplicationDriver 将始终将 JDBC URL 中的第一个主机视为写入主机,其他主机视为只读。 - manish
谢谢。看起来很有前途。我们有一个多主设置,我们需要转移到主从设置才能使其工作吗? - Wand Maker
我曾经构建过一个示例应用程序,其中只有一个MySQL主服务器,每个应用服务器都运行一个从服务器副本。所有这些都在同一机架上,因此复制不会太经常失败。然后,我们使用JDBC URL jdbc:mysql:replication://master.example.com,local.example.com,其中master.example.com解析为主服务器,local.example.com解析为本地服务器。因此,尽管有许多从服务器,但由于JDBC URL的构造方式,每个应用服务器只会意识到一个(自己)。 - manish
2个回答

15

在使用MySQL时,Java开发人员通常会使用Connector/J作为JDBC驱动程序。开发人员通常使用Connector/J的com.mysql.jdbc.Driver类,并使用类似于jdbc:mysql://host[:port]/database的URL连接到MySQL数据库。

Connector/J提供了另一个名为ReplicationDriver的驱动程序,允许应用程序在多个MySQL主机之间进行负载平衡。当使用ReplicationDriver时,JDBC URL将更改为jdbc:mysql:replication://master-host[:master-port][,slave-1-host[:slave-1-port]][,slave-2-host[:slave-2-port]]/database。这使得应用程序可以连接到多个服务器中的一个,具体取决于任何给定时间可用的服务器。

在使用ReplicationDriver时,如果将JDBC连接设置为read-only,则驱动程序将把URL中声明的第一个主机视为read-write主机,而将所有其他主机视为read-only主机。开发人员可以通过以下方式构建他们的Spring应用程序来利用这一点:

@Service
@Transactional(readOnly = true)
public class SomeServiceImpl implements SomeService {
   public SomeDataType readSomething(...) { ... }

   @Transactional(readOnly = false)
   public void writeSomething(...) { ... }
}

有了这样的代码,每当调用方法readSomething时,Spring事务管理代码将获取一个JDBC Connection并在其上调用setReadOnly(true),因为服务方法默认情况下带有注释@Transactional(readOnly = true)。这将使从readSomething方法中的所有数据库查询都转到非主MySQL主机之一,以轮询方式进行负载平衡。同样,在调用writeSomething时,Spring将在底层JDBC Connection上调用setReadOnly(false),强制数据库查询到主服务器。

这种策略允许应用程序将所有只读流量定向到一组MySQL服务器,并将所有读写流量定向到不同的服务器,而不必更改应用程序的逻辑架构或开发人员担心不同的数据库主机和角色。


我知道这是一个较旧的帖子,但我正在使用Spring(4.2.x)与hibernate(5.1.x)以及mysql复制驱动程序,但即使使用@Transactional(readOnly = true),所有查询仍然似乎都去主节点。看来底层连接没有被设置为只读,还有其他人遇到/解决了这个问题吗? - csyperski
1
我最终重构了我的代码,将其从Hibernate特定的API(即SessionFactory)转换为标准的JPA(即EntityManager),这解决了我的问题。 - csyperski

3

您所讲的实际上是CQRS (http://martinfowler.com/bliki/CQRS.html)。

在尝试实现之前,建议先阅读一些概念指南。

至于您的问题,为了取得简短的第一胜利,建议将DAL的服务分成Finder类和Repository类,这些类将被更高级别的面向业务的服务使用。

Finders适合只读访问,仅公开getBy...()方法和查找返回自定义结果对象(如报告)的方法,并且它们的底层实现是针对只读数据库进行的。

另一方面,Repositories适合写入/通过getById()方法进行访问,它们的底层实现是针对写入数据库进行的。

唯一剩下的就是这些数据库之间的同步。这可以通过技术解决方案来实现,例如:数据库复制、在对写入数据库进行更改后延迟更新只读数据库(最终一致性)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接