Redis optimistic lock with golang demo

4046 ワード

redisトランザクションコマンド
  • MULTI:
  • トランザクションを開始
  • EXEC:トランザクションが実行され、トランザクション内のすべてのコマンド
  • が一度に実行されます.
  • DISCARD:トランザクションのキャンセル
  • WATCH+MULTI方式を用いて楽観的ロックを実現
    WATCH:1つ以上のキーを監視し、トランザクションの実行前にキーが変更された場合、トランザクションも中断されますUNWATCH:WATCHコマンドによるすべてのキーの監視を解除します
    go-redis packageを使用してユーザーのチケット獲得の流れをシミュレートする
  • 複数goroutineシミュレーション同時チケット奪取
  • を開始
  • go-redis TxPipelined実行トランザクション
  • go-redis client.Watchあるキー
  • を監視する
    package main
    
    import (
        "errors"
        "fmt"
        "sync"
    
        "github.com/go-redis/redis/v7"
    )
    
    func main() {
        client := redis.NewClient(&redis.Options{
            Addr:     "localhost:6379",
            Password: "", // no password set
            DB:       0,  // use default DB
        })
    
        key := "ticket_count"
        client.Set(key, "5", 0).Err()
        val, _ := client.Get(key).Result()
        fmt.Println("current ticket_count key val: ", val)
    
        getTicket(client, key)
    }
    
    func runTx(key string, id int) func(tx *redis.Tx) error {
        txf := func(tx *redis.Tx) error {
            n, err := tx.Get(key).Int()
            if err != nil && err != redis.Nil {
                return err
            }
    
            if n == 0 {
                return errors.New("   ")
            }
    
            // actual opperation (local in optimistic lock)
            n = n - 1
    
            // runs only if the watched keys remain unchanged
            _, err = tx.TxPipelined(func(pipe redis.Pipeliner) error {
                // pipe handles the error case
                pipe.Set(key, n, 0)
                return nil
            })
            return err
        }
        return txf
    }
    
    func getTicket(client *redis.Client, key string) {
        routineCount := 8
        var wg sync.WaitGroup
        wg.Add(routineCount)
    
        for i := 0; i < routineCount; i++ {
            go func(id int) {
                defer wg.Done()
    
                for {
                    err := client.Watch(runTx(key, id), key)
                    if err == nil {
                        fmt.Println(id, "  ")
                        return
                    } else if err.Error() == "   " {
                        fmt.Println(id, "   ")
                        return
                    } else {
                        fmt.Println(err, "retry")
                    }
                }
            }(i)
        }
        wg.Wait()
    }

    Github code: https://github.com/defp/redis...
    current ticket_count key val:  5
    7   
    6   
    redis: transaction failed retry
    redis: transaction failed retry
    redis: transaction failed retry
    redis: transaction failed retry
    redis: transaction failed retry
    redis: transaction failed retry
    redis: transaction failed retry
    3   
    redis: transaction failed retry
    redis: transaction failed retry
    redis: transaction failed retry
    redis: transaction failed retry
    2   
    redis: transaction failed retry
    redis: transaction failed retry
    redis: transaction failed retry
    redis: transaction failed retry
    5   
    redis: transaction failed retry
    redis: transaction failed retry
    redis: transaction failed retry
    4    
    1    
    0    

    links
  • How to create Redis Transaction in Go using go-redis/redis package? - Stack Overflow
  • redis package · pkg.go.dev
  • Redisライトキャッシュ
  • defp/redis-watch-multi-exec-demo
  • 42丨はどのようにRedisを使ってマルチユーザーの切符を奪う問題を実現します