在 Spring/J2EE 应用程序中分离只读和读写
Segregating the read-only and read-write in Spring/J2EE Apps
我们在项目中使用 Spring、Spring-Data 和 JPA。
对于生产服务器,我们希望设置数据库集群,以便所有读取查询都定向到一台服务器,所有写入查询都定向到另一台服务器。
这显然需要对 DAO 的构建方式进行一些更改。
有没有人知道如何实现这一点,如果到目前为止,一直在使用 Spring-Data/JPA 遵循烹饪书风格的 DAO 创建,其中 DAO 实现负责读取和写入?隔离这两种类型的调用需要对体系结构进行什么样的更改?
嗯,你说的其实是CQRS(http://martinfowler.com/bliki/CQRS.html)。
我建议在尝试实施之前先阅读一些概念指南。
至于你的问题,为了取得短暂的第一场胜利,我建议开始将 DAL 的服务划分为 Finder 类 和 Repository 类,它们将被更高的、面向业务的服务使用。
Finders 将适合只读访问,仅公开 getBy...() 方法和 return 自定义结果对象(例如报告)的查找,它们的底层实现是针对读取而定制的- 只有数据库。
另一方面,存储库将适合只写/getById() 方法,并且它们的底层实现是针对只写数据库进行定制的。
唯一剩下的就是这些数据库之间的同步。
这可以通过技术解决方案非常简单地实现,例如:数据库复制,对只写数据库进行更改后对只读数据库的延迟更新(最终一致性)。
当使用 MySQL 时,Java 开发人员通常使用 Connector/J 作为 JDBC 驱动程序。开发人员通常使用 Connector/J com.mysql.jdbc.Driver
class 和 URL 例如 jdbc:mysql://host[:port]/database
连接到 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 中声明的第一个主机视为 [=18] =] 主机和所有其他主机为 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、Spring-Data 和 JPA。
对于生产服务器,我们希望设置数据库集群,以便所有读取查询都定向到一台服务器,所有写入查询都定向到另一台服务器。
这显然需要对 DAO 的构建方式进行一些更改。
有没有人知道如何实现这一点,如果到目前为止,一直在使用 Spring-Data/JPA 遵循烹饪书风格的 DAO 创建,其中 DAO 实现负责读取和写入?隔离这两种类型的调用需要对体系结构进行什么样的更改?
嗯,你说的其实是CQRS(http://martinfowler.com/bliki/CQRS.html)。 我建议在尝试实施之前先阅读一些概念指南。
至于你的问题,为了取得短暂的第一场胜利,我建议开始将 DAL 的服务划分为 Finder 类 和 Repository 类,它们将被更高的、面向业务的服务使用。
Finders 将适合只读访问,仅公开 getBy...() 方法和 return 自定义结果对象(例如报告)的查找,它们的底层实现是针对读取而定制的- 只有数据库。
另一方面,存储库将适合只写/getById() 方法,并且它们的底层实现是针对只写数据库进行定制的。
唯一剩下的就是这些数据库之间的同步。 这可以通过技术解决方案非常简单地实现,例如:数据库复制,对只写数据库进行更改后对只读数据库的延迟更新(最终一致性)。
当使用 MySQL 时,Java 开发人员通常使用 Connector/J 作为 JDBC 驱动程序。开发人员通常使用 Connector/J com.mysql.jdbc.Driver
class 和 URL 例如 jdbc:mysql://host[:port]/database
连接到 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 中声明的第一个主机视为 [=18] =] 主机和所有其他主机为 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 服务器,并将所有读写流量定向到另一台服务器,而无需更改应用程序的逻辑架构或开发人员不必担心关于不同的数据库主机和角色。