Vår transaktionsdirigering
Först skapar vi en DataSourceType
Java Enum som definierar våra transaktionsdirigeringsalternativ:
public enum DataSourceType {
READ_WRITE,
READ_ONLY
}
För att dirigera läs- och skrivtransaktionerna till den primära noden och skrivskyddade transaktioner till replikanoden kan vi definiera en ReadWriteDataSource
som ansluter till den primära noden och en ReadOnlyDataSource
som ansluter till replikanoden.
Läs-skriv- och skrivskyddad transaktionsdirigering görs av Spring AbstractRoutingDataSource
abstraktion, som implementeras av TransactionRoutingDatasource
, som illustreras av följande diagram:
TransactionRoutingDataSource
är mycket lätt att implementera och ser ut som följer:
public class TransactionRoutingDataSource
extends AbstractRoutingDataSource {
@Nullable
@Override
protected Object determineCurrentLookupKey() {
return TransactionSynchronizationManager
.isCurrentTransactionReadOnly() ?
DataSourceType.READ_ONLY :
DataSourceType.READ_WRITE;
}
}
I grund och botten inspekterar vi Spring TransactionSynchronizationManager
klass som lagrar den aktuella transaktionskontexten för att kontrollera om den aktuella Spring-transaktionen är skrivskyddad eller inte.
determineCurrentLookupKey
metod returnerar diskriminatorvärdet som kommer att användas för att välja antingen läs-skriv eller skrivskyddad JDBC DataSource
.
Vår läs-skriv och skrivskyddad JDBC DataSource-konfiguration
DataSource
konfigurationen ser ut som följer:
@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
resursfil tillhandahåller konfigurationen för läs-skriv- och skrivskyddad JDBC DataSource
komponenter:
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
egenskapen definierar URL:en för den primära noden medan jdbc.url.replica
definierar URL:en för replikanoden.
readWriteDataSource
Spring-komponenten definierar läs-skriv JDBC DataSource
medan readOnlyDataSource
komponenten definierar den skrivskyddade JDBC DataSource
.
Observera att både läs-skriv- och skrivskyddad datakälla använder HikariCP för anslutningspoolning.
actualDataSource
fungerar som en fasad för läs-skriv- och skrivskyddade datakällor och implementeras med hjälp av TransactionRoutingDataSource
verktyg.
readWriteDataSource
är registrerad med DataSourceType.READ_WRITE
nyckeln och readOnlyDataSource
med DataSourceType.READ_ONLY
nyckel.
Så när du kör en läs-skriv @Transactional
metoden, readWriteDataSource
kommer att användas när en @Transactional(readOnly = true)
körs metoden, readOnlyDataSource
kommer att användas istället.
Observera att
additionalProperties
metoden definierarhibernate.connection.provider_disables_autocommit
Hibernate-egenskap, som jag lade till i Hibernate för att skjuta upp databasförvärvet för RESOURCE_LOCAL JPA-transaktioner.Inte bara att
hibernate.connection.provider_disables_autocommit
tillåter dig att bättre använda databasanslutningar, men det är det enda sättet vi kan få det här exemplet att fungera eftersom, utan denna konfiguration, anslutningen förvärvas innandetermineCurrentLookupKey
anropas metodTransactionRoutingDataSource
.
De återstående Spring-komponenterna som behövs för att bygga JPA EntityManagerFactory
definieras av AbstractJPAConfiguration
basklass.
I grund och botten är actualDataSource
lindas vidare av DataSource-Proxy och tillhandahålls till JPA EntityManagerFactory
. Du kan kontrollera källkoden på GitHub för mer information.
Testtid
För att kontrollera om transaktionsdirigeringen fungerar, kommer vi att aktivera PostgreSQL-frågeloggen genom att ställa in följande egenskaper i postgresql.conf
konfigurationsfil:
log_min_duration_statement = 0
log_line_prefix = '[%d] '
log_min_duration_statement
egenskapsinställningen är för att logga alla PostgreSQL-satser medan den andra lägger till databasnamnet till SQL-loggen.
Så när du ringer newPost
och findAllPostsByTitle
metoder, så här:
Post post = forumService.newPost(
"High-Performance Java Persistence",
"JDBC", "JPA", "Hibernate"
);
List<Post> posts = forumService.findAllPostsByTitle(
"High-Performance Java Persistence"
);
Vi kan se att PostgreSQL loggar följande meddelanden:
[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
Loggsatserna som använder high_performance_java_persistence
prefixet kördes på den primära noden medan de som använde high_performance_java_persistence_replica
på repliknoden.
Så allt fungerar som en smäck!
All källkod finns i mitt högpresterande Java Persistence GitHub-förråd, så du kan prova det också.
Slutsats
Du måste se till att du ställer in rätt storlek för dina anslutningspooler eftersom det kan göra en enorm skillnad. För detta rekommenderar jag att du använder Flexy Pool.
Du måste vara mycket noggrann och se till att du markerar alla skrivskyddade transaktioner i enlighet med detta. Det är ovanligt att endast 10 % av dina transaktioner är skrivskyddade. Kan det vara så att du har ett sådant skriv-mest-program eller att du använder skrivtransaktioner där du bara utfärdar frågeutlåtanden?
För batchbehandling behöver du definitivt läs-skrivtransaktioner, så se till att du aktiverar JDBC-batchning, så här:
<property name="hibernate.order_updates" value="true"/>
<property name="hibernate.order_inserts" value="true"/>
<property name="hibernate.jdbc.batch_size" value="25"/>
För batchning kan du också använda en separat DataSource
som använder en annan anslutningspool som ansluter till den primära noden.
Se bara till att din totala anslutningsstorlek för alla anslutningspooler är mindre än antalet anslutningar som PostgreSQL har konfigurerats med.
Varje batchjobb måste använda en dedikerad transaktion, så se till att du använder en rimlig batchstorlek.
Mer, du vill hålla lås och slutföra transaktioner så snabbt som möjligt. Om batchprocessorn använder arbetare för samtidig bearbetning, se till att den tillhörande anslutningspoolstorleken är lika med antalet arbetare, så att de inte väntar på att andra ska släppa anslutningar.