利用redis实现分布式锁

来源:互联网 发布:windows tasks sched 编辑:程序博客网 时间:2024/04/30 08:06

通过SET命令实现

从 Redis 2.6.12 版本开始,SET命令支持了NX参数,能实现 "只在键不存在时,才对键进行设置操作" ,我们可以利用这一特性实现我们的锁。

例如我们想得到名为lock1的锁,我们可以用如下操作进行加锁:

SET lock1 lock NX

若返回OK,表明加锁成功;返回nil表明此键值已存在,即存在其他用户得到了该锁。

当然,解锁也非常简单

DEL lock1

这里还有一个问题,若加锁后因为程序崩溃或代码BUG,忘记释放锁,就会造成死锁。我们可以将上述加锁过程稍加改造,就能解决这个问题

SET lock1 lock PX 1000 NX

这样若出现死锁的情况,这把锁也会在1000ms超时后自动释放。


加锁示例代码:

bool lock(redisContext *c,const char *lock_name,uint32_t timeout,uint64_t &seq){struct timeval t;gettimeofday(&t, NULL);seq = (uint64_t)t.tv_sec * 1000 + t.tv_usec / 1000 + timeout;redisReply *reply = (redisReply *)redisCommand(c,"SET %s %lu PX %u NX",lock_name,seq,timeout);if (reply == NULL) {/* 发生错误 */return false;}if (reply->type == REDIS_REPLY_STATUS && !strcmp(reply->str, "OK")) {/* 加锁成功 */freeReplyObject(reply);return true;}if (reply->type == REDIS_REPLY_NIL) {/* 加锁失败 */freeReplyObject(reply);return false;}/* error */freeReplyObject(reply);return false;}
解锁示例代码:

void unlock(redisContext *c, const char *lock_name, uint64_t seq){struct timeval t;gettimeofday(&t, NULL);if (seq < (uint64_t)t.tv_sec * 1000 + t.tv_usec / 1000) {/* 已超时自动释放 */return;}redisReply *reply = (redisReply *)redisCommand(c,"DEL %s",lock_name);if (reply == NULL) {/* 发生错误 */return;}if (reply->type != REDIS_REPLY_INTEGER || reply->integer != 1) {/* TODO: DEL失败 */}freeReplyObject(reply);}


通过SETNX命令实现
上面的方法只适用于Redis 2.6.12版本以上,对于低于2.6.12版本的redis,我们可以用SETNX实现。具体细节可以参考http://www.jeffkit.info/2011/07/1000/,这里只把流程图和示例代码画一下。

加锁的流程图:



加锁示例代码:

bool lock(redisContext *c,const char *lock_name,uint32_t timeout,uint64_t &seq){struct timeval t;gettimeofday(&t, NULL);uint64_t now = (uint64_t)t.tv_sec * 1000 + t.tv_usec / 1000;seq = now + timeout;redisReply *reply = (redisReply *)redisCommand(c,"SETNX %s %lu",lock_name,seq);if (reply == NULL) {/* 发生错误 */return false;}if (reply->type != REDIS_REPLY_INTEGER) {/* 发生错误 */freeReplyObject(reply);return false;}if (reply->integer == 1) {/* 加锁成功 */freeReplyObject(reply);return true;}freeReplyObject(reply);reply = (redisReply *)redisCommand(c,"GET %s",lock_name);if (reply == NULL) {/* 发生错误 */return false;}if (reply->type != REDIS_REPLY_STRING) {/* 发生错误 */freeReplyObject(reply);return false;}if (strtoull(reply->str, NULL, 10) > now) {/* 未因死锁而超时 */freeReplyObject(reply);return false;}freeReplyObject(reply);reply = (redisReply *)redisCommand(c,"GETSET %s %lu",lock_name,seq);if (reply == NULL) {/* 发生错误 */return false;}if (reply->type != REDIS_REPLY_STRING) {/* 发生错误 */freeReplyObject(reply);return false;}if (strtoull(reply->str, NULL, 10) > now) {/* 发生冲突 */freeReplyObject(reply);return false;}freeReplyObject(reply);/* 上把锁因死锁而超时,重新锁定 */return true;}

这里存在两个问题:

1、计算超时的过程依赖本机时间,若两台机器时钟不同步,很容易计算超时出错。例如

A 加锁,超时时间设置为1000ms

同一时刻B 加锁,此时会加锁失败,进入判断死锁的逻辑。假设B机器时钟比A机器快1000ms,就会断定已死锁,从而获取锁成功。

此时就会出现A和B同时获取锁成功。

2、极端情况会出现加锁失败时仍然能更新锁,见原文。


这两个问题可以用下面的方案来解决。


通过LUA实现

由于redis是单线程的,执行lua脚本的过程中不会被其他指令打断。基于此,我们可以这样设计锁。


这个过程模拟了SET key value PX milliseconds NX的过程。考虑到若发送SETNX成功EXPIRE失败时,会出现死锁,这里在SETNX失败时加了一步判断是否有设置过期时间的逻辑。若未设置,就表明出现了死锁,重新锁定即可。

lua代码如下:

if redis.call('SETNX', KEYS[1], ARGV[1]) == 1 or redis.call('TTL', KEYS[1]) < 0 thenredis.call('PEXPIRE', KEYS[1], ARGV[1])return 1elsereturn 0end


加锁代码如下:

bool lock(redisContext *c,const char *lock_name,uint32_t timeout,uint64_t &seq){struct timeval t;gettimeofday(&t, NULL);seq = (uint64_t)t.tv_sec * 1000 + t.tv_usec / 1000 + timeout;const char *lua ="if redis.call('SETNX', KEYS[1], ARGV[1]) == 1 or redis.call('TTL', KEYS[1]) < 0 then ""redis.call('PEXPIRE', KEYS[1], ARGV[1]) ""return 1 ""else ""return 0 ""end ";redisReply *reply = (redisReply *)redisCommand(c,"EVAL %s 1 %s %u",lua,lock_name,timeout);if (reply == NULL) {/* 发生错误 */return false;}if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 1) {/* 加锁成功 */freeReplyObject(reply);return true;}/* 加锁失败 */freeReplyObject(reply);return false;}





0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 word文档复制过来有边框怎么办 wps表格跨页断开怎么办 锅的铆钉老是松怎么办 文胸不知道怎么染色了怎么办 未后的信息我该怎么办? 做leep手术后大出血怎么办 眼线笔出不了水怎么办 手机字体变成空心字怎么办 平安树树枝黑了怎么办 柳树被虫钻洞了怎么办 柳树叶子上有虫子怎么办 小金鱼翻肚皮了怎么办 秋天树叶没了小鸟怎么办 去国外旅游不会英语怎么办 橡皮树长了2米高怎么办 榕树盆景长的高怎么办? 2岁宝宝看书弯腰低头怎么办 excel表格打开很慢怎么办 3d模型有红线框怎么办 电视页面加载时错误怎么办 投屏显示加载视频错误怎么办 word遇到问题需要关闭怎么办 画眼线看不出来怎么办 14岁眼皮很松怎么办啊 ps存不了psd格式怎么办 花草上有白色物怎么办 ps抠出来有白边头发怎么办 脸上结痂掉了有红印怎么办 海棠花瓣干枯怎么办茎变软 微信上的图片打不开怎么办 口红吊兰老掉叶子怎么办 翠叶竹芋叶子卷怎么办 牙有龋齿垫底以后酸怎么办 事业单位辞职请示30天不批怎么办 孕早期吃了油菜怎么办 实体店买到翻新苹果手机怎么办 太阳花叶子蔫了怎么办 刚摘下来的多肉怎么办 购车4s不给合同怎么办 橙光游戏2.0商城怎么办 飞羽花卷叶了怎么办