PostgreSQL
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> PostgreSQL

Cách tách các giao dịch chỉ đọc và đọc ghi với JPA và Hibernate

Đị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_WRITEreadOnlyDataSource 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 định hibernate.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ọi determineCurrentLookupKey phương thức TransactionRoutingDataSource .

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 newPostfindAllPostsByTitle 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.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Hibernate + PostgreSQL + Loại địa chỉ mạng (inet, cdir)

  2. Lỗi Postgres:Nhiều hơn một hàng được trả về bởi một truy vấn con được sử dụng làm biểu thức

  3. Cách hoạt động của hàm Sign () trong PostgreSQL

  4. Rails phương thức không xác định cho ActiveRecord_Associations_CollectionProxy

  5. Cần tìm gì nếu Bản sao PostgreSQL của bạn đang bị trễ