Redis知识点总结
0. 简单入门知识点
1.redis是key-value的数据结构
每条数据都是一个键值对
键的类型是字符串,并且不能重复
值的数据类型分为五种:string
、hash
、list
、set
、zset
2.数据库没有名字,默认有16个库,通过0-15来标识连接redis数据库。默认选择第一个数据库,即0
1. 六大底层数据结构
可以使用OBJECT ENCODING key
来查看某个数据类型的底层实现
1.1. SDS(simple dynamic string)简单动态字符串sdshdr
// 不以空字符’\0’作为字符串的结束标志
struct sdshdr{
//等于 SDS 保存字符串的长度
int len;
//记录 buf 数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
}
1.2. 链表list
- 双端:链表具有前置节点和后置节点的引用
- 无环:表头节点的
prev
指针和表尾节点的next
指针都指向NULL
,对链表的访问都是以NULL
结束; - 带链表长度计数器:通过
len
属性获取链表长度的时间复杂度为O(1)
; - 多态:链表节点使用
void*
指针来保存节点值,可以保存各种不同类型的值。
1.3. 哈希表dictht
typedef struct dictEntry{
// 键
void *key;
// 值
union{
void *val;
uint64_t u64;
int64_t s64;
}v;
// 链地址法
struct dictEntry *next;
}dictEntry
哈希算法如下:
// 1、使用字典设置的哈希函数,计算键 key 的哈希值
hash = dict->type->hashFunction(key);
// 2、使用哈希表的sizemask属性和第一步得到的哈希值,计算索引值
index = hash & dict->ht[x].sizemask;
1.4. 跳跃表zskiplist
跳跃表具有以下性质:
- 由很多层结构组成;
- 每一层都是一个有序的链表,排列顺序为由高层到底层,都至少包含两个链表节点,分别是前面的
head
节点和后面的nil
节点; - 最底层的链表包含了所有的元素;
- 如果一个元素出现在某一层的链表中,那么在该层之下的链表也全都会出现(上一层的元素是当前层的元素的子集);
- 链表中的每个节点都包含两个指针,一个指向同一层的下一个链表节点,另一个指向下一层的同一个链表节点;
1.5. 整数集合intset
typedef struct intset{
//编码方式
uint32_t encoding;
//集合包含的元素数量
uint32_t length;
//保存元素的数组
int8_t contents[];
}intset;
1.6. 压缩列表ziplist
压缩列表的每个节点构成如下:
previous_entry_length
用于反向遍历
encoding
保存的是节点的content
的内容类型以及长度,encoding
类型一共有两种,字节数组
和整数
,encoding
区域长度为1字节
、2字节
或者5字节
长
2. 五大数据类型的底层实现
string
、hash
、list
、set
、zset
可以使用type key
来查看对应value
的类型
Redis
中的每个对象都是由redisObject
结构来表示
typedef struct redisObject{
//类型(五大数据类型)
unsigned type;
//编码(底层实现的数据类型)
unsigned encoding;
//指向底层数据结构的指针
void *ptr;
//引用计数
int refcount;
//记录最后一次被程序访问的时间
unsigned lru;
}robj
其中type
和encoding
的对应关系如下:
2.1. string的底层实现
字符串对象的编码可以是int
,raw
或者embstr
:
int
编码:保存的是可以用long
类型表示的整数值;raw
编码:保存长度大于44
字节的字符串(redis3.2
版本之前是39
字节,之后是44
字节);embstr
编码:保存长度小于44
字节的字符串(redis3.2
版本之前是39
字节,之后是44
字节)。
2.2. list的底层实现
当同时满足下面两个条件时,使用ziplist
(压缩列表)编码:
- 列表保存元素个数小于
512
个; - 每个元素长度小于
64
字节;
不能满足这两个条件的时候使用linkedlist
(双端链表)编码。
上面两个条件可以在redis.conf
配置文件中的list-max-ziplist-value
选项和list-max-ziplist-entries
选项进行配置。
2.3. hash的底层实现
哈希对象的编码可以是ziplist
或者dictht
,具体规则与list
类似
2.4. set的底层实现
集合对象的编码可以是intset
或者dictht
当集合同时满足以下两个条件时,使用intset
编码:
- 集合对象中所有元素都是整数
- 集合对象所有元素数量不超过
512
不能满足这两个条件的就使用dictht
编码。第二个条件可以通过配置文件的set-max-intset-entries
进行配置。
2.5. zset的底层实现
有序集合的编码可以是ziplist
或者skiplist
一个zset结构同时包含一个
字典和一个
跳跃表,字典的键保存元素的值,字典的值则保存元素的分值;跳跃表节点的
object属性保存元素的成员,跳跃表节点的
score`属性保存元素的分值。这两种数据结构会通过指针来共享相同元素的成员和分值,所以不会产生重复成员和分值,造成内存的浪费。
当有序集合对象同时满足以下两个条件时,对象使用ziplist
编码:
- 保存的元素数量小于
128
; - 保存的所有元素长度都小于
64
字节;
不能满足上面两个条件的使用skiplist
编码。以上两个条件也可以通过配置文件的zset-max-ziplist-entries
选项和zset-max-ziplist-value
进行修改
3. 持久化
3.1. RDB
优点:
- 直接二进制dump,文件紧凑,体积小;
- 恢复快;
缺点:
- 只能全量持久化,一次耗时较长;
- 二进制,基本没有可读性和可修改性。
3.2. AOF
优点:
- 记录所有更新日志,可读性和可修改性强;
- 支持持续无间断备份;
缺点:
- 文件体积大,恢复慢;
- 比较影响整体性能。
4. 缓存淘汰
当 Redis 内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换 (swap)。交换会让 Redis 的性能急剧下降,对于访问量比较频繁的 Redis 来说,这样龟速的存取效率基本上等于不可用。
在生产环境中我们是不允许 Redis 出现交换行为的,为了限制最大使用内存,Redis 提供了配置参数 maxmemory 来限制内存超出期望大小。
当实际内存超出 maxmemory 时,Redis 提供了几种可选策略 (maxmemory-policy) 来让用户自己决定该如何腾出新的空间以继续提供读写服务。
(volatile策略只会针对带过期时间的 key 进行淘汰,allkeys策略会对所有的 key 进行淘汰)
1.noeviction:不会继续服务写请求 (DEL 请求可以继续服务),读请求可以继续进行。这样可以保证不会丢失数据,但是会让线上的业务不能持续进行。这是默认的淘汰策略。
2.volatile-lru:尝试淘汰设置了过期时间的 key,最少使用的 key 优先被淘汰。没有设置过期时间的 key 不会被淘汰,这样可以保证需要持久化的数据不会突然丢失。
3.volatile-ttl:跟上面一样,除了淘汰的策略不是 LRU,而是 key 的剩余寿命 ttl 的值,ttl 越小越优先被淘汰。
4.volatile-random:跟上面一样,不过淘汰的 key 是过期 key 集合中随机的 key。
5.allkeys-lru 区别于 volatile-lru,这个策略要淘汰的 key 对象是全体的 key 集合,而不只是过期的 key 集合。这意味着没有设置过期时间的 key 也会被淘汰。
6.allkeys-random 跟上面一样,不过淘汰的策略是随机的 key。
5. 缓存穿透、缓存击穿、缓存雪崩
1.缓存穿透: 用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。一般通过布隆过滤器、缓存空对象来解决
2.缓存击穿:一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库。一般通过设置热点数据永远不过期、加互斥锁来解决
3.缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。一般通过过期时间设置随机、数据预热来解决
6.架构模式
- 单机版:①内存容量有限;②处理能力有限;③无法高可用。
- 主从复制:①降低master读压力在转交从库;②无法保证高可用;③没有解决master写的压力
- 哨兵:Redis sentinel 是一个分布式系统中监控 redis 主从服务器,并在主服务器下线时自动进行故障转移。①保证高可用;②主从模式,切换需要时间丢数据;③没有解决 master 写的压力
- 集群(proxy 型):增加了新的 proxy,需要维护其高可用。代表为Twemproxy、Codis
- 集群(直连型):增加了新的 proxy,需要维护其高可用。代表为Redis Cluster(redis3之后的官方方案)。分布算法使用数据分片(sharding)而非一致性哈希(consistency hashing)来实现: 一个 Redis 集群包含 16384 个哈希槽。采用客户端重定向的方式