sql >> Databáze >  >> NoSQL >> Redis

Jak implementovat distribuovanou transakci napříč Mysql, Redis a Mongo

Mysql, Redis a Mongo jsou velmi oblíbené obchody a každý má své výhody. V praktických aplikacích je běžné používat více obchodů současně a zajištění konzistence dat ve více obchodech se stává požadavkem.

Tento článek uvádí příklad implementace distribuované transakce napříč více stroji obchodů, Mysql, Redis a Mongo. Tento příklad je založen na Distributed Transaction Framework https://github.com/dtm-labs/dtm a doufejme, že pomůže vyřešit vaše problémy s konzistencí dat napříč mikroslužbami.

Schopnost flexibilně kombinovat více úložišť za účelem vytvoření distribuované transakce je nejprve navržena společností DTM a žádný jiný rámec distribuovaných transakcí takovou schopnost neuvádí.

Scénáře problémů

Nejprve se podívejme na scénář problému. Předpokládejme, že se uživatel nyní účastní promo akce:má zůstatek, dobíjí telefonní účet a akce rozdá body v nákupním centru. Zůstatek je uložen v Mysql, účet je uložen v Redis, nákupní body jsou uloženy v Mongo. Vzhledem k tomu, že propagace je časově omezená, existuje možnost, že se účast nezdaří, takže je vyžadována podpora pro vrácení zpět.

Pro výše uvedený problémový scénář můžete použít transakci Saga společnosti DTM a řešení podrobně vysvětlíme níže.

Příprava dat

Prvním krokem je příprava dat. Abychom uživatelům usnadnili rychlý začátek s příklady, připravili jsme příslušná data na adrese en.dtm.pub, která zahrnuje Mysql, Redis a Mongo, a konkrétní uživatelské jméno a heslo připojení najdete na https:// github.com/dtm-labs/dtm-examples.

Pokud si chcete datové prostředí připravit lokálně sami, můžete ke spuštění Mysql, Redis, Mongo použít https://github.com/dtm-labs/dtm/blob/main/helper/compose.store.yml; a poté spusťte skripty na https://github.com/dtm-labs/dtm/tree/main/sqls, abyste připravili data pro tento příklad, kde busi.* jsou obchodní data a barrier.* je pomocná tabulka používaná DTM

Psaní obchodního zákoníku

Začněme obchodním kódem pro nejznámější Mysql.

Následující kód je v Golangu. Další jazyky jako C#, PHP, Java naleznete zde:DTM SDK

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
}

Tento kód provádí především úpravu zůstatku uživatele v databázi. V našem příkladu se tato část kódu používá nejen pro forwardovou operaci Saga, ale také pro kompenzační operaci, kde je třeba předat pouze zápornou částku jako kompenzaci.

Pro Redis a Mongo je obchodní kód zpracován podobně, pouze zvyšuje nebo snižuje odpovídající zůstatky.

Jak zajistit idempotenci

V případě vzoru transakce Saga, když dojde k dočasnému selhání v dílčí transakční službě, bude neúspěšná operace zopakována. K tomuto selhání může dojít před nebo po potvrzení dílčí transakce, takže operace dílčí transakce musí být idempotentní.

DTM poskytuje pomocné tabulky a pomocné funkce, které uživatelům pomáhají rychle dosáhnout idempotence. Pro Mysql vytvoří pomocnou tabulku barrier v obchodní databázi, když uživatel zahájí transakci za účelem úpravy zůstatku, nejprve vloží Gid v barrier stůl. Pokud existuje duplicitní řádek, vložení se nezdaří a pak přeskočte úpravu vyvážení, abyste zajistili idempotent. Kód využívající pomocnou funkci je následující:

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 zvládá idempotenci podobně jako Mysql, takže se nebudu znovu rozepisovat.

Redis řeší idempotenci jinak než Mysql, hlavně kvůli rozdílu v principu transakcí. Transakce Redis jsou zajištěny především atomovou exekucí Lua. pomocná funkce DTM upraví rovnováhu pomocí skriptu Lua. Před úpravou váhy se zeptá Gid v Redis. Pokud Gid existuje, přeskočí úpravu vyvážení; pokud ne, zaznamená Gid a proveďte seřízení vyvážení. Kód použitý pro pomocnou funkci je následující:

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

Jak provést kompenzaci

Pro Sagu se také musíme vypořádat s operací kompenzace, ale kompenzace není jen obrácená úprava a existuje mnoho úskalí, kterých bychom si měli být vědomi.

Na jedné straně musí kompenzace zohledňovat idempotenci, protože selhání a opakování popsané v předchozím pododdílu existují také v kompenzaci. Na druhou stranu musí kompenzace také brát v úvahu „nulovou kompenzaci“, protože dopředný provoz Saga může vrátit selhání, ke kterému mohlo dojít před nebo po úpravě dat. U poruch, u kterých došlo k seřízení, musíme provést reverzní seřízení; ale v případě selhání, kdy nastavení nebylo provedeno, musíme přeskočit zpětnou operaci.

V pomocné tabulce a pomocných funkcích poskytovaných DTM na jedné straně určí, zda je kompenzace nulovou kompenzací na základě Gid vloženého dopřednou operací, a na druhé straně znovu vloží Gid+'kompenzace' určit, zda je kompenzace duplicitní operací. Pokud dojde k normální kompenzační operaci, provede se úprava údajů o podniku; pokud existuje nulová kompenzace nebo duplicitní kompenzace, přeskočí úprava na podnikání.

Mysql kód je následující.

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, "")
    })
}))

Kód pro Redis je následující.

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

Kód kompenzační služby je téměř identický s předchozím kódem forwardové operace, až na to, že částka je vynásobena -1. Pomocná funkce DTM automaticky správně zpracuje idempotenci a nulovou kompenzaci.

Další výjimky

Při zápisu dopředných operací a kompenzačních operací ve skutečnosti existuje další výjimka zvaná „Pozastavení“. Globální transakce se vrátí zpět, když vyprší časový limit nebo počet opakovaných pokusů dosáhne nastaveného limitu. Normálním případem je, že dopředná operace je provedena před kompenzací, ale v případě pozastavení procesu může být kompenzace provedena před dopřednou operací. Dopředná operace tedy také potřebuje určit, zda byla kompenzace provedena, a v případě, že ano, je třeba přeskočit i úpravu dat.

Pro uživatele DTM byly tyto výjimky zpracovány elegantně a správně a vy jako uživatel musíte pouze postupovat podle MustBarrierFromGin(c).Call zavolejte výše popsané a nemusíte se o ně vůbec starat. Princip zpracování těchto výjimek DTM je podrobně popsán zde:Výjimky a dílčí transakční bariéry

Zahájení distribuované transakce

Po zapsání jednotlivých dílčích transakčních služeb zahájí následující kódy kódu globální transakci Saga.

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()

V této části kódu je vytvořena globální transakce Saga, která se skládá ze 3 dílčích transakcí.

  • Přeneste 50 z Mysql
  • Přestup za 30 do Mongo
  • Převod za 20 do Redis

Pokud se v průběhu transakce všechny dílčí transakce úspěšně dokončí, bude úspěšná i globální transakce; pokud jedna z dílčích transakcí vrátí obchodní selhání, pak se globální transakce vrátí zpět.

Spustit

Pokud chcete spustit úplný příklad výše uvedeného, ​​postupujte následovně.

  1. Spustit DTM
git clone https://github.com/dtm-labs/dtm && cd dtm
go run main.go
  1. Spusťte úspěšný příklad
git clone https://github.com/dtm-labs/dtm-examples && cd dtm-examples
go run main.go http_saga_multidb
  1. Spusťte neúspěšný příklad
git clone https://github.com/dtm-labs/dtm-examples && cd dtm-examples
go run main.go http_saga_multidb_rollback

Příklad můžete upravit tak, aby simuloval různá dočasná selhání, situace nulové kompenzace a různé další výjimky, kdy jsou data po dokončení celé globální transakce konzistentní.

Souhrn

Tento článek uvádí příklad distribuované transakce přes Mysql, Redis a Mongo. Podrobně popisuje problémy, které je třeba řešit, a jejich řešení.

Principy v tomto článku jsou vhodné pro všechny úložné moduly, které podporují transakce ACID, a můžete je rychle rozšířit o další moduly, jako je TiKV.

Vítejte na stránkách github.com/dtm-labs/dtm. Jedná se o specializovaný projekt, který usnadňuje distribuované transakce v mikroslužbách. Podporuje více jazyků a více vzorů, jako je 2-fázová zpráva, Saga, Tcc a Xa.


  1. Jak se dostat do výroby s MongoDB - deset tipů

  2. Filtrujte podpole pole podle některých kritérií

  3. Automatizace a správa open source databází v cloudu – oznámení ClusterControl 1.6

  4. Uložte datum v MongoDB bez ohledu na časové pásmo