使用Redis构建全局并发锁
谈起Redis的用途,小伙伴们都会说使用它作为缓存,目前很多公司都用Redis作为缓存,但是使用Redis仅仅作为缓存未免太大材小用了。深究Redis的原理后你会发现它有很多用途,在很多场景下能够使用它快速地解决问题。常见的用途有:分布式锁控制并发、结合bloom filter用于推荐去重、HyperLogLog用于统计UV、限流控制流量等等;这里我谈下Redis分布式锁控制并发的问题。
高并发是个老生常谈的问题,当产品达到一定规模用户量后,这个问题是不得不考虑的,即使当前用户量不大(例如博主现在的公司),但自己平时在设计API的时候最好也尽可能地考虑到并发问题。
Redis分布式锁控制并发主要是通过在Redis里面创建一个key,当其它进程准备占用的时候只能等待key释放再占用。Redis里面有一个原子性指令setnx,当key存在时,它返回0,表示当前已有进程占用,当它返回1时可以执行业务逻辑,此时没有进程占用,等逻辑执行完后,可以删除key释放锁,这样可以简单的控制并发。

但是细想之下你会发现,在业务逻辑执行的过程中如果发生异常,此时key并没有删除,这样就会造成死锁,死锁带来的后果想必大家都很清楚。为了解决这个问题,可以在setnx加锁后设置key的过期时间,当key到期自动删除。

但是仔细想想你还会发现,如果在执行setnx后,执行expire前Redis发生宕机了,这样就不会执行expire,也会造成死锁。由于setnx与expire是两条命令,并且expire依赖setnx的执行结果,为了解决这个问题可以使用set key value [expiration EX seconds|PX milliseconds] [NX|XX] ,这是一条原子性的指令,同时包含setnx和expire。

使用python实现的代码:
class RedisLock(object):
"""
踩坑 Redis并发锁
""" def __init__(self, key):
self.redis_conn = get_redis_conn()
self.lock_key = "{}_redis_gil".format(key) @staticmethod
def get_lock_value(cls):
"""
获取value
:param cls:
:return:
"""
cls.get_lok = cls.redis_conn.get(cls.lock_key)
return cls.get_lok @staticmethod
def set_lock(cls, random_value):
"""
不能使用setnx 没有设置过期时间,可能会出现死锁
引入random_value :自己加的锁只能自己释放
:param cls:
:param random_value:
:return:
"""
cls._lock = cls.redis_conn.set(cls.lock_key, random_value, nx=True, ex=5) # 如果返回null 表示key存在存在并发
if cls._lock:
return True
else:
LOGGER = logging.getLogger('core.utils')
LOGGER.warning(u"试题复制存在并发")
raise RsError("试题复制存在并发,请稍后再试") @staticmethod
def release(cls):
"""
释放锁
:param cls:
:return:
"""
cls.redis_conn.delete(cls.lock_key) @staticmethod
def redis_lock(cls):
"""
只有当设置的value与do_something执行完后所获取的值相同时才删除key
防止在分布式中: clientA由于执行时间过期(clientA的执行时间比设置的过期时间大),clientB获取锁,
clientA执行完后释放锁(删除key),其实这时候删除的是B的key,
为防止这种情况引入random_value 只有当前值为random_value时才删除
:param cls:
:return:
"""
random_value = time.time()
if cls.set_lock(cls, random_value):
do_something()
now_value = cls.get_lock_value(cls)
if now_value == random_value:
cls.release()
return True
else:
return False def do_something():
pass
在实际业务中调用Redis全局锁,进行加锁示例:
# 公库试题复制到平台考虑并发问题,加锁处理
if self.visible_scope == 10:
key = hash(self.question_id)
cls = RedisLock(key)
cls.redis_lock(cls)
try:
self.insert_question()
except Exception:
raise RsError("试题插入失败")
finally:
cls.release(cls)
如果是Redis集群下此方法可能仍然有问题,试想下:在一个redis集群中,主节点由于某种原因挂掉了,从节点变成了主节点,而此时redis锁还未同步到原从节点中,那么这个锁也就失效了,当其它进程申请锁时仍然可以申请成功。
针对这个问题,新版的redis引入了redlock,通过redlock.Redlock对多个redis节点进行加锁,当超过一半的节点加锁成功时锁才生效。这样在一定程度上提高了高可用性,但由于每次加锁和释放锁要对多个节点进行读写,所以性能上肯定是没有单节点锁高的。
使用Redis构建全局并发锁的更多相关文章
- Redis构建全局并发锁
Redis构建全局并发锁 https://www.cnblogs.com/FG123/p/9990336.html 谈起Redis的用途,小伙伴们都会说使用它作为缓存,目前很多公司都用Redis作为缓 ...
- nginx+lua+redis构建高并发应用(转)
nginx+lua+redis构建高并发应用 ngx_lua将lua嵌入到nginx,让nginx执行lua脚本,高并发,非阻塞的处理各种请求. url请求nginx服务器,然后lua查询redis, ...
- 【redis】基于redis实现分布式并发锁
基于redis实现分布式并发锁(注解实现) 说明 前提, 应用服务是分布式或多服务, 而这些"多"有共同的"redis"; (2017-12-04) 笑哭, 写 ...
- 使用redis构建可靠分布式锁
关于分布式锁的概念,具体实现方式,直接参阅下面两个帖子,这里就不多介绍了. 分布式锁的多种实现方式 分布式锁总结 对于分布式锁的几种实现方式的优劣,这里再列举下 1. 数据库实现方式 优点:易理解 缺 ...
- Nginx+Lua+Redis构建高并发应用
一. 源文来自:http://www.ttlsa.com/nginx/nginx-lua-redis/ 二. 预览如下:
- Redis构建分布式锁
1.前言 为什么要构建锁呢?因为构建合适的锁可以在高并发下能够保持数据的一致性,即客户端在执行连贯的命令时上锁的数据不会被别的客户端的更改而发生错误.同时还能够保证命令执行的成功率. 看到这里你不禁要 ...
- 《Redis官方文档》用Redis构建分布式锁
用Redis构建分布式锁 在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但是这些库实现的方式差别很大,而且很多简 ...
- Redis修改数据多线程并发—Redis并发锁
本文版权归博客园和作者本人吴双共同所有 .转载爬虫请注明地址,博客园蜗牛 http://www.cnblogs.com/tdws/p/5712835.html 蜗牛Redis系列文章目录http:// ...
- Redis并发锁控制
为了防止用户在页面上重复点击或者同时发起多次请求,请求处理需要操作redis缓存,这个时候需要对并发边界进行并发锁控制,实现思路: 由于每个页面发起的请求带的token具备唯一性,可以将token作为 ...
随机推荐
- Java连接远程Mysql过程中遇到的各种问题
2018-11-16 10:46 2018-11-19 21:35 前言 本篇文章记录的是本人在使用Java程序连接另一台电脑(同一局域网)上的Mysql数据库的过程中遇到的各种问题及解决方案.希望能 ...
- Java【第八篇】面向对象之高级类特性
static 关键字 当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用.我们有 ...
- CAN总线报文浅析
CAN的报文格式 在总线中传送的报文,每帧由7部分组成.CAN协议支持两种报文格式,其唯一的不同是标识符(ID)长度不同,标准格式为11位,扩展格式为29位. 在标准格式中,报文的起始位称为帧起始(S ...
- vegas 为盖斯
vegas 为盖斯 S键 分割素材U键 分开视频和音频I键渲染开始O渲染结束 默认布局 为盖斯新建项目的参数 剪好后渲染 插入字幕
- Java异常知识整理_处理异常时的性能开销
1.首先列两个从别的地方看到的说法: try-catch代码段会产生额外的性能开销,或者换个角度说,它往往会影响JVM对代码进行优化,所以建议仅捕获有必要的代码段,尽量不要一个大的try包住整段的代码 ...
- SpringCloud笔记五:Feign
目录 什么是Feign? 有了Ribbon我还要Feign干嘛? 新建consumer-feign 修改api项目 引入Maven文件 新建feign的接口 启动项目 报错 发泄发泄心情,一个段落 什 ...
- 为Nexus配置阿里云代理仓库
Nexus默认远程仓库为https://repo1.maven.org/maven2/ 慢死,还常连不上. 可以添加阿里云代理仓库 URL:http://maven.aliyun.com/nexus/ ...
- Docker下安装Jenkins
Docker安装参见:https://www.cnblogs.com/hackyo/p/9280042.html 安装Jenkins: docker run \ -u root \ --rm \ -d ...
- ElasticSearch 索引 剖析
ElasticSearch index 剖析 在看ElasticSearch权威指南基础入门中关于:分片内部原理这一小节内容后,大致对ElasticSearch的索引.搜索底层实现有了一个初步的认识. ...
- js 数字前自动补零
num为传入的数字,n为需要的字符长度 return (Array(n).join(0) + num).slice(-n); 例如 我想返回两位数 输入6 然后返回06 就可以这样写: return ...