一、简述
梳理总结redis知识点 ,如数据结构、缓存策略、持久化方式、处理模式、高可用方案、使用场景及配置参数调优;通过思维导图系统化的展现,构建较为清洗的体系化认识,方便后续的学习和实践运用。
二、思维导图
三、知识要点
- 内部数据结构
redis内部整体的存储结构是一个大字典,dict 包含 dictht , dictht 包含dictEntry , hash冲突使用拉链法 ,扩容交换两个dictht 功能,rehash 渐进方式,不是一次性执行
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
unsigned long iterators; /* number of iterators currently running */
} dict;
/* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry;
/*
* Redis 对象
*/
typedef struct redisObject {
// 类型 4bits
unsigned type:4;
// 编码方式 4bits
unsigned encoding:4;
// LRU 时间(相对于 server.lruclock) 24bits
unsigned lru:22;
// 引用计数 Redis里面的数据可以通过引用计数进行共享 32bits
int refcount;
// 指向对象的值 64-bit
void *ptr;
} robj;
struct sdshdr {
int len ;
int free ;
char buf[]; // 字符串末尾有个空字符\0 , 为了用 <string.h>
}
- 内部数据类型
REDIS_ENCODING_INT(long 类型的整数)
REDIS_ENCODING_EMBSTR embstr (编码的简单动态字符串)
REDIS_ENCODING_RAW (简单动态字符串)
REDIS_ENCODING_HT (字典)
REDIS_ENCODING_LINKEDLIST (双端链表)
REDIS_ENCODING_ZIPLIST (压缩列表)
REDIS_ENCODING_INTSET (整数集合)
REDIS_ENCODING_SKIPLIST (跳跃表和字典)
3、数据淘汰策略
数据淘汰策略 Redis 具体有 6 种淘汰策略:
volatile-lru 从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
volatile-ttl 从已设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random 从已设置过期时间的数据集中任意选择数据淘汰
allkeys-lru 从所有数据集中挑选最近最少使用的数据淘汰
allkeys-random 从所有数据集中任意选择数据进行淘汰
noeviction 禁止驱逐数据
Redis 4.0 引入了 volatile-lfu 和 allkeys-lfu 淘汰策略,LFU 策略通过统计访问频率,将访问频率最少的键值对淘汰。
4、AOF方式持久化
将写命令添加到 AOF 文件(Append Only File)的末尾。
使用 AOF 持久化需要设置同步选项,从而确保写命令同步到磁盘文件上的时机。这是因为对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。
有以下同步选项:
always ## 每个写命令都同步 ,选项会严重减低服务器的性能;
everysec ##每秒同步一次 ,选项比较合适,可以保证系统崩溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响 ;
no ##让操作系统来决定何时同步 ,选项并不能给服务器性能带来多大的提升,而且也会增加系统崩溃时数据丢失的数量。
随着服务器写请求的增多,AOF 文件会越来越大。Redis提供了一种将AOF重写的特性,能够去除 AOF 文件中的冗余写命令。
AOF重写过程 :
1、AOF重写的内部运行原理,我们有必要了解一下。
2、在重写即将开始之际,redis会创建(fork)一个“重写子进程”,这个子进程会首先读取现有的AOF文件,并将其包含的指令进行分析压缩并写入到一个临时文件中。
3、与此同时,主工作进程会将新接收到的写指令一边累积到内存缓冲区中,一边继续写入到原有的AOF文件中,这样做是保证原有的AOF文件的可用性,避免在重写过程中出现意外。
4、当“重写子进程”完成重写工作后,它会给父进程发一个信号,父进程收到信号后就会将内存中缓存的写指令追加到新AOF文件中。
5、当追加结束后,redis就会用新AOF文件来代替旧AOF文件,之后再有新的写指令,就都会追加到新的AOF文件中了。
5、通信模型
服务器通过套接字与客户端或者其它服务器进行通信,文件事件就是对套接字操作的抽象。
Redis 基于 Reactor 模式开发了自己的网络事件处理器,使用 I/O 多路复用程序来同时监听多个套接字,并将到达的事件传送给文件事件分派器,分派器会根据套接字产生的事件类型调用相应的事件处理器。
通信 基于reactor模型, 事件处理循环 文件事件(请求) 时间事件(定时任务 或周期性任务 如清理失效数据)
IO多路复用
6、时间时间--内部任务
服务器有一些操作需要在给定的时间点执行,时间事件是对这类定时操作的抽象。
时间事件又分为:
定时事件:是让一段程序在指定的时间之内执行一次;
周期性事件:是让一段程序每隔指定时间就执行一次。
Redis 将所有时间事件都放在一个无序链表中,通过遍历整个链表查找出已到达的时间事件,并调用相应的事件处理器。
服务器需要不断监听文件事件的套接字才能得到待处理的文件事件,但是不能一直监听,否则时间事件无法在规定的时间内执行,因此监听时间应该根据距离现在最近的时间事件来决定。
7、事务
一个事务包含了多个命令,服务器在执行事务期间,不会改去执行其它客户端的命令请求。
事务中的多个命令被一次性发送给服务器,而不是一条一条发送,这种方式被称为流水线,它可以减少客户端与服务器之间的网络通信次数从而提升性能。
Redis 最简单的事务实现方式是使用 MULTI 和 EXEC 命令将事务操作包围起来。
操作的原子性, 合并的几个操作也是原子性
8、Redis Cluster 客户端
客户端可以通过key计算出落在哪个哈希槽上。除此之外客户端还需要知道哪个实例对应有哪些哈希槽。客户端在访问任何一个实例的时候就会获得所有实例的哈希槽信息,同时缓存在自己本地,每次通过计算可以得到对应的哈希槽,哈希槽所对应的实例,就可以去拿到正确的数据了
9、配置参数
环境参数
bind \ protected-mode
tcp-backlog 连接队列长度 , timeout 默认0 不关 ,tcp-keepalive 默认300秒
daemonize 默认 yes ; pidfile
loglevel : debug \ verbose \ notice \ warning ; logfile
databases 默认 16
requirepass
maxclients 最大连接数量
maxmemory ; maxmemory-policy 达到最大内存的移除策略 ;-maxmemory-samples
lua-time-limit
持久化
appendonly appendfilename appendfsync no-appendfsync-on-rewrite
auto-aof-rewrite-percentage auto-aof-rewrite-min-size
aof-load-truncated
aof-use-rdb-preamble 混合持久化
aof-rewrite-incremental-fsync 每生成32MB文件 写磁盘一次
rdb-save-incremental-fsync yes 每生成32MB文件 写磁盘一次
集群
cluster-enabled
cluster-config-file
cluster-node-timeout
cluster-migration-barrier
cluster-require-full-coverage
cluster-replica-no-failover
cluster-allow-reads-when-down
慢日志
slowlog-log-slower-than 、slowlog-max-len
调优
latency-monitor-threshold
碎片整理 activedefrag ...
hash-max-ziplist-entries \ hash-max-ziplist-value \ list-max-ziplist-value \ list-compress-depth
set-max-intset-entries zset-max-ziplist-entries zset-max-ziplist-value
activerehashing 默认yes , no 减小延时
client-output-buffer-limit 客户端输出缓存控制 ,超限则断开连接
client-query-buffer-limit
proto-max-bulk-len 批量请求字符限制
执行任务频率 hz 影响延时 , dynamic-hz
四、应用场景
- 分布式锁
设计考量:
互斥性:和我们本地锁一样互斥性是最基本,但是分布式锁需要保证在不同节点的不同线程的互斥
可重入性:同一个节点上的同一个线程如果获取了锁之后那么也可以再次获取这个锁
锁超时:和本地锁一样支持锁超时,防止死锁
高效,高可用:加锁和解锁需要高效,同时也需要保证高可用防止分布式锁失效,可以增加降级
支持阻塞和非阻塞:和ReentrantLock一样支持lock和trylock以及tryLock(long timeOut)
支持公平锁和非公平锁(可选):公平锁的意思是按照请求加锁的顺序获得锁,非公平锁就相反是无序的。这个一般来说实现的比较少
## 一个简单的分布式锁用
setnx key value ; del key ; expire key timeout ;
## 可能在节点挂掉后 形成死锁
if (redis.call('setnx', KEYS[1], ARGV[1]) < 1)
then return 0;
end;
redis.call('expire', KEYS[1], tonumber(ARGV[2]));return 1;
// 使用实例
EVAL "if (redis.call('setnx',KEYS[1],ARGV[1]) < 1) then return 0; end; redis.call('expire',KEYS[1],tonumber(ARGV[2])); return 1;" 1 key value 100
## 可能存在错误解锁 、 超时自动解锁并发执行
分布式锁中的图片引用自一位工作同事的PPT