首页 Redis学习
文章
取消

Redis学习

Redis学习

Redis数据结构

  • string–>简单的key-value
  • list–>有序列表(底层是双向链表)–>可做简单队列
  • set–>无序列表(去重)–>提供一系列的交集、并集、差集的命令
  • hash–>哈希表–>存储结构化数据
  • sortset–>有序集合映射(member-score)–>排行榜

img

string

  • encode_int
  • encode_embstr
  • encode_ram

List

  • ziplist
  • linkedlist

hash

  • ziplist
  • hash table

set

  • intset 整数有序集合(一个数组)
  • hash table

sortset

  • ziplist
  • skiplist 跳表

为什么Redis选择使用跳表而不是红黑树来实现有序集合?

Redis 中的有序集合(zset) 支持的操作:

  1. 插入一个元素
  2. 删除一个元素
  3. 查找一个元素
  4. 有序输出所有元素
  5. 按照范围区间查找元素(比如查找值在 [100, 356] 之间的数据)

其中,前四个操作红黑树也可以完成,且时间复杂度跟跳表是一样的。但是,按照区间来查找数据这个操作,红黑树的效率没有跳表高。按照区间查找数据时,跳表可以做到 O(logn) 的时间复杂度定位区间的起点,然后在原始链表中顺序往后遍历就可以了,非常高效。

Redis 持久化

两种

  • RDB(快照持久化)

    RDB持久化所生成的RDB文件是一个经过压缩的二进制文件,Redis可以通过这个文件还原数据库的数据。

  • AOF文件

    AOF是通过保存Redis服务器所执行的写命令来记录数据库的数据的。

    -AOF 重写

主从复制模式

三个阶段

  • 连接建立阶段

    保持主节点信息,建立socket连接,身份验证等等

  • 数据同步阶段

    • 完全同步:常见于服务器第一次同步,主服务器生成RDB文件,将RDB文件发给从服务器,从服务器由RDB文件建立缓存

    • 部分同步:常见于短线重连,断线重连时只同步缺失的数据。主从服务器分别记录数据偏移量offset,主服务器不仅将命令发给从服务器,还将写命令写入复制积压缓冲区。在短线重连时,只要缺失的写命令还能从复制积压缓冲区中,就只需要部分同步。否则完全同步。

      另外,从服务器会记录上次复制的主服务器的 Run ID,断线重连时如果run id 不一致,说明之前复制的主服务器和现在服务器时两台服务器,则进行完全同步

  • 命令传播阶段

    每次主服务器执行写命令后,将命令发给各个从服务器,从服务器执行相同的命令。

    另外还会默认以1s一次向主服务器发送心跳检测

    • 检测主从网络状态
    • 检测是否由命令丢失,从服务器发送的包带有offset

主从复制的一些问题

  • 数据存在延迟和不一致

  • 数据过期的问题

    数据已经过期,但是从服务器上还存在

  • 可用性问题

    主节点死掉,则无法执行写命令——通过哨兵机制解决。

  • 写单点问题,存储能力受单机限制—— 通过集群模式解决

哨兵模式

启动多个哨兵,哨兵是一种特殊的redis节点,本身不存储数据,只监视其他节点。

定时任务:每个哨兵执行3个定时任务来监视其他节点

  • 向主从节点发送info 命令获取最新的主从结构
  • 接收其他哨兵节点的信息(推选,客观下线时有用)
  • 向其他节点发送ping命令进行心跳检测,判断是否下线

主观下线:心跳检测时节点超过一定时间没有回复,则认为主观下线

客观下线:如果时主节点被主观下线后,哨兵节点会询问其他哨兵节点,如果超过一半的哨兵节点都认为其下线(所以哨兵节点数量应为奇数),则认为主节点下线,开始故障转移。

选举领导哨兵节点:先到先得选取领导者

故障转移:领导哨兵节点 选取最合适的从节点成为新的主节点。根据优先级/延迟/复制偏移量等选取。

集群模式

img

集群模式首先时将数据分片,将整体数据分布到多个redis节点上,从而扩展容量实现集群。数据分片算法采用哈希槽的分片算法。

(1)Redis对数据的特征值(一般是key)计算哈希值,使用的算法是CRC16。

(2)根据哈希值,计算数据属于哪个槽。

(3)根据槽与节点的映射关系,计算数据属于哪个节点。

将数据分开存储在不同主节点上后,就实现了数据的扩容和单写节点的问题。每个主节点还可以再搭配多个从节点,实现主从复制,实现读写分离。另外每个主节点充当集群中的哨兵,保障高可用性。

集群模式就是再哨兵机制上加一层数据分片。在Redis3.0前,官方并不支持集群模式,可以通过一个中间件代理(Proxy),该中间件存储数据分片的位置,每个数据分片就是一个哨兵模式(主从+哨兵)。也可以实现集群模式。

集群模式的缺点:

实现复杂,资源开销大,不适合数据量小的情况。

默认从节点不分担读请求,只作为数据备份保障高可靠性,资源开销大

Key批量操作受限制,数据分布再多个节点上,对Key进行批量操作时只能对同一节点的key操作,不同节点受限制。

Key 作为数据分区的最小粒度,不能将一个很大的键值对象如 hash、list 等映射到不同的节点。

Redis 分布式锁

https://xiaomi-info.github.io/2019/12/17/redis-distributed-lock/

Redis 锁主要利用 Redis 的 setnx 命令。(set if not exit)

加锁命令:SETNX key value

锁超时:EXPIRE key timeout(必须要有的,保证即使锁未被显示释放,也能超时释放,避免死锁)

解锁命令:DEL key

  1. SETNX 和 EXPIRE 非原子性——通过lua脚本解决(原子性操作)
  2. 业务执行时间大于锁超时—— 锁误消除,并发执行
    • 锁的value记录占有锁的线程ID,释放锁时判断线程ID是否为本线程

    • 将过期时间设置足够长,确保代码逻辑在锁释放之前能够执行完成。

    • 为获取锁的线程增加守护线程,为将要过期但未释放的锁增加有效时间。

  3. 不可重入

    • 本地记录锁重入次数
    • Redis Map 记录
  4. 无法等待锁释放:被锁阻挡的线程希望在锁释放后继续执行操作
    • 轮询
    • 使用Redis 发布订阅功能,订阅锁释放消息

常见问题

缓存雪崩

缓存雪崩的情况是说,当某一时刻发生大规模的缓存失效的情况,比如你的缓存服务宕机了/大量缓存过期,会有大量的请求进来直接打到DB上面。结果就是DB 称不住,挂掉。

解决办法:

  • 使用集群,保障高可用性 (事前)
  • Mysql限流+本地缓存,避免主要业务挂掉(事中)
  • 开启Redsi持久化机制,尽快恢复缓存(事后)
  • 对于大量缓存同时间过期,给过期时间加上随机值

缓存穿透

请求去查询一条压根儿数据库中根本就不存在的数据,也就是缓存和数据库都查询不到这条数据,但是请求每次都会打到数据库上面去,这种查询不存在数据的现象我们称为缓存穿透

解决办法:

  • 缓存空值
  • Bool 过滤

缓存击穿

在平常高并发的系统中,大量的请求同时查询一个 key 时,此时这个key正好失效了,就会导致大量的请求都打到数据库上面去。这种现象我们称为缓存击穿

解决办法:

已知多个线程查询同一条数据,可以加上互斥锁,第一个线程从数据库查询,其他线程等待。第一个线程查询到数据后加入缓存,之后线程直接查缓存就OK。

缓存与数据库双写一致

可以先删除缓存,然后更新数据库。——延时双删

也可以先更新数据库,然后删除缓存。——消息队列

缓存和数据库会存在短期内的不一致,所以最简单的办法是设置缓存的过期时间(虽然存在短时间的脏数据,旧数据)

复杂一些的解决办法是设置消息队列,

热Key问题

线上redis集群,如果一个key非常火(比如明星效应)导致所有访问都冲往一个redis节点,redis直接崩溃。

咋解决:分两步:

1.首先发现热key,发现热key的方法有许多:

  • 事先估计,通过人为估计热key

  • 客户端对key进行流量统计,流量超过阈值的视为热key
  • 在代理层流量统计
  • 无代码侵入式的,其他系统监听数据包/爬取其他top榜单等

2.通知系统进行处理

  • 客户端本地缓存(如开一个Map,或者ehcache)
  • Redis 集群数据备份,开几个相同的Redis来顶。

Redis 一致性hash算法

将hash值映射到一个2^32大小的圆环上,每次查询顺时针方向上距离最近的服务器。

如果新增/删除服务器,只会影响邻近的服务器,rehash影响有限。

问题:数据倾斜不均衡

解决:增加大量虚拟节点,保证虚拟节点的均衡,之后将虚拟节点映射到真实节点。通过这种方式,新增/删除服务器只需要修改虚拟节点的分配就行。

本文由作者按照 CC BY 4.0 进行授权

-

面试题目总结