Redis知识点
2021-11-29 17:03:28 0 举报
AI智能生成
Redis缓存相关知识点
作者其他创作
大纲/内容
关于网络IO
网络IO的本质是socket的读取,socket在linux系统被抽象为流,IO可以理解为对流的操作,
对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间
对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间
所以IO操作可以分为两部分
第一阶段:等待数据准备 (Waiting for the data to be ready)。
第二阶段:将数据从内核拷贝到进程中
对于IO的实现方式--socket 而言,也是两部分:
第一步:通常涉及等待网络上的数据分组到达,然后被复制到内核的某个缓冲区。
第二步:把数据从内核缓冲区复制到应用进程缓冲区。
IO模型的类型:
同步模型(synchronous IO)
异步IO(asynchronous IO)
阻塞IO(bloking IO)
非阻塞IO(non-blocking IO)
多路复用IO(multiplexing IO)
由于同步非阻塞方式需要不断主动轮询,轮询占据了很大一部分过程,轮询会消耗大量的CPU时间,
而 “后台” 可能有多个任务在同时进行,人们就想到了循环查询多个任务的完成状态,
只要有任何一个任务完成,就去处理它。如果轮询不是进程的用户态,而是有人帮忙就好了。
那么这就是所谓的 “IO 多路复用”。UNIX/Linux 下的 select、poll、epoll 就是干这个的
(epoll 比 poll、select 效率高,做的事情是一样的)
而 “后台” 可能有多个任务在同时进行,人们就想到了循环查询多个任务的完成状态,
只要有任何一个任务完成,就去处理它。如果轮询不是进程的用户态,而是有人帮忙就好了。
那么这就是所谓的 “IO 多路复用”。UNIX/Linux 下的 select、poll、epoll 就是干这个的
(epoll 比 poll、select 效率高,做的事情是一样的)
FD:File Descriptor,文件描述符,判断文件是否可读、可写。
IO多路复用的底层select 就是根据FD的状态来做判断的
IO多路复用的底层select 就是根据FD的状态来做判断的
信号驱动式IO(signal-driven IO)
数据结构
Redis 使用数组作为其存储结构,value是数组元素,key是数组索引
几个核心的数据结构
typedef struct redisDb {
dict *dict; //存储了key和value
dict *expires; //存储了key的过期信息
dict *watched_keys; //被wathch 的key
int id; //数据库id
.......
} redisDb;
dict *dict; //存储了key和value
dict *expires; //存储了key的过期信息
dict *watched_keys; //被wathch 的key
int id; //数据库id
.......
} redisDb;
typedef struct dict {
//(dict hash table)存储数据的 hash 表
// 数据在ht[0]中,ht[1]在 rehash 时会被使用
dictht ht[2];
} dict;
//(dict hash table)存储数据的 hash 表
// 数据在ht[0]中,ht[1]在 rehash 时会被使用
dictht ht[2];
} dict;
typedef struct dictht {
// 哈希表节点的指针数组
// key 的哈希值最终映射到这个数组的某个位置上
dictEntry **table;
} dictht;
// 哈希表节点的指针数组
// key 的哈希值最终映射到这个数组的某个位置上
dictEntry **table;
} dictht;
typedef struct dictEntry {
void *key; // 键, void 代表任意类型
union { // 值,union表示结构内的数据类型是同一种
void *val;
uint64_t u64;
int64_t s64;
} v;
struct dictEntry *next; // 链往后继节点
} dictEntry;
void *key; // 键, void 代表任意类型
union { // 值,union表示结构内的数据类型是同一种
void *val;
uint64_t u64;
int64_t s64;
} v;
struct dictEntry *next; // 链往后继节点
} dictEntry;
key
key 索引了 value 在内存中的位置,通过对客户端的 rehash 来存储或查找key
key在请求过来的时候会判断是否过期,过期会被移除
value
string
SDS(动态字符串)
// 3.0
struct sdshdr {
unsigned int len;// 记录buf数组中已使用字节的数量,即SDS所保存字符串的长度
unsigned int free;// 记录buf数据中未使用的字节数量
char buf[];// 字节数组,用于保存字符串
};
struct sdshdr {
unsigned int len;// 记录buf数组中已使用字节的数量,即SDS所保存字符串的长度
unsigned int free;// 记录buf数据中未使用的字节数量
char buf[];// 字节数组,用于保存字符串
};
//3.0 之后会根据字符长度选择不同的构造
static inline char sdsReqType(size_t string_size) {
if (string_size < 1<<5) // 32
return SDS_TYPE_5;
if (string_size < 1<<8) // 256
return SDS_TYPE_8;
if (string_size < 1<<16) // 65536 64k
return SDS_TYPE_16;
if (string_size < 1ll<<32) // 4294967296 4G
return SDS_TYPE_32;
return SDS_TYPE_64;
}
static inline char sdsReqType(size_t string_size) {
if (string_size < 1<<5) // 32
return SDS_TYPE_5;
if (string_size < 1<<8) // 256
return SDS_TYPE_8;
if (string_size < 1<<16) // 65536 64k
return SDS_TYPE_16;
if (string_size < 1ll<<32) // 4294967296 4G
return SDS_TYPE_32;
return SDS_TYPE_64;
}
list
双向链表
typedef struct listNode {
struct listNode *prev;// 前置节点
struct listNode *next;// 后置节点
void *value;// 节点值
} listNode;
struct listNode *prev;// 前置节点
struct listNode *next;// 后置节点
void *value;// 节点值
} listNode;
map
字典
typedef struct dictht {
dictEntry **table;// 哈希表数组
unsigned long size;// 哈希表大小
unsigned long sizemask;// 哈希表大小掩码,用于计算索引值,等于size-1
unsigned long used;// 哈希表已有节点的数量
} dictht;
dictEntry **table;// 哈希表数组
unsigned long size;// 哈希表大小
unsigned long sizemask;// 哈希表大小掩码,用于计算索引值,等于size-1
unsigned long used;// 哈希表已有节点的数量
} dictht;
set
类似hashset,他是一个value=null的字典
它内部的键值对是无序、唯一的。
它的内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值 NULL
它的内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值 NULL
zset
跳跃表
这个数据结构很复杂,zset用这个实现的
Redis要注意的问题
缓存雪崩
原因:Redis宕机或者缓存失效,大量请求会发送到数据库层,导致数据库层的压力激增。
解决方式:
1、让缓存过期时间错开。
2、通过服务降级让一部分请求返回默认值
3、集群配置,防止所有节点宕机
1、让缓存过期时间错开。
2、通过服务降级让一部分请求返回默认值
3、集群配置,防止所有节点宕机
缓存穿透
原因:访问的某个数据,既不在缓存,也不再数据库,这种情况可能是:
1、恶意攻击,专门访问数据库中没有的数据;
2、业务请求的误操作,缓存和数据库中的数据都被误删除了,导致都没有数据;
3、新业务上线时,缓存和数据库都没有用户的业务数据;
1、恶意攻击,专门访问数据库中没有的数据;
2、业务请求的误操作,缓存和数据库中的数据都被误删除了,导致都没有数据;
3、新业务上线时,缓存和数据库都没有用户的业务数据;
解决方式:
1、设置空值或缺省值,防止不存在的数据访问到数据库
2、使用布隆过滤器,对不存在的数据进行处理
3、拦截恶意请求,前端/后端都可以做
1、设置空值或缺省值,防止不存在的数据访问到数据库
2、使用布隆过滤器,对不存在的数据进行处理
3、拦截恶意请求,前端/后端都可以做
缓存击穿
原因:某个热点数据的缓存失效,导致请求一直访问数据库
解决方式:这种请求一般是缓存到时间过期导致的,所以将相关数据的缓存设置为永不过期,就可以了
key的控制
控制 key 的长度:
最简单直接的内存优化,就是控制 key 的长度。
如果 key 数量达到了百万级别,那么,过长的 key 名也会占用过多的内存空间。
最简单直接的内存优化,就是控制 key 的长度。
如果 key 数量达到了百万级别,那么,过长的 key 名也会占用过多的内存空间。
避免存储 bigkey
如果大量存储 bigkey,也会导致 Redis 内存增长过快。
如果大量存储 bigkey,也会导致 Redis 内存增长过快。
Redis锁
INCR
这种加锁的思路是, key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作进行+1。
加锁和设置过期时间是分开的,不是原子性。
如果没设置过期时间,这个锁会一直存在,后期操作会受影响。
如果没设置过期时间,这个锁会一直存在,后期操作会受影响。
SETNX
如果 key 不存在,将 key 设置为 value
如果 key 已存在,则 SETNX 不做任何动作
返回0/1表示是否存在
如果 key 已存在,则 SETNX 不做任何动作
返回0/1表示是否存在
用法-设置key名为lock的锁:
加锁成功:setnx lock java -> 1
加锁失败:setnx lock c++ -> 0
设置过期时间100s:EXPIRE lock 100
释放锁成功:del lock --> 1
加锁成功:setnx lock java -> 1
加锁失败:setnx lock c++ -> 0
设置过期时间100s:EXPIRE lock 100
释放锁成功:del lock --> 1
加锁和设置过期时间是分开的,不是原子性。
如果没设置过期时间,这个锁会一直存在,后期操作会受影响。
如果没设置过期时间,这个锁会一直存在,后期操作会受影响。
SETEX
相对于setnx 增加了过期时间的设置,使得加锁和过期时间设置保持原子性
用法-用法-设置key名为lock的锁,30秒过期:
加锁成功:setex lock 30 java -> OK
解锁成功:del lock -> 1
加锁成功:setex lock 30 java -> OK
解锁成功:del lock -> 1
PSETEX
PSETEX key milliseconds value
同setex 不过这里用的是毫秒
SET
2.6.12 版本可以使用set 调用nx、ex
set key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL]
Redis线程
redis 线程分两部分
在执行io操作,也就是网络读写的时候,是多线程
这是基于IO多路复用,也就是reactor 模式的
这是基于IO多路复用,也就是reactor 模式的
详细的看下面的《关于网络IO》
Redis 服务采用 Reactor 的方式来实现文件事件处理器(每一个网络连接其实都对应一个文件描述符)
请求作为FD
使用了事件处理器
在网络请求和IO操作部分使用的是多线程,
在执行命令方面使用的是单线程
在执行命令方面使用的是单线程
key的模糊检索会因为单线程的问题而阻塞,所以要使用scan
redis业务上的使用
缓存
分布式锁
全页缓存(FPC)
排行榜/计数器
会话缓存(Session Cache)
会话过期
0 条评论
下一页