Programmering

Distribuerede transaktioner i foråret, med og uden XA

Selvom det er almindeligt at bruge Java Transaction API og XA-protokollen til distribuerede transaktioner i foråret, har du andre muligheder. Den optimale implementering afhænger af de ressourcetyper, som din applikation bruger, og de afvejninger, du er villig til at foretage mellem ydeevne, sikkerhed, pålidelighed og dataintegritet. I denne JavaWorld-funktion guider SpringSource David Syer dig gennem syv mønstre til distribuerede transaktioner i Spring-applikationer, tre af dem med XA og fire uden. Niveau: Mellemliggende

Spring Framework's support til Java Transaction API (JTA) gør det muligt for applikationer at bruge distribuerede transaktioner og XA-protokollen uden at køre i en Java EE-container. Selv med denne support er XA imidlertid dyrt og kan være upålidelig eller besværlig at administrere. Det kan derfor være en velkommen overraskelse, at en bestemt applikationsklasse helt kan undgå brugen af ​​XA.

For at hjælpe dig med at forstå de overvejelser, der er involveret i forskellige tilgange til distribuerede transaktioner, analyserer jeg syv transaktionsbehandlingsmønstre og leverer kodeeksempler for at gøre dem konkrete. Jeg præsenterer mønstrene i omvendt rækkefølge af sikkerhed eller pålidelighed, begyndende med dem med den højeste garanti for dataintegritet og atomicitet under de mest generelle omstændigheder. Når du bevæger dig ned på listen, gælder der flere forbehold og begrænsninger. Mønstrene er også omtrent i omvendt rækkefølge af runtime-omkostninger (startende med de dyreste). Mønstrene er alle arkitektoniske eller tekniske i modsætning til forretningsmønstre, så jeg fokuserer ikke på forretningsbrugssagen, kun på den minimale mængde kode for at se hvert mønster fungere.

Bemærk, at kun de første tre mønstre involverer XA, og de er muligvis ikke tilgængelige eller acceptable af ydeevnehensyn. Jeg diskuterer ikke XA-mønstrene så udførligt som de andre, fordi de er dækket andetsteds, selvom jeg giver en simpel demonstration af den første. Ved at læse denne artikel lærer du, hvad du kan og ikke kan gøre med distribuerede transaktioner, og hvordan og hvornår man skal undgå brugen af ​​XA - og hvornår man ikke skal.

Distribuerede transaktioner og atomicitet

EN distribueret transaktion er en, der involverer mere end en transaktionsressource. Eksempler på transaktionsressourcer er konnektorerne til kommunikation med relationsdatabaser og messaging-mellemware. Ofte har en sådan ressource en API, der ligner noget begynde(), tilbageførsel (), begå(). I Java-verdenen vises en transaktionsressource normalt som et produkt fra en fabrik, der leveres af den underliggende platform: for en database er det en Forbindelse (produceret af Datakilde) eller Java Persistence API (JPA) EntityManager; for Java Message Service (JMS) er det en Session.

I et typisk eksempel udløser en JMS-besked en databaseopdatering. Opdelt i en tidslinje går en vellykket interaktion sådan:

  1. Start meddelelsestransaktion
  2. Modtag besked
  3. Start databasetransaktion
  4. Opdater database
  5. Foretag databasetransaktion
  6. Foretag meddelelsestransaktion

Hvis der opstod en databasefejl som en overtrædelse af begrænsningen i opdateringen, ville den ønskelige sekvens se sådan ud:

  1. Start meddelelsestransaktion
  2. Modtag besked
  3. Start databasetransaktion
  4. Opdater database, mislykkes!
  5. Tilbagevend databasetransaktion
  6. Rul tilbage-meddelelsestransaktion

I dette tilfælde går meddelelsen tilbage til middleware efter sidste tilbageførsel og vender tilbage på et tidspunkt for at blive modtaget i en anden transaktion. Dette er normalt en god ting, for ellers har du muligvis ingen registrering af, at der opstod en fejl. (Mekanismer til håndtering af undtagelser med automatisk forsøg og håndtering er uden for denne artikels anvendelsesområde.)

Det vigtige træk ved begge tidslinjer er, at de er atomar, der danner en enkelt logisk transaktion, der enten lykkes fuldstændigt eller mislykkes fuldstændigt.

Men hvad garanterer, at tidslinjen ser ud som en af ​​disse sekvenser? Der skal ske en vis synkronisering mellem transaktionsressourcerne, så hvis man forpligter sig til begge dele, og omvendt. Ellers er hele transaktionen ikke atomær. Transaktionen distribueres, fordi der er flere ressourcer involveret, og uden synkronisering er den ikke atomær. De tekniske og konceptuelle vanskeligheder med distribuerede transaktioner vedrører alle synkronisering af ressourcerne (eller manglen på den).

De første tre mønstre, der diskuteres nedenfor, er baseret på XA-protokollen. Fordi disse mønstre er blevet bredt dækket, vil jeg ikke gå i detaljer om dem her. De, der er fortrolige med XA-mønstre, vil måske springe videre til Shared Transaction Resource-mønsteret.

Fuld XA med 2PC

Hvis du har brug for tæt på skudsikker garanti for, at din applikations transaktioner vil komme sig efter et udfald, inklusive et servernedbrud, er Full XA dit eneste valg. Den delte ressource, der bruges til at synkronisere transaktionen i dette tilfælde, er en speciel transaktionshåndtering, der koordinerer oplysninger om processen ved hjælp af XA-protokollen. I Java, set fra udviklerens synspunkt, eksponeres protokollen gennem en JTA UserTransaction.

At være et systeminterface er XA en mulig teknologi, som de fleste udviklere aldrig ser. De har brug for at vide, at det er der, hvad det muliggør, hvad det koster, og konsekvenserne for, hvordan de bruger transaktionsressourcer. Omkostningerne kommer fra den tofasede commit-protokol (2PC), som transaktionsadministratoren bruger for at sikre, at alle ressourcer er enige om resultatet af en transaktion, før den slutter.

Hvis applikationen er fjederaktiveret, bruger den foråret JtaTransactionManager og Spring-deklarativ transaktionsstyring for at skjule detaljerne i den underliggende synkronisering. Forskellen for udvikleren mellem at bruge XA og ikke at bruge XA handler om at konfigurere fabriksressourcerne: Datakilde forekomster og transaktionsadministrator for applikationen. Denne artikel inkluderer en prøveansøgning ( atomikos-db projekt), der illustrerer denne konfiguration. Det Datakilde forekomster og transaktionsadministrator er de eneste XA- eller JTA-specifikke elementer i applikationen.

For at se prøven fungerer skal du køre enhedstestene under com.springsource.open.db. En simpel MulipleDataSourceTests klasse indsætter bare data i to datakilder og bruger derefter supportfunktionerne til Spring-integration til at tilbageføre transaktionen, som vist i liste 1:

Notering 1. Tilbageførsel af transaktion

@Transactional @Test ugyldig testInsertIntoTwoDataSources () kaster undtagelse {int count = getJdbcTemplate (). Opdatering ("INSERT into T_FOOS (id, name, foo_date) values ​​(?,?, Null)", 0, "foo"); assertEquals (1, count); count = getOtherJdbcTemplate () .update ("INSERT into T_AUDITS (id, operation, name, audit_date) values ​​(?,?,?,?)", 0, "INSERT", "foo", new Date ()); assertEquals (1, count); // Ændringer ruller tilbage, når denne metode er afsluttet}

Derefter MulipleDataSourceTests verificerer, at de to operationer begge blev rullet tilbage, som vist i liste 2:

Liste 2. Bekræftelse af tilbageførsel

@AfterTransaction public void checkPostConditions () {int count = getJdbcTemplate (). QueryForInt ("select count (*) from T_FOOS"); // Denne ændring blev rullet tilbage af test framework assertEquals (0, count); count = getOtherJdbcTemplate (). queryForInt ("select count (*) from T_AUDITS"); // Dette rullede også tilbage på grund af XA assertEquals (0, count); }

For en bedre forståelse af, hvordan Spring-transaktionsstyring fungerer, og hvordan man generelt konfigurerer den, se Spring Reference Guide.

XA med 1PC-optimering

Dette mønster er en optimering, som mange transaktionsadministratorer bruger for at undgå omkostningerne ved 2PC, hvis transaktionen inkluderer en enkelt ressource. Du forventer, at din applikationsserver kan finde ud af dette.

XA og den sidste ressource Gambit

Et andet træk ved mange XA-transaktionsadministratorer er, at de stadig kan give de samme genopretningsgarantier, når alle undtagen en ressource er XA-kompatible, som de kan, når de alle er. De gør dette ved at bestille ressourcerne og bruge ikke-XA-ressourcen som afgivende stemme. Hvis den ikke forpligter sig, kan alle de andre ressourcer rulles tilbage. Det er tæt på 100 procent skudsikker - men er ikke helt det. Og når det mislykkes, fejler det uden at efterlade meget af et spor, medmindre der træffes ekstra trin (som det gøres i nogle af top-end-implementeringerne).

Delt transaktionsressourcemønster

Et godt mønster til at mindske kompleksiteten og øge kapaciteten i nogle systemer er at fjerne behovet for XA helt ved at sikre, at alle transaktionsressourcer i systemet faktisk er bakket op af den samme ressource. Dette er helt klart ikke muligt i alle behandlingsanvendelsessager, men det er lige så solidt som XA og normalt meget hurtigere. Shared Transaction Resource-mønsteret er skudsikkert, men specifikt for visse platforme og behandlingsscenarier.

Et simpelt og velkendt (for mange) eksempel på dette mønster er deling af en database Forbindelse mellem en komponent, der bruger objektrelationskortlægning (ORM) med en komponent, der bruger JDBC. Dette er, hvad der sker, du bruger Spring-transaktionsadministratorer, der understøtter ORM-værktøjer som Hibernate, EclipseLink og Java Persistence API (JPA). Den samme transaktion kan sikkert bruges på tværs af ORM- og JDBC-komponenter, normalt drevet ovenfra af en metodeudførelse på serviceniveau, hvor transaktionen kontrolleres.

En anden effektiv anvendelse af dette mønster er tilfældet med meddelelsesdrevet opdatering af en enkelt database (som i det enkle eksempel i denne artikels introduktion). Messaging-middleware-systemer skal gemme deres data et eller andet sted, ofte i en relationsdatabase. For at implementere dette mønster er alt, hvad der kræves, at pege messaging-systemet på den samme database, som forretningsdataene går ind i. Dette mønster er afhængig af, at messaging-middleware-leverandøren udsætter detaljerne i sin lagerstrategi, så den kan konfigureres til at pege på den samme database og tilslutte sig den samme transaktion.

Ikke alle leverandører gør det nemt. Et alternativ, der fungerer i næsten enhver database, er at bruge Apache ActiveMQ til messaging og tilslutte en lagringsstrategi til meddelelsesmægleren. Dette er ret let at konfigurere, når du kender tricket. Det er demonstreret i denne artikels delt-jms-db prøver projekt. Applikationskoden (enhedstest i dette tilfælde) behøver ikke at være opmærksom på, at dette mønster er i brug, fordi det hele er aktiveret erklærende i Spring-konfiguration.

En enhedstest i prøven kaldet SynchronousMessageTriggerAndRollbackTests bekræfter, at alt fungerer med modtagelse af synkron besked. Det testReceiveMessageUpdateDatabase metode modtager to meddelelser og bruger dem til at indsætte to poster i databasen. Når denne metode afsluttes, ruller testrammen transaktionen tilbage, så du kan kontrollere, at meddelelserne og databaseopdateringerne begge rulles tilbage, som vist i liste 3:

Liste 3. Bekræftelse af tilbageførsel af meddelelser og databaseopdateringer

@AfterTransaction offentlig ugyldig checkPostConditions () {assertEquals (0, SimpleJdbcTestUtils.countRowsInTable (jdbcTemplate, "T_FOOS")); Liste liste = getMessages (); assertEquals (2, list.size ()); }

De vigtigste funktioner i konfigurationen er ActiveMQ-persistensstrategien, der forbinder meddelelsessystemet med det samme Datakilde som forretningsdata og flag på foråret JmsTemplate bruges til at modtage meddelelserne. Liste 4 viser, hvordan man konfigurerer ActiveMQ-persistensstrategien:

Fortegnelse 4. Konfiguration af ActiveMQ-persistens

    ...             

Fortegnelse 5 viser flag på foråret JmsTemplate der bruges til at modtage meddelelserne:

Fortegnelse 5. Opsætning af JmsTemplate til transaktionsbrug

 ...   

Uden sessionTransacted = sand, JMS-sessionstransaktion-API-opkald foretages aldrig, og meddelelsesmodtagelsen kan ikke rulles tilbage. De vigtige ingredienser her er den indlejrede mægler med en speciel asynkronisering = falsk parameter og en indpakning til Datakilde der tilsammen sikrer, at ActiveMQ bruger den samme transaktionsjDBC Forbindelse som forår.