Redis Tips & Tracks

Redis

Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache, and message broker.

Redis知识的全景图

Redis 知识全景图

  • 应用纬度:
    • 缓存应用
      • 内嵌Key过期机制和淘汰策略
    • 集群应用
      • 主从集群、切片集群
    • 数据结构应用
      • String
      • Hash
      • List
      • Set
      • Sorted Set
  • 系统纬度:
    • 处理层:
      • 线程模型
    • 内存层:
      • 数据结构
    • 存储层:
      • AOF
      • RDB
    • 网络层:
      • epool网络框架
  • 高性能主线:
    • 线程模型
    • 数据结构
      • 集合类型采用有序索引,可以支持范围操作
      • 考虑不同数据结构的内存效率、设计了压缩列表、整数数组这些精简的底层数据结构、节省内存开销
    • 内存分配器
      • jemalloc和tcmalloc比glibc效率高
    • 持久化
      • 支持RDB、AOF持久化数据,支持主从库集群
    • 网络框架
  • 高可用主线:
    • 主从复制
    • 哨兵机制
    • 切片集群
  • 高扩展主线:
    • 数据分片
    • 负载均衡

Redis问题画像图

Redis问题画像

Redis 基础

  • 基础架构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    # 数据模型: key-value
    > Redis能够在实际业务场景中广泛的应用,就是得益于支持多样化类型的value

    # 操作接口:
    - PUT:
    - GET:
    - DELETE:
    - SCAN:

    # 访问框架:
    - 动态库访问
    - 网络框架访问:包括Socket Server和协议解析
    # 操作模块:
    - Redis 键-值映射数据类型
    - 哈希表: 就是一个数组,数组的每个元素netry称为一个哈希桶(桶中元素存储的是指向key和value的指针).
    - O(1)时间复杂度快速查找键值对
    - 哈希表冲突问题
    - Redis解决哈希冲突使用链式哈希,指向同一个哈希桶中的多个元素用链表保存,之间依次用指针连接
    - Rehash操作:
    - 装载因子 load factor = 所有entry个数/哈希表的哈希桶个数
    - rehash操作就是增加现有哈希桶数量,让增多的entry元素能在更多的桶之间分散保存,减少单个桶中元素数量,从而减少单个桶中冲突
    - Redis默认采用两个全局哈希表,哈希表1,哈希表2, Rehash过程:
    - hash2分配更大空间 2 * hash1
    - 将hash1中数据重新映射并拷贝到hash2
    - 释放hash1空间
    - 渐进式rehash:
    - 巧妙的把一次性大量拷贝的开销分摊到多次处理请求的过程中,避免耗时操作,保证数据的快速访问

    - Redis值的数据类型:
    - 字符串String:
    - 底层数据类型:
    - 简单动态字符串
    - 列表List
    - 底层数据类型:
    - 双向链表
    - 压缩列表
    - 集合Set
    - 底层数据类型
    - 压缩列表
    - 整数数组
    - 有序集合Sorted Set
    - 底层数据类型
    - 压缩列表
    - 跳表
    - 哈希Hash
    - 底层数据类型
    - 压缩列表
    - 哈希表
    - Redis底层数据结构
    > Redis之所以采用不同的数据结构,是在性能和内存使用效率之间进行平衡
    - 简单动态字符串
    - 双向链表
    - 压缩列表:类似数组
    - 表头:
    - zlbytes: 列表长度
    - zltail: 列表尾的偏移量
    - zllen: 列表中entry个数
    - 表尾:
    - zlend: 表示结束
    - 哈希表
    - 跳表: 跳表在链表的基础上增加多级索引,通过索引位置的几个跳转,实现数据的快速定位
    - 查找复杂度O(logN)
    - 整数数组

    # 索引模块:
    - 索引的作用是让键值数据库根据Key找到响应value的存储位置,进而执行操作
    - 哈希表: redis, mamcached采用
    - 内存的高性能随机访问可以很好与哈希表O(1)操作复杂度相匹配
    - B+树
    - 字典树
    - skip list: RocksDB采用
    # 存储模块:
    - 内存分配器
    - glibc: malloc/free
    - 持久化
    - 日志AOF
    - 快照RDB
    # IO模型设计
    > Redis单线程是指网络IO和键值对读写是由一个线程完成. 其他功能持久化、异步删除、集群数据同步是由额外的线程执行.
    - 多线程编程模式:
    - 面临共享资源的并发访问控制问题
    - 采用多线程开发会引入同步原语来保护共享资源的并发访问
    - 单线程编程模式:
    - Redis基本IO模型中,主要是主线程在执行操作,任何耗时的操作,bigkey、全量返回等操作,都是潜在的性能瓶颈
  • Socker网络模型

    调用方法 返回套接字类型 非阻塞模式 效果
    socket() 主动套接字
    listen() 监听套接字 可设置 accept()非阻塞
    accept() 已连接套接字 可设置 send()/recv()非阻塞
  • Redis多路复用IO模型
    基于多路复用的Redis高性能IO模型

    一个线程处理多个IO流.该机制允许内核中同时存在多个监听套接字和已连接套接字.
    select/epoll提供基于事件的回调机制,针对不同事件的发生,调用响应的处理函数

  • 底层数据结构时间复杂度分类

    名称 时间复杂度
    哈希表 O(1)
    跳表 O(logN)
    双向链表 O(N)
    压缩列表 O(N)
    整数数组 O(N)
    1
    2
    3
    4
    5
    6
    # 单元素操作是基础
    # 范围操作非常耗时
    - 范围操作是指集合类型中的遍历操作: O(N)
    - SCAN操作实现渐进式遍历,只返回有限数量的数据.
    # 统计操作通常高校
    压缩列表和双向链表都会记录表头和表尾的偏移量

源码分析

  • 通信协议
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    redis客户端和服务器发送的命令和数据一律以\r\n (CR LF)结尾

    # 请求协议:
    *<参数数量> CR LF
    $<参数的字节数量> CR LF
    <参数的数据> CR LF

    ubuntu in /  at 3BPlus took 11s
    ➜ tail appendonly.aof
    *3 - 表示当前命令有单个部分
    $3 - 字节数
    set
    $4
    name
    $10
    chyiyaqing

    # 回复协议:
    > 在回复协议中,可以通过检查第一个字节,确定这个回复是什么类型
    - 状态回复 (status reply) 第一个字节是 "+"
    - 错误回复 (error reply) 第一个字节是 "-"
    - 整数回复 (integer reply) 第一个字节是 "."
    - 批量回复 (bulk reply) 第一个字节是 "$"
    - 多条批量回复 (multi bulk reply) 第一个字节是 "*"

性能调优

数据持久化

Redis 持久化主要两大机制: AOF (Append Only File) 日志和RDB 快照

  • AOF - Append Only File

    1
    > AOF日志是Redis执行完命令,把数据写入内存,然后才记录日志,避免出现记录错误命令的情况,记录的是Redis收到的每一条命令, 这些命令以文本形式保存
    • AOF 潜在的风险
      1
      2
      3
      4
      5
      6
      7
      8
      9
      - 执行完命令还没有来得及记日志就宕机,这个命令和相关的数据有丢失的风险?
      - AOF写日志在主线程中执行,如果磁盘写压力过大,就会导致写盘很慢,进而导致后续的操作无法执行
      # 写回磁盘策略 appendfsync
      1. Always: 同步写回, 每个写命令执行完,立马同步将日志写回磁盘
      > 落盘操作属于慢速,回影响主线程性能
      2. Everysec: 每秒写回,每个写命令执行完,先把日志写到AOF文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘
      > 在避免影响主线程和避免数据丢失两者之间trade-off方式
      3. No: 操作系统控制写回,每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
      > 落盘时机交给操作系统,只要AOF记录没有写回磁盘,宕机对应的数据就丢失
      配置项 写回时机 优点 缺点
      Always 同步写回 可靠性高,数据基本不丢失 每个写命令都要落盘,性能影响较大
      EverySec 每秒写回 性能适中 宕机时丢失1秒内的数据
      No 操作系统控制写回 性能好 宕机丢失数据较多
    • AOF文件过大的性能问题
      1
      2
      3
      1. 文件系统本身对文件大小有限制,无法保存过大的文件
      2. 文件过大,追加命令记录效率变低
      3. 发生宕机,AOF记录命令要被重新执行,故障恢复比较缓慢,会影响Redis正常使用
    • AOF重写机制:
      1
      2
      3
      4
      5
      6
      直接根据数据库中数据的最新状态,生成这些数据的插入命令
      > 重写机制可以将旧日志文件中的多条命令,在重写后的新日志中变成一条命令
      > AOF重写过程是由后台子进程bgrewriteaof完成,避免阻塞主线程,导致性能下降
      - "一个拷贝,两处日志"
      - 一个拷贝: 每次执行重写时,主线程fork出后台的bgrewriteaof子进程,此时,fork会把主线程的内存拷贝一份给bgrewriteaof子进程,然后bgrewriteaof子进程在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志.
      - 两处日志: 一个是正在使用的AOF日志,Redis会把操作写到它的缓冲区,第二处日志是新的AOF重写日志,等到拷贝数据的所有操作记录重写完成后,重写日志记录的操作也会写入新的AOF文件,保证数据库最新状态的记录
      AOF非阻塞重写过程
  • RDB(Redis DataBase) - 内存快照

    1
    > 内存快照:指内存中的数据在某一个时刻的状态记录
    • Redis两种命令生成RDB文件
      • save: 在主线程执行,会导致阻塞
      • bgsave: 创建子进程,专门用于写入RDB文件,避免主线程阻塞, 默认配置
        1
        2
        3
        > bgsave子进程由主线程fork生成,共享主线程的所有内存数据,bgsave子进程运行后,开始读取主线程的内存数据,并把数据写入RDB文件
        1. 频繁将全量数据写入磁盘,会给磁盘带来很大压力.
        2. bgsave子进程需要fork操作从主线程创建出来, fork操作会阻塞主线程
    • COW - Copy-On-Write
      写时复制机制
      1
      2
      写时复制技术具体来说,主线程在有写操作时,才会把这个新写或者修改的数据写入到新的物理地址,并修改自己的页表映射.
      保证快照的完整性,也允许主线程同时对数据进行修改,避免对正常业务的影响
    • Redis 4.0 混合AOF+RDB
      1
      2
      3
      4
      5
      # 增量快照:
      > 做一次全量快照后,后续的快照支队修改的数据进行快照记录,可以避免每次全量快照的开销

      # 混合使用AOF日志和内存快照
      > 内存快照以一定的频率执行,两次快照之间,使用AOF日志记录这期间的所有命令操作

数据同步

  • Redis 主从库模式
    1
    2
    3
    > 主从库采用读写分离方式
    - 读操作: 主库、从库都可以接收
    - 写操作: 首先到主库执行,然后,主库将写操作同步给从库
    • 主从库如何进行第一次同步?
      主从库第一次同步流程
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      # 三个阶段
      1. 第一阶段: 建立连接、协商同步
      - psync ? -1
      - runID: Redis实例自动生成的随机ID
      - offset: 复制的偏移量
      - FULLRESYNC: 表示第一次复制采用全量复制,主库会把当前所有的数据都复制给从库
      2. 第二阶段: 主库同步数据给从库
      - 主库将所有数据同步给从库,从库收到数据后,在本地完成数据加载
      - replication buffer: 主从库在进行全量复制时,主库上用于和从库连接的客户端buffer
      3. 第三阶段: 主库发送新写命令给从库
      - 主库把replication buffer 操作发给从库
      - 主库会给每个从库建立一个客户端,所以replication buffer不是共享的,而是每个从库都有一个对应的客户端

      # 主从库复制采用RDB,不采用AOF?
      1. RDB 文件是二进制文件,无论要把RDB写入磁盘还是通过网络传输RDB,IO效率比记录的纯文本AOF高
      2. 在从库进行恢复时,用RDB的恢复效率比AOF高
    • 主 - 从 - 从
      1
      > 通过“主-从-从”模式将主库生成RDB和传输RDB的压力,以级联的方式分散到从库中
    • 基于长连接的命令传播
      1
      2
      # repl_backlog_size:
      # repl_backlog_buffer: 为了支持从库增量复制,主库上用于持续保持写操作的一块专用buffer, 所有从库共享
  • 哨兵机制
    哨兵机制任务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
      > Redis主从集群中,哨兵机制实现主从库自动切换, 解决主从复制模式下故障转移
    # 哨兵集群:
    > 采用多实例组成的集群模式进行部署, 引入多哨兵判断避免单个哨兵自身网络状态不好,误判主库下线
    > N个哨兵实例,最好要有N/2+1实例判断主库为"主观下线", 最终判定主库为“客观下线”

    # 哨兵负责的任务:
    - 监控
    - 周期性向所有的主从库发送PING命令
    - 哨兵对主库下线判断:
    - 主观下线: 哨兵进程使用PING命令检测自己和主、从库的网络连接情况,判断实例的状态
    - 误判: 发生在集群网络压力较大、网络拥赛,或者主库本身压力较大
    - 客观下线:
    - 选主
    - 主库挂了之后,哨兵需要从很多从库中选择一个从库实例
    - "筛选" + "打分":
    - "筛选": 检查从库的当前在线状态,判断网络连接状态
    - "打分": 从库优先级、从库复制进度以及从库ID号
    - replica-priority 100
    - 通知
    - 哨兵会把新主库的连接信息发给其他从库,让他们执行replicaof命令,和新主库建立连接,并进行数据复制,同时哨兵把新主库的连接信息通知给客户端,请求操作发到新主库上.

    # 基于pub/sub 机制的哨兵集群
    > 哨兵只要和主库建立连接,就可以在主库上发布消息,比如发布自己的连接信息(IP和端口),从主库上订阅消息,获取其他哨兵发布的连接信息

    - 哨兵获取从库的IP+端口
    - 哨兵向主库发送INFO命令,查看从库列表,进而与从库建立连接
    - pub/sub客户端事件通知

Redis切片集群

1
> 切片集群(分片集群)可以保存大量数据,对Redis主线程阻塞影响较小
  • 纵向扩展(scale up):
  • 横向扩展(scale out):
    • Redis Cluster:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      > Redis Cluster 采用哈希槽(Hash Slot)处理数据和实例之间的映射关系. 2^14

      # key映射哈希slot
      1. 根据键值key,按照CRC16算法计算16-bit值,然后对16384取模,每个摸数代表响应编号的哈希槽。

      # 重定向机制
      > 客户端给一个实例发送数据读写操作时,这个实例上并没有相应的数据,客户端要再给一个新实例发送操作命令
      127.0.0.1:6001> get name
      -> Redirected to slot [5798] located at 172.30.1.23:6002
      "chyiyaqing"
      172.30.1.23:6002>

      # ASK:
      ASK命令并不会更新客户端缓存的哈希槽分配信息
      # MOVED:
      更改本地缓存,让后续所有命令都发往新实例

Redis 应用场景 - 消息队列

1
2
3
4
5
6
7
# 消息队列需要满足的功能:

1. 支持阻塞等待拉取消息
2. 支持发布/订阅模式
3. 消费失败,可重新消费,消息不丢失
4. 实例宕机,消息不丢失、数据可持久化
5. 消息可堆积
  • List - 队列:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    ubuntu in ~ at 3BPlus
    ➜ redis-cli -c -p 6001
    127.0.0.1:6001> ping
    PONG
    127.0.0.1:6001> LPUSH queue msg1 # 增加消息
    -> Redirected to slot [13011] located at 172.30.1.23:6003
    (integer) 1
    172.30.1.23:6003> LPUSH queue msg2
    (integer) 2
    172.30.1.23:6003> RPOP queue # 拉取消息
    "msg1"
    172.30.1.23:6003> RPOP queue
    "msg2"
    127.0.0.1:6001> RPOP queue # 队列为空,RPOP返回NULL
    -> Redirected to slot [13011] located at 172.30.1.23:6003
    (nil)
    127.0.0.1:6001> BRPOP queue 5 # BRPOP阻塞式拉取消息,支持传入超时时间
    -> Redirected to slot [13011] located at 172.30.1.23:6003
    (nil)
    (5.00s)
    172.30.1.23:6003> BRPOP queue 0 # 不设置超时,直到有新消息才返回
    ^C
    172.30.1.23:6003> exit

    注意: 如果设置超时时间太长,连接太久没有活跃,有可能会被Redis server判定为无效连接,之后Redis Server会强制把这个客户端踢下线,客户端要有重连机制

    1. 不支持重复消费:消费者拉取消息后,该消息就从List中删除,无法被其他消费者在此消费,即不支持多个消费者消费同一批数据
    2. 消息丢失,消费者拉取到消息后,如果发生异常宕机,这条消息就丢失

    # 单机测评
    ubuntu in ~ at 3BPlus took 37s
    ➜ redis-benchmark -n 1000000 -t lpush,rpop -P 16 -q -h 127.0.0.1 -p 6001 --cluster
    Cluster has 3 master nodes:

    Master 0: a733c21d3b735b9d026eb4d462ef6b367d8ebb98 172.30.1.23:6002
    Master 1: 9c35a4e211f6534861ed768dba592e85539b1377 172.30.1.23:6003
    Master 2: a901e497cb72819cf0765e9e4eb16c36399c437b 172.30.1.23:6001

    LPUSH: 41529.96 requests per second, p50=17.199 msec
    RPOP: 46539.77 requests per second, p50=15.463 msec
  • Pub/Sub - 发布订阅模型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    # 消费者1
    ubuntu in ~ at 3BPlus
    ➜ redis-cli -c -p 6001
    127.0.0.1:6001> SUBSCRIBE queue
    Reading messages... (press Ctrl-C to quit)
    1) "subscribe"
    2) "queue"
    3) (integer) 1
    1) "message"
    2) "queue"
    3) "msg1"

    # 消费者2
    ubuntu in ~ at 3BPlus
    ➜ redis-cli -c -p 6001
    127.0.0.1:6001> SUBSCRIBE queue
    Reading messages... (press Ctrl-C to quit)
    1) "subscribe"
    2) "queue"
    3) (integer) 1
    1) "message"
    2) "queue"
    3) "msg1"

    # 生产者
    ubuntu in ~ at 3BPlus
    ➜ redis-cli -c -p 6001
    127.0.0.1:6001> PUBLISH queue msg1
    (integer) 2
    127.0.0.1:6001> PUBLISH queue msg2
    (integer) 2
    127.0.0.1:6001> exit

    # Pub/Sub 支持阻塞式拉取消息,满足多组消费者,匹配订阅模式,允许消费者订阅多个队列
    127.0.0.1:6001> PUBLISH queue.p1 msg1 -- 生产者
    (integer) 1
    127.0.0.1:6001> PUBLISH queue.p2 msg2
    (integer) 1

    127.0.0.1:6001> PSUBSCRIBE queue.* -- 消费者 匹配订阅
    Reading messages... (press Ctrl-C to quit)
    1) "psubscribe"
    2) "queue.*"
    3) (integer) 1
    1) "pmessage"
    2) "queue.*"
    3) "queue.p1"
    4) "msg1"
    1) "pmessage"
    2) "queue.*"
    3) "queue.p2"
    4) "msg2"

    # Pub/Sub 实现原理
    > 没有基于任何数据类型,没有做任何数据存储,只是单纯地为生产者、消费者建立数据转发通道,把符合规则的数据,从一端转发到另一端
    > **在使用Pub/Sub时,消费者必须先订阅队列,生产者才能发布消息,否则消息会丢失**
    > List属于“拉取模式”, Pub/Sub属于“推模式”

    # 注意使用Pub/Sub会丢数据
    1. 消费者下线,重新上线,只能接收新的消息,在下线期间生产者发布的消息,因为找不到消费者,会被丢弃掉.
    2. Redis宕机, Pub/Sub相关操作,不会写入RDB和AOF中,当Redis宕机重启,Pub/Sub的数据会全部丢失
    3. 消费堆积, 每个消费者订阅一个队列,Redis都会在Server给消费者分配一个【缓冲区】,这个缓冲区其实就是一块内存,当生产者发布消息时,Redis先把消息写到对应消费者的缓冲区,之后消费者不断从缓冲区读取、处理消息, 缓冲区上线,会被强制下线.
  • Stream

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    # Stream 通过XADD 和 XREAD完成简单的生产、消费模型
    XADD: 发布消息
    XREAD: 读取消息

    # 生产者发布消息
    127.0.0.1:6001> XADD queue * name 2021 # * 表示自动生成唯一消息ID
    -> Redirected to slot [13011] located at 172.30.1.23:6003
    "1630886765690-0" #消息ID格式: 时间戳-自增序号
    172.30.1.23:6003> XADD queue * name 09-06
    "1630886777470-0"
    172.30.1.23:6003>

    # 消费者消费消息
    127.0.0.1:6001> XREAD COUNT 5 STREAMS queue 0-0 # 从开头读取5条消息,0-0表示从开头读取
    -> Redirected to slot [13011] located at 172.30.1.23:6003
    1) 1) "queue"
    2) 1) 1) "1630886765690-0"
    2) 1) "name"
    2) "2021"
    2) 1) "1630886777470-0"
    2) 1) "name"
    2) "09-06"
    172.30.1.23:6003> XREAD COUNT 5 STREAMS queue 1630886777470-0 # 继续拉取消息,传入上一条消息的ID
    172.30.1.23:6003> XREAD COUNT 5 BLOCK 0 STREAMS queue 1630886777470-0 # BLOCK 阻塞式拉取消息

    # Stream 支持发布/订阅模式
    - XGROUP: 创建消费者组
    - XREADGROUP: 在指定消费组下,开启消费者拉取消息

    172.30.1.23:6003> XGROUP CREATE queue group1 0-0 # 创建消费者组1, 0-0表示从头拉取消费
    OK
    172.30.1.23:6003> XGROUP CREATE queue group2 0-0 # 创建消费者组2,0-0
    OK

    172.30.1.23:6003> XREADGROUP GROUP group1 consumer COUNT 5 STREAMS queue > # group1 的consumer开始消费,>表示拉取最新数据
    1) 1) "queue"
    2) 1) 1) "1630886765690-0"
    2) 1) "name"
    2) "2021"
    2) 1) "1630886777470-0"
    2) 1) "name"
    2) "09-06"
    3) 1) "1630887243865-0"
    2) 1) "name"
    2) "08-14"
    172.30.1.23:6003> XREADGROUP GROUP group2 consumer COUNT 5 STREAMS queue >
    1) 1) "queue"
    2) 1) 1) "1630886765690-0"
    2) 1) "name"
    2) "2021"
    2) 1) "1630886777470-0"
    2) 1) "name"
    2) "09-06"
    3) 1) "1630887243865-0"
    2) 1) "name"
    2) "08-14"
    172.30.1.23:6003> XACK queue group1 1630887243865-0 # XACK 命令告知Redis消费者处理完
    (integer) 1

    # Stream 数据会写入RDB和AOF做持久化A
    # 消息堆积,Stream会只保留固定长度的新消息,当队列长度超过上限,就消息会被删除,只保留固定长度的新消息

Redis面试

  • 击穿、穿透、雪崩?
    • 击穿
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      # 击穿发生的原因?
      - Key过期
      > Key有过期时间,如果某一个时刻Key失效,那么之后的查询请求将全部压倒数据库上,导致数据库崩溃.
      - Key被页面置换淘汰
      > 内存有限,要时刻缓存新的数据,淘汰旧的数据

      # 应对击穿的处理思路?
      > 由于Key过期在所难免,大流量来到Redis时,根据Redis的单线程特性,可以认为任务是在队列里一次执行,当请求到达redis发现key过期,进行一个操作: 设置锁
      1.请求到达Redis,发现Redis Key过期,查看有没有锁,没有锁的话回到队列后面排队
      2.设置锁,注意,这儿应该是setnx(),而不是set(),因为可能有其他县城已经设置锁了
      3.获取锁,拿到就去数据库取数据,请求返回后释放锁
  • 穿透

    1
    > 穿透主要原因是很多请求都在访问数据库不存在的数据,应对这种请求的处理办法对访问请求加一层过滤器(布隆过滤器、增强版布隆过滤器、布谷鸟过滤器)
  • 雪崩

    1
    2
    3
    4
    5
    > 雪崩和击穿类似,不同的是击穿是一个热点Key某时刻失效,而雪崩是大量的热点Key在一瞬间失效

    # 处理思路?
    1. 查看Key过期是不是时点性有关,时点性无关的话,可以随机过期时间解决
    2. 如果是时点性有关,利用强依赖击穿方案,单飞策略(先过去的线程更新一下所有Key,在后台更新热点Key的同时,业务层将进来的请求延时一下)

参考资料