redis 高级特性和底层原理

midoll 541 2022-05-31

发布订阅模式

除了通过list 实现消息队列之外,Redis 还提供了一组命令实现发布/订阅模式。
这种方式,发送者和接收者没有直接关联(实现了解耦),接收者也不需要持续尝
试获取消息。
需要注意的注意是,发出去的消息不会被持久化,因为它已经从队列里面移除了,
所以消费者只能收到它开始订阅这个频道之后发布的消息。

  • eg. 订单过期推送
  1. 事件通过 Redis 的订阅与发布功能(pub/sub)来进行分发,故需要订
    keyevent@0:expired 通道
    0表示db0 根据自己的dbindex选择合适的数字

  2. 修改 redis.conf 文件
    修改 notify-keyspace-events Ex

    • K 键空间通知,以__keyspace@__为前缀
    • E 键事件通知,以__keysevent@__为前缀
    • g del , expipre , rename 等类型无关的通用命令的通知, …
    • $ String命令
    • l List命令
    • s Set命令
    • h Hash命令
    • z 有序集合命令
    • x 过期事件(每次key过期时生成)
    • e 驱逐事件(当key在内存满了被清除时生成)
    • A g$lshzxe的别名,因此”AKE”意味着所有的事件
  1. 重启redis , 即可测试失效事件的触发, 监听获取的值为 key

redis事务

  • Redis 的单个命令是原子性的
  • Redis 的事务涉及到四个命令:multi(开启事务),exec(执行事务),discard
    (取消事务),watch(监视)
  • 通过multi 的命令开启事务。事务不能嵌套,多个multi 命令效果一样。
  • multi 执行后,客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被
    执行, 而是被放到一个队列中, 当exec 命令被调用时, 所有队列中的命令才会被执
    行。
  • 通过exec 的命令执行事务。如果没有执行exec,所有的命令都不会被执行。
  • 如果中途不想执行事务了,怎么办?
    可以调用discard 可以清空事务队列,放弃执行。
  • watch命令
    用watch 监视一个或者多个key,如果开启事务之后,至少有一个被监视
    key 键在exec 执行之前被修改了, 那么整个事务都会被取消(key 提前过期除外)。可
    以用unwatch 取消。
    在执行exec 之前发生错误,事务会被拒绝执行,也就是队列中所有的命令都不会得到执行
    在执行exec 之后发生错误,在这种发生了运行时异常的情况下,只有错误的命令没有被执行,但是其他命令没有受到影响。不符合原子性。
    失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。

Lua 脚本

  • Lua/ˈluə/是一种轻量级脚本语言,它是用C 语言编写的,跟数据的存储过程有点类似
    好处:
    1、一次发送多个命令,减少网络开销。
    2、Redis 会将整个脚本作为一个整体执行,不会被其他请求打断,保持原子性。
    3、对于复杂的组合命令,我们可以放在文件中,可以实现程序之间的命令集复
    用。

  • 缓存lua脚本
    Redis 在执行script load 命令时会计算脚本的SHA1 摘要并记录在脚本缓存中,执
    行EVALSHA 命令时Redis 会根据提供的摘要从脚本缓存中查找对应的脚本内容

  • 脚本超时
    Redis 的指令执行本身是单线程的,这个线程还要执行客户端的Lua 脚本,如果Lua
    脚本执行超时或者陷入了死循环,是不是没有办法为客户端提供服务了呢?
    为了防止某个脚本执行时间过长导致Redis 无法提供服务, Redis 提供了
    lua-time-limit 参数限制脚本的最长运行时间,默认为5 秒钟。

性能

根据官方的数据,Redis 的QPS 可以达到10 万左右(每秒请求数)。

  1. 纯内存结构、
    KV 结构的内存数据库,时间复杂度O(1)。
  2. 单线程、
    1、没有创建线程、销毁线程带来的消耗
    2、避免了上线文切换导致的CPU 消耗
    3、避免了线程之间带来的竞争问题,例如加锁释放锁死锁等等
  3. 多路复用
    异步非阻塞I/O,多路复用处理并发连接。
  • 用户空间和内核空间
    内核是操作系统的核心,独立于普通的应用程序,可以访问受保护的内存空间,也
    有访问底层硬件设备的权限。
    内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代
    码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中,都是对物理地址的
    映射。
    当进程运行在内核空间时就处于内核态,而进程运行在用户空间时则处于用户态。
    进程在内核空间以执行任意命令,调用系统的一切资源;在用户空间只能执行简单
    的运算,不能直接调用系统资源,必须通过系统接口(又称system call),才能向内核
    发出指令。

  • top 命令:
    clipboard(9)
    us 代表CPU 消耗在User space 的时间百分比;
    sy 代表CPU 消耗在Kernel space 的时间百分比。

为了控制进程的执行,内核必须有能力挂起正在CPU 上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。

在每个任务运行前,CPU 都需要知道任务从哪里加载、又从哪里开始运行,也就是说,需要系统事先帮它设置好CPU 寄存器和程序计数器(Program Counter),这个叫做CPU 的上下文。

Linux 系统将所有设备都当作文件来处理,而Linux 用文件描述符来标识每个文件对象
文件描述符(File Descriptor)是内核为了高效管理已被打开的文件所创建的索引,
用于指向被打开的文件,所有执行I/O 操作的系统调用都通过文件描述符;文件描述符
是一个简单的非负整数,用以表明每个被进程打开的文件。

  • 传统I/O数据拷贝
    read系统调用
    先将数据从磁盘加载数据到内核缓冲区中,再从内核缓冲区拷贝到用户进程的页内存中
    clipboard(7)

  • I/O 多路复用
    I/O 指的是网络I/O。
    多路指的是多个TCP 连接(Socket 或Channel)。
    复用指的是复用一个或多个线程。
    它的基本原理就是不再由应用程序自己监视连接,而是由内核替应用程序监视文件描述符。
    clipboard(10)

    I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,
    而这些文件描述符(套接字描述符)其中的任意一个进入读就绪(readable)状态,select()
    函数就可以返回

内存回收

一类是key 过期,一类是内存使用达到上限(max_memory)触发内存淘汰。

  • 定时过期(主动淘汰)
    每个设置过期时间的key 都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU 资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。

  • 惰性过期(被动淘汰)
    只有当访问一个key 时,才会判断该key 是否已过期,过期则清除。该策略可以最大化地节省CPU 资源,却对内存非常不友好。极端情况可能出现大量的过期key 没有再次被访问,从而不会被清除,占用大量内存。

  • 定期过期
    每隔一定的时间,会扫描一定数量的数据库的expires 字典中一定数量的key,并清
    除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和
    每次扫描的限定耗时,可以在不同情况下使得CPU 和内存资源达到最优的平衡效果。
    Redis 中同时使用了惰性过期和定期过期两种过期策略。

  • 淘汰策略

    Redis 的内存淘汰策略,是指当内存使用达到最大内存极限时,需要使用淘汰算法来
    决定清理掉哪些数据,以保证新数据的存入。
    1设置最大内存- - 》达到最大值,
    2内存淘汰策略
    LRU,Least Recently Used:最近最少使用;随机采样来调整算法的精度。
    LFU,Least Frequently Used,最不常用,4.0 版本新增。
    random,随机删除。

持久化机制

一种是RDB 快照(Redis DataBase),一种是AOF(Append Only File)

RDB

RDB 是Redis 默认的持久化方案。当满足一定条件的时候,会把当前内存中的数据写入磁盘,生成一个快照文件dump.rdb。Redis 重启会通过加载dump.rdb 文件恢复数据。

  • 自动触发
save 900 1 		# 900 秒内至少有一个key 被修改(包括添加)
save 300 10 		# 300 秒内至少有10 个key 被修改
save 60 10000 	# 60 秒内至少有10000 个key 被修改

注意上面的配置是不冲突的,只要满足任意一个都会触发。
RDB 还有两种触发方式:
b)shutdown 触发,保证服务器正常关闭。
c)flushall,RDB 文件是空的,没什么意义

  • 手动触发 两个命令
    save 在生成快照的时候会阻塞当前Redis 服务器, Redis 不能处理其他命令
    bgsave 执行bgsave 时,Redis 会在后台异步进行快照操作,快照同时还可以响应客户端请求。

Redis 进程执行fork 操作创建子进程(copy-on-write),RDB 持久化过程由子进程负责,完成后自动结束。它不会记录fork 之后后续的命令。阻塞只发生在fork 阶段,一般时间很短。用lastsave 命令可以查看最近一次成功生成快照的时间。

  • 一、优势
    1.RDB 是一个非常紧凑(compact)的文件,它保存了redis 在某个时间点上的数据集。这种文件非常适合用于进行备份和灾难恢复。
    2.生成RDB 文件的时候,redis 主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO 操作。
    3.RDB 在恢复大数据集时的速度比AOF 的恢复速度要快。
  • 二、劣势
    1、RDB 方式数据没办法做到实时持久化/秒级持久化。因为bgsave 每次运行都要执行fork 操作创建子进程,频繁执行成本过高。
    2、在一定间隔时间做一次备份,所以如果redis 意外down 掉的话,就会丢失最后一次快照之后的所有修改(数据有丢失)。如果数据相对来说比较重要,希望将损失降到最小,则可以使用AOF 方式进行持久化

AOF

Append Only File
AOF:Redis 默认不开启。AOF 采用日志的形式来记录每个写操作,并追加到文件中。开启后,执行更改Redis 数据的命令时,就会把命令写入到AOF 文件中。Redis 重启时会根据日志文件的内容把写指令从前到后执行一次以完成数据的恢复工作。
配置文件redis.conf
开关

appendonly no

文件名

appendfilename "appendonly.aof"
  • 什么时候把缓冲区的内容写入到AOF 文件?
    参数说明
    appendfsync everysec AOF 持久化策略(硬盘缓存到磁盘),默认everysec
     no 表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快,但是不太安全;
     always 表示每次写入都执行fsync,以保证数据同步到磁盘,效率很低;
     everysec 表示每秒执行一次fsync,可能会导致丢失这1s 数据。通常选择everysec ,兼顾安全性和效率。
  • 解决aof文件越来越大
    例如set gupao 666,执行1000 次,结果都是gupao=666。
    Redis 新增了重写机制,只保留可以恢复数据的最小指令集。
    但是AOF 文件重写并不是对原文件进行重新整理,而是直接读取服务器现有的键值对,然后用一条命令去代替之前记录这个键值对的多条命令,生成一个新的文件后去替换原来的AOF 文件
    可以使用命令bgrewriteaof 来重写。
# 重写触发机制
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
参数说明
auto-aof-rewrite-percentage	默认值为100。aof 自动重写配置,当目前aof 文件大小超过上一次重写的aof 文件大小的百分之多少进行重写,
即当aof 文件增长到一定大小的时候,Redis 能够调bgrewriteaof对日志文件进行重写。
当前AOF 文件大小是上次日志重写得到AOF 文件大小的二倍(设置为100)时,自动启动新的日志重写过程。
auto-aof-rewrite-min-size 默认64M。设置允许重写的最小aof 文件大小,
避免了达到约定百分比但尺寸仍然很小的情况还要重写。

clipboard(8)

  • AOF 优势与劣势
    优点:
    1、AOF 持久化的方法提供了多种的同步频率,即使使用默认的同步频率每秒同步
    一次,Redis 最多也就丢失1 秒的数据而已。
    缺点:
    1、对于具有相同数据的的Redis,AOF 文件通常会比RDF 文件体积更大(RDB存的是数据快照)。
    2、虽然AOF 提供了多种同步的频率,默认情况下,每秒同步一次的频率也具有较高的性能。在高并发的情况下,RDB 比AOF 具好更好的性能保证。

一般情况下建议不要单独使用某一种持久化机制,而是应该两种一起用,在这种情况下,当redis 重启的时候会优先载入AOF 文件来恢复原始的数据,因为在通常情况下AOF 文件保存的数据集要比RDB 文件保存的数据集要完整。


# redis