Định tuyến giao dịch mùa xuân
Đầu tiên, chúng tôi sẽ tạo một DataSourceType
Java Enum xác định các tùy chọn định tuyến giao dịch của chúng tôi:
public enum DataSourceType {
READ_WRITE,
READ_ONLY
}
Để định tuyến các giao dịch đọc-ghi đến nút Chính và các giao dịch chỉ đọc đến nút Bản sao, chúng ta có thể xác định một ReadWriteDataSource
kết nối với nút Chính và một ReadOnlyDataSource
kết nối với nút Replica.
Định tuyến giao dịch chỉ đọc và đọc được thực hiện bởi Spring AbstractRoutingDataSource
trừu tượng, được triển khai bởi TransactionRoutingDatasource
, như được minh họa bằng sơ đồ sau:
TransactionRoutingDataSource
rất dễ thực hiện và trông như sau:
public class TransactionRoutingDataSource
extends AbstractRoutingDataSource {
@Nullable
@Override
protected Object determineCurrentLookupKey() {
return TransactionSynchronizationManager
.isCurrentTransactionReadOnly() ?
DataSourceType.READ_ONLY :
DataSourceType.READ_WRITE;
}
}
Về cơ bản, chúng tôi kiểm tra Spring TransactionSynchronizationManager
lớp lưu trữ ngữ cảnh giao dịch hiện tại để kiểm tra xem giao dịch Spring hiện đang chạy có phải là giao dịch chỉ đọc hay không.
determineCurrentLookupKey
phương thức trả về giá trị phân biệt sẽ được sử dụng để chọn đọc-ghi hoặc chỉ đọc JDBC DataSource
.
Cấu hình JDBC DataSource chỉ đọc và ghi trong Spring
DataSource
cấu hình như sau:
@Configuration
@ComponentScan(
basePackages = "com.vladmihalcea.book.hpjp.util.spring.routing"
)
@PropertySource(
"/META-INF/jdbc-postgresql-replication.properties"
)
public class TransactionRoutingConfiguration
extends AbstractJPAConfiguration {
@Value("${jdbc.url.primary}")
private String primaryUrl;
@Value("${jdbc.url.replica}")
private String replicaUrl;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource readWriteDataSource() {
PGSimpleDataSource dataSource = new PGSimpleDataSource();
dataSource.setURL(primaryUrl);
dataSource.setUser(username);
dataSource.setPassword(password);
return connectionPoolDataSource(dataSource);
}
@Bean
public DataSource readOnlyDataSource() {
PGSimpleDataSource dataSource = new PGSimpleDataSource();
dataSource.setURL(replicaUrl);
dataSource.setUser(username);
dataSource.setPassword(password);
return connectionPoolDataSource(dataSource);
}
@Bean
public TransactionRoutingDataSource actualDataSource() {
TransactionRoutingDataSource routingDataSource =
new TransactionRoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(
DataSourceType.READ_WRITE,
readWriteDataSource()
);
dataSourceMap.put(
DataSourceType.READ_ONLY,
readOnlyDataSource()
);
routingDataSource.setTargetDataSources(dataSourceMap);
return routingDataSource;
}
@Override
protected Properties additionalProperties() {
Properties properties = super.additionalProperties();
properties.setProperty(
"hibernate.connection.provider_disables_autocommit",
Boolean.TRUE.toString()
);
return properties;
}
@Override
protected String[] packagesToScan() {
return new String[]{
"com.vladmihalcea.book.hpjp.hibernate.transaction.forum"
};
}
@Override
protected String databaseType() {
return Database.POSTGRESQL.name().toLowerCase();
}
protected HikariConfig hikariConfig(
DataSource dataSource) {
HikariConfig hikariConfig = new HikariConfig();
int cpuCores = Runtime.getRuntime().availableProcessors();
hikariConfig.setMaximumPoolSize(cpuCores * 4);
hikariConfig.setDataSource(dataSource);
hikariConfig.setAutoCommit(false);
return hikariConfig;
}
protected HikariDataSource connectionPoolDataSource(
DataSource dataSource) {
return new HikariDataSource(hikariConfig(dataSource));
}
}
/META-INF/jdbc-postgresql-replication.properties
tệp tài nguyên cung cấp cấu hình cho JDBC DataSource
đọc-ghi và chỉ đọc thành phần:
hibernate.dialect=org.hibernate.dialect.PostgreSQL10Dialect
jdbc.url.primary=jdbc:postgresql://localhost:5432/high_performance_java_persistence
jdbc.url.replica=jdbc:postgresql://localhost:5432/high_performance_java_persistence_replica
jdbc.username=postgres
jdbc.password=admin
jdbc.url.primary
thuộc tính xác định URL của nút Chính trong khi jdbc.url.replica
xác định URL của nút Replica.
readWriteDataSource
Thành phần Spring xác định JDBC DataSource
đọc-ghi trong khi readOnlyDataSource
thành phần xác định JDBC DataSource
chỉ đọc .
Lưu ý rằng cả nguồn dữ liệu đọc-ghi và chỉ đọc đều sử dụng HikariCP để tổng hợp kết nối.
actualDataSource
hoạt động như một mặt tiền cho các nguồn dữ liệu chỉ đọc và đọc và được triển khai bằng cách sử dụng TransactionRoutingDataSource
tiện ích.
readWriteDataSource
được đăng ký bằng DataSourceType.READ_WRITE
và readOnlyDataSource
sử dụng DataSourceType.READ_ONLY
phím.
Vì vậy, khi thực thi đọc-ghi @Transactional
, readWriteDataSource
sẽ được sử dụng khi thực thi @Transactional(readOnly = true)
, readOnlyDataSource
sẽ được sử dụng thay thế.
Lưu ý rằng
additionalProperties
phương thức xác địnhhibernate.connection.provider_disables_autocommit
Thuộc tính Hibernate mà tôi đã thêm vào Hibernate để hoãn việc mua lại cơ sở dữ liệu cho các giao dịch RESOURCE_LOCAL JPA.Không chỉ vậy,
hibernate.connection.provider_disables_autocommit
cho phép bạn sử dụng tốt hơn các kết nối cơ sở dữ liệu, nhưng đó là cách duy nhất chúng tôi có thể làm cho ví dụ này hoạt động vì, không có cấu hình này, kết nối được thu thập trước khi gọidetermineCurrentLookupKey
phương thứcTransactionRoutingDataSource
.
Các thành phần Spring còn lại cần thiết để xây dựng JPA EntityManagerFactory
được xác định bởi AbstractJPAConfiguration
lớp cơ sở.
Về cơ bản, actualDataSource
được bao bọc thêm bởi DataSource-Proxy và được cung cấp cho JPA EntityManagerFactory
. Bạn có thể kiểm tra mã nguồn trên GitHub để biết thêm chi tiết.
Thời gian kiểm tra
Để kiểm tra xem định tuyến giao dịch có hoạt động hay không, chúng tôi sẽ bật nhật ký truy vấn PostgreSQL bằng cách đặt các thuộc tính sau trong postgresql.conf
tệp cấu hình:
log_min_duration_statement = 0
log_line_prefix = '[%d] '
log_min_duration_statement
cài đặt thuộc tính là để ghi lại tất cả các câu lệnh PostgreSQL trong khi câu lệnh thứ hai thêm tên cơ sở dữ liệu vào nhật ký SQL.
Vì vậy, khi gọi newPost
và findAllPostsByTitle
như sau:
Post post = forumService.newPost(
"High-Performance Java Persistence",
"JDBC", "JPA", "Hibernate"
);
List<Post> posts = forumService.findAllPostsByTitle(
"High-Performance Java Persistence"
);
Chúng ta có thể thấy rằng PostgreSQL ghi lại các thông báo sau:
[high_performance_java_persistence] LOG: execute <unnamed>:
BEGIN
[high_performance_java_persistence] DETAIL:
parameters: $1 = 'JDBC', $2 = 'JPA', $3 = 'Hibernate'
[high_performance_java_persistence] LOG: execute <unnamed>:
select tag0_.id as id1_4_, tag0_.name as name2_4_
from tag tag0_ where tag0_.name in ($1 , $2 , $3)
[high_performance_java_persistence] LOG: execute <unnamed>:
select nextval ('hibernate_sequence')
[high_performance_java_persistence] DETAIL:
parameters: $1 = 'High-Performance Java Persistence', $2 = '4'
[high_performance_java_persistence] LOG: execute <unnamed>:
insert into post (title, id) values ($1, $2)
[high_performance_java_persistence] DETAIL:
parameters: $1 = '4', $2 = '1'
[high_performance_java_persistence] LOG: execute <unnamed>:
insert into post_tag (post_id, tag_id) values ($1, $2)
[high_performance_java_persistence] DETAIL:
parameters: $1 = '4', $2 = '2'
[high_performance_java_persistence] LOG: execute <unnamed>:
insert into post_tag (post_id, tag_id) values ($1, $2)
[high_performance_java_persistence] DETAIL:
parameters: $1 = '4', $2 = '3'
[high_performance_java_persistence] LOG: execute <unnamed>:
insert into post_tag (post_id, tag_id) values ($1, $2)
[high_performance_java_persistence] LOG: execute S_3:
COMMIT
[high_performance_java_persistence_replica] LOG: execute <unnamed>:
BEGIN
[high_performance_java_persistence_replica] DETAIL:
parameters: $1 = 'High-Performance Java Persistence'
[high_performance_java_persistence_replica] LOG: execute <unnamed>:
select post0_.id as id1_0_, post0_.title as title2_0_
from post post0_ where post0_.title=$1
[high_performance_java_persistence_replica] LOG: execute S_1:
COMMIT
Các câu lệnh nhật ký sử dụng high_performance_java_persistence
tiền tố được thực thi trên nút Chính trong khi các tiền tố sử dụng high_performance_java_persistence_replica
trên nút Replica.
Vì vậy, mọi thứ hoạt động như một sự quyến rũ!
Tất cả mã nguồn có thể được tìm thấy trong kho lưu trữ Java Persistence GitHub Hiệu suất cao của tôi, vì vậy bạn cũng có thể dùng thử.
Kết luận
Bạn cần đảm bảo rằng bạn đặt kích thước phù hợp cho các nhóm kết nối của mình vì điều đó có thể tạo ra sự khác biệt rất lớn. Đối với điều này, tôi khuyên bạn nên sử dụng Flexy Pool.
Bạn cần phải rất siêng năng và đảm bảo rằng bạn đánh dấu tất cả các giao dịch chỉ đọc cho phù hợp. Thật bất thường khi chỉ 10% giao dịch của bạn ở chế độ chỉ đọc. Có thể là bạn có một ứng dụng ghi hầu hết như vậy hoặc bạn đang sử dụng các giao dịch ghi trong đó bạn chỉ đưa ra các câu lệnh truy vấn?
Để xử lý hàng loạt, bạn chắc chắn cần các giao dịch đọc-ghi, vì vậy hãy đảm bảo rằng bạn bật JDBC theo lô, như sau:
<property name="hibernate.order_updates" value="true"/>
<property name="hibernate.order_inserts" value="true"/>
<property name="hibernate.jdbc.batch_size" value="25"/>
Để phân lô, bạn cũng có thể sử dụng DataSource
riêng biệt sử dụng nhóm kết nối khác kết nối với nút Chính.
Chỉ cần đảm bảo tổng kích thước kết nối của tất cả các nhóm kết nối nhỏ hơn số lượng kết nối mà PostgreSQL đã được định cấu hình.
Mỗi công việc hàng loạt phải sử dụng một giao dịch chuyên dụng, vì vậy hãy đảm bảo bạn sử dụng kích thước lô hợp lý.
Hơn nữa, bạn muốn giữ khóa và kết thúc giao dịch càng nhanh càng tốt. Nếu bộ xử lý hàng loạt đang sử dụng bộ xử lý đồng thời, hãy đảm bảo kích thước nhóm kết nối được liên kết bằng số lượng bộ xử lý để họ không đợi người khác giải phóng kết nối.