- redis常见的数据类型:string,list,set,hash,sorted set
- redis使用SDS(simple dynamic string)作为默认字符串表示。
struct sdshdr{
//记录buf数组中已使用字节的数量
//等于 SDS 保存字符串的长度
int len;
//记录 buf 数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
}
它不会出现字符串变更造成的内存溢出;获取字符串时间复杂度为1;二进制安全,字节数组保存的是二进制数据而不是字符,使用len而不是空字符来判断字符串是否结束;空间预分配(小于1M的翻倍,超过1M的多分配1M),惰性空间释放free字段,会留空间来防止多次多重分配内存。
- List:用链表(或ziplist)实现;双端(有prev和next指针)、无环、带head和tail指针;带链表长度计数器len;多态:可以通过dup复制链表节点值,free释放链表节点值,match判断值是否相等。
- Hash:
- 使用哈希表(或ziplist)实现,里面保存了指向哈希表节点数组的指针、哈希表的大小、已有节点对数量和sizemask;
- 哈希表的节点是一个单链表节点;
- 使用hash函数(MurmurHash算法)来计算key的hash值,和sizemask相与计算出索引,采用链地址法来解决键冲突,新节点被放在已有节点的前面;
- rehash:为字典指针ht[1]分配空间,大小取决于ht[0]的键值对数量,扩展操作是扩展到第一个大于ht[0].used*2的,收缩操作是缩小到第一个大于等于ht[0].used的;将ht[0]的键值对rehash后,释放ht[0],将ht[1]设置为ht[0]
- 渐进式rehash:为ht[1]分配空间,在字典中维持一个索引计数器rehashidx,设置为0;在程序每次对字典删查改(增会直接在ht[1]内执行)时,会将键值对rehash到ht[1]后,增加rehashidx,将所有的键值对都被rehash后,将rehashidx设置为-1,表明操作完成。
- Set:使用hashtable(或inset)来实现,值为NULL
- Sorted Set:使用hashmap和skipList来保持数据存储和有序,hashmap放成员到score的映射,skiplist放按照score排序的成员。skiplist通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。
- redis单线程执行却依然很快的原因:纯内存操作,避免大量访问磁盘数据,不收到硬盘IO的限制;单线程也没有上下文的切换和加锁上锁,减少了对CPU的消耗;采用非阻塞IO多路复用机制。
- key过期删除:定时删除:设置timer,内存友好,但会占用CPU时间;惰性删除:CPU时间友好,但内存不友好;定期删除:CPU友好,内存也友好,但是难以确定删除操作执行的市场和频率;
- 旧版复制:分为同步(将从服务器状态更新到主服务器状态的操作)和命令传播(当主服务器数据库状态被修改,导致主从不一致,让主从重新一致的操作)。
- 旧版同步(sync):slave向master发送sync命令,master收到后执行BGSAVE生成RDB文件,salve载入文件,更新状态;master将缓冲区的写命令发送给slave,slave执行。
- 新版复制(PSYNC):分为完整重同步和部分重同步,前者和sync步骤一样,后者用于处理断线后重复制的情况,slave只执行在断线期间的写命令。
- 部分重同步是靠master和slave的复制偏移量、master的复制积压缓冲区和服务器的run ID来实现:通过对比主从服务器的复制偏移量来知道是否主从一致;积压缓冲区是一个固定长度的队列,master传播写命令时,不仅发送给slave,还会发送给复制积压缓冲区,保留着字节值和偏移量,如果slave断线重连后的offset依然在复制积压缓冲区内,会执行部分重同步,否则会执行完整重同步。run ID是服务器启动时生成的一个40位的十六进制字符,psync时master会将自己的运行ID传给slave,slave将其保存。断线重连后,slave会将这个ID传给master,如果一致就可以尝试执行部分重同步,否则就执行完整重同步。之后master执行一个写命令,会同步给slave
- 心跳检测:发送slave的复制偏移量,检测主从服务网络连接状态,辅助实现min_slaves,检测命令丢失。
- redis的高可用性:sentinel(哨兵)监视主从服务器,并在master下线时,将一个slave变为master。
- 集群:通过分片来进行数据共享,提供复制和故障转移功能。在集群中有若干个节点(node),当数据库中16384个槽都有节点在进行管理,这个集群进入上线状态。
- 当客户端向节点发送数据库有关的键命令时,节点会计算出属于哪个槽,如果是自己会直接执行命令,如果不是,会像客户端返回MOVED错误,并redirect到正确的节点再次发送相同指令。当节点A正在迁移槽到节点B,那么当A没找到属于该槽的key时,会返回ASK错误,引导客户端去节点B找到这个key。
- 集群分为master和slave,master处理槽,而slave复制master并在master下线后称为master。集群内的节点会通过互发消息来通信,包括MEET、PING、PONG、PUBLISH和FAIL,也通过判断对方节点是否下线,超过半数以上的节点认为一个主节点下线后,会将这个节点标记为下线并向集群广播FAIL,该节点的一个slave被选中称为master,并向集群广播一条pong消息。
- 事务:通过MULTI、EXEC来实现事务功能,Discard取消事务,WATCH监视一个或多个key
- 事务特征:redis不支持事务回滚机制,即使事务队列中某个命令执行时出现了错误,后续的命令也会继续执行。如果执行事务的过程中,出现停机,之后的语句都不会被执行;隔离性是由单线程执行保证的;当使用Append-Only模式时,Redis会通过调用系统函数write将该事务内的所有写操作在本次调用中全部写入磁盘。
- 缓存淘汰策略:1) 先进先出算法 2)最近使用最少(Least Frequently Used) 3)最长未被使用(Least Recently Used),存在热点数据时,LRU效率很好,但偶发性周期性的批量操作会导致LRU命中率下降,缓存污染比较严重。
- 缓存击穿:频繁请求查询系统中不存在的数据导致; 处理方法:
(1):cache null策略,查询反馈结果为null仍然缓存这个null结果,设置不超过5分钟过期时间
(2):布隆过滤器,所有可能存在的数据映射到足够大的bitmap中 google布隆过滤器:基于内存,重启失效不支持大数据量,无法在分布式场景 redis布隆过滤器:可扩展性,不存在重启失效问题,需要网络io,性能低于google - 缓存雪崩:同一时间大量缓存失效; 处理方法:
(1):缓存数据增加过期标记
(2):设置不同的缓存失效时间
(3):双层缓存策略C1为短期,C2为长期
(4):定时更新策略 - RDB和AOF:
- RDB:对于master,载入RDB文件时会只保存未过期的key,对于slave,会保存所有的key,因为主从同步时,slave数据库会被清空,所有没有什么影响。
- RDB持久化:通过SAVE(阻塞)和BGSAVE(非阻塞)命令将某个时间点上的数据库状态以压缩二进制文件的形式保存到RDB文件中,保存到磁盘里。BGSAVE是fork一个子进程,创建后通过copy on write来和父进程桐乡读写服务,并逐渐将写进页面的数据和父进程分开来。服务器启动自动载入RDB文件
- AOF:AOF文件载入是创建一个不带网络连接的伪客户端,从AOF文件中读取一条写指令并执行,直到所有写命令执行完毕。
- AOF的重写:为了解决AOF文件体积膨胀,会进行AOF文件重写,是通过对数据库状态的读取来实现的:读取key的值,用一条命令来记录键值对。为了解决AOF重写期间阻塞线程,会新建一个子进程来执行AOF重写,但缺点是重写期间的数据库状态变化没办法被子进程获取到。
为了避免这个问题,服务器在子进程创建后,设置了一个AOF重写缓冲区,父进程执行完写命令后,会将这个命令同时发送到AOF缓冲区和AOF重写缓冲区,当子进程完后才能后,会向父进程发送一个信号,父进程接收到之后,阻塞当前进程,将AOF重写缓冲区的内容写到新的AOF文件中,并且对新AOF文件改名,完成替换。
AOF重写时也会对过期key进行检查,服务器开启了AOF持久化模式后,对于过期key没有任何处理,当过期key被删除后,会向AOF文件追加DEL命令。
- AOF持久化:以redis命令的请求协议格式保存的,更新频率比RDB更高,会优先使用AOF来还原数据库。命令会先被写入到缓冲区,再根据设置的appendfsync时间来执行写入AOF文件。
Redis与Memcached的区别
两者都是非关系型内存键值数据库,现在公司一般都是用 Redis 来实现缓存,而且 Redis 自身也越来越强大了!Redis 与 Memcached 主要有以下不同:
对比参数 | Redis | Memcached |
类型 | 1. 支持内存 2. 非关系型数据库 | 1. 支持内存 2. 键值对形式 3. 缓存形式 |
数据存储类型 | 1. String 2. List 3. Set 4. Hash 5. Sort Set 【俗称ZSet】 | 1. 文本型 2. 二进制类型 |
查询【操作】类型 | 1. 批量操作 2. 事务支持 3. 每个类型不同的CRUD | 1.常用的CRUD 2. 少量的其他命令 |
附加功能 | 1. 发布/订阅模式 2. 主从分区 3. 序列化支持 4. 脚本支持【Lua脚本】 | 1. 多线程服务支持 |
网络IO模型 | 1. 单线程的多路 IO 复用模型 | 1. 多线程,非阻塞IO模式 |
事件库 | 自封转简易事件库AeEvent | 贵族血统的LibEvent事件库 |
持久化支持 | 1. RDB 2. AOF | 不支持 |
集群模式 | 原生支持 cluster 模式,可以实现主从复制,读写分离 | 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据 |
内存管理机制 | 在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘 | Memcached 的数据则会一直在内存中,Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题。但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。 |
适用场景 | 复杂数据结构,有持久化,高可用需求,value存储内容较大 | 纯key-value,数据量非常大,并发量非常大的业务 |
(1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
(2) redis的速度比memcached快很多
(3) redis可以持久化其数据