Golang使用redigo实现redis的分布式锁

有些分布式场景会有分布式锁的需求,可以为了原子操作,也可能为了性能的原因,不管是分布式锁市面是有不少解决方法的,比如etcd、consul、zookeeper… 初次之外redis这样的nosql也是可以实现分布式锁的。  python党喜欢用redis、etcd、consul来搞。   java这帮人更喜欢用zookeeper来实现分布式锁,zookeeper做分布式锁有临时节点(Ephemeral Node)的效果,也就是说当客户端出问题时,watch在zookeeper的服务会监听到的,  另外由这个客户端建立的键值也会被干掉…  


该文章写的有些乱,欢迎来喷 ! 另外文章后续不断更新中,请到原文地址查看更新http://xiaorui.cc/?p=3028

如果我们用redis来实现分布式锁,是怎么实现? (真想直接贴代码,不废话…)

首先借助于redis的setnx命令来操作,setnx本身针对key赋值的时候会判断redis中是否存在这个key,如果有返回-1,  如果没有的化,他会直接set键值。那他跟直接set键值有啥区别?  setnx是原子操作,而set不能保证原子性。


为了防止锁被长久锁定,或者防止客户端崩掉了没有删掉锁,可以用expire加入过期时间。 但这过期时间也不解决那种客户端异常退出,又没删除锁的情况。  我在使用etcd做服务发现注册时候,用了一个笨办法,把过期时间调的很细,可以开一个线程不停的去设置锁及过期时间.  这样能缓解一般的过期情况.

代码是基于https://github.com/everalbum/redislock/blob/master/redislock.go 修改的,这老外写的代码太高调了。 另外我在这基础上加入了增加过期时间的方法,及自定义prefix key和token,超时时间。

代码我已经整理到 https://github.com/rfyiamcool/go_redis_lock  ,暂不能构建一个可直接用来使用包,没想过要构建一个库包.   下面是实现的代码及test实例…   

#http://xiaorui.cc
package main

import (
	"fmt"
	"log"
	"time"

	"github.com/garyburd/redigo/redis"
)

type Lock struct {
	resource string
	token    string
	conn     redis.Conn
	timeout  int
}

func (lock *Lock) tryLock() (ok bool, err error) {
	_, err = redis.String(lock.conn.Do("SET", lock.key(), lock.token, "EX", int(lock.timeout), "NX"))
	if err == redis.ErrNil {
		// The lock was not successful, it already exists.
		return false, nil
	}
	if err != nil {
		return false, err
	}
	return true, nil
}

func (lock *Lock) Unlock() (err error) {
	_, err = lock.conn.Do("del", lock.key())
	return
}

func (lock *Lock) key() string {
	return fmt.Sprintf("redislock:%s", lock.resource)
}

func (lock *Lock) AddTimeout(ex_time int64) (ok bool, err error) {
	ttl_time, err := redis.Int64(lock.conn.Do("TTL", lock.key()))
	fmt.Println(ttl_time)
	if err != nil {
		log.Fatal("redis get failed:", err)
	}
	if ttl_time > 0 {
		fmt.Println(11)
		_, err := redis.String(lock.conn.Do("SET", lock.key(), lock.token, "EX", int(ttl_time+ex_time)))
		if err == redis.ErrNil {
			return false, nil
		}
		if err != nil {
			return false, err
		}
	}
	return false, nil
}

func TryLock(conn redis.Conn, resource string, token string, DefaulTimeout int) (lock *Lock, ok bool, err error) {
	return TryLockWithTimeout(conn, resource, token, DefaulTimeout)
}

func TryLockWithTimeout(conn redis.Conn, resource string, token string, timeout int) (lock *Lock, ok bool, err error) {
	lock = &Lock{resource, token, conn, timeout}

	ok, err = lock.tryLock()

	if !ok || err != nil {
		lock = nil
	}

	return
}

func main() {
	fmt.Println("start")
	DefaultTimeout := 10
	conn, err := redis.Dial("tcp", "localhost:6379")

	lock, ok, err := TryLock(conn, "xiaoru.cc", "token", int(DefaultTimeout))
	if err != nil {
		log.Fatal("Error while attempting lock")
	}
	if !ok {
		log.Fatal("Lock")
	}
	lock.AddTimeout(100)

	time.Sleep(time.Duration(DefaultTimeout) * time.Second)
	fmt.Println("end")
	defer lock.Unlock()
}


这段golang代码运行后的正常结果是:

$ go run lock.go
start
10
11
end


如果同时起多个进程去测试,会遇到这么一个结果:

$ go run lock.go
start
2016/03/23 01:23:22 Lock
exit status 1

END.


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc

发表评论

电子邮件地址不会被公开。 必填项已用*标注