使用Redis实现分布式锁

delaywu · 2021-11-11 16:36:56 · 1101 次点击 · 预计阅读时间 3 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2021-11-11 16:36:56 的文章,其中的信息可能已经有所发展或是发生改变。

材料准备

github.com/go-redis/redis/v8  // 操作redis
github.com/gin-gonic/gin       
go v1.17

核心封装代码

package lib

import (
    "context"
    "fmt"
    "github.com/go-redis/redis/v8"
    "log"
    "time"
)

type Locker struct {
    key    string
    expire time.Duration
    unlock bool
    incLua *redis.Script
}

const incLua = `if redis.call('get',KEYS[1])==ARGV[1] then
          return redis.call('expire',KEYS[1],ARGV[2])
          else return '0'
          end`

// ==========> option 模式初始化
type CmdOption func(locker *Locker)
type CmdOptions []CmdOption

func (this CmdOptions) Apply(locker *Locker)  {
    for _, option := range this {
        option(locker)
    }
}

func New(opts ...CmdOption) *Locker {
    locker := &Locker{}

    CmdOptions(opts).Apply(locker)
    return locker
}

func WithKey(key string) CmdOption {
    return func(locker *Locker) {
        locker.key = key
    }
}
// ==========> option 模式初始化 end

func NewLocker(key string) *Locker {
    return &Locker{key: key, expire: time.Second * 30, incLua: redis.NewScript(incLua)}
}

func NewLockerWithTTL(key string, expire time.Duration) *Locker {
    if expire.Seconds() <= 0 {
        panic("error expire")
    }

    return &Locker{key: key, expire: expire, incLua: redis.NewScript(incLua)}
}

func (this *Locker) Lock() *Locker {
    boolCmd := redisClient.SetNX(context.Background(), this.key, "1", 0)

    if ok, err := boolCmd.Result(); err != nil || !ok {
        panic(fmt.Sprintf("locker error with key:%s", this.key))
    }

    this.expandLockTime()
    return this
}

func (this *Locker) UnLock() *Locker {
    redisClient.Del(context.Background(), this.key)
    this.unlock = true
    return this
}

func (this *Locker) expandLockTime() {
    sleepTime := this.expire.Seconds() * 2 / 3
    go func() {
        for {
            time.Sleep(time.Duration(sleepTime))
            if this.unlock {
                break
            }
            this.resetExpire()
        }
    }()
}

// 重新设置过期时间
func (this *Locker) resetExpire() {
    cmd := this.incLua.Run(context.Background(), redisClient, []string{this.key}, 1, this.expire.Seconds())
    v, err := cmd.Result()

    log.Printf("key=%s,续期结果:%v,%v\n", this.key, err, v)
}

调用实例

package main

import (
    "GoRedis/src/lib"
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
    "time"
)

var a = 0

func main() {
    r := gin.New()

    r.Use(func(ctx *gin.Context) {
        defer func() {
            if err := recover(); err != nil {
                ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"message": err})
            }
        }()

        ctx.Next()
    })

    r.Handle("GET", "/", func(ctx *gin.Context) {
        var locker = lib.NewLockerWithTTL("locker1", time.Second*30).Lock()
        defer locker.UnLock()

        fmt.Println(ctx.Query("t"))

        if ctx.Query("t") != "" {
            time.Sleep(time.Second * 5)
        }

        a++

        ctx.JSON(http.StatusOK, gin.H{"message": a})
    })

    r.Run(":9091")
}

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1101 次点击  ∙  1 赞  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传