sql >> Database >  >> NoSQL >> Redis

Een gedistribueerde transactie implementeren in Mysql, Redis en Mongo

Mysql, Redis en Mongo zijn allemaal erg populaire winkels en elk heeft zijn eigen voordelen. In praktische toepassingen is het gebruikelijk om meerdere winkels tegelijkertijd te gebruiken en wordt het waarborgen van gegevensconsistentie over meerdere winkels een vereiste.

Dit artikel geeft een voorbeeld van het implementeren van een gedistribueerde transactie over meerdere winkelengines, Mysql, Redis en Mongo. Dit voorbeeld is gebaseerd op het Distributed Transaction Framework https://github.com/dtm-labs/dtm en zal hopelijk helpen om uw problemen met gegevensconsistentie tussen microservices op te lossen.

De mogelijkheid om meerdere storage-engines flexibel te combineren om een ​​gedistribueerde transactie te vormen, wordt in de eerste plaats voorgesteld door DTM, en geen enkel ander gedistribueerd transactieraamwerk heeft dit op deze manier mogelijk gemaakt.

Probleemscenario's

Laten we eerst naar het probleemscenario kijken. Stel dat een gebruiker nu deelneemt aan een actie:hij of zij heeft een saldo, laadt de telefoonrekening op en de actie geeft winkelpunten weg. Het saldo wordt opgeslagen in Mysql, de rekening wordt opgeslagen in Redis, de winkelpunten worden opgeslagen in Mongo. Omdat de promotie beperkt is in de tijd, bestaat de mogelijkheid dat deelname mislukt, dus rollback-ondersteuning is vereist.

Voor het bovenstaande probleemscenario kunt u de Saga-transactie van DTM gebruiken en we zullen de oplossing hieronder in detail uitleggen.

De gegevens voorbereiden

De eerste stap is het voorbereiden van de gegevens. Om het voor gebruikers gemakkelijker te maken om snel met de voorbeelden aan de slag te gaan, hebben we de relevante gegevens voorbereid op en.dtm.pub, waaronder Mysql, Redis en Mongo, en de specifieke gebruikersnaam en het wachtwoord voor de verbinding zijn te vinden op https:// github.com/dtm-labs/dtm-examples.

Als je de data-omgeving zelf lokaal wilt voorbereiden, kun je https://github.com/dtm-labs/dtm/blob/main/helper/compose.store.yml gebruiken om Mysql, Redis, Mongo; en voer vervolgens scripts uit in https://github.com/dtm-labs/dtm/tree/main/sqls om de gegevens voor dit voorbeeld voor te bereiden, waarbij busi.* is de bedrijfsgegevens en barrier.* is de hulptabel die door DTM wordt gebruikt

De bedrijfscode schrijven

Laten we beginnen met de bedrijfscode voor de meest bekende Mysql.

De volgende code is in Golang. Andere talen zoals C#, PHP, Java zijn hier te vinden:DTM SDK's

func SagaAdjustBalance(db dtmcli.DB, uid int, amount int) error {
    _, err := dtmimp.DBExec(db, "update dtm_busi.user_account set balance = balance + ? where user_id = ?" , amount, uid)
    return err
}

Deze code voert voornamelijk de aanpassing van het saldo van de gebruiker in de database uit. In ons voorbeeld wordt dit deel van de code niet alleen gebruikt voor de voorwaartse operatie van Saga, maar ook voor de compensatieoperatie, waarbij alleen een negatief bedrag moet worden doorgegeven voor compensatie.

Voor Redis en Mongo wordt de bedrijfscode op dezelfde manier behandeld, alleen het verhogen of verlagen van de bijbehorende saldi.

Hoe u onmacht kunt garanderen

Voor het Saga-transactiepatroon, wanneer we een tijdelijke storing hebben in de subtransactieservice, wordt de mislukte bewerking opnieuw geprobeerd. Deze fout kan optreden voor of na het vastleggen van de subtransactie, dus de bewerking van de subtransactie moet idempotent zijn.

DTM biedt hulptabellen en hulpfuncties om gebruikers te helpen snel idempotentie te bereiken. Voor Mysql zal het een hulptabel creëren barrier in de zakelijke database, wanneer de gebruiker een transactie start om het saldo aan te passen, zal hij eerst Gid invoegen in de barrier tafel. Als er een dubbele rij is, mislukt het invoegen en slaat u de balansaanpassing over om de idempotent te garanderen. De code met behulp van de helperfunctie is als volgt:

app.POST(BusiAPI+"/SagaBTransIn", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
    return MustBarrierFromGin(c).Call(txGet(), func(tx *sql.Tx) error {
        return SagaAdjustBalance(tx, TransInUID, reqFrom(c).Amount, reqFrom(c).TransInResult)
    })
}))

Mongo behandelt idempotentie op een vergelijkbare manier als Mysql, dus ik zal niet opnieuw in detail treden.

Redis behandelt idempotentie anders dan Mysql, voornamelijk vanwege het verschil in het principe van transacties. Redis-transacties worden voornamelijk verzekerd door atomaire uitvoering van Lua. de DTM-helperfunctie past de balans aan via een Lua-script. Voordat het saldo wordt aangepast, wordt Gid . opgevraagd in Redis. Als Gid bestaat, slaat het de balansaanpassing over; zo niet, dan zal het Gid . opnemen en voer de balansaanpassing uit. De code die wordt gebruikt voor de helperfunctie is als volgt:

app.POST(BusiAPI+"/SagaRedisTransOut", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
    return MustBarrierFromGin(c).RedisCheckAdjustAmount(RedisGet(), GetRedisAccountKey(TransOutUID), -reqFrom(c).Amount, 7*86400)
}))

Hoe compensatie te doen

Voor Saga hebben we ook te maken met de compensatieoperatie, maar de compensatie is niet alleen een omgekeerde aanpassing en er zijn veel valkuilen waar je rekening mee moet houden.

Enerzijds moet bij compensatie rekening worden gehouden met idempotentie, omdat de in de vorige subparagraaf beschreven mislukkingen en nieuwe pogingen ook in compensatie voorkomen. Aan de andere kant moet bij compensatie ook rekening worden gehouden met "nulcompensatie", aangezien de voorwaartse bewerking van Saga een storing kan opleveren, die voor of na de gegevensaanpassing kan zijn opgetreden. Voor storingen waarbij de aanpassing is gepleegd, moeten we de omgekeerde aanpassing uitvoeren; maar voor storingen waarbij de aanpassing niet is uitgevoerd, moeten we de omgekeerde bewerking overslaan.

In de helpertabel en helperfuncties die door DTM worden geleverd, zal het enerzijds bepalen of de compensatie een nulcompensatie is op basis van de Gid die is ingevoegd door de voorwaartse bewerking, en anderzijds zal het Gid+'compensate' opnieuw invoegen om te bepalen of de vergoeding een duplicaat is. Als er een normale compensatieoperatie is, zal deze de gegevensaanpassing op het bedrijf uitvoeren; als er een nulcompensatie of dubbele compensatie is, wordt de aanpassing voor het bedrijf overgeslagen.

De Mysql-code is als volgt.

app.POST(BusiAPI+"/SagaBTransInCom", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
    return MustBarrierFromGin(c).Call(txGet(), func(tx *sql.Tx) error {
        return SagaAdjustBalance(tx, TransInUID, -reqFrom(c).Amount, "")
    })
}))

De code voor Redis is als volgt.

app.POST(BusiAPI+"/SagaRedisTransOutCom", dtmutil.WrapHandler2(func(c *gin.Context) interface{} {
    return MustBarrierFromGin(c).RedisCheckAdjustAmount(RedisGet(), GetRedisAccountKey(TransOutUID), reqFrom(c).Amount, 7*86400)
}))

De code van de compensatieservice is bijna identiek aan de vorige code van de forward-operatie, behalve dat het bedrag wordt vermenigvuldigd met -1. De DTM-helperfunctie verwerkt automatisch idempotentie en nulcompensatie correct.

Andere uitzonderingen

Bij het schrijven van voorwaartse bewerkingen en compensatiebewerkingen is er eigenlijk nog een uitzondering genaamd "Opschorting". Een globale transactie wordt teruggedraaid wanneer deze een time-out heeft of wanneer nieuwe pogingen de geconfigureerde limiet hebben bereikt. Het normale geval is dat de voorwaartse operatie wordt uitgevoerd vóór de compensatie, maar in het geval van procesonderbreking kan de compensatie worden uitgevoerd vóór de voorwaartse operatie. De voorwaartse bewerking moet dus ook bepalen of de compensatie is uitgevoerd, en in het geval dat dat het geval is, moet de gegevensaanpassing ook worden overgeslagen.

Voor DTM-gebruikers zijn deze uitzonderingen netjes en correct afgehandeld en hoeft u als gebruiker alleen de MustBarrierFromGin(c).Call te volgen. hierboven beschreven bellen en u er helemaal geen omkijken naar heeft. Het principe voor het afhandelen van deze uitzonderingen door DTM wordt hier in detail beschreven:Uitzonderingen en belemmeringen voor subtransacties

Een gedistribueerde transactie initiëren

Na het schrijven van de afzonderlijke subtransactieservices, initiëren de volgende codes van de code een wereldwijde Saga-transactie.

saga := dtmcli.NewSaga(dtmutil.DefaultHTTPServer, dtmcli.MustGenGid(dtmutil.DefaultHTTPServer)).
  Add(busi.Busi+"/SagaBTransOut", busi.Busi+"/SagaBTransOutCom", &busi.TransReq{Amount: 50}).
  Add(busi.Busi+"/SagaMongoTransIn", busi.Busi+"/SagaMongoTransInCom", &busi.TransReq{Amount: 30}).
  Add(busi.Busi+"/SagaRedisTransIn", busi.Busi+"/SagaRedisTransOutIn", &busi.TransReq{Amount: 20})
err := saga.Submit()

In dit deel van de code wordt een wereldwijde Saga-transactie gemaakt die uit 3 subtransacties bestaat.

  • Maak 50 over van Mysql
  • Overdracht in 30 naar Mongo
  • Over 20 minuten overboeken naar Redis

Als alle subtransacties tijdens de hele transactie succesvol zijn voltooid, is de globale transactie geslaagd; als een van de subtransacties een bedrijfsfout oplevert, wordt de globale transactie teruggedraaid.

Uitvoeren

Als u een compleet voorbeeld van het bovenstaande wilt uitvoeren, zijn de stappen als volgt.

  1. DTM uitvoeren
git clone https://github.com/dtm-labs/dtm && cd dtm
go run main.go
  1. Voer een succesvol voorbeeld uit
git clone https://github.com/dtm-labs/dtm-examples && cd dtm-examples
go run main.go http_saga_multidb
  1. Voer een mislukt voorbeeld uit
git clone https://github.com/dtm-labs/dtm-examples && cd dtm-examples
go run main.go http_saga_multidb_rollback

U kunt het voorbeeld wijzigen om verschillende tijdelijke storingen, null-compensatiesituaties en verschillende andere uitzonderingen te simuleren waarbij de gegevens consistent zijn wanneer de gehele globale transactie is voltooid.

Samenvatting

Dit artikel geeft een voorbeeld van een gedistribueerde transactie over Mysql, Redis en Mongo. Het beschrijft in detail de problemen die moeten worden aangepakt en de oplossingen.

De principes in dit artikel zijn geschikt voor alle storage-engines die ACID-transacties ondersteunen, en je kunt het snel uitbreiden naar andere engines zoals TiKV.

Welkom op github.com/dtm-labs/dtm. Het is een speciaal project om gedistribueerde transacties in microservices gemakkelijker te maken. Het ondersteunt meerdere talen en meerdere patronen zoals een 2-fasenbericht, Saga, Tcc en Xa.


  1. Mongo unieke index niet hoofdlettergevoelig

  2. JHipster Redis Integratie-element ongebonden fout

  3. Mongo Aggregation:$groep en $projectarray om bezwaar te maken voor tellingen

  4. Een tekenreeks en een getal samenvoegen in SQL