程序笔记   发布时间:2022-07-13  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了Redis如何实现分布式锁大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。

   今天我们来聊一聊分布式锁的那些事。

   相信大家对锁已经不陌生了,我们在多线程环境中,如果需要对同一个资源进行操作,为了避免数据不一致,我们需要在操作共享资源之前进行加锁操作。在计算机科学中,锁(lock)或互斥(muteX)是一种同步机制,用于在有许多执行线程的环境中强制对资源的访问限制。

   @H_450_5@比如你去相亲,发现你和一大哥同时和一个女的相亲,那怎么行呢...,搞不好还要被揍一顿。

@H_450_5@   那什么是@H_450_5@分布式锁呢。当多个客户端需要争抢锁时,我们就需要分布式锁。这把锁不能是某个客户端本地的锁,否则的话,其它客户端是无法访问的。所以分布式锁是需要存储在共享存储系统中的,比如redis、Zookeeper等,可以被多个客户端共享访问和获取。今天我们就来看一下如何使用redis来实现分布式锁。

@H_450_5@一、前言

   在正式开始之前,我们先来了解两个redis的命令:

SETNX key value

   这个命名的含义是,当key存在时,不做任何赋值操作;当key不存在时,就创建key,并赋值成value,即(不存在即设置)。

SET key value [EX seconds | PX milliseconds] NX

   SET后加NX选项,就和SETNX命令类似了,也实现不存在即设置的功能。此外,这个命令在执行时,可以通过EX或者PX设置键值对的过期时间。

@H_450_5@二、正文

   开始之前,我们先引入一个场景:

   @H_450_5@假设要给某个商品举行秒杀活动,我们事先把库存数据100已经存入到了redis中,我们现在需要来进行库存扣减。

   如图所示,我们假设有1000个客户端来进行库存扣减操作,那我们该如何做,才能保证库存扣减顺序一致且不会超扣呢。

   

Redis如何实现分布式锁

 

    我们首先想到的就是加锁,@H_450_5@在进行库存扣减之前,我们先拿到锁,然后进行扣减,最后再释放锁。在redis我们创建一个key来代表一个锁变量,然后对应的值来表示锁变量的值。我们来看一下如何进行加锁。

   假设1000个客户端同时进行加锁请求。因为redis使用单线程来处理请求,所以redis会串行执行他们的请求操作。假设redis先处理客户端2的请求,读取@H_450_5@lock_key的值,发现@H_450_5@lock_key为0,所以客户端2就把@H_450_5@lock_key的@H_450_5@value设置成1,表示已经进行了加锁操作。如果此时客户端3被处理,发现@H_450_5@lock_key的值已经为1了,所以就返回加锁失败的信息。

Redis如何实现分布式锁

 

 

   当拿到锁的客户端2处理完共享资源后,就要进行释放锁的操作,释放锁很简单,就是将lock_key重新设置为0。

   由于加锁操作包含了三个操作(读取锁变量、判断锁变量的值以及把锁变量的值设置成1),而这三个操作在执行的过程中需要保证原子性。那怎么保证原子性呢?

   我们可以使用@H_450_5@SETNX命令来实现加锁操作,@H_450_5@SETNX命令表示key不存在时就创建,key存在时就不做任何赋值操作,当加锁时候,我们执行

SETNX lock_key 1

  

    对于释放锁操作来说,我们可以使用DEL命令来删除锁变量。比如客户端2进行加锁,执行@H_450_5@SETNX lock_key 1,如果@H_450_5@lock_key不存在,则会创建@H_450_5@lock_key,返回加锁成功,此时客户端2可以进行共享资源的访问。如果这时客户端1来发起请求加锁操作,而此时@H_450_5@lock_key已经存在,@H_450_5@SETNX lock_key 1不做任何赋值操作操作,返回加锁失败,所以客户端1加锁失败。当客户端2执行完共享资源访问后,执行@H_450_5@DEL命令来释放锁。此时当有其它客户端再来访问时,@H_450_5@lock_key已经不存在了,就可以进行正常的加锁操作了。所以,我们可以使用@H_450_5@SETNX和@H_450_5@DEL命令组合来进行加锁和释放锁的操作。

  不过这里有两个问题:

  1.当某个客户端执行完@H_450_5@SETNX命令、加锁后,此时发生了异常,结果一直没有执行@H_450_5@DEL操作命令来释放锁。因此,这个客户端一直占用着这个锁,其它客户端无法拿到锁。

   解决这个问题,一个有效的方法就是,给锁变量设置一个过期时间。这样一来,即使持有锁的客户端发生了异常,无法主动的释放锁,redis也会根据锁变量的过期时间把它删除。其它客户端在锁变量过期后,就可以重新进行加锁操作了。

  2.如果客户端1执行了@H_450_5@SETNX 命令加锁后。如果此时客户端2执行DEL命令删除锁,这时,客户端A的锁就被误释放了。这是我们不能接受的。

   为了解决这个问题,我们需要能区分来自不同客户端的锁操作。我们该如何做呢?我们可以给每个客户端生成一个唯一值,在进行加锁时,我们把锁变量赋值成这个唯一值。这样在释放锁的时候,客户端需要判断,当前锁变量的值是否和自己的唯一标识相等,在相等的情况下,才能释放锁。

   下面来看一下如何在redis中进行实现。我们可以使用SET加EX/PX和NX选项,来进行加锁操作。

SET lock_key uuid NX PX 100

 其中@H_450_5@lock_key是锁变量,@H_450_5@uuid表示客户端的唯一标识,@H_450_5@PX 100表示100ms过期。由于我们在释放锁时需要对比客户端的标识和锁变量的值是否一致,这包含了多个操作,为了保证原子性,我们需要使用lua脚本,下面是lua脚本的实现。

if redis.call("get",KEYS[1]) == ARGV[1] then   
   return redis.call("del",KEYS[1])
else  
   return 0
end

    其中@H_450_5@KEY[1]表示@H_450_5@lock_key,@H_450_5@ARGV[1]表示当前客户端的唯一标识,这两个值是我们在执行lua脚本时作为参数传入的。下面我们来看一下完整的代码实现。

import redis
import traceBACk
import uuid
import time

class Inventory(object):
    def __init__(self):
        pool = redis.ConnectionPool(host='localhost', port=6379, decode_responses=TruE)
        client = redis.Strictredis(connection_pool=pool, max_connections=20)
        self.client=client
        self.uuid=str(uuid.uuid1())
        print(self.uuid)
        self.key="lock_key"
        self.inventory_key="inventory"
    def unlock(self):
        unlock_script="" 
                      "if redis.call("get",KEYS[1]) == ARGV[1] then" 
                      "   return redis.call("del",KEYS[1])" 
                      "else" 
                      "   return 0 " 
                      "end"
        try:
            unlock_cmd=self.client.register_script(unlock_script)
            result=unlock_cmd(keys=[self.key],args=[self.uuid])
            if result==1:
                print("释放成功")
            else:
                print("释放出错")
        except:
            print(traceBACk.format_exc())

    def lock(self):
        try:
          while True:
             result=self.client.set(self.key,self.uuid,px=100,nx=TruE)
             print(result)
             if result==1:
                 break

             print("sleep 1s")
             time.sleep(1)
          print("加锁成功")
          return True
        except:
          print(traceBACk.format_exc())
    def inventory(self):
        if self.lock():
           print("库存扣减")
           self.client.decr(self.inventory_key)
           print("扣减完成")
           self.unlock()


inv=Inventory()
inv.inventory()

   到此,我们就把redis实现分布式锁就聊完了。既然都读到了这里,不妨给个「@H_450_5@三连」吧,你的三连就是我最大的动力。

@H_450_5@三、后记

    更多硬核知识,请关注公众号【程序员学长】。回复【资料】可以获得上百本电子书资料

    我们下期见。

Redis如何实现分布式锁

 

大佬总结

以上是大佬教程为你收集整理的Redis如何实现分布式锁全部内容,希望文章能够帮你解决Redis如何实现分布式锁所遇到的程序开发问题。

如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。