使用Redis分布式锁解决并发线程资源共享问题

代码界的吴彦祖 2019-07-29 15:58:26 ⋅ 1001 阅读

众所周知, 在多线程中,因为共享全局变量,会导致资源修改结果不一致,所以需要加锁来解决这个问题,保证同一时间只有一个线程对资源进行操作

但是在分布式架构中,我们的服务可能会有n个实例,但线程锁只对同一个实例有效,就需要用到分布式锁----redis setnx

原理:

  修改某个资源时, 在redis中设置一个key,value根据实际情况自行决定如何表示

  我们既然要通过检查key是否存在(存在表示有线程在修改资源,资源上锁,其他线程不可同时操作,若key不存在,表示资源未被线程占用,允许线程抢占,然后将通过setnx设置vlaue,表示资源上锁,其他线程不可同时操作)

  图示:

分析:

  我们的服务处于一个集群中,如果只是简单的的使用线程锁来解决以上问题,是存在问题的:因为线程是基于进程的,两个web server处于不同的进程空间

  也就是说,user1的请求发往web server1,那只能与web server1的其他请求进行锁的操作,而不能对web server2的请求产生影响

  上面的图中,user1发往web server1的请求负责处理的线程为Thread1,同理负责处理user2发往web server2的请求的线程thread2

  在同一时刻1,两个线程都读取了mysql中residue_ticket的值为100,对应上图 (1)(2), 各自对100进行-1操作,更新到数据库,对应(3)(4)

  我们预期的情况是residue_ticket值被减少了两次,应该为98,但是实际情况下,两个线程都做了100-1=99的操作,并都将mysql中的值改为了99, 的这就会导致最终数据不一致,所以就要用到分布式锁。

为什么用redis?

  因为redis是单线程的,不存在多线程资源竞争,并且它真的很快

为什么用setnx 而不是set?

  setnx表示只有在key不存在时才能设置成功,但是set会在key存在的情况下修改value

 

利用setnx的特性,我们可以这样这样设计:

  伪代码:

# 设置redis锁的  redis key = 'residue_ticket_lock'
  # get_ticket是处理购票的逻辑  def get_ticket():    time_out = 5 # 为了防止线程过多,当前线程获取不到锁,长时间处于循环中而导致的性能影响,我们设置一个超时时间,如果当前线程在超时时间内还没有抢占到分布式锁,就返回失败的结果    while True:       if redis.setnx('residue_ticket_lock''lock',5):          # 如果setnx返回True, 表示此刻没有其他线程在操作数据库,当前线程可以上锁成功,注意不仅设置了value=lock,还设置了过期时间,这是必要的,为了防止上锁的线程异常崩掉导致不能释放(删除key)而导致其他所有线程永远拿不到操作权          residue_ticket = mysql.get('residue_ticket') # 从mysql中获取当前剩余票数          mysql.update('residue_ticket',residue_ticket-1) # 订购成功,将票数-1,更新数据到mysql          # 删除key,释放锁          redis.del('residue_ticket')          return True       else:          # 如果setnx返回False,表示有其他线程对在操作,当前线程等待0.01s,并继续循环          time.sleep(0.01)          time_out -= 0.01          continue    return False
原文链接:https://www.cnblogs.com/wangbaojun/p/11251403.html



全部评论: 0

    我有话说:

    Redis系列四

      本文目标 1. 熟悉乐观ABA概念 2. 理解掌握redis事务以及watch回滚; 3. 实战redis 乐观 乐观是一种不会阻塞其他线并发的机制,它不会使用数据库的

    Martian框架发布 3.0.3 版本,Redis分布式

    项目简介 Martian 是一个声明式 API 编程(DAP)框架,可以帮助你快速开发后端服务。 以HttpServer作为 http服务,彻底脱离Tomcat这一类的Web容器和Servlet,同时也让项目减少了几个依赖 声明式API,让Co...

    Redis线演进

    Redis作为一个基于内存的缓存系统,一直以高性能著称,因没有上下文切换以及无操作,即使在单线处理情况下,读速度仍可达到11万次/s,写速度达到8.1万次/s。但是,单线的设计也给Redis

    京东技术:如何实现靠谱的分布式?(附SharkLock的设计选择)

    分布式,是用来控制分布式系统中互斥访问共享资源的一种手段,从而避免并行导致的结果不可控。

    Java并发解决方案:分布式应用限流实践

    任何限流都不是漫无目的的,也不是一个开关就可以解决问题,常用的限流算法有:令牌桶,漏桶。在之前的文章中,也讲到过,但是那是基于单机场景来写。 之前文章:接口限流算法:漏桶算法&令牌桶算法

    架构实战篇(十二):Spring Boot 分布式Session共享Redis

    分布式Web网站一般都会碰到集群session共享问题,小编整理了一套解决方案,内附GitHub 源码地址哦~~~

    为什么单线Redis能够达到百万级的QPS?

    作者:在江湖中coding链接:https://juejin.im/post/5e6097846fb9a07c9f3fe744 性能测试报告 查看了下阿里云 Redis 的性能测试报告如下,能够

    JavaWeb实战篇:视图化了解多线Wait、NotifyAll使用案例

    内容简介本节针对于我们常听说而不常使用的 wait 和 notify 方法做个生产者和消费者案例效果图多线......

    Redis系列一 基本用法&应用场景

        说明 redis的最基本使用方法以及使用场景。 字符串 // stringasync function stringFun() { const [key

    Redis系列二:位图实战,实现打卡签到

    前言 如果要统计一篇文章的阅读量,可以直接使用 Redis 的 incr 指令来完成。 如果要求阅读量必须按用户去重,那就可以使用 set 来记录阅读了这篇文章的所有用户 id,获取

    并发分布式事务的解决方案-MQ消息事务+最终一致性

    分布式事务分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上

    Redis系列七 Debug Lua

      调试redis+lua 学了lua的基本语法,了解了redis+lua的配套用法,但是却不知道怎么断点调试。学就学全面点, 官网中有dubug相关说明。地址:Redis Lua

    聊一聊分布式场景下redis和memcached,及各自经典使用场景和优缺点

    在BAT里,redis已经逐渐取代了memcached,成为分布式场景广泛使用的缓存方案。接下来,我们就分析下,redis是如何取代memcached,成为开发者的宠儿的。

    Redis系列六 Lua

      本文目标 学习lua基本语法 能够采用redis+lua lua 基本语法 Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用

    SpringBoot2.0填坑(一):使用CROS解决跨域并解决swagger 访问不了问题

    公司后台是采用SpringBoot2.0 搭建的微服务架构,前端框架用的是vue 使用前后端分离的开发方式,在开发联调的时候需要进行跨域访问,那么使用CROS解决了跨域问题,但是swagger 却用

    「轻阅读」图解 Java 线生命周期

    Java 线生命周期中都包含哪些状态?生命周期中各个状态都是什么含义?

    面试官:HashMap为什么是线不安全的?

    一直以来只是知道HashMap是线不安全的,但是到底HashMap为什么线不安全?

    Redis系列八 抢红包

      本文概述 掌握红包的两种常见生成算法 掌握lua+redis 实现原子性抢红包 项目中还有mysql相关内容 了解jmeter的基本用法 遗留问题 redis同步DB时机问题