PostgreSQL
 sql >> डेटाबेस >  >> RDS >> PostgreSQL

जेपीए और हाइबरनेट के साथ केवल-पढ़ने और पढ़ने-लिखने के लेनदेन को कैसे विभाजित करें

स्प्रिंग ट्रांजैक्शन रूटिंग

सबसे पहले, हम एक DataSourceType बनाएंगे Java Enum जो हमारे लेन-देन रूटिंग विकल्पों को परिभाषित करता है:

public enum  DataSourceType {
    READ_WRITE,
    READ_ONLY
}

रीड-राइट ट्रांजैक्शन को प्राइमरी नोड और रीड-ओनली ट्रांजैक्शन को रेप्लिका नोड में रूट करने के लिए, हम एक ReadWriteDataSource को परिभाषित कर सकते हैं। जो प्राथमिक नोड और एक ReadOnlyDataSource . से जुड़ता है जो रेप्लिका नोड से जुड़ते हैं।

रीड-राइट और रीड-ओनली ट्रांजैक्शन रूटिंग स्प्रिंग AbstractRoutingDataSource द्वारा की जाती है एब्स्ट्रैक्शन, जिसे TransactionRoutingDatasource . द्वारा कार्यान्वित किया जाता है , जैसा कि निम्नलिखित आरेख द्वारा दिखाया गया है:

TransactionRoutingDataSource लागू करना बहुत आसान है और इस प्रकार दिखता है:

public class TransactionRoutingDataSource 
        extends AbstractRoutingDataSource {

    @Nullable
    @Override
    protected Object determineCurrentLookupKey() {
        return TransactionSynchronizationManager
            .isCurrentTransactionReadOnly() ?
            DataSourceType.READ_ONLY :
            DataSourceType.READ_WRITE;
    }
}

मूल रूप से, हम स्प्रिंग का निरीक्षण करते हैं TransactionSynchronizationManager क्लास जो वर्तमान ट्रांजेक्शनल संदर्भ को स्टोर करती है ताकि यह जांचा जा सके कि वर्तमान में चल रहा स्प्रिंग ट्रांजैक्शन केवल-पढ़ने के लिए है या नहीं।

determineCurrentLookupKey विधि विवेचक मान देता है जिसका उपयोग या तो पढ़ने-लिखने या केवल-पढ़ने के लिए JDBC DataSource को चुनने के लिए किया जाएगा ।

स्प्रिंग रीड-राइट और रीड-ओनली JDBC डेटासोर्स कॉन्फ़िगरेशन

DataSource विन्यास इस प्रकार दिखता है:

@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 संसाधन फ़ाइल रीड-राइट और रीड-ओनली JDBC के लिए कॉन्फ़िगरेशन प्रदान करती है DataSource घटक:

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 संपत्ति प्राथमिक नोड के यूआरएल को परिभाषित करती है जबकि jdbc.url.replica रेप्लिका नोड के URL को परिभाषित करता है।

readWriteDataSource स्प्रिंग घटक रीड-राइट JDBC को परिभाषित करता है DataSource जबकि readOnlyDataSource घटक केवल-पढ़ने के लिए JDBC को परिभाषित करता है DataSource

<ब्लॉककोट>

ध्यान दें कि रीड-राइट और रीड-ओनली डेटा स्रोत कनेक्शन पूलिंग के लिए HikariCP का उपयोग करते हैं।

actualDataSource रीड-राइट और रीड-ओनली डेटा स्रोतों के लिए एक मुखौटा के रूप में कार्य करता है और इसे TransactionRoutingDataSource का उपयोग करके कार्यान्वित किया जाता है। उपयोगिता।

readWriteDataSource DataSourceType.READ_WRITE . का उपयोग करके पंजीकृत है कुंजी और readOnlyDataSource DataSourceType.READ_ONLY . का उपयोग करके कुंजी।

इसलिए, एक पठन-लेखन @Transactional executing निष्पादित करते समय विधि, readWriteDataSource @Transactional(readOnly = true) . को निष्पादित करते समय उपयोग किया जाएगा विधि, readOnlyDataSource इसके बजाय इस्तेमाल किया जाएगा।

<ब्लॉककोट>

ध्यान दें कि additionalProperties विधि hibernate.connection.provider_disables_autocommit को परिभाषित करती है हाइबरनेट संपत्ति, जिसे मैंने RESOURCE_LOCAL जेपीए लेनदेन के लिए डेटाबेस अधिग्रहण को स्थगित करने के लिए हाइबरनेट में जोड़ा था।

इतना ही नहीं hibernate.connection.provider_disables_autocommit आपको डेटाबेस कनेक्शन का बेहतर उपयोग करने की अनुमति देता है, लेकिन यह एकमात्र तरीका है जिससे हम इस उदाहरण को काम कर सकते हैं, क्योंकि इस कॉन्फ़िगरेशन के बिना, determineCurrentLookupKey को कॉल करने से पहले कनेक्शन प्राप्त कर लिया जाता है। विधि TransactionRoutingDataSource

JPA के निर्माण के लिए आवश्यक शेष स्प्रिंग घटक EntityManagerFactory AbstractJPAConfiguration . द्वारा परिभाषित किया गया है बेस क्लास।

मूल रूप से, actualDataSource आगे डेटासोर्स-प्रॉक्सी द्वारा लपेटा गया है और जेपीए को प्रदान किया गया है EntityManagerFactory . अधिक विवरण के लिए आप GitHub पर स्रोत कोड देख सकते हैं।

परीक्षण का समय

लेन-देन रूटिंग काम करता है या नहीं, यह जांचने के लिए, हम postgresql.conf में निम्नलिखित गुणों को सेट करके PostgreSQL क्वेरी लॉग को सक्षम करने जा रहे हैं। कॉन्फ़िगरेशन फ़ाइल:

log_min_duration_statement = 0
log_line_prefix = '[%d] '

log_min_duration_statement प्रॉपर्टी सेटिंग सभी पोस्टग्रेएसक्यूएल स्टेटमेंट्स को लॉग करने के लिए है जबकि दूसरा डेटाबेस नाम को एसक्यूएल लॉग में जोड़ता है।

इसलिए, newPost . को कॉल करते समय और findAllPostsByTitle तरीके, इस तरह:

Post post = forumService.newPost(
    "High-Performance Java Persistence",
    "JDBC", "JPA", "Hibernate"
);

List<Post> posts = forumService.findAllPostsByTitle(
    "High-Performance Java Persistence"
);

हम देख सकते हैं कि PostgreSQL निम्नलिखित संदेशों को लॉग करता है:

[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

high_performance_java_persistence . का उपयोग कर लॉग स्टेटमेंट उपसर्ग को प्राथमिक नोड पर निष्पादित किया गया था, जबकि वाले high_performance_java_persistence_replica का उपयोग कर रहे थे प्रतिकृति नोड पर।

तो, सब कुछ एक आकर्षण की तरह काम करता है!

सभी स्रोत कोड मेरे उच्च-प्रदर्शन जावा पर्सिस्टेंस गिटहब भंडार में पाए जा सकते हैं, ताकि आप इसे भी आजमा सकें।

निष्कर्ष

आपको यह सुनिश्चित करने की ज़रूरत है कि आपने अपने कनेक्शन पूल के लिए सही आकार निर्धारित किया है क्योंकि इससे बहुत बड़ा अंतर आ सकता है। इसके लिए, मैं फ्लेक्सी पूल का उपयोग करने की सलाह देता हूं।

आपको बहुत मेहनती होने की आवश्यकता है और सुनिश्चित करें कि आप सभी केवल-पढ़ने के लिए लेनदेन को उसी के अनुसार चिह्नित करते हैं। यह असामान्य है कि आपके केवल 10% लेन-देन केवल-पढ़ने के लिए होते हैं। क्या ऐसा हो सकता है कि आपके पास इतना अधिक लिखने वाला एप्लिकेशन है या आप लिखित लेनदेन का उपयोग कर रहे हैं जहां आप केवल क्वेरी स्टेटमेंट जारी करते हैं?

बैच प्रोसेसिंग के लिए, आपको निश्चित रूप से पढ़ने-लिखने के लेन-देन की आवश्यकता होती है, इसलिए सुनिश्चित करें कि आपने JDBC बैचिंग को इस तरह सक्षम किया है:

<property name="hibernate.order_updates" value="true"/>
<property name="hibernate.order_inserts" value="true"/>
<property name="hibernate.jdbc.batch_size" value="25"/>

बैचिंग के लिए आप एक अलग DataSource . का भी उपयोग कर सकते हैं जो एक अलग कनेक्शन पूल का उपयोग करता है जो प्राथमिक नोड से जुड़ता है।

बस सुनिश्चित करें कि आपके सभी कनेक्शन पूलों का कुल कनेक्शन आकार उन कनेक्शनों की संख्या से कम है जिनके साथ PostgreSQL कॉन्फ़िगर किया गया है।

प्रत्येक बैच कार्य को एक समर्पित लेनदेन का उपयोग करना चाहिए, इसलिए सुनिश्चित करें कि आप उचित बैच आकार का उपयोग करते हैं।

इसके अलावा, आप ताले रखना चाहते हैं और जितनी जल्दी हो सके लेनदेन समाप्त करना चाहते हैं। यदि बैच प्रोसेसर समवर्ती प्रसंस्करण श्रमिकों का उपयोग कर रहा है, तो सुनिश्चित करें कि संबंधित कनेक्शन पूल का आकार श्रमिकों की संख्या के बराबर है, ताकि वे दूसरों के कनेक्शन जारी करने की प्रतीक्षा न करें।



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. मैं jsonb कॉलम को क्वेरी करने के लिए स्प्रिंग डेटा जेपीए का उपयोग कैसे करूं?

  2. PostgreSQL:सीरियल बनाम पहचान

  3. आप एक साथ काम करने के लिए PyPy, Django और PostgreSQL कैसे प्राप्त करते हैं?

  4. एक ही क्वेरी में एकाधिक array_agg () कॉल

  5. OUT पैरामीटर वाले फ़ंक्शन से लौटना