高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。redis
对多个资源、数据库表、对象同时加锁时,须要保持一致的加锁顺序,不然可能会形成死锁。 说明:线程一须要对表 A、B、C 依次所有加锁后才能够进行更新操做,那么线程二的加锁顺序也必须是 A、B、C,不然可能出现死锁。数据库
分布式锁特色:安全
分布式锁服务,你须要考虑下面几个设计session
这个方案相对于memcached()的add()方案,redis占优点的是,其支持的数据类型更多,而memcached只支持String一种数据类型。除此以外,不管是从性能上来讲,仍是操做方便性来讲,其实都没有太多的差别,彻底看你的选择,好比公司中用哪一个比较多,你就能够用哪一个。数据结构
首先说明一下setnx()命令,setnx的含义就是SET if Not Exists,其主要有两个参数 setnx(key, value)。该方法是原子的,若是key不存在,则设置当前key成功,返回1;若是当前key已经存在,则设置当前key失败,返回0。可是要注意的是setnx命令不能设置key的超时时间,只能经过expire()来对key设置。并发
具体的使用步骤以下:dom
setnx(lockkey, 1) 若是返回0,则说明占位失败;若是返回1,则说明占位成功分布式
expire()命令对lockkey设置超时时间,为的是避免死锁问题。memcached
执行完业务代码后,能够经过delete命令删除key。高并发
这个方案实际上是能够解决平常工做中的需求的,但从技术方案的探讨上来讲,可能还有一些能够完善的地方。好比,若是在第一步setnx执行成功后,在expire()命令执行成功前,发生了宕机的现象,那么就依然会出现死锁的问题,因此若是要对其进行完善的话,可使用redis的setnx()、get()和getset()方法来实现分布式锁。
这个方案的背景主要是在setnx()和expire()的方案上针对可能存在的死锁问题,作了一版优化。
那么先说明一下这三个命令,对于setnx()和get()这两个命令,相信不用再多说什么。那么getset()命令?这个命令主要有两个参数 getset(key,newValue)。该方法是原子的,对key设置newValue这个值,而且返回key原来的旧值。假设key原来是不存在的,那么屡次执行这个命令,会出现下边的效果:
getset(key, “value1″) 返回nil 此时key的值会被设置为value1
getset(key, “value2″) 返回value1 此时key的值会被设置为value2
依次类推!
介绍完要使用的命令后,具体的使用步骤以下:
setnx(lockkey, 当前时间+过时超时时间) ,若是返回1,则获取锁成功;若是返回0则没有获取到锁,转向2。
get(lockkey)获取值oldExpireTime ,并将这个value值与当前的系统时间进行比较,若是小于当前系统时间,则认为这个锁已经超时,能够容许别的请求从新获取,转向3。
计算newExpireTime=当前时间+过时超时时间,而后getset(lockkey, newExpireTime) 会返回当前lockkey的值currentExpireTime。
判断currentExpireTime与oldExpireTime 是否相等,若是相等,说明当前getset设置成功,获取到了锁。若是不相等,说明这个锁又被别的请求获取走了,那么当前请求能够直接返回失败,或者继续重试。
在获取到锁以后,当前线程能够开始本身的业务处理,当处理完毕后,比较本身的处理时间和对于锁设置的超时时间,若是小于锁设置的超时时间,则直接执行delete释放锁;若是大于锁设置的超时时间,则不须要再锁进行处理。
SET resource_name my_random_value NX PX 30000
四、SET EX
SETEX cd 3000 "goodbye my love"
my_random_value是由客户端生成的一个随机字符串,至关因而客户端持有锁的标志
NX表示只有当resource_name对应的key值不存在的时候才能SET成功,至关于只有第一个请求的客户端才能得到锁
PX 30000表示这个锁有一个30秒的自动过时时间。
至于解锁,为了防止客户端1得到的锁,被客户端2给释放,采用下面的Lua脚原本释放锁
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
在执行这段LUA脚本的时候,KEYS[1]的值为resource_name,ARGV[1]的值为my_random_value。原理就是先获取锁对应的value值,保证和客户端传进去的my_random_value值相等,这样就能避免本身的锁被其余人释放。另外,采起Lua脚本操做保证了原子性。
例如,下面的例子演示了不区分 Client 会致使错误
经过执行上面脚本的方式释放锁,Client 的解锁操做只会解锁本身曾经加锁的资源,因此是安全的。
基于数据库表作乐观锁,用于分布式锁。
使用memcached的add()方法,用于分布式锁。
使用redis的setnx()、expire()方法,用于分布式锁。
使用redis的setnx()、get()、getset()方法,用于分布式锁。
使用redis的setex(),用于分布式锁