Redis基本概念和作用

Posted 瑾!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis基本概念和作用相关的知识,希望对你有一定的参考价值。

        Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。

        REmote DIctionary Server(Redis) 是一个由 Salvatore Sanfilippo 写的 key-value 存储系统,是跨平台的非关系型数据库。

        Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API。

        Redis 通常被称为数据结构服务器,因为值(value)可以是字符串(String)、哈希(Hash)、列表(list)、集合(sets)和有序集合(sorted sets)等类型。

        Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库,缓存和消息中间件。它支持多种类型的数据结构,如字符串(strings)、哈希列(hashes)列表(lists)集合(sets)有序集合(sorted sets)与范围查询,bitmapshyperloglogs地理空间(geospatial)索引半径查询。Redis内置了复制(replication)LUA脚本(Luascripting)LRU驱动事件(LRU eviction)事务(transactions)和不同级别的磁盘持久化(persistence),并通过Redis哨兵(Sentinel)和自动分区(Cluster)提高可用性(highavailability)

        Redis 是当前互联网世界最流行的 NoSQL(Not Only SQL)数据库。NoSQL在互联网系统中的作用很大,因为它可以在很大程度上提高互联网系统的性能。

        Redis 具备一定持久层的功能,也可以作为一种缓存工具。对于NoSQL数据库而言,作为持久层,它存储的数据是半结构化的没这就意味着计算机在读入内存中有更少的规则,读入速度更快。

        对于那些结构化、多范式规则的数据库系统而言,它更具有性能优势。作为缓存,它可以支持大数据存入内存中,只要命中率高,它就能快速响应,因为在内存中的数据读/写比数据库读/写磁盘的速度快即使到上百倍。

目前NoSQL 有很多争议,有人认为它可以取代数据库,我却不这么认为。

因为数据库系统有更好的规范性和数据完整性,功能更强大,作为持久层更为完善,安全性更高。而NoSQL结构松散、不完整、攻能有限,目前尚不具备取代数据库的实力,但是作为缓存工具,它的高性能、高相应等功能,使它成为一个很重要的工具。

Redis 的优势是什么?

  1. 相应快速 Redis 相应非常快,每秒可以执行大约110000个写入操作,或者81000个读取操作,其速度远超数据库。如果存入一些常用的数据,就能有效的提高系统的性能
  2. 支持5中数据结构 它们是字符串、哈希结构、列表、集合、有序集合。比如对于字符串可以存入一些java基础数据类型,哈希可以存储对象,列表可以存储List对象等。这使得在应用中很容易根据自己的需求进行选择存储的数据类型,方便开发。对于Redis而言,虽然只有5中数据类型,但是有两大好处:一方面可以满足存储各种数据结构体的需求;另一方面数据类型少,使得规则少,需要的判断和逻辑就少,这样读/写的速度就更快
  3. 操作都是原子的 所有Redis的操作都是原子的,从而确保党两个客户同时访问Redis服务器时,得到的是更新后的值(最新值)。在需要高并发的场合可以考虑使用Redis的事务,处理一些需要锁的业务。
  4. MultiUtility工具 Redis可以在如缓存、消息传递队列中使用(Redis支持“发布+订阅”的消息模式),在应用程序如Web应用程序会话,网站页面点击数等任何短暂的数据中使用

Redis的数据结构有哪些

1.string 字符串    与其他编程语言或者其他键值存储提供的字符串非常相似,键(key)---值(value)(字符串格式),字符串拥有一些操作命令,如 get、set、del还有一些比如自增或者自减操作等等。Redis是使用c语言开发,但c中并没有字符串类型,只能使用指针或者符数组的形式表示一个字符串,所以Redis设计了一个简单动态字符串(SDS[Simple Dynamic String])作为低实现:

        定义SDS对象,此对象中包含三个属性:

  • Len buf 中已经占有的长度(表示此字符串的实际长度)
  • Free buf中未使用的缓冲区长度
  • Buf【】实际保存字符串数据的地方

所以取字符串的长度的时间复杂度为o(1),另,buf【】中依然采用了c语言的以\\0结尾可以直接使用C语言的部分标准C字符串库函数。

空间分配原则:党len小于|mb(1024*1024)时增加字符串分配空间大小为原来的2倍,党len大于等于1mb时每次分配额外多分配1m的空间

由此可以得出一下特征:

  • Redis为字符分配空间的次数时小于等于字符串的长度N,而c语言中的分配原则必为N 降低了分配次数提高了追加速度,代价就是多占用一些内存空间,并且这些空间不会自动释放
  • 二进制安全的
  • 高效的计算字符串的长度
  • 高效的追加字符串操作

2.列表 (List) redis对键表的结构支持使得它在键值存储的世界中独树一帜,一个列表结构可以有序的存储多个字符串,拥有例如lpush lpop rpush rpop等等操作命令。在3.2版本之前,列表时使用ziplist和linkedlist实现的 在这些老版本中,当列表对象同时满足一下两个条件时,列表对象使用ziplist编码:

  • 列表对象保存的所有字符串元素长度都小于64字节
  • 列表对象保存的元素数量小于512个

  当有任意条件不满足时将尽享一次转码,使用linkedlist

3.哈希(hash) redis的散列可以存储多个键值对之间的映射,单列存储的值既可以是字符串又可以时数字值,并且用户同样可以对散列存储的数字值执行自增操作或者自减操作。散列可以看作时一个文档或者关系型数据库里的一行。Has底层的数据实现有两种:一是ziplist 另一种时hashtable

4.集合(set) redis的集合和列表都可以存储多个字符串,他们之间的不同在于,列表可以存储多个相同的字符串,而集合则通过使用散列表(hsahtable)来保证自己存储的每一个字符串都是各不相同的(这些散列表只有键,但没有与键相关联的值),redis中的集合是无序的。还可能存在另一种集合那就是intset,它是用于存储正式的有序集合,里面存放同一类型的整数。 注意intset只支持升级不支持奖及操作。

5.有序集合(zset) 有序集合和散列 一样,都用于存储键值对,有序集合的键被称为成员(member),每个成员都是各不相同的。有序几集合的值则被称为分值(score),分值必须是浮点数。有序集合是redis里面唯一一个既可以根据成员访问元素(这一点跟散列一样),又可以根据分值以及分值的排序舒徐访问的元素结构

Redis的应用场景

  1. 缓存会话(单点登录)
  2. 分布式锁,比如:使用setnx
  3. 各种排行榜或计数器
  4. 商品列表或者用户基础数据列表等
  5. 使用list作为消息队列
  6. 秒杀,库存扣减等

五种类型的应用场景

  1. 字符串(string) redis对于KV的操作效率很高,可以直接用作计数器。例如,统计在线人数等,另外string类型是二进制存储安全的,所以可以使用它来存储图片、视频等
  2. 哈希(hash) 存放键值对,一般可以用来存放某个对象的基本属性信息,例如,用户信息,商品信息等,
  3. 列表(list) 可以用于实现消息队列,也可以使用它提供range命令,做分页查询等功能。
  4. 集合(set) 证书的有序列表可以直接使用集合。可以用作某些去重功能,比如 奖池抽奖
  5. 有序集合(zset) 可以使用范围查询,排行榜功能或者topN功能

锁的基本概念到 Redis 分布式锁实现

文章原载于:宜信技术学院
SegmentFault 社区专栏:宜信技术学院



近来,分布式的问题被广泛提及,比如分布式事务、分布式框架、ZooKeeper、SpringCloud 等等。

本文先回顾锁的概念,再介绍分布式锁,以及如何用 Redis 来实现分布式锁。

一、锁的基本了解


首先,回顾一下我们工作学习中的锁的概念。

为什么要先讲锁再讲分布式锁呢?

我们都清楚,锁的作用是要解决多线程对共享资源的访问而产生的线程安全问题,而在平时生活中用到锁的情况其实并不多,可能有些朋友对锁的概念和一些基本的使用不是很清楚,所以我们先看锁,再深入介绍分布式锁。


通过一个卖票的小案例来看,比如大家去抢 dota2 ti9 门票,如果不加锁的话会出现什么问题?此时代码如下:
 
   
   
 

   
     
     
   
package Thread;

import java.util.concurrent.TimeUnit;

public  class Ticket {

     /**
     * 初始库存量
     * */

    Integer ticketNum =  8;

     public void reduce(int num){
         //判断库存是否够用
         if((ticketNum - num) >=  0){
             try {
                TimeUnit.MILLISECONDS.sleep( 200);
            } catch (InterruptedException e){
                e.printStackTrace();
            }
            ticketNum -= num;
            System.out.println(Thread.currentThread().getName() +  "成功卖出"
            + num +  "张,剩余" + ticketNum +  "张票");
        } else {
            System.err.println(Thread.currentThread().getName() +  "没有卖出"
                    + num +  "张,剩余" + ticketNum +  "张票");
        }
    }

     public static void main(String[] args) throws InterruptedException{
        Ticket ticket =  new Ticket();
         //开启10个线程进行抢票,按理说应该有两个人抢不到票
         for( int i= 0;i< 10;i++){
             new Thread(() -> ticket.reduce( 1), "用户" + (i +  1)).start();
        }
        Thread.sleep( 1000L);
    }

}

代码分析:这里有 8 张 ti9 门票,设置了 10 个线程 (也就是模拟 10 个人) 去并发抢票,如果抢成功了显示成功,抢失败的话显示失败。按理说应该有 8 个人抢成功了,2 个人抢失败,下面来看运行结果:

锁的基本概念到 Redis 分布式锁实现


我们发现运行结果和预期的情况不一致,居然 10 个人都买到了票,也就是说出现了线程安全的问题,那么是什么原因导致的呢?


原因就是多个线程之间产生了时间差。

如图所示,只剩一张票了,但是两个线程都读到的票余量是 1,也就是说线程 B 还没有等到线程 A 改库存就已经抢票成功了。
锁的基本概念到 Redis 分布式锁实现


怎么解决呢?想必大家都知道,加个 synchronized 关键字就可以了,在一个线程进行 reduce 方法的时候,其他线程则阻塞在等待队列中,这样就不会发生多个线程对共享变量的竞争问题。

举个例子

比如我们去健身房健身,如果好多人同时用一台机器,同时在一台跑步机上跑步,就会发生很大的问题,大家会打得不可开交。如果我们加一把锁在健身房门口,只有拿到锁的钥匙的人才可以进去锻炼,其他人在门外等候,这样就可以避免大家对健身器材的竞争。代码如下:


   
     
     
   
public  synchronized  void reduce( int  num){
         //判断库存是否够用
         if((ticketNum -  num) >=  0){
             try {
                TimeUnit.MILLISECONDS.sleep( 200);
            } catch (InterruptedException e){
                e.printStackTrace();
            }
            ticketNum -=  num;
            System.out.println(Thread.currentThread().getName() +  "成功卖出"
            +  num +  "张,剩余" + ticketNum +  "张票");
        } else {
            System.err.println(Thread.currentThread().getName() +  "没有卖出"
                    +  num +  "张,剩余" + ticketNum +  "张票");
        }
    }


运行结果:

锁的基本概念到 Redis 分布式锁实现


果不其然,结果有两个人没有成功抢到票,看来我们的目的达成了。

二、锁的性能优化


2.1 缩短锁的持有时间


事实上,按照我们对日常生活的理解,不可能整个健身房只有一个人在运动。所以我们只需要对某一台机器加锁就可以了,比如一个人在跑步,另一个人可以去做其他的运动。


对于票务系统来说,我们只需要对库存的修改操作的代码加锁就可以了,别的代码还是可以并行进行,这样会大大减少锁的持有时间,代码修改如下:


   
     
     
   
public  void reduceByLock( int  num){
        boolean flag =  false;

        synchronized (ticketNum){
             if((ticketNum -  num) >=  0){
                ticketNum -=  num;
                flag =  true;
            }
        }
         if(flag){
            System.out.println(Thread.currentThread().getName() +  "成功卖出"
                        +  num +  "张,剩余" + ticketNum +  "张票");
        }
         else {
            System.err.println(Thread.currentThread().getName() +  "没有卖出"
                        +  num +  "张,剩余" + ticketNum +  "张票");
        }
         if(ticketNum ==  0){
            System.out.println( "耗时" + (System.currentTimeMillis() - startTime) +  "毫秒");
        }
    }


这样做的目的是充分利用 cpu 的资源,提高代码的执行效率。


这里我们对两种方式的时间做个打印:


   
     
     
   
public synchronized  void reduce( int  num){
         //判断库存是否够用
         if((ticketNum -  num) >=  0){
             try {
                TimeUnit.MILLISECONDS.sleep( 200);
            } catch (InterruptedException e){
                e.printStackTrace();
            }
            ticketNum -=  num;
             if(ticketNum ==  0){
                System.out.println( "耗时" + (System.currentTimeMillis() - startTime) +  "毫秒");
            }
            System.out.println(Thread.currentThread().getName() +  "成功卖出"
            +  num +  "张,剩余" + ticketNum +  "张票");
        } else {
            System.err.println(Thread.currentThread().getName() +  "没有卖出"
                    +  num +  "张,剩余" + ticketNum +  "张票");
        }
    }


锁的基本概念到 Redis 分布式锁实现
锁的基本概念到 Redis 分布式锁实现


果然,只对部分代码加锁会大大提供代码的执行效率。

所以,在解决了线程安全的问题后,我们还要考虑到加锁之后的代码执行效率问题。


2.2 减少锁的粒度


举个例子,有两场电影,分别是最近刚上映的魔童哪吒和蜘蛛侠,我们模拟一个支付购买的过程,让方法等待,加了一个 CountDownLatch 的 await 方法,运行结果如下:
 
   
   
 

   
     
     
   
package Thread;

import java.util.concurrent.CountDownLatch;

public  class Movie {
     private  final CountDownLatch latch =   new CountDownLatch( 1);
     //魔童哪吒
     private Integer babyTickets =  20;

     //蜘蛛侠
     private Integer spiderTickets =  100;

     public synchronized void showBabyTickets() throws InterruptedException{
        System.out.println( "魔童哪吒的剩余票数为:" + babyTickets);
         //购买
        latch.await();
    }

     public synchronized void showSpiderTickets() throws InterruptedException{
        System.out.println( "蜘蛛侠的剩余票数为:" + spiderTickets);
         //购买
    }

     public static void main(String[] args) {
        Movie movie =  new Movie();
         new Thread(() -> {
             try {
                movie.showBabyTickets();
            } catch (InterruptedException e){
                e.printStackTrace();
            }
        }, "用户A").start();

         new Thread(() -> {
             try {
                movie.showSpiderTickets();
            } catch (InterruptedException e){
                e.printStackTrace();
            }
        }, "用户B").start();
    }

}

执行结果:

 
   
   
 
魔童哪吒的剩余票数为:20


我们发现买哪吒票的时候阻塞会影响蜘蛛侠票的购买,而实际上,这两场电影之间是相互独立的,所以我们需要减少锁的粒度,将 movie 整个对象的锁变为两个全局变量的锁,修改代码如下:
 
   
   
 

   
     
     
   
public void showBabyTickets() throws InterruptedException{
         synchronized (babyTickets) {
            System.out.println( "魔童哪吒的剩余票数为:" + babyTickets);
             //购买
            latch.await();
        }
    }

     public void showSpiderTickets() throws InterruptedException{
         synchronized (spiderTickets) {
            System.out.println( "蜘蛛侠的剩余票数为:" + spiderTickets);
             //购买
        }
    }

执行结果:

 
   
   
 
魔童哪吒的剩余票数为:20
蜘蛛侠的剩余票数为:100


现在两场电影的购票不会互相影响了,这就是第二个优化锁的方式:减少锁的粒度。顺便提一句,Java 并发包里的 ConcurrentHashMap 就是把一把大锁变成了 16 把小锁,通过分段锁的方式达到高效的并发安全。


2.3 锁分离


锁分离就是常说的读写分离,我们把锁分成读锁和写锁,读的锁不需要阻塞,而写的锁要考虑并发问题。

三、锁的种类


  • 公平锁:ReentrantLock
  • 非公平锁:Synchronized、ReentrantLock、cas
  • 悲观锁:Synchronized
  • 乐观锁:cas
  • 独享锁:Synchronized、ReentrantLock
  • 共享锁:Semaphore


这里就不一一讲述每一种锁的概念了,大家可以自己学习,锁还可以按照偏向锁、轻量级锁、重量级锁来分类。

四、Redis 分布式锁


了解了锁的基本概念和锁的优化后,重点介绍分布式锁的概念。

锁的基本概念到 Redis 分布式锁实现


上图所示是我们搭建的分布式环境,有三个购票项目,对应一个库存,每一个系统会有多个线程,和上文一样,对库存的修改操作加上锁,能不能保证这 6 个线程的线程安全呢?


当然是不能的,因为每一个购票系统都有各自的 JVM 进程,互相独立,所以加 synchronized 只能保证一个系统的线程安全,并不能保证分布式的线程安全。

所以需要对于三个系统都是公共的一个中间件来解决这个问题。

这里我们选择 Redis 来作为分布式锁,多个系统在 Redis 中 set 同一个 key,只有 key 不存在的时候,才能设置成功,并且该 key 会对应其中一个系统的唯一标识,当该系统访问资源结束后,将 key 删除,则达到了释放锁的目的。


4.1 分布式锁需要注意哪些点


1)互斥性


在任意时刻只有一个客户端可以获取锁。
这个很容易理解,所有的系统中只能有一个系统持有锁。


2)防死锁


假如一个客户端在持有锁的时候崩溃了,没有释放锁,那么别的客户端无法获得锁,则会造成死锁,所以要保证客户端一定会释放锁。

Redis 中我们可以设置锁的过期时间来保证不会发生死锁。


3)持锁人解锁


解铃还须系铃人,加锁和解锁必须是同一个客户端,客户端A的线程加的锁必须是客户端 A 的线程来解锁,客户端不能解开别的客户端的锁。


4)可重入


当一个客户端获取对象锁之后,这个客户端可以再次获取这个对象上的锁。


4.2 Redis 分布式锁流程

Redis 分布式锁的具体流程:

1)首先利用 Redis 缓存的性质在 Redis 中设置一个 key-value 形式的键值对,key 就是锁的名称,然后客户端的多个线程去竞争锁,竞争成功的话将 value 设为客户端的唯一标识。

2)竞争到锁的客户端要做两件事:

  • 设置锁的有效时间 目的是防死锁  (非常关键)


需要根据业务需要,不断的压力测试来决定有效期的长短。

  • 分配客户端的唯一标识,目的是保证持锁人解锁 (非常重要)


所以这里的 value 就设置成唯一标识 (比如uuid)

3)访问共享资源

4)释放锁,释放锁有两种方式,第一种是有效期结束后自动释放锁,第二种是先根据唯一标识判断自己是否有释放锁的权限,如果标识正确则释放锁。


4.3 加锁和解锁


4.3.1 加锁


1)setnx 命令加锁

set if not exists 我们会用到 Redis 的命令 setnx,setnx 的含义就是只有锁不存在的情况下才会设置成功。


2)设置锁的有效时间,防止死锁 expire

加锁需要两步操作,思考一下会有什么问题吗?

假如我们加锁完之后客户端突然挂了呢?那么这个锁就会成为一个没有有效期的锁,接着就可能发生死锁。虽然这种情况发生的概率很小,但是一旦出现问题会很严重,所以我们也要把这两步合为一步。

幸运的是,Redis3.0 已经把这两个指令合在一起成为一个新的指令。


来看 jedis 的官方文档中的源码:


   
     
     
   
  public  String  set( String key,  String value,  String nxxx,  String expx, long time) {
         this.checkIsInMultiOrPipeline();
         this.client. set(key, value, nxxx, expx, time);
         return  this.client.getStatusCodeReply();
    }


这就是我们想要的!


4.3.2 解锁


  • 检查是否自己持有锁 (判断唯一标识)
  • 删除锁。


解锁也是两步,同样也要保证解锁的原子性,把两步合为一步。

这就无法借助于 Redis 了,只能依靠 Lua 脚本来实现。


   
     
     
   
  if Redis. call( "get", key==argv[ 1]) then
     return Redis. call( "del", key)
else  return  0  end


这就是一段判断是否自己持有锁并释放锁的 Lua 脚本。


为什么 Lua 脚本是原子性呢?因为 Lua 脚本是 jedis 用 eval() 函数执行的,如果执行则会全部执行完成。

五、Redis 分布式锁代码实现


   
     
     
   
  public  class  RedisDistributedLock  implements  Lock {

     //上下文,保存当前锁的持有人id
     private ThreadLocal<String> lockContext =  new ThreadLocal<String>();

     //默认锁的超时时间
     private  long time =  100;

     //可重入性
     private Thread ownerThread;

     public RedisDistributedLock({
    }

     public void lock({
         while (!tryLock()){
             try {
                Thread.sleep( 100);
            } catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }


     public boolean tryLock({
         return tryLock(time,TimeUnit.MILLISECONDS);
    }


     public boolean tryLock(long time, TimeUnit unit){
        String id = UUID.randomUUID().toString();  //每一个锁的持有人都分配一个唯一的id
        Thread t = Thread.currentThread();
        Jedis jedis =  new Jedis( "127.0.0.1", 6379);
         //只有锁不存在的时候加锁并设置锁的有效时间
         if( "OK". equals(jedis. set( "lock",id,  "NX""PX", unit.toMillis(time)))){
             //持有锁的人的id
            lockContext. set(id); ①
             //记录当前的线程
            setOwnerThread(t); ②
             return  true;
        } else  if(ownerThread == t){
             //因为锁是可重入的,所以需要判断当前线程已经持有锁的情况
             return  true;
        } else {
             return  false;
        }
    }

     private void setOwnerThread(Thread t){
         this.ownerThread = t;
    }

     public void unlock({
        String script =  null;
         try{
            Jedis jedis =  new Jedis( "127.0.0.1", 6379);
            script = inputStream2String(getClass().getResourceAsStream( "/Redis.Lua"));
             if(lockContext. get()== null){
                 //没有人持有锁
                 return;
            }
             //删除锁  ③
            jedis.eval(script, Arrays.asList( "lock"), Arrays.asList(lockContext. get()));
            lockContext. remove();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

     /**
     * 将InputStream转化成String
     * @param is
     * @return
     * @throws IOException
     */

     public String inputStream2String(InputStream is) throws IOException {
        ByteArrayOutputStream baos =  new ByteArrayOutputStream();
         int i =  -1;
         while ((i =  is.read()) !=  -1) {
            baos.write(i);
        }
         return baos.toString();
    }

     public void lockInterruptibly() throws InterruptedException {

    }

     public Condition newCondition({
         return  null;
    }
}


  • 用一个上下文全局变量来记录持有锁的人的 uuid,解锁的时候需要将该 uuid 作为参数传入 Lua 脚本中,来判断是否可以解锁。
  • 要记录当前线程,来实现分布式锁的重入性,如果是当前线程持有锁的话,也属于加锁成功。
  • 用 eval 函数来执行 Lua 脚本,保证解锁时的原子性。

六、分布式锁的对比


6.1 基于数据库的分布式锁


1)实现方式

获取锁的时候插入一条数据,解锁时删除数据。

2)缺点

  • 数据库如果挂掉会导致业务系统不可用。
  • 无法设置过期时间,会造成死锁。


6.2 基于 zookeeper 的分布式锁


1)实现方式

加锁时在指定节点的目录下创建一个新节点,释放锁的时候删除这个临时节点。因为有心跳检测的存在,所以不会发生死锁,更加安全。

2)缺点

性能一般,没有 Redis 高效。


所以:
  • 从性能角度:Redis > zookeeper > 数据库 
  • 从可靠性 (安全) 性角度:zookeeper > Redis > 数据库

七、总结


本文从锁的基本概念出发,提出多线程访问共享资源会出现的线程安全问题,然后通过加锁的方式去解决线程安全的问题,这个方法会性能会下降,需要通过:缩短锁的持有时间、减小锁的粒度、锁分离三种方式去优化锁。

之后介绍了分布式锁的 4 个特点:

  • 互斥性 
  • 防死锁 
  • 加锁人解锁 
  • 可重入性


然后用 Redis 实现了分布式锁,加锁的时候用到了 Redis 的命令去加锁,解锁的时候则借助了 Lua 脚本来保证原子性。


最后对比了三种分布式锁的优缺点和使用场景。

希望大家对分布式锁有新的理解,也希望大家在考虑解决问题的同时要多想想性能的问题。



注:文章封面原图素材来源于网络,若有侵权请留言删除。
社区原文链接:https://segmentfault.com/a/1190000020488077


- END -

以上是关于Redis基本概念和作用的主要内容,如果未能解决你的问题,请参考以下文章

Redis专题:锁的基本概念到Redis分布式锁实现

面试被问到Redis实现发布与订阅,手摸手教

java复制文件到指定目录,面试资料分享

redis面试题

Redis 面试宝典之 Redis 如何处理已经过期的数据?

.NET面试题解析(07)-SQL语言基础及数据库基本原理