Redis(补档)


什么是 Redis

Redis 是一个基于 C 语言开发的非关系型数据库,Redis 的数据是存在内存中的,读写速度非常快,被广泛应用于缓存方向。Redis 存储的是 KV 键值对数据。

Redis 为什么这么快

  • Redis 基于内存,内存的访问速度是磁盘的上千倍;
  • Redis 采用IO多路复用机制
  • Redis 单线程的优势,没有线程上下文切换的开销
  • Redis 内置了多种优化过后的数据结构实现,性能非常高。

Redis和Memcached相比有哪些优势

  • Redis数据结构更丰富,支持string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合)等数据结构存储,Memcached仅支持String数据类型。

  • Redis支持数据的持久化,可以把内存中的数据持久化到硬盘中,而Memcached不支持持久化,数据只能存在内存中,重启后数据就没了。

  • Memcached没有原生的集群模式,需要依靠客户端自己实现集群分片,而Redis原生支持集群模式。

  • Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路 IO 复用模型。

Redis常用指令

  • 设置key-value :set key value
  • 获取值:get key
  • 删除:del key
  • 判断key是否存在:exists key
  • 设置10秒过期:expire key 10
  • 设置10毫秒过期:pexpire key 10
  • 删除过期时间:persist key
  • 切换数据库:redis有16个数据库,默认使用0号数据库,切换数据库的命令为:select index
  • 清空当前选中的数据库:flushdb
  • 清空所有数据库:flushall
  • 查看当前数据库的所有key:keys *
  • 查看字段类型:type key

Redis有哪些数据结构 应⽤场景

Redis的数据结构有:

  1. String字符串:可以⽤来做最简单的数据,可以缓存某个简单的字符串,也可以缓存某个json格式的字符串,Redis分布式锁的实现就利⽤了这种数据结构,还包括可以实现计数器、Session共享、分布式ID

  2. HashMap哈希表:可以⽤来存储⼀些key-value对,更适合⽤来存储对象

  3. List列表:Redis的列表通过命令的组合,既可以当做栈,也可以当做队列来使⽤,可以⽤来缓存类似微信公众号、微博等消息流数据

  4. Set集合:和列表类似,也可以存储多个元素,但是不能重复,集合可以进⾏交集、并集、差集操作,从⽽可以实现类似,我和某⼈共同关注的⼈、朋友圈点赞等功能

  5. zSet有序集合:集合是⽆序的,有序集合可以设置顺序,可以⽤来实现排⾏榜功能

String 的底层实现

redis的String类型在底层实现中有三种实现方式。

  1. 使用整数值实现的字符串对象,使用的是8个字节的long类型进行存储。
  2. 使用embstr编码的动态字符串实现的字符串对象,存储长度小于44字节的字符串
  3. 动态字符串实现的字符串对象,存储长度大于44字节的字符串。

redis会根据值的类型和长度自动选择存储的类型。

动态字符串是redis写的一个抽象数据类型,存储了字符串的长度、空闲长度和字符数组等字段。

Redis的过期策略有哪些

  1. 惰性删除 :只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。
  2. 定期删除 : 每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。

定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,所以 Redis 采用的是 定期删除+惰性删除

如何解决key-valve数据量很大的问题

  1. Redis 配置:修改配置文件或者在启动 Redis 服务器时指定参数来设置最大内存限制。
  2. 数据分片:将数据分片存储到多个 Redis 实例中。根据数据的某种规则(例如哈希函数),将不同的 key-value 对映射到不同的 Redis 实例中。
  3. 合适的数据结构:Redis 提供了多种数据结构,如字符串、哈希表、列表、集合和有序集合等。根据实际需求选择合适的数据结构来存储数据。
  4. 压缩数据:可以使用 Redis 提供的压缩功能来减小数据占用的空间。Redis 支持使用 Gzip 或 LZF 算法对数据进行压缩。
  5. 分解为多个字段或多个键:如果单个 key-value 对的数据结构复杂,可以考虑将其拆分为多个字段或多个键进行存储。

如何解决大量key集中过期导致卡顿

定期删除执行过程中,如果突然遇到大量过期 key 的话,客户端请求必须等待定期清理过期 key 任务线程执行完成,因为这个这个定期任务线程是在 Redis 主线程中执行的

  1. 给 key 设置随机过期时间。
  2. 开启 lazy-free(惰性删除/延迟释放)。 Redis 4.0 开始引入的,指的是让 Redis 采用异步方式延迟释放 key 使用的内存,将该操作交给单独的子线程处理,避免阻塞主线程。

Redis 内存淘汰机制有哪些

  1. volatile-lru(least recently used):从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
  2. volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
  3. volatile-random:从已设置过期时间的数据集中随机选择数据淘汰
  4. allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(最常用的)
  5. allkeys-random:从数据集中任意选择数据淘汰
  6. no-eviction:禁止驱逐数据,当内存不足以容纳新写入数据时,新写入操作会报错。

Redis 持久化机制有哪些

RDB:Redis DataBase,在指定的时间间隔内将内存中的数据集快照写⼊磁盘,实际操作过程是fork⼀个⼦进程,先将数据集写⼊临时⽂件,写⼊成功后,再替换之前的⽂件,⽤⼆进制压缩存储。(Redis 默认)

Redis提供了两个命令来生成 RDB 文件:

  • save:在主进程中执行,会导致写请求阻塞。
  • bgsave:fork一个子进程,专门用于写入 RDB 文件,避免了主进程的阻塞。

优点:

  • RDB文件紧凑,体积小,网络传输快,适合全量复制

  • 与AOF方式相比,通过RDB文件恢复数据比较快更快

  • RDB最大化了Redis的性能,因为Redis父进程持久化时只需要fork一个子进程,这个子进程可以共享主进程的所有内存数据,子进程会去读取主进程的内存数据,并把它们写入RDB文件。

缺点:

  • 快照是定期生成的,所有在 Redis 故障时或多或少会丢失一部分数据

  • 当数据量比较大时,fork 的过程是非常耗时的,fork 子进程时是会阻塞的,在这期间 Redis 是不能响应客户端的请求的。

AOF:Append Only File,以⽇志的形式记录服务器所处理的每⼀个写、删除操作,查询操作不会记录,以⽂本的⽅式记录,可以打开⽂件看到详细的操作记录

优点:

  • 数据安全,Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是⾮常⾼的,所差的是⼀旦系统出现宕机现象,那么这⼀秒钟之内修改的数据将会丢失。
  • 通过 append 模式写⽂件,即使中途服务器宕机也不会破坏已经存在的内容,可以通过 redis-check-aof ⼯具解决数据⼀致性问题。
  • AOF 机制的 rewrite 模式。定期对AOF⽂件进⾏重写,以达到压缩的⽬的

缺点:

  • AOF ⽂件⽐ RDB ⽂件⼤,且恢复速度慢。
  • 数据集⼤的时候,⽐ rdb 启动效率低。
  • 运⾏效率没有RDB⾼

Redis事务机制

Redis通过MULTI、EXEC、WATCH等一组命令集合,来实现事务机制。顺序性、一次性、排他性的执行一个队列中的一系列命令。

Redis执行事务的流程如下:

  • 开始事务(MULTI)
  • 命令入队
  • 执行事务(EXEC)、撤销事务(DISCARD )

Redis 事务是不支持回滚(roll back)操作的,不满足原子性的,而且不满足持久性

缓存穿透、缓存击穿、缓存雪崩分别是什么

  1. 缓存雪崩:如果缓存中某⼀时刻⼤批热点数据同时过期,那么就可能导致⼤量请求直接访问Mysql了。解决办法就是在过期时间上增加⼀点随机值,另外如果搭建⼀个⾼可⽤的Redis集群也是防⽌缓存雪崩的有效⼿段

  2. 缓存击穿:和缓存雪崩类似,缓存雪崩是⼤批热点数据失效,⽽缓存击穿是指某⼀个热点key突然失效,也导致了⼤量请求直接访问Mysql数据库,这就是缓存击穿。解决⽅案就是考虑这个热点key不设过期时间

  3. 缓存穿透:假如某⼀时刻访问redis的⼤量key都在redis中不存在(⽐如⿊客故意伪造⼀些乱七⼋糟的key),那么也会给数据造成压⼒,这就是缓存穿透。解决⽅案是使⽤布隆过滤器,它的作⽤就是如果它认为⼀个key不存在,那么这个key就肯定不存在,所以可以在缓存之前加⼀层布隆过滤器来拦截不存在的key

Redis和Mysql如何保证数据⼀致

延迟双删:

先删除Redis缓存数据,再更新Mysql,延迟⼏百毫秒再删除Redis缓存数据,这样就算在更新Mysql时,有其他线程读了Mysql,把⽼数据读到了Redis中,那么也会被删除掉,从⽽把数据保持⼀致

Redis集群策略有哪些

Redis提供了三种集群策略:

  1. 主从模式:这种模式⽐较简单,主库可以读写,并且会和从库进⾏数据同步,这种模式下,客户端直接连主库或某个从库,但是但主库或从库宕机后,客户端需要⼿动修改IP,另外,这种模式也⽐较难进⾏扩容,整个集群所能存储的数据受到某台机器的内存容量,所以不可能⽀持特⼤数据量

  2. 哨兵模式:这种模式在主从的基础上新增了哨兵节点,但主库节点宕机后,哨兵会发现主库节点宕机,然后在从库中选择⼀个库作为进的主库,另外哨兵也可以做集群,从⽽可以保证但某⼀个哨兵节点宕机后,还有其他哨兵节点可以继续⼯作,这种模式可以⽐较好的保证Redis集群的⾼可⽤,但是仍然不能很好的解决Redis的容量上限问题。

  3. Cluster模式:Cluster模式是⽤得⽐较多的模式,它⽀持多主多从,这种模式会按照key进⾏槽位的分配,可以使得不同的key分散到不同的主节点上,利⽤这种模式可以使得整个集群⽀持更⼤的数据容量,同时每个主节点可以拥有⾃⼰的多个从节点,如果该主节点宕机,会从它的从节点中选举⼀个新的主节点。

Redis 主从复制的核⼼原理

通过执⾏slaveof命令或设置slaveof选项,让⼀个服务器去复制另⼀个服务器的数据。主数据库可以进⾏读写操作,当写操作导致数据变化时会⾃动将数据同步给从数据库。⽽从数据库⼀般是只读的,并接受主数据库同步过来的数据。⼀个主数据库可以拥有多个从数据库,⽽⼀个从数据库只能拥有⼀个主数据库。

全量复制:

  1. 主节点通过bgsave命令fork⼦进程进⾏RDB持久化,该过程是⾮常消耗CPU、内存(⻚表复制)、硬盘IO的

  2. 主节点通过⽹络将RDB⽂件发送给从节点,对主从节点的带宽都会带来很⼤的消耗

  3. 从节点清空⽼数据、载⼊新RDB⽂件的过程是阻塞的,⽆法响应客户端的命令;如果从节点执⾏bgrewriteaof,也会带来额外的消耗

部分复制:

  1. 复制偏移量:执⾏复制的双⽅,主从节点,分别会维护⼀个复制偏移量offset

  2. 复制积压缓冲区:主节点内部维护了⼀个固定⻓度的、先进先出(FIFO)队列 作为复制积压缓冲区,当主从节点offset的差距过⼤超过缓冲区⻓度时,将⽆法执⾏部分复制,只能执⾏全量复制。

  3. 服务器运⾏ID(runid):每个Redis节点,都有其运⾏ID,运⾏ID由节点在启动时⾃动⽣成,主节点会将⾃⼰的运⾏ID发送给从节点,从节点会将主节点的运⾏ID存起来。 从节点Redis断开重连的时候,就是根据运⾏ID来判断同步的进度:○ 如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使⽤部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);○ 如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进⾏全量复制。

Redis分布式锁底层是如何实现的

通常依赖于Redis提供的原子性操作和特定的数据结构。

  1. 获取锁:

    • 利⽤setnx来保证:如果key不存在才能获取到锁,如果key存在,则获取不到锁。(SETNX key value:将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则 SETNX 不做任何动作,该命令在设置成功时返回 1,设置失败时返回 0。)
    • 如果setnx命令执行失败,说明锁已被其他客户端占用,需要等待一段时间后再次尝试获取锁(通常使用循环重试来实现)。
  2. 释放锁:

    • 使用DEL命令删除之前存储的锁,释放当前客户端持有的锁
  3. 结合Lua脚本

    • Lua脚本能够在Redis服务器端原子地执行一系列命令,可以减少网络开销和提高性能。例如,使用Lua脚本可以在一次网络往返的过程中检查锁是否存在并获取锁。
  4. 考虑锁过期

    • setnx同时设置一个适当的过期时间(避免锁无法释放导致死锁)

    • 看⻔狗定时任务来监听锁是否需要续约

      1. 续约逻辑:客户端需要定时(一般为锁过期时间的一半)向Redis服务器发送指令,告知服务器当前客户端依然持有锁,并请求更新锁的过期时间。可以使用Redis的EXPIRE命令来更新锁的过期时间。
      2. 超时处理:如果客户端未能及时发送续约指令,或者续约指令发送失败,则说明该客户端出现了异常,无法继续持有锁。此时,Redis服务器需要将锁分配给其他等待获取锁的客户端。
      3. 竞争解决:如果有多个客户端同时发送续约指令,Redis服务器需要根据一定的规则(如先到先得或者权重分配)来判断哪个客户端可以继续持有锁。
  5. 避免死锁和活锁等问题

  • 避免死锁:

    • 使用唯一标识符(例如UUID)作为锁的值,确保每个客户端持有的锁都是唯一的。
    • 释放锁时,核对锁的值是否与之前获取锁时设置的值一致,以防止误删其他客户端的锁。
  • 避免活锁:

    • 引入随机的等待时间或指数退避策略。在获取锁失败时,等待一段随机时间后再次尝试获取锁,以减少同时竞争锁的概率。
    • 设置一个最大尝试次数或超时时间,在达到限制后停止尝试获取锁,避免无限循环导致活锁。
  1. 考虑到redis节点挂掉后的情况
  • 采⽤(RedLock)红锁的⽅式来同时向N/2+1个节点申请锁,都申请到了才证明获取锁成功,红锁是分布式环境下的多实例互斥锁算法
  1. 将锁名称、唯一标识符和过期时间发送给集群中的每个Redis节点。
  2. 在每个节点上尝试获取锁。
  3. 如果有超过 N/2+1 个节点成功获取了锁,那么认为获取锁成功;否则,认为获取锁失败。
  4. 如果成功获取了锁,开始执行临界区代码。
  5. 执行完临界区代码后,释放锁,即在所有节点上删除锁。

核心思想:在多个独立的Redis节点获取到锁的数量进行判断,确保在大多数节点可用的情况下才能获取锁。防止因某个节点不可用而导致的锁失效问题。


文章作者: Aiaa
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Aiaa !
  目录