zoukankan      html  css  js  c++  java
  • Redis_如何保证原子操作

     
    需求:两个客户端同时对[key1]执行自增操作,不会相互影响
     
    操作:下面两个客户端并发操作会导致[key1]输出结果与预期不一致
    1. [客户端一]读取[key1],值为[1]
    2. [客户端二]读取[key1],值为[1]
    3. [客户端一]将[key1]自增1,值为[2]
    4. [客户端二]将[key1]自增1,值为[2]
    5. [客户端一]输出[key1],值为[2]
    6. [客户端二]输出[key2],值为[2]
     
    解决思路
    1. [客户端一]、[客户端二]的R(读)、M(自增)、W(写)三个操作作为一个原子操作执行
    2. [客户端]对RMW整个操作过程加锁,加锁期间其它客户端不能对[key1]执行写操作
    3. Lua脚本
     
    思路一:单命令操作
    1. 概念
    Redis 提供了 INCR/DECR/SETNX 命令,把RMW三个操作转变为一个原子操作 Redis 是使用单线程串行处理客户端的请求来操作命令,所以当 Redis 执行某个命令操作时,其他命令是无法执行的,这相当于命令操作是互斥执行的
     
    思路二:加锁
    1. 概念
    加锁主要是将多客户端线程调用相同业务方法转换为串行化处理,比如多个客户端调用同一个方法对某个键自增(这里不考虑其它方法或业务会对该键同时执行自增操作)
     
    调用SETNX命令对某个键进行加锁(如果获取锁则执行后续RMW操作,否则直接返回未获取锁提示) 执行RMW业务操作 调用DEL命令删除锁
    

      

    2. 加锁风险一
    假如某个客户端在执行了SETNX命令加锁之后,在后面操作业务逻辑时发生了异常,没有执行 DEL 命令释放锁。 
    该锁就会一直被这个客户端持有,其它客户端无法拿到锁,导致其它客户端无法执行后续操作。 

    解决思路:给锁变量设置一个过期时间,到期自动释放锁
    SET key value [EX seconds
    | PX milliseconds] [NX]
    3. 加锁风险二
    如果客户端 A 执行了 SETNX 命令加锁后,客户端 B 执行 DEL 命令释放锁,此时,客户端 A 的锁就被误释放了。如果客户端 C 正好也在申请加锁,则可以成功获得锁。 
    
    解决思路:加锁操作时给每个客户端设置一个唯一值(比如UUID),唯一值可以用来标识当前操作的客户端。在释放锁操作时,客户端判断当前锁变量的值是否和唯一标识相等,只有在相等的情况下,才能释放锁。(同一客户端线程中加锁、释放锁) 
    
    SET lock_key unique_value NX PX 10000
    思路三:Lua脚本
     
    1. 概念
    多个操作写到一个 Lua 脚本中(Redis 会把整个 Lua 脚本作为一个整体执行,在执行的过程中不会被其他命令打断,从而保证了 Lua 脚本中操作的原子性)
     
    2. 需求
    限制所有客户端在一定时间范围内对某个方法(键)的访问次数。客户端 IP 作为 key,某个方法(键)的访问次数作为 value
     
    3. 脚本
    local current current = redis.call("incr",KEYS[1]) 

    if tonumber(current) == 1
    then redis.call("expire",KEYS[1],60)
    end
    4. 调用执行
    redis-cli --eval lua.script keys , args

  • 相关阅读:
    关于yarn的spark配置属性
    spark1.2.0编译
    sqoop1.99.4 JAVA API操作
    数据库范式(1NF 2NF 3NF BCNF)
    HTTP协议详解【转载】
    ESI 动态缓存技术[转载]
    ESI+varnish页面片段缓存
    用 Gearman 分发 PHP 应用程序的工作负载【转载】
    介绍 JSON的
    跨多种环境部署 Gearman -改善应用程序性能和降低服务器负载
  • 原文地址:https://www.cnblogs.com/gossip/p/13901425.html
Copyright © 2011-2022 走看看