有些分布式场景会有分布式锁的需求,可以为了原子操作,也可能为了性能的原因,不管是分布式锁市面是有不少解决方法的,比如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实例…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
#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代码运行后的正常结果是:
1 2 3 4 5 6 |
$ go run lock.go start 10 11 end |
如果同时起多个进程去测试,会遇到这么一个结果:
1 2 3 4 5 |
$ go run lock.go start 2016/03/23 01:23:22 Lock exit status 1 |
有疑问加站长微信联系(非本文作者)