4、Redis核心原理

一、Redis为什么快(10W TPS)

1、纯内存结构

kv结构的内存数据库,时间复杂度0(1)。

2、请求处理单线程

处理客户端的请求是单线程的,即主线程是单线程的。

单线程的优势:

  • 没有创建线程、销毁线程带来的消耗。
  • 避免了上线文切换导致的CPU消耗。
  • 避免了线程之间带来的竞争问题,例如加锁释放锁死锁等等。

业务系统使用多线程是为了更好的利用CPU资源。Redis使用单线程已经足够,其瓶颈可能是机器内存或网络带宽。

因为请求处理是单线程的,不要在生产环境运行长命令,比如keys *,flushall,flushdb,否则会导致请求被阻塞。

3、IO多路复用机制

Redis 将所有客户端 socket 注册到一个 事件循环(Event Loop) 中,由底层 I/O 多路复用 API 监听这些 socket 的事件。当某个 socket 可读(有请求)或可写(可回包)时,Redis 主线程立即处理。

二、Redis通信协议

Redis服务器与客户端通过RESP(REdis Serialization Protocol)协议通信。它是一种直观的文本协议,优势在于实现异常简单,解析性能极好。

RESP传输的最小单元类型:

  • 单行字符串: 以 + 符号开头
  • 多行字符串 以 $ 符号开头,后跟字符串长度
  • 整数值 以 : 符号开头,后跟整数的字符串形式
  • 错误消息 以 - 符号开头
  • 数组 以 * 号开头,后跟数组的长度

注:每个单元结束时统一加上回车换行符号\r\n

客户端向服务器发送的指令只有一种格式,多行字符串数组。比如一个简单的 set 指令set 。

服务器向客户端回复的响应要支持多种数据结构,所以消息响应在结构上亦是如此。

三、Redis回收机制

1、过期策略

(1)立即过期

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

(2)惰性过期

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

(3)定期过期

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

Redis中同时使用了惰性过期和定期过期两种过期策略,并不是实时地清除过期的key。

2、淘汰策略

当内存使用达到最大内存极限时,需要使用淘汰算法来决定清理掉哪些数据,以保证新数据的存入。redis.conf参数配置:

复制代码
maxmemory <bytes>

如果不设置maxmemory或者设置为0, 32位系统最多使用3GB内存,64位系统不限制内存。淘汰策略参数在redis.conf中配置 maxmemory-policy noeviction:

复制代码
#volatile-lru -> Evict using approximated LRU, only keys with an expire set.
#allkeys-lru -> Evict any key using approximated LRU.
#volatile-lfu -> Evict using approximated LFU, only keys with an expire set.
#allkeys-lfu -> Evict any key using approximated LFU.
#volatile-random -> Remove a random key having an expire set.
#allkeys-random -> Remove a random key, any key.
#volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
  • LRU-Least Recently Used:最近最少使用。判断最近被使用的时间,目前最远的数据优先被淘汰。
  • LFU-Least Frequently Used:最不常用,按照使用频率删除,4.0版本新增。
  • random:随机删除

前缀:volatile是针对设置了 ttl 的key,all keys是针对所有key。

如果没有设置ttl或者没有符合前提条件的key被淘汰,那么volatile-lru、 volatile-random、volatile-ttl 相当于 noeviction (不做内存回收)。

建议使用volatile-lru,在保证正常服务的情况下,优先删除最近最少使用的key。

(1)LRU(Least Recently Used)

LRU是一个很常见的算法,比如InnoDB的Buffer Pool也用到了 LRU。传统的LRU:通过链表+HashMap实现,设置链表长度,如果新增或者被访问,就移动到头节点。超过链表长度,末尾的节点被删除。

若基于传统LRU算法实现会需要额外的数据结构存储,消耗内存。Redis LRU对传统的LRU算法进行了改良,通过随机采样来调整算法的精度。

Redis LRU根据配置的采样值maxmemory_samples (默认是5个),随机从数据库中选择m个key,淘汰热度最低的key对应的缓存数据。采样参数m数值越大,越能精确的查找到待淘汰的缓存数据,但也消耗更多的CPU计算,执行效率降低。

如何找出热度最低的数据?Redis中所有对象结构都有一个Iru字段, 且使用了 unsigned 的低24位 ,这个字段用来记录对象的热度对象被创建时会记录Iru值。在被访问的时候也会更新Iru的值。 但并不是获取系统当前的时间戳,而是设置为全局变量server.lruclock的值。

Redis中有个定时处理的函数serverCron,默认每100毫秒调用函数 updateCachedTime 更新一次全局变量的server.lruclock的值,值为当前 unix 时间戳

Redis并没有获取精确的时间而是放在全局变量中,这样函数査询key调用lookupKey中更新数据的Iru热度值时,就不用每次调用系统函数time,可以提高执行效率。

当对象里面已经有了 LRU字段的值,就可以评估对象的热度了。

函数estimateObjectldleTime评估指定对象的Iru热度,方法就是对象的Iru值和全局的server.lruclock的差值越大(越久没有得到更新),该对象热度越低。

server.lruclock只有24位,按秒为单位来表示才能存储194天。当超过24bit能表示的最大时间的时候,它会从头开始计算。在这种情况下,可能会出现对象的Iru大于server.lruclock的情况,如果这种情况出现那么就两个相加而不是相减来求最久的key。

Redis没有用常规的哈希表+双向链表的方式实现,因为需要额外的数据结构,消耗资源。

(2)LFU(Least Frequently Used)

LRU并不一定是最优算法,有时候一些数据虽然访问时间间隔不规律(有时候比较长),但是整体访问频率比较高,针对这种情况,就需要使用LFU算法。

当这24 bits用作LFU时,其被分为两部分:

  • 高16位用来记录访问时间(单位为分钟,Idt, last decrement time)
  • 低 8 位用来记录访问频率,简称counter (logc, logistic counter)

counter是用基于概率的对数计数器实现的,8位可以表示百万次的访问频率。 对象被读写的时候,lfu的值会被更新

并不是访问一次,计数加1 ,增长速率由一个参数决定,Ifu-log-factor越大,counter增长的越慢。

如果一段时间热点高就一直保持这个热度,肯定也是不行的,体现不了整体频率。所以,没有被访问的时候,计数器还要递减。减少的值由衰减因子Ifu-decay-time (分钟)来控制,如果值是1的话,N分钟没有访问,计数器就要减少N。Ifu-decay-time越大,衰减越慢。

四、Redis持久化机制

因为Redis数据存储在内存中,为了实现重启后数据不丢失,Redis提供了两种持久化的方案,---种是 RDB 快照(Redis DataBase),---种是 AOF (Append Only File)。 持久化是Redis跟Memcache的主要区别之一。

(1)RDB

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

自动触发

配置参数 redis.conf -> SNAPSHOTTING,定义了触发把数据保存到磁盘的触发频率。如果不需要rdb方案,注释save或者配置成空字符串""。

复制代码
save 900 1 # 900秒内至少有一个key被修改(包括添加) 
save 300 10 # 400秒内至少有10个key被修改
save 60 10000 # 60秒内至少有10000个key被修改

上面的配置是不冲突的,只要满足任意一个都会触发。用lastsave命令可以查看最近一次成功生成快照的时间。]rdb文件位置和目录(默认在安装根目录下):

复制代码
#文件路径,
dir ./
#文件名称
dbfilename dump.rdb
#是否以LZF压缩rdb文件 rdbcompression yes
#开启数据校验
rdbchecksum yes

除了根据配置触发生成RDB, RDB还有两种自动触发方式:

  • shutdown触发,保证服务器正常关闭。
  • flushall,rdb文件是空的,没什么意义

手动触发

需要重启服务或者迁移数据,这个时候就需要手动触RDB快照保存。Redis 提供了两条命令:

复制代码
命令:save

save在生成快照的时候会阻塞当前Redis服务器,Redis不能处理其他命令。
如果内存中的数据比较多,会造成Redis长时间的阻塞。生产环境不建议使用这个命令。



命令:bgsave
执行bgsave时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。
Redis进程执行fork操作创建子进程(copy-on-write),RDB持久化过程由子进程负责,完成后自动结束。
它不会记录fork之后产生的数据。阻塞只发生在 fork阶段,一般时间很短。

优缺点

优点:

  • RDB是一个非常紧凑(compact)的文件,保存了 redis在某个时间点上的数据集。这种文件非常适合用于进行备份和灾难恢复。
  • 生成RDB文件的时候,redis主进程会fork。一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。
  • RDB在恢复大数据集时的速度比AOF的恢复速度要快。

缺点:

  • RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,频繁执行成本过高。
  • 在一定间隔时间做一次备份,如果redis意外down掉的话,就会丢失最后一次快照之后的所有修改(数据有丟失)。
  • 如果数据相对来说比较重要,希望将损失降到最小,则可以使用AOF方式进行持久化。

(2)AOF

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

通过配置文件redis.conf,开启AOF

复制代码
#开关 
appendonly no
#文件名
appendfilename "appendonly.aof"

重写机制

由于AOF持久化是Redis不断将写命令记录到AOF文件中,AOF的文件会越来越大,文件越大,占用服务器内存越大,AOF恢复时间越长。

当AOF文件大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。

重写不是对原文件进行重新整理,而是直接读取服务器现有的键值对, 然后用一条命令去代替之前记录这个键值对的多条命令,生成一个新的文件后去替换原来的AOF文件。

复制代码
#重写触发机制
auto-aof-rewrite-percentage 100
auto-aof^rewrite-min-size 64mb

重写过程中,子进程进行重写,主进程负责处理命令、将写命令同时追加到新的和旧的AOF文件。

优缺点

优点:

  • 提供多种同步频率,使用默认同步频率,每秒同步一次,Redis最多丢失1秒的数据。

缺点:

  • AOF文件通常会比RDF文件体积更大(RDB存的是数据快照)。
  • 高并发的情况下,RDB比AOF具好更好的性能保证。

两种方案比较

  • 如果可以忍受一小段时间内数据的丢失,使用RDB是最好的,否则就使用AOF。
  • 一般情况下,两种一起用。当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
相关推荐
山峰哥1 小时前
JOIN - 多表关联的魔法——3000字实战指南
java·大数据·开发语言·数据库·sql·编辑器
wzy06231 小时前
Redis 集群迁移方案:从三节点到三节点的无缝过渡
数据库·redis·缓存
墨笔之风1 小时前
MySQL与PostgreSQL选型对比及适用场景说明
数据库·mysql·postgresql
肥大毛1 小时前
Oracle中Merge Using用法
数据库·oracle
程序员Agions2 小时前
N+1 查询:那个让你 API 慢成 PPT 的隐形杀手
数据库·后端
banjin2 小时前
轻量化时序数据库新选择:KaiwuDB-Lite 实战体验
数据库·oracle·边缘计算·时序数据库·kaiwudb·kwdb
阳光九叶草LXGZXJ2 小时前
达梦数据库-学习-43-定时备份模式和删除备份(Python+Crontab)
linux·运维·开发语言·数据库·python·学习
DemonAvenger2 小时前
Redis监控系统搭建:关键指标与预警机制实现
数据库·redis·性能优化