1.分布式应用进行逻辑处理时经常会遇到并发问题

比如一个操作要修改用户的状态,修改状态需要先读出用户的状态,在内存里进行修

改,改完了再存回去。如果这样的操作同时进行了,就会出现并发问题,因为读取和保存状态这两个操作不是原子的。(Wiki 解释:所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch 线程切换。)

这个时候就要使用到分布式锁来限制程序的并发执行。

2.分布式锁

(1)分布式锁本质上要实现的目标就是在 Redis 里面占一个“茅坑”,当别的进程也要来占时,发现已经有人蹲在那里了,就只好放弃或者稍后再试。

(2)占坑一般是使用 setnx(set if not exists) 指令,只允许被一个客户端占坑。先来先占, 用完了,再调用 del 指令释放茅坑。

> setnx lock:codehole true
OK
... do something critical ...
> del lock:codehole
(integer) 1

这样的代码可能出现的第一个问题就是逻辑执行到中间出现问题而没有执行del,这样就会造成死锁,锁得不到释放

(3)通过添加过期时间自动将锁释放

> setnx lock:codehole true
OK
> expire lock:codehole 5
... do something critical ...
> del lock:codehole
(integer) 1

这样的代码还是可能出现问题:如果在 setnx 和 expire 之间服务器进程突然挂掉了,可能是因为机器掉电或者是被人为杀掉的,就会导致 expire 得不到执行,也会造成死锁。

(4)将setnx和expire两条指令变成一个原子指令

如果这两条指令可以一起执行就不会出现问题。也许你会想到用 Redis 事务来解决。但是这里不行,因为 expire是依赖于 setnx 的执行结果的,如果 setnx 没抢到锁,expire 是不应该执行的。事务里没有 if-else 分支逻辑,事务的特点是一口气执行,要么全部执行要么一个都不执行。

Redis 2.8 版本中作者加入了 set 指令的扩展参数,使得 setnx 和expire 指令可以一起执行,解决了将两条指令变成一个原子操作。 > set lock:codehole true ex 5 nx OK ... do something critical ... > dellock:codehole 上面这个指令就是 setnx 和 expire 组合在一起的原子指令,它就是分布式锁的奥义所在。

(5)Redis分布式锁不能解决超时问题

如果在加锁和释放锁之间的逻辑执行的太长,以至于超出了锁的超时限制,就会出现问题。因为这时候锁过期了,第二个线程重新持有了这把锁,但是紧接着第一个线程执行完了业务逻辑,就把锁给释放了,第三个线程就会在第二个线程逻辑执行完之间拿到了锁。

为了避免这个问题,Redis 分布式锁不要用于较长时间的任务。如果真的偶尔出现了,数据出现的小波错乱可能需要人工介入解决。

当然也有更安全的方案:

为 set 指令的 value 参数设置为一个随机数,释放锁时先匹配随机数是否一致,然后再删除 key。但是匹配 value 和删除 key 不是一个原子操作,Redis 也没有提供类似于delifequals这样的指令,这就需要使用 Lua 脚本来处理了,因为 Lua 脚本可以保证连续多个指令的原子性执行。

tag = random.nextint() # 随机数
if redis.set(key, tag, nx=True, ex=5):
do_something()
redis.delifequals(key, tag) # 假象的 delifequals 指令(匹配删除工作)lua脚本
# delifequals
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
elsereturn 0
end

3.可重入性

(1)可重入性是指线程在持有锁的情况下再次请求加锁,如果一个锁支持同一个线程的多次加锁,那么这个锁就是可重入的。

(2)Redis 分布式锁如果要支持可重入,需要对客户端的 set 方法进行包装,使用线程的 Threadlocal 变量存储当前持有锁的计数。(Threadlocal 变量可以自行搜索了解,是一个线程局部变量)

(3)可重入锁的部分实现(以下还不是可重入锁的全部,精确一点还需要考虑内存锁计数的过期时间,代码复杂度将会继续升高)

# -*- coding: utf-8
import redis
import threadinglocks = threading.local()
locks.redis = {}def key_for(user_id):return "account_{}".format(user_id)def _lock(client, key):return bool(client.set(key, True, nx=True, ex=5))def _unlock(client, key):client.delete(key)def lock(client, user_id):key = key_for(user_id)if key in locks.redis:locks.redis[key] += 1return Trueok = _lock(client, key)if not ok:return Falselocks.redis[key] = 1return Truedef unlock(client, user_id):key = key_for(user_id)if key in locks.redis:locks.redis[key] -= 1if locks.redis[key] <= 0:del locks.redis[key]return Truereturn Falseclient = redis.StrictRedis()
print "lock", lock(client, "codehole")
print "lock", lock(client, "codehole")
print "unlock", unlock(client, "codehole")
print "unlock", unlock(client, "codehole")

以上为学习《Redis深度历险核心原理和应用实践》笔记

应用 1:千帆竞发 ——分布式锁相关推荐

  1. 《Redis深度历险:核心原理和应用实践》千帆竞发——分布式锁

    转载于:https://www.cnblogs.com/gogogofh/p/11269088.html

  2. etcd 笔记(08)— 基于 etcd 实现分布式锁

    1. 为什么需要分布式锁? 在分布式环境下,数据一致性问题一直是个难点.分布式与单机环境最大的不同在于它不是多线程而是多进程.由于多线程可以共享堆内存,因此可以简单地采取内存作为标记存储位置.而多进程 ...

  3. Redis 缓存穿透、雪崩、缓存数据库不一致、持久化方式、分布式锁、过期策略

    1. Redis 缓存穿透 1.1 Redis 缓存穿透概念 访问了不存在的 key,缓存未命中,请求会穿透到 DB,量大时可能会对 DB 造成压力导致服务异常. 由于不恰当的业务功能实现,或者外部恶 ...

  4. Python 精选笔试面试习题—sorted 与 sort 单例模式、统计字符个数Count、垃圾回收、lambda函数、静态方法、类方法、实例方法、分布式锁、

    1. 字典根据键从小到大排序? In[38]: dic = {"name": "Tom", "age": 30, "country ...

  5. 服务注册发现consul之四: 分布式锁之四:基于Consul的KV存储和分布式信号量实现分布式锁...

    一.基于key/value实现 我们在构建分布式系统的时候,经常需要控制对共享资源的互斥访问.这个时候我们就涉及到分布式锁(也称为全局锁)的实现,基于目前的各种工具,我们已经有了大量的实现方式,比如: ...

  6. redis分布式锁 在集群模式下如何实现_收藏慢慢看系列:简洁实用的Redis分布式锁用法...

    在微服务中很多情况下需要使用到分布式锁功能,而目前比较常见的方案是通过Redis来实现分布式锁,网上关于分布式锁的实现方式有很多,早期主要是基于Redisson等客户端,但在Spring Boot2. ...

  7. api 创建zookeeper客户端_zookeeper分布式锁原理及实现

    前言 本文介绍下 zookeeper方式 实现分布式锁 原理简介 zookeeper实现分布式锁的原理就是多个节点同时在一个指定的节点下面创建临时会话顺序节点,谁创建的节点序号最小,谁就获得了锁,并且 ...

  8. 快来学习Redis 分布式锁的背后原理

    以前在学校做小项目的时候,用到Redis,基本也只是用来当作缓存.可阿粉在工作中发现,Redis在生产中并不只是当作缓存这么简单.在阿粉接触到的项目中,Redis起到了一个分布式锁的作用,具体情况是这 ...

  9. 分布式锁的三种实现方式_基于 redis 的分布式锁实现

    云龙 资深运维开发工程师,负责游戏系统配置管理平台的设计和开发,目前专注于新 CMDB 系统的开发,平时也关注运维自动化,devops,python 开发等技术. 背景 CMDB 系统里面的机器数据会 ...

  10. springboot mysql行锁_SpringBoot基于数据库实现简单的分布式锁

    本文介绍SpringBoot基于数据库实现简单的分布式锁. 1.简介 分布式锁的方式有很多种,通常方案有: 基于mysql数据库 基于redis 基于ZooKeeper 网上的实现方式有很多,本文主要 ...

最新文章

  1. Java并发编程 synchronized保证线程安全的原理
  2. Arduino Yun的硬件——Arduino Yun快速入门教程
  3. scrollBarStyle- listview滑动条调整
  4. 3.3 集束搜索-深度学习第五课《序列模型》-Stanford吴恩达教授
  5. 【atcoder】Enclosed Points [abc136F]
  6. 1.4 消息循环和回调函数
  7. requirejs学习之-- 初始化(一)
  8. MATLAB可以打开gms文件吗,gms文件扩展名,gms文件怎么打开?
  9. 设置dns_网络速度缓慢怎么办?轻松一键修改DNS设置让网速提升五倍
  10. 一文看懂边缘云在广电行业的应用
  11. 视频直播常见问题与解决办法汇总【系列三—直播推流】
  12. python数据集的预处理_关于Pytorch的MNIST数据集的预处理详解
  13. 实现机器学习的循序渐进指南I——KNN
  14. python更改端口
  15. iOS 推送 获取手机设备的 deviceToken
  16. 中国用量子计算机仿生生物,中国科学技术大学研制一种可持续生物合成仿生多层级太阳能蒸汽发生器...
  17. 记 第一次游戏测试实习经历
  18. Windows-caffe安装
  19. 为什么硅谷初级程序员工资堪比腾讯T3技术专家级
  20. 编程中经常用到的工具

热门文章

  1. 【LWIP】(补充)STM32H743(M7内核)CubeMX配置LWIP并ping通
  2. 计算机CPU核心部件简介
  3. Python获取计算机CPU核数
  4. 微软必应词典客户端的案例分析——个人Week3作业
  5. 第17章:使用 concurrent.futures 模块处理并发-使用 futures.as_completed 函数立刻获取多线程任务执行结果
  6. 互联网2018校招时间_供参考
  7. 飞猪java怎么样_2021春招面试必看:飞猪/新浪/饿了么内部Java面经手册首次发布...
  8. 2018,来年只剩追忆
  9. TrueCrypt 使用经验[3]:关于加密
  10. openffice安装(windows和linux)