基于Redis实现分布式锁

来源:互联网 发布:mysql 官网下载旧版本 编辑:程序博客网 时间:2024/06/06 00:17

基本概念:

分布式锁,是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。


举个例子:

1.假设有一个进程A,每小时准点给用户发送一条短信"Hello world",为了高可用,就必须在多台机器上面部署多个进程,避免宕机的情况

2.假设部署在两台机器,那么问题来了,用户每个小时就会收到两条"Hello world",信息就重复了

3.我们希望只发送一条"Hello world",那么就可以引入分布式锁的概念了,

4.进程A和进程B发送短信前先去注册一个锁,假设进程A抢到了锁,进程B就等待结果,如果发送成功了,那么就B就放弃此次任务,等待下一个小时。

5.问题的核心就在于怎么注册锁,检查锁的存在和注册锁是一个原子性操作,类似MySQL的主键,存在则不能insert,就说是你不能把我的锁覆盖了,你得等着

6.我们有多种方式可以实现分布式锁,最简单的就是以每小时准点这个时间作为主键,到mysql写入一条数据,利用数据库来维持一致性

7.当然分布式锁也会存在很多暗坑,不在这里展开。


分布式锁的几种实现:

1.zookeeper分布式锁,基于自增节点

2.redis分布式锁,基于setnx命令,

基于Redis实现分布式锁:http://blog.csdn.net/daiyudong2020/article/details/51760648

官网:http://redis.io/topics/distlock

译文:http://www.oschina.net/translate/redis-distlock

3.memcache分布式锁,基于add函数

这几种方案在网上有很多技术文章,不重复叙述,需要的Google一下


分布式锁的基本功能:

1.同一时刻只能存在一个锁
2.需要解决意外死锁问题,也就是锁能超时自动释放
3.支持主动释放锁

分布式锁解决什么问题:

多进程并发执行任务时,需要保证任务的有序性或者唯一性

准备:
redis版本>=2.6
redis是主从+sentinel模式(为了高可用)

原理:
redis2.6之后,SET命令支持超时和key存在检查,这是一个原子操作

获取锁并设置超时时间:
SET key value [EX seconds] [PX milliseconds] [NX|XX]

删除锁:
DEL key

EX:单位是秒
PX:单位是毫秒
NX:如果key存在,返回nil(失败),不存在返回ok
XX:如果key存在,返回ok,不存在返回nil(失败)

基本操作如图示:


案例:
有一个系统,需要对用户信息进行增删改查操作,系统是多进程的,要求对用户的操作是有序的串行的


上测试代码,可以多开进程观看效果:

[python] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. # -*- coding: utf-8 -*-  
  2.   
  3. import time  
  4. import redis  
  5.   
  6. # 获取锁  
  7. def get_lock(uid):  
  8.     # 连接redis  
  9.     r = redis.StrictRedis(host='localhost', port=6379, db=0, socket_timeout=1, socket_connect_timeout=3)  
  10.       
  11.     # 当前时间戳,用于删除锁时check  
  12.     sec = str(time.time())  
  13.       
  14.     # 处理超时时间  
  15.     timeout = 300  
  16.       
  17.     # 试图锁住uid  
  18.     while True:  
  19.         res = r.set(uid, sec, ex=timeout, nx=True)  
  20.         if res == True:  
  21.             print "get lock succeed, return"  
  22.             return True, sec  
  23.         else:  
  24.             print "get lock failed, lock exist, wait"  
  25.             time.sleep(0.001)  
  26.     return FalseNone  
  27.   
  28.   
  29. # 释放锁  
  30. def del_lock(uid, sec):  
  31.     # 连接redis  
  32.     r = redis.StrictRedis(host='localhost', port=6379, db=0, socket_timeout=1, socket_connect_timeout=3)  
  33.   
  34.     # 校验  
  35.     redis_sec = r.get(uid)  
  36.     if sec != redis_sec:  
  37.         print "check permission failed :%s" % uid  
  38.         return False  
  39.     print "check permission succeed :%s" % uid  
  40.   
  41.     #删除  
  42.     res = r.delete(uid)  
  43.     if res:  
  44.         print "del key succeed :%s" % uid  
  45.         return False  
  46.     else:  
  47.         print "del key failed :%s" % uid  
  48.         return True  
  49.   
  50.   
  51. if __name__ == '__main__':  
  52.   
  53.     uid = "001"  
  54.   
  55.     while True:  
  56.         status, sec = get_lock(uid)  
  57.         if status:  
  58.             del_lock(uid, sec)  
  59.         time.sleep(0.001)  



缺陷:

a) 一旦redis发生主从切换,可能会丢失一些锁,

b) 如果对锁的要求很高,可以参考redis官方提供的方案:http://redis.io/topics/distlock

c) 范例代码只能当作原理来理解,实际上有很多需要优化的地方,优化可参考:https://pypi.python.org/pypi/python-redis-lock


End;


原文:http://blog.csdn.net/daiyudong2020/article/details/51760648

1 0