Redis optimistic lock with golang demo

redis 事务处理命令

  • MULTI:开启一个事务
  • EXEC:事务执行,将一次性执行事务内的所有命令
  • DISCARD:取消事务

使用 WATCH+MULTI 的方式来实现乐观锁

WATCH:监控一个或多个键,如果事务执行前某个键发生了改动,那么事务也会被打断
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

你可能感兴趣的:(golang,redis)