概述

网上有很多golang操作redis的例子,我使用github.com/gomodule/redigo/redis包写一个关于redis分布式锁的问题。

redis分布式锁

锁需要的几个组件

  • 获取锁
  • 删除锁

分布式锁还需要

  • 给锁加上唯一id (只能获取和删除自己的锁)
  • 给锁加上过期时间 (防止死锁)

golang代码

推荐使用redis的连接池

pool := redis.Pool{
		MaxIdle:         8,
		MaxActive:       0,
		IdleTimeout:     100,
		Dial: func() (redis.Conn, error) {
			return redis.Dial("tcp", "127.0.0.1:6100")
		},
	}

通过lua脚本保证获取锁/加过期时间获取锁的值/删除锁为原子操作

var (
	updateLockExpireUidScript = redis.NewScript(1, `
		local res = redis.call("SETNX", KEYS[1], ARGV[1]) 
		if res == 1 then
			return redis.call("EXPIRE", KEYS[1], ARGV[2])
		end
		return res
	`)
	deleteLockByUidScript = redis.NewScript(1, `
		local res = redis.call("GET", KEYS[1]) 
		if res == ARGV[1] then
			return redis.call("DEL", KEYS[1])
		end
		return res 
	`)
)

golang完整代码

package main

import (
	"fmt"
	"github.com/gomodule/redigo/redis"
	"time"
)

func getLock(conn redis.Conn, key []string, uid int, expire int) bool  {
	lock := false
	for !lock {
		res, err := updateLockExpireUidScript.Do(conn, key, uid, expire)
		if err != nil {
			fmt.Println(err.Error())
			break
		}
		if res.(int64) == 1 {
			lock = true
			fmt.Println("获取锁成功")
			return true
		}
	}
	fmt.Println("获取锁失败")
	return false
}

func delLock(conn redis.Conn, key []string, uid int) bool {
	_, err := deleteLockByUidScript.Do(conn, key, uid)
	if err != nil {
		fmt.Println("删除锁失败")
		fmt.Println(err.Error())
		return false
	}
	fmt.Println("删除锁成功")
	return true
}

var (
	updateLockExpireUidScript = redis.NewScript(1, `
		local res = redis.call("SETNX", KEYS[1], ARGV[1]) 
		if res == 1 then
			return redis.call("EXPIRE", KEYS[1], ARGV[2])
		end
		return res
	`)
	deleteLockByUidScript = redis.NewScript(1, `
		local res = redis.call("GET", KEYS[1]) 
		if res == ARGV[1] then
			return redis.call("DEL", KEYS[1])
		end
		return res 
	`)
)

func main() {
	pool := redis.Pool{
		MaxIdle:         8,
		MaxActive:       0,
		IdleTimeout:     100,
		Dial: func() (redis.Conn, error) {
			return redis.Dial("tcp", "127.0.0.1:6100")
		},
	}
	key := []string{"distribute"}
	uid := 1
	expire := 20

	fmt.Println(time.Now())
	for i:= 1; i< 100; i++ {
		go func() {
			conn := pool.Get()
			if getLock(conn, key, uid, expire) {
				fmt.Println("开始执行逻辑...")
				fmt.Println("逻辑结束,删除锁")
				delLock(conn, key, uid)
			}
		}()
	}
	fmt.Println(time.Now())
	time.Sleep(time.Second * 20)
}

--完--