Redis相关知识

本文详细介绍了Redis中的数据类型,如String、List(QuickList)、Hash、Set和ZSet,以及Redis的线程模型、事务处理、持久化机制。讨论了Redis在不同数据量下的存储策略,如ziplist和QuickList的使用。此外,还涵盖了Redis的部署方案,包括主从模式、哨兵模式和Redis Cluster。最后,讨论了Redis的过期键删除策略,包括惰性删除、定期删除和内存不足时的清理策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、数据类型及使用场景

(2)QuickList

3、Hash

4、set

5、zset

二、Redis

1、redis线程模型

2、redis事务

4、持久化机制

三、部署方案


一、数据类型及使用场景

 

redis是kv存储系统,key为字符串类型,值为redis对象。

由5种常用数据类型:String,List,Hash,Set,ZSet。

1、String

底层编码格式:

  • int:当保存数据时整形(64位long)时,如果是浮点数类型,redis是现将浮点数转为字符串然后再保存;
  • 简单动态字符串。

底层没有直接使用C字符串,而是使用简单动态字符串实现。区别:(1)是二进制安全的,不会遇到'\0'而终止,可以存储图像和序列化对象;(2)结构中有表示长度的字段,获取字符串长度时间复杂度时O(1);(3)空间不足时会自动扩容,大小小于1M时每次扩容一倍空间,大小大于等于1M时,每次扩容1M。惰性空间释放,数据修改导致长度变短时不会立即释放多余空间,而是把多余空间大小记录到free字段中,下次不够时优先使用多余空间,再进行扩容,减少了内存分配开销。

2、List

数据量小时使用ziplist存储,节省内存;当ziplist节点数超过512个,或者单个节点大小超过64字节,就会使用QuikList存储。

(1)ziplist

img ziplist是把节点存放在一块连续内存里,节点内存储上一节点大小和当前节点的大小。不采用双向链表形式是因为(1)链表的前驱和后继指针都占用额外的内存空间,在数据量少的情况下实际数据所占大小还不如指针浪费的多(2)每个链表节点单独分配内存,内存不连续续会产生内存碎片(3)根据局部性原理,连续内存也能更好的命中CPU Cache。

 操作时间复杂度:

  • 头尾压入/弹出操作,查询头尾元素时间复杂度都是O(1);
  • 对于中间元素查找复杂度是O(N),当新增/修改某个元素时内存空间要重新分配,可能会触发连锁更新。
  • 每种redis数据结构都有单独的字段保存该数据长度,因此获取长度是O(1).

ziplist连锁更新:

每个节点都包含一个字段prevlen表示上一节点的所占大小,该字段是变长编码的。当长度小于254时使用1字节存储prevlen,当大于254时使用5个字节存储prevlen。所以当在e1,e2之间插入新节点,新节点大小超过254而e1小于254,那么e2的prevlen存储方式就会发生改变,以此类推会触发连锁更新。

(2)QuickList

quicklist是使用ziplist组成的双向链表,每个节点使用ziplist存储数据。

3、Hash

数据量小时使用ziplist存储,当ziplist节点数超过512,或者单个节点大小超过64字节使用dict存储。ziplist上面介绍过,这里只介绍dict实现方式。

typedf struct dict{
    // 两张hash表 
    dictht ht[2];
    // rehash索引,字典没有进行rehash时,此值为-1
    int rehashidx;
    ...
    
}dict;

dict中定义了两张哈希表,当数据量增加出现哈希冲突,哈希表通过数组+链表来解决。数据量进一步增加,负载因子(数据量/哈希表槽数)大于一定值时进行扩容操作,当负载因子小于0.1进行缩容。

扩容或缩容过程中,由于哈希表大小改变了存储位置又是hash 值/len,所以会发生rehash过程。dict使用渐进rehash方式:每次执行操作时都将第一张表相同位置的键值rehash到另一张表中,最终所有键值都迁移过去,rehash过程结束,rehashidex=-1.

  • 查询时先查第一个哈希表,如果此时正在rehash,查询另一张表;
  • 插入时,如果正在rehash直接插入到另一张表中,否则插入到第一张表;
  • 删除时删除第一张表的元素。

优缺点:避免一次集中rehash计算量太大阻塞住redis,将计算量分散到每次操作;同时保留两张表,内存占用大,如果机器内存打满此时rehash会导致大量key丢失。

4、set

当数据都是整数值,并且数据个数小于512时使用intset存储,节省内存,否则使用hashtable存储。

整数集合intset

整数集合底层为数组,以有序无重复的方式存储整形数据,插入、查找时间复杂度o(n)?

5、zset

数据量小使用ziplist,当ziplist节点个数大于128,或者节点大小大于64字节时使用skiplist跳表来存储。

跳表:

二、Redis

1、redis线程模型

redis是基于单线程的Reactor模式实现,通过IO多路复用的select、epoll操作监听读写等IO事件,读写事件收集完后,循环的在内存中处理读写请求。

为什么这么快?

  • 单线程模型,避免了上下文切换、同步机制以及互斥锁带来的开销;而且更好地利用cpu cache提速;
  • 纯内存操作,所有读写均在内存中执行,不访问磁盘,cpu不是redis瓶颈,瓶颈仍然在网络io,redis6.0多线程是是在监听io事件开启多线程;
  • 高效数据结构,string、list、hash、set、zset。

redis应用场景有哪些?

  • 缓存热点数据,减轻数据库压力;
  • 分布式锁
  • 分布式限流器

2、redis事务

(1)使用方式:

使用MULTI开启一个事务;之后的命令都会被插入到队列中去,不会立即执行;使用EXEC命令提交事务,也可以使用discard放弃组队。

组队阶段会检查命令语法,如果语法错误会报错;如果是业务逻辑出错(比如逻辑bug),组队过程中不会报错,提交之后才会出错但不影响其他命令执行,不影响其他命令执行。

特点:

  • redis是单线程运行,事务队列中的命令会按序执行不会被其他事务所打断,所有命令都会执行,天然具备隔离性;
  • redis事务中某条命令执行出错不会影响其他命令执行,不具备原子性,不支持回滚。
  • 不支持持久性。redis数据持久依赖于RDB和AOF操作,
    • 使用RDB模式,一个事务执行后、下一RDB快照执行前实例崩溃,事务无法持久化;
    • 使用AOF模式,AOF配置no、eveerysec都存在数据丢失的情况,always可以保证持久性但性能太差,一般不使用。

(2)lua脚本

lua脚本同样具备隔离性,不具备原子性,不支持回滚。redis中的命令都是原子性的,因为redis是单线程程序,命令执行过程中不会被其他命令影响到。而运行lua脚本使用eval命令,eval是原子性的,当lua脚本执行出错时,之前的操作不会回滚。

3、watch命令实现乐观锁

WATCH命令可以监控一个或多个键,一旦其中有一个键被修改,之后的事务就不会执行。可以实现乐观锁。

WATCH version;
set version 1;
MULTI;
lpush mylist 123;
...
set version 2;
EXEC;
//如果version等于2说明事务执行了,其他事务不会执行,否则说明当前事务执行失败了

4、持久化机制

(1)RDB:默认方式,是在指定时间间隔,定期将内存数据的快照保存到磁盘上。保存在dump.rdb文件中,重启时根据dump.rdb文件恢复数据到内存。

由自动和手动两种方式来执行rdb持久化。通过save命令可以设定周期,定期同步rdb文件,save执行过程中会阻塞客户端请求,运行过程中不要使用save。bgsave命令是异步方式,会fork一个子进程执行快照保存,不会阻塞主进程处理请求。

优缺点:

  • 比aof体积小,rdb数据只是内存快照;
  • 比aof速度快,重启时内存快照不用像aof挨个执行命令;
  • 效率高,bgsave子进程同步不影响主进程处理请求。
  • 故障时丢失数据多,无法做到实时的持久化,bgsave执行时fork子进程属于洪亮吉操作,频繁执行成本高。

(2)AOF(append only file):以日志方式记录redis写命令,重启时会重新执行aof文件中的命令来恢复数据。在配置中开启appendonliy yes,redis每次执行写命令时会将命令写入内存aof buf缓冲区,然后根据具体策略将aof buf命令追加到磁盘的aof文件,具体策略有:everysec:每秒进行同步,no:右槽组系统决定何时同步,always:每执行一条谢明令都会同步。

当aof文件大小大于阈值时会自动重写aof,对同一key的操作进行合并。

  • 如果数据比较重要,且能够承受几分钟的数据丢失,比如缓存等,只需要使用RDB即可。
  • 如果是用做内存数据,要使用Redis的持久化,建议是RDB和AOF都开启。
  • 如果只用AOF,优先使用everysec的配置选择,因为它在可靠性和性能之间取了一个平衡。

三、部署方案

1、主从模式

一主多从,主节点负责写请求,并将数据同步到从节点,全部读请求都走从节点,可以轻松实现水平扩容。主节点挂掉后需要手动执行主节点,可用度不高,基本不用。

主从模式无法做到故障自动转移,当主节点故障时需要人为的手动指定主节点,这段时间内主服务不可用,而且这段时间的数据变更无法同步。

2、哨兵模式:

  • 每个哨兵以每秒一次的频率向主、从节点、其他哨兵节点发送PING命令,如果在规定时间内没有收到实例的有效回复,那么该实例会被标记为主观下线;
  • 对于被标记为主观下线的主节点,哨兵会向主节点发送命令确认是否真正下线,当超过半数哨兵认为主节点下线后,主节点被标记为客观下线。
  • 哨兵节点通过投票机制,从从节点中重新选举出主节点。

3、Redis Cluster

主动模式(包括哨兵模式)下,只有一master只能单点写,无法对写请求水平扩容,而且每个节点保存所有数据,内存占用高,数据恢复慢。Redis Cluster对数据进行分片,每个节点对应一个分片,Cluster内有主备两类节点,主节点处理读写请求,备节点作为备份不处理请求,主备节点通过异步方式,同步数据保证一致性。

Redis cluster使用哈希槽进行数据分片,而不是一致性哈希,内部有16384个哈希槽,key的存储位置为hash(key) % 16384;

四、常见问题:

1、过期键删除策略:

1、惰性删除。在访问key时,如果发现key已经过期,那么会将key删除,对内存不友好。

2、定期删除。定时清理key,每次清理会依次遍历所有DB,从db随机取出20个key,如果过期就删除,如果其中有5个key过期,那么就继续对这个db进行清理,否则开始清理下一个db。

3、内存不够时清理。Redis有最大内存的限制,通过maxmemory参数可以设置最大内存,当使用的内存超过了设置的最大内存,就要进行内存释放, 在进行内存释放的时候,会按照配置的淘汰策略清理内存。

2、

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值