Forårstransaktionsruting
Først vil vi oprette en DataSourceType
Java Enum, der definerer vores muligheder for transaktionsrouting:
public enum DataSourceType {
READ_WRITE,
READ_ONLY
}
For at dirigere læse-skrive-transaktionerne til den primære node og skrivebeskyttede transaktioner til replika-noden kan vi definere en ReadWriteDataSource
der forbinder til den primære node og en ReadOnlyDataSource
der forbinder til replika-noden.
Read-write og read-only transaktionsrouting udføres af Spring AbstractRoutingDataSource
abstraktion, som er implementeret af TransactionRoutingDatasource
, som illustreret af følgende diagram:
TransactionRoutingDataSource
er meget nem at implementere og ser ud som følger:
public class TransactionRoutingDataSource
extends AbstractRoutingDataSource {
@Nullable
@Override
protected Object determineCurrentLookupKey() {
return TransactionSynchronizationManager
.isCurrentTransactionReadOnly() ?
DataSourceType.READ_ONLY :
DataSourceType.READ_WRITE;
}
}
Grundlæggende inspicerer vi Spring TransactionSynchronizationManager
klasse, der gemmer den aktuelle transaktionskontekst for at kontrollere, om den aktuelt kørende Spring-transaktion er skrivebeskyttet eller ej.
determineCurrentLookupKey
metoden returnerer diskriminatorværdien, der vil blive brugt til at vælge enten read-write eller read-only JDBC DataSource
.
Forår læse-skrive og skrivebeskyttet JDBC DataSource-konfiguration
DataSource
konfigurationen ser ud som følger:
@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
ressourcefil giver konfigurationen for læse-skrive- og skrivebeskyttet 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
egenskaben definerer URL'en for den primære node, mens jdbc.url.replica
definerer URL'en for replika-noden.
readWriteDataSource
Spring-komponent definerer læse-skrive JDBC DataSource
mens readOnlyDataSource
komponent definerer den skrivebeskyttede JDBC DataSource
.
Bemærk, at både læse-skrive- og skrivebeskyttet datakilder bruger HikariCP til forbindelsespooling.
actualDataSource
fungerer som en facade for læse-skrive- og skrivebeskyttede datakilder og implementeres ved hjælp af TransactionRoutingDataSource
værktøj.
readWriteDataSource
er registreret ved hjælp af DataSourceType.READ_WRITE
nøglen og readOnlyDataSource
ved hjælp af DataSourceType.READ_ONLY
nøgle.
Så når du udfører en read-write @Transactional
metode, readWriteDataSource
vil blive brugt, mens der udføres en @Transactional(readOnly = true)
metoden, readOnlyDataSource
vil blive brugt i stedet.
Bemærk, at additionalProperties
metoden definerer hibernate.connection.provider_disables_autocommit
Hibernate-egenskab, som jeg føjede til Hibernate for at udsætte databaseanskaffelsen for RESOURCE_LOCAL JPA-transaktioner.
Ikke kun at hibernate.connection.provider_disables_autocommit
giver dig mulighed for at gøre bedre brug af databaseforbindelser, men det er den eneste måde, vi kan få dette eksempel til at fungere, da forbindelsen, uden denne konfiguration, opnås, før du kalder determineCurrentLookupKey
metode TransactionRoutingDataSource
.
De resterende Spring-komponenter, der er nødvendige for at bygge JPA EntityManagerFactory
er defineret af AbstractJPAConfiguration
basisklasse.
Grundlæggende er actualDataSource
er yderligere pakket ind af DataSource-Proxy og leveret til JPA EntityManagerFactory
. Du kan tjekke kildekoden på GitHub for flere detaljer.
Testtid
For at kontrollere, om transaktionsroutingen virker, vil vi aktivere PostgreSQL-forespørgselsloggen ved at indstille følgende egenskaber i postgresql.conf
konfigurationsfil:
log_min_duration_statement = 0
log_line_prefix = '[%d] '
log_min_duration_statement
egenskabsindstillingen er til at logge alle PostgreSQL-sætninger, mens den anden tilføjer databasenavnet til SQL-loggen.
Så når du ringer til newPost
og findAllPostsByTitle
metoder som denne:
Post post = forumService.newPost(
"High-Performance Java Persistence",
"JDBC", "JPA", "Hibernate"
);
List<Post> posts = forumService.findAllPostsByTitle(
"High-Performance Java Persistence"
);
Vi kan se, at PostgreSQL logger følgende meddelelser:
[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
Logsætningerne ved hjælp af high_performance_java_persistence
præfiks blev udført på den primære node, mens dem, der brugte high_performance_java_persistence_replica
på replika-noden.
Så alt fungerer som en charme!
Al kildekoden kan findes i mit High-Performance Java Persistence GitHub-lager, så du kan også prøve det.
Konklusion
Du skal sørge for at indstille den rigtige størrelse til dine tilslutningsbassiner, for det kan gøre en kæmpe forskel. Til dette anbefaler jeg at bruge Flexy Pool.
Du skal være meget omhyggelig og sørge for at markere alle skrivebeskyttede transaktioner i overensstemmelse hermed. Det er usædvanligt, at kun 10 % af dine transaktioner er skrivebeskyttede. Kan det være, at du har sådan en applikation, hvor du skriver mest, eller du bruger skrivetransaktioner, hvor du kun udsteder forespørgselserklæringer?
Til batchbehandling har du helt sikkert brug for læse-skrive-transaktioner, så sørg for at aktivere JDBC-batching, sådan her:
<property name="hibernate.order_updates" value="true"/>
<property name="hibernate.order_inserts" value="true"/>
<property name="hibernate.jdbc.batch_size" value="25"/>
Til batching kan du også bruge en separat DataSource
der bruger en anden forbindelsespulje, der forbinder til den primære node.
Bare sørg for, at din samlede forbindelsesstørrelse for alle forbindelsespuljer er mindre end antallet af forbindelser, PostgreSQL er blevet konfigureret med.
Hvert batchjob skal bruge en dedikeret transaktion, så sørg for at bruge en rimelig batchstørrelse.
Mere, du ønsker at holde låse og afslutte transaktioner så hurtigt som muligt. Hvis batchprocessoren bruger samtidige behandlingsarbejdere, skal du sørge for, at den tilknyttede forbindelsespuljestørrelse er lig med antallet af arbejdere, så de ikke venter på, at andre frigiver forbindelser.