# Redis 分布式锁 **Repository Path**: wyfproject/redis-distributed-lock ## Basic Information - **Project Name**: Redis 分布式锁 - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-01-25 - **Last Updated**: 2022-03-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Redis ## 1、Redis分布式锁 ### 1.1什么时候用分布式锁? 当并发去读写一个【共享资源】的时候,我们为了保证数据的正确,需要控制同一时刻只有一个线程访问。 分布式锁就是用来控制同一时刻,只有一个 JVM 进程中的一个线程可以访问被保护的资源。 ### 1.2分布式锁应该满足哪些特性? 互斥:在任何给定时刻,只有一个客户端可以持有锁; 无死锁:任何时刻都有可能获得锁,即使获取锁的客户端崩溃; 容错:只要大多数 Redis的节点都已经启动,客户端就可以获取和释放锁。 ## 2、说说Redis为什么快 ![输入图片说明](image/1.jpg) ### 2.1 基于内存存储实现 内存读写是比磁盘快很多,Redis基于内存存储实现的数据库,相对于数据存在磁盘的Mysql数据库,省去磁盘I/O的消耗。 ### 2.2 高效的数据结构 Mysql索引为了提高效率,选择B+树的数据结构。其实合理的数据结构,就是可以让你的应用\程序更快。先看下Redis的数据结构&内部编码图: ![输入图片说明](image/2.jpg) #### 2.2.1 SDS简单动态字符串 ![输入图片说明](image/3.jpg) (1):字符串长度处理 Redis获取字符串长度,时间复杂度O(1),而C语言中,需要从头开始遍历,复杂度O(n); (2):空间预分配 字符串修改越频繁的话,内存分配越频繁,就会消耗性能,而SDS修改和空间扩充,会额外分配未使用的空间,减少性能损耗。 (3):惰性空间释放 SDS缩短时,不是回收多余的内存空间,而是free记录下多余的空间,后续有变更,直接使用free中记录的空间,减少分配。 #### 2.2.2 字典 Redis作为K-V型内存数据库,所有的键值就是用字典来存储。字典就是哈希表,比如HashMap,通过key就可以直接获取到对应的value。而哈希表的特性,在O(1)时间复杂度就可以获得对应的值。 #### 2.2.3 跳跃表 ![输入图片说明](image/4.jpg) (1)跳跃表是Redis特有的数据结构,就是在链表的基础上,增加多级索引提升查询效率。 (2)跳跃表支持平均O(logN),最坏O(N)复杂度的节点查找,还可以通过顺序性操作批量处理节点。 ### 2.3 合理的数据编码 Redis支持多种数据类型,每种基本类型,可能对多种数据结构。什么时候用什么样数据结构,使用什么样编码,是redis设计者总结优化的结果。 (1)String:如果存储数字的话,是用int类型的编码;如果存储非数字,小于等于39字节的字符串,是embstr;大于39字节的用的raw编码; (2)List:如果列表的元素个数小于512个,列表每个元素的值都小于64字节,则使用ziplist编码;否则使用linkedlist编码; (3) Hash: 哈希类型元素个数小于512,所有值小于64字节,则使用zip编码,否则使用hashtableb编码; (4) Set: 如果集合中的元素都是整数且元素小于512个,使用intset编码,否则使用hashtable编码; (5) Zset: 当有序集合的元素个数小于128个,每个元素的值小于64字节时,使用ziplist编码,否则使用skiplist(跳跃表)编码。 ### 2.4 合理的线程模型 I/O 多路复用 ![输入图片说明](image/5.jpg) 多路I/O复用技术可以让单个线程高效的处理多个连接请求,而Redis使用epoll作为I/O多路复用技术的实现。并且,Redis自身的事件处理模型将epoll的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间。 ## 3、Redis有几种数据结构,底层分别是怎么存储的 常用的,Redis有以下这五种基本类型: String(字符串) Hash(哈希) List(列表) Set(集合) zset(有序集合) 它还有三种特殊的数据结构类型 Geospatial、Hyperloglog、Bitmap ### 3.1 Redis 的五种基本数据类型 ![输入图片说明](image/6.jpg) ### 3.1 String(字符串) 简介:String是Redis最基础的数据结构类型,它是二进制安全的,可以存储图片或者序列化的对象,值最大存储为512M 简单使用举例: set key value、get key等 应用场景:共享session、分布式锁,计数器、限流。 内部编码有3种,int(8字节长整型)/embstr(小于等于39字节字符串)/raw(大于39个字节字符串) ### 3.2 Hash(哈希) 简介:在Redis中,哈希类型是指v(值)本身又是一个键值对(k-v)结构 简单使用举例:hset key field value 、hget key field 内部编码:ziplist(压缩列表) 、hashtable(哈希表) 应用场景:缓存用户信息等。 ### 3.3 List(列表) 简介:列表(list)类型是用来存储多个有序的字符串,一个列表最多可以存储2^32-1个元素。 简单实用举例:lpush key value [value ...] 、lrange key start end 内部编码:ziplist(压缩列表)、linkedlist(链表) 应用场景:消息队列,文章列表 ### 3.4 Set(集合) 简介:集合(set)类型也是用来保存多个的字符串元素,但是不允许重复元素 简单使用举例:sadd key element [element ...]、smembers key 内部编码:intset(整数集合)、hashtable(哈希表) 应用场景:用户标签,生成随机数抽奖、社交需求。 ### 3.5 有序集合(zset) 简介:已排序的字符串集合,同时元素不能重复 简单格式举例:zadd key score member [score member ...],zrank key member 底层内部编码:ziplist(压缩列表)、skiplist(跳跃表) 应用场景:排行榜,社交需求(如用户点赞)。 ### 3.6 Redis 的三种特殊数据类型 Geo:Redis3.2推出的,地理位置定位,用于存储地理位置信息,并对存储的信息进行操作。 HyperLogLog:用来做基数统计算法的数据结构,如统计网站的UV。 Bitmaps :用一个比特位来映射某个元素的状态,在Redis中,它的底层是基于字符串类型实现的,可以把bitmaps成作一个以比特位为单位的数组 ## 4、Redis有几种持久化方式 Redis是基于内存的非关系型K-V数据库,既然它是基于内存的,如果Redis服务器挂了,数据就会丢失。为了避免数据丢失了,Redis提供了持久化,即把数据保存到磁盘。 Redis提供了RDB和AOF两种持久化机制,它持久化文件加载流程如下: ![输入图片说明](image/7.jpg) ### 4.1 RDB RDB,就是把内存数据以快照的形式保存到磁盘上。 什么是快照?可以这样理解,给当前时刻的数据,拍一张照片,然后保存下来。 RDB持久化,是指在指定的时间间隔内,执行指定次数的写操作,将内存中的数据集快照写入磁盘中,它是Redis默认的持久化方式。执行完操作后,在指定目录下会生成一个dump.rdb文件,Redis 重启的时候,通过加载dump.rdb文件来恢复数据。RDB触发机制主要有以下几种: ![输入图片说明](image/8.jpg) RDB 的优点 适合大规模的数据恢复场景,如备份,全量复制等 RDB缺点 没办法做到实时持久化/秒级持久化。 新老版本存在RDB格式兼容问题 ### 4.2 AOF AOF(append only file) 持久化,采用日志的形式来记录每个写操作,追加到文件中,重启时再重新执行AOF文件中的命令来恢复数据。它主要解决数据持久化的实时性问题。默认是不开启的。 AOF的工作流程如下: ![输入图片说明](image/9.jpg) AOF的优点 数据的一致性和完整性更高 AOF的缺点 AOF记录的内容越多,文件越大,数据恢复变慢。 ## 5、多线程情况下,如何保证线程安全? 加锁,比如悲观锁select for update,sychronized等,如,乐观锁,乐观锁如CAS等,还有redis分布式锁等等。 ## 6、 用过volatile吗?它是如何保证可见性的,原理是什么 volatile关键字是Java虚拟机提供的的最轻量级的同步机制,它作为一个修饰符, 用来修饰变量。它保证变量对所有线程可见性,禁止指令重排,但是不保证原子性。 我们先来看下java内存模型(jmm): Java虚拟机规范试图定义一种Java内存模型,来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台上都能达到一致的内存访问效果。 Java内存模型规定所有的变量都是存在主内存当中,每个线程都有自己的工作内存。这里的变量包括实例变量和静态变量,但是不包括局部变量,因为局部变量是线程私有的。 线程的工作内存保存了被该线程使用的变量的主内存副本,线程对变量的所有操作都必须在工作内存中进行,而不能直接操作操作主内存。并且每个线程不能访问其他线程的工作内存。 ![输入图片说明](image/10.jpg) volatile变量,保证新值能立即同步回主内存,以及每次使用前立即从主内存刷新,所以我们说volatile保证了多线程操作变量的可见性。 volatile保证可见性跟内存屏障有关。我们来看一段volatile使用的demo代码: ``` public class Singleton { private volatile static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } ``` 编译后,对比有volatile关键字和没有volatile关键字时所生成的汇编代码,发现有volatile关键字修饰时,会多出一个lock addl $0x0,(%esp),即多出一个lock前缀指令,lock指令相当于一个内存屏障 lock指令相当于一个内存屏障,它保证以下这几点: 重排序时不能把后面的指令重排序到内存屏障之前的位置 将本处理器的缓存写入内存 如果是写入动作,会导致其他处理器中对应的缓存无效。 第2点和第3点就是保证volatile保证可见性的体现嘛 ## 7、 redis官方去中心化集群redis-cluster ### 7.1 图示 ![输入图片说明](image/11.png) 其中,master1,master2,master3互相通信;每个master保存了部分信息,所有master合起来保存了所有的信息 ### 7.2 redis-cluster运行原理 Redis Cluster是一种服务器Sharding技术,3.0版本开始正式提供。 redis cluster是去中心化,去中间件的,也就是说,集群中所有的Master节点都可以进行读写数据,不分主次,集群中的每个节点都是平等的关系,都是对等的,每个节点都保存各自的数据和整个集群的状态。每个节点都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获得其他节点的数据。即master节点之间是相互连接的。 对客户端来说,整个cluster被看做是一个整体,客户端可以连接任意一个node进行操作,就是操作单一Redis实例一样,当客户端操作的key没有分配到该node上时,redis会返回转向指令,指向正确的node。每个master节点和slave节点间通过goossip协议内部通信,异步复制。(所有的redis节点彼此互联(ping-pong机制,内部使用二进制协议优化传输速度和带宽),但是异步赋值会导致某种特定情况下的数据丢失,即,redis集群不能保证数据的强一致性 ### 7.3 主从复制 如果单个主节点失效,redis cluster会根据选举算法从slave节点中选择一个上升为主节点,整个集群继续对外提供服务,redis cluster 本身提供了故障转移容错的能力。 ### 7.4 槽的计算 redis cluster中,sharding 采用slot(槽)的概念,一共分成16384个槽,这有点类似pre sharding 思路。每个节点维护部分槽及槽所映射的键值数据。对于每个进入redis的键值对,根据key进行散列,分配到这16384个slot中的某一个中。使用的hash算法也比较简单,就是CRC16后16384取模。具体算法就是:CRC16(key)%16384. ### 7.5 集群进入fail状态的必要条件 A、某个主节点和所有从节点全部挂掉,我们集权就进入faili B、如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态 C、如果集群任意master挂掉,且当前master没有slave,集群进入fail状态 ### 7.6 集群最少的节点有几个? 官方推荐集群中至少有三个主节点 ### 7.7 rediscluster缺陷(虚拟槽分区的缺点) a,键的批量操作支持有限,比如mset,mget,如果多个键映射在不同的槽,就不支持了 b,键事务支持有限,当多个key分布在不同节点时无法使用事务,同一个节点是支持事务 c,键是数据分区的最小粒度,不能将一个很大的键值对映射到不同的节点 d,不支持多数据库,只有0,select 0 e,复制结构只支持单层结构,不支持树型结构。 ### 7.8 客户端与redis集群交互方式 由于cluster架构中无Proxy层,客户端是直接与集群中的任意可用节点直接交互的 "用了哈希槽的概念,而没有用一致性哈希算法,不都是哈希么?这样做的原因是为什么呢? redis cluster是自己做的crc16的简单hash算法,没有用一致性hash。redis的作者认为它的crc16(key)mod 16384的效果已经不错了,虽然没有一致性hash灵活,但实现很简单,节点增删时处理起来也很方便。 ### 7.9 redis集群重新分片(新增/移除节点)机制 新增节点:别的节点上的槽分一些出来给新的节点 删除节点:删除节点的槽分别别的节点 但这些操作是需要手动完成的,可以在不停止服务器的情况下执行 ### 7.10 什么情况下会导致整个集群不可用? 有A,B,C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少5501-11000这个范围的槽而不可用。