Redis
 sql >> Baza danych >  >> NoSQL >> Redis

Jak zaimplementować transakcję rozproszoną w MySQL, Redis i Mongo

Mysql, Redis i Mongo to bardzo popularne sklepy i każdy ma swoje zalety. W praktycznych zastosowaniach powszechne jest jednoczesne korzystanie z wielu sklepów i zapewnienie spójności danych w wielu sklepach staje się wymogiem.

W tym artykule przedstawiono przykład wdrożenia transakcji rozproszonej w wielu silnikach sklepów, Mysql, Redis i Mongo. Ten przykład jest oparty na Distributed Transaction Framework https://github.com/dtm-labs/dtm i, miejmy nadzieję, pomoże rozwiązać problemy związane ze spójnością danych w mikroserwisach.

Możliwość elastycznego łączenia wielu silników pamięci masowej w celu utworzenia transakcji rozproszonej jest po raz pierwszy zaproponowana przez DTM i żadna inna platforma transakcji rozproszonych nie wykazała takiej możliwości.

Scenariusze problemów

Przyjrzyjmy się najpierw scenariuszowi problemu. Załóżmy, że użytkownik bierze teraz udział w promocji:ma saldo, doładuj rachunek telefoniczny, a promocja rozda punkty w centrum handlowym. Saldo jest przechowywane w Mysql, rachunek w Redis, punkty handlowe w Mongo. Ponieważ promocja jest ograniczona w czasie, istnieje możliwość, że uczestnictwo może się nie powieść, dlatego wymagana jest obsługa wycofywania.

W przypadku powyższego scenariusza problemu możesz użyć transakcji Saga firmy DTM, a szczegółowo wyjaśnimy rozwiązanie poniżej.

Przygotowywanie danych

Pierwszym krokiem jest przygotowanie danych. Aby ułatwić użytkownikom szybkie rozpoczęcie pracy z przykładami, przygotowaliśmy odpowiednie dane na en.dtm.pub, które obejmują Mysql, Redis i Mongo, a nazwę użytkownika i hasło do konkretnego połączenia można znaleźć na https:// github.com/dtm-labs/dtm-examples.

Jeśli chcesz samodzielnie przygotować środowisko danych lokalnie, możesz użyć https://github.com/dtm-labs/dtm/blob/main/helper/compose.store.yml, aby uruchomić Mysql, Redis, Mongo; a następnie uruchom skrypty w https://github.com/dtm-labs/dtm/tree/main/sqls, aby przygotować dane dla tego przykładu, gdzie busi.* to dane biznesowe i barrier.* jest tabelą pomocniczą używaną przez DTM

Pisanie kodu biznesowego

Zacznijmy od kodu biznesowego dla najbardziej znanego MySQL.

Poniższy kod jest w Golangu. Inne języki, takie jak C#, PHP, Java można znaleźć tutaj:DTM SDKs

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
}

Ten kod wykonuje głównie korektę salda użytkownika w bazie danych. W naszym przykładzie ta część kodu jest używana nie tylko do operacji forward Saga, ale także do operacji kompensacji, gdzie tylko ujemna kwota musi zostać przekazana jako kompensata.

W przypadku Redis i Mongo kod biznesowy jest obsługiwany podobnie, po prostu zwiększając lub zmniejszając odpowiednie salda.

Jak zapewnić idempotentność

W przypadku wzorca transakcji Saga, gdy mamy chwilową awarię w usłudze pod-transakcji, nieudana operacja zostanie ponowiona. To niepowodzenie może wystąpić przed lub po zatwierdzeniu pod-transakcji, więc operacja pod-transakcji musi być idempotentna.

DTM udostępnia tabele pomocnicze i funkcje pomocnicze, aby pomóc użytkownikom szybko osiągnąć idempotentność. Dla Mysql utworzy tabelę pomocniczą barrier w biznesowej bazie danych, gdy użytkownik rozpocznie transakcję w celu dostosowania salda, najpierw wstawi Gid w barrier stół. Jeśli istnieje zduplikowany wiersz, wstawienie nie powiedzie się, a następnie pomiń korektę salda, aby zapewnić idempotentność. Kod korzystający z funkcji pomocniczej wygląda następująco:

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 obsługuje idempotentność w podobny sposób jak Mysql, więc nie będę więcej wchodzić w szczegóły.

Redis obsługuje idempotentność inaczej niż Mysql, głównie ze względu na różnicę w zasadzie transakcji. Transakcje Redis są zapewniane głównie przez atomowe wykonanie Lua. funkcja pomocnika DTM dostosuje równowagę za pomocą skryptu Lua. Przed dostosowaniem salda wyśle ​​zapytanie Gid w Redis. Jeśli Gid istnieje, pominie korektę salda; jeśli nie, zapisze Gid i wykonaj regulację balansu. Kod używany do funkcji pomocniczej wygląda następująco:

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

Jak zrobić odszkodowanie

W przypadku Sagi musimy również zająć się operacją kompensacji, ale kompensacja nie jest po prostu odwrotną korektą i istnieje wiele pułapek, o których należy pamiętać.

Z jednej strony odszkodowanie musi uwzględniać idempotentność, ponieważ niepowodzenie i ponawianie prób opisane w poprzednim podrozdziale również występują w odszkodowaniu. Z drugiej strony, kompensacja musi również uwzględniać „kompensację zerową”, ponieważ operacja do przodu Saga może zwrócić błąd, który mógł mieć miejsce przed lub po dostosowaniu danych. W przypadku niepowodzeń, w których dokonano korekty, musimy wykonać korektę odwrotną; ale w przypadku niepowodzeń, w których korekta nie została dokonana, musimy pominąć operację odwrotną.

W tabeli pomocniczej i funkcjach pomocniczych dostarczanych przez DTM z jednej strony określi, czy kompensacja jest kompensacją zerową opartą na Gid wprowadzonym przez operację naprzód, a z drugiej strony wstawi ponownie Gid+'compensate' aby ustalić, czy rekompensata jest powieloną operacją. Jeśli istnieje normalna operacja kompensacji, wykona korektę danych w firmie; w przypadku zerowej lub podwójnej rekompensaty pominie ona korektę w firmie.

Kod MySQL wygląda następująco.

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

Kod dla Redis jest następujący.

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

Kod usługi kompensacyjnej jest prawie identyczny z poprzednim kodem operacji forward, z tą różnicą, że kwota jest mnożona przez -1. Funkcja pomocnicza DTM automatycznie obsługuje idempotentność i kompensację wartości zerowej.

Inne wyjątki

Podczas zapisywania operacji forward i operacji kompensacyjnych istnieje w rzeczywistości inny wyjątek o nazwie „Zawieszenie”. Transakcja globalna zostanie wycofana, gdy upłynie limit czasu lub ponowna próba osiągnie skonfigurowany limit. Normalnym przypadkiem jest to, że operacja do przodu jest wykonywana przed kompensacją, ale w przypadku zawieszenia procesu kompensacja może być wykonana przed operacją do przodu. Tak więc operacja forward musi również określić, czy kompensacja została wykonana, a jeśli tak, należy również pominąć korektę danych.

W przypadku użytkowników DTM te wyjątki zostały potraktowane z wdziękiem i prawidłowo, a Ty jako użytkownik musisz tylko postępować zgodnie z MustBarrierFromGin(c).Call zadzwoń opisane powyżej i nie musisz się nimi w ogóle przejmować. Zasada obsługi tych wyjątków przez DTM została szczegółowo opisana tutaj:Wyjątki i bariery podtransakcji

Inicjowanie transakcji rozproszonej

Po zapisaniu poszczególnych usług pod-transakcji, poniższe kody kodu inicjują transakcję globalną 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()

W tej części kodu tworzona jest globalna transakcja Saga, która składa się z 3 pod-transakcji.

  • Przenieś 50 z MySQL
  • Transfer za 30 do Mongo
  • Przenieś za 20 do Redis

W trakcie transakcji, jeśli wszystkie transakcje podrzędne zakończą się pomyślnie, transakcja globalna zakończy się sukcesem; jeśli jedna z pod-transakcji zwróci niepowodzenie biznesowe, transakcja globalna zostanie wycofana.

Uruchom

Jeśli chcesz uruchomić pełny przykład powyższego, kroki są następujące.

  1. Uruchom DTM
git clone https://github.com/dtm-labs/dtm && cd dtm
go run main.go
  1. Uruchom udany przykład
git clone https://github.com/dtm-labs/dtm-examples && cd dtm-examples
go run main.go http_saga_multidb
  1. Uruchom nieudany przykład
git clone https://github.com/dtm-labs/dtm-examples && cd dtm-examples
go run main.go http_saga_multidb_rollback

Możesz zmodyfikować przykład, aby symulować różne tymczasowe awarie, sytuacje zerowej rekompensaty i różne inne wyjątki, w których dane są spójne po zakończeniu całej globalnej transakcji.

Podsumowanie

Ten artykuł podaje przykład transakcji rozproszonej w Mysql, Redis i Mongo. Opisuje szczegółowo problemy, które należy rozwiązać, oraz rozwiązania.

Zasady przedstawione w tym artykule są odpowiednie dla wszystkich silników pamięci masowej obsługujących transakcje ACID i można je szybko rozszerzyć na inne silniki, takie jak TiKV.

Witamy na stronie github.com/dtm-labs/dtm. Jest to projekt dedykowany ułatwiający transakcje rozproszone w mikroserwisach. Obsługuje wiele języków i wiele wzorców, takich jak wiadomość dwufazowa, Saga, Tcc i Xa.


  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Co to jest Express.js?

  2. jaka jest średnia różnica między klejnotami Nest i redis-namespace, gdy używamy redis z rails/ruby?

  3. ServiceStack:Przywrócić potok podczas ręcznego wywoływania usługi?

  4. TTL bazy danych Redis

  5. Redis aktywna-aktywna replikacja