Redis
2019-06-25 16:30:25 0 举报
AI智能生成
Redis知识全面概览
作者其他创作
大纲/内容
核心对象
struct RedisObject {
int4 type; // 4bits
int4 encoding; // 4bits
int24 lru; // 24bits
int32 refcount; // 4bytes
void *ptr; // 8bytes,64-bit system
} robj;
注意:byte与bit,1byte=8bit
不同的对象具有不同的类型type(4bit)
同一个类型的 type 会有不同的存储形式 encoding(4bit)
如:string类型就有raw和embstr
记录对象的 LRU 信息,使用了 24 个 bit 来记录 LRU 信息,便于数据淘汰
对象的引用计数,当引用计数为零时,对象就会被销毁,内存被回收
ptr 指针将指向对象内容 (body) 的具体存储位置
即光个对象头则占用16byte,字节
存储结构
String
可变字符串,以字节数组的形式保存,
就想java中arrayList
struct SDS {
int8 capacity; // 1byte // 数组容量
int8 len; // 1byte // 数组长度
int8 flags; // 1byte // 特殊标识位,不理睬它
byte[] content; // 内联数组,存储具体的内容,长度为 capacity
}
长度不超过44
以 embstr 形式存储
embstr连续存储,raw分开存储
对象头和内容存在一起。只需要一次分配内存
长度超过44
以 raw 形式存储
对象头和内容分开存储。需要 2 次分配内存
为什么是 44 ,因为malloc分配内存是2,4,8,16,32,64等分配,
当达到64时Redis认为是大对象,采用raw存储。
而64-对象头(16+3)=45,再去除结束标识,就是44
当达到64时Redis认为是大对象,采用raw存储。
而64-对象头(16+3)=45,再去除结束标识,就是44
加上对象头,一个Redis字符串至少占用19byte,字节大小
扩容策略
当大小在1M之内时,每次扩容采用翻倍策略,至少有一半内存冗余,
当大小超过1M后,每次扩容只增加1M大小
所以扩容时最多冗余1M内存
dict 字典
应用
Redis 数据库的所有 key 和 value 也组成了一个全局字典,还有带过期时间的 key 集合也是一个字典。
set也是通过dict来实现的,只是所有的value都是null。还是和java中的HashSet结构一样(直接使用了HashMap)
hash 结构的数据基于字典结构实现的
数据结构
有2个hashtable的,
有一个一般为空,只有扩容时才有值
2个hashtable,ht[0], ht[1]. 一般一个存储数据,另一个为空,发生扩容时分配新的hashtable,
当发生扩容时,则将旧值渐进式迁移到新的ht中,迁移完成后将旧的ht删除
hashtable结构
和java中的HashMap结构基本一致,初始化容量的一维数组,然后数组中保存链表的指针
扩容策略
2个hashtable,ht[0], ht[1]. 一般一个存储数据,另一个为空,发生扩容时分配新的hashtable,
当发生扩容时,则将旧值渐进式迁移到新的ht中,迁移完成后将旧的ht删除
扩容-渐进式rehash
在某些指令执行时进行迁移,如hset,hdel
空闲时通过定时任务执行迁移
ziplist 压缩列表
Redis 为了节约内存空间使用,zset 和 hash 容器对象在元素个数较少的时候,采用压缩列表 (ziplist) 进行存储。
struct ziplist {
int32 zlbytes; // 整个压缩列表占用字节数
int32 zltail_offset; // 最后一个元素距离压缩列表起始位置的偏移量,用于快速定位到最后一个节点
int16 zllength; // 元素个数
T[] entries; // 元素内容列表,挨个挨个紧凑存储
int8 zlend; // 标志压缩列表的结束,值恒为 0xFF
}
结构
压缩列表为了支持双向遍历,所以才会有 ztail_offset 这个字段,用来快速定位到最后一个元素,然后倒着遍历
元素的结构
struct entry {
int prevlen; // 前一个 entry 的字节长度
int encoding; // 元素类型编码
optional byte[] content; // 元素内容
}
压缩列表是一块连续的内存空间,元素之间紧挨着存储,没有任何冗余空隙。
可以双向遍历
ziplist和linkedlist的优缺点
ziplist
优点
1.一整块内存,内存效率高
2.内存碎片少
缺点
1.正式因为一整块内存,所以不利于修改
2.每次数据变动都会引发一次内存的realloc
3.当ziplist长度很长的时候,一次remalloc可能会导致大批量的数据拷贝,进一步降低性能
linkedlist
优点
1.便于在表的两端进行push和pop操作
缺点
1.内存开销比较大
2.在每个节点上除了要保存数据之外,还要额外保存两个指针
3.双向链表的各个节点是单独的内存块,地址不连续,节点多了容易产生内存碎片
quicklist 快速列表
quicklist是ziplist和linkedlist的结合体,
将数据进行"分片",每"片"的数据用ziplist来存储,
然后每个ziplist用双向连表(linkedlist)的形式进行连接起来
ziplist
连续的内存空间
+
linkedlist
每个节点,单独的内存空间
这样既避免了用ziplist存储时元素过多,也避免了linkedlist存储的节点过多
每个ziplist存储元素的个数是多少呢?
默认单个 ziplist 长度为 8k 字节
ziplist 的长度由配置参数list-max-ziplist-size决定
ziplist 压缩存储,使用 LZF 算法压缩,可以选择压缩深度
默认的压缩深度是 0
不压缩
压缩深度是 1
quicklist 的首尾两个 ziplist 不压缩,
为了支持快速的 push/pop 操作
压缩深度是 2
quicklist 的首尾第一个 ziplist 以及首尾第二个 ziplist 都不压缩
实际深度由配置参数list-compress-depth决定
数据结构
typedef struct quicklist {
quicklistNode *head; //指向头部(最左边)quicklist节点的指针
quicklistNode *tail; //指向尾部(最右边)quicklist节点的指针
long count; //ziplist中的entry节点计数器
int len; //quicklist的quicklistNode节点计数器
int fill : 16; //保存ziplist的大小,配置文件设定,占16bits
unsigned int compress : 16; //保存压缩程度值,配置文件设定,占16bits,0表示不压缩
} quicklist;
节点的数据结构
typedef struct quicklistNode {
quicklistNode *prev; //前驱节点指针
quicklistNode *next; //后继节点指针
char *zl; //不设置压缩数据参数recompress时指向一个ziplist结构
//设置压缩数据参数recompress指向quicklistLZF结构
int sz; //压缩列表ziplist的总长度
int count : 16; //ziplist中包的节点数,占16 bits长度
//表示是否采用了LZF压缩算法压缩quicklist节点,1表示压缩过,2表示没压缩,占2 bits长度
int encoding : 2; /* RAW==1 or LZF==2 */
//表示一个quicklistNode节点是否采用ziplist结构保存数据,2表示压缩了,1表示没压缩,默认是2,占2bits长度
int container : 2; /* NONE==1 or ZIPLIST==2 */
//标记quicklist节点的ziplist之前是否被解压缩过,占1bit长度 //如果recompress为1,则等待被再次压缩
int recompress : 1; /* was this node previous compressed? */
int attempted_compress : 1; //测试时使用 /* node can't compress; too small */
int extra : 10; //额外扩展位,占10bits长度 /* more bits to steal for future usage */
} quicklistNode;
quicklist图
skiplist 跳跃列表
在学习redis的skiplist数据结构前,先来看看skiplist算法
随机生成level值,形成跳跃列表
Redis的skiplist结构与skiplist算法的不同
1.Redis中分数(score)允许重复,而skiplist算法是不允许的
2.在比较时,不仅比较分数(相当于skiplist的key),还比较数据本身。
在Redis的skiplist实现中,数据本身的内容唯一标识这份数据,而不是由key来唯一标识。
另外,当多个元素分数相同的时候,还需要根据数据内容来进字典排序。
3.第1层链表不是一个单向链表,而是一个双向链表。这是为了方便以倒序方式获取一个范围内的元素
4.在Redis的skiplist中可以很方便地计算出每个元素的排名(rank)
结构
struct zskiplist {
zslnode* header; // 跳跃列表头指针
zslnode* tail; // 跳跃列表尾指针
long length; //节点的数目
int maxLevel; // 跳跃列表当前的最高层
}
节点数据结构
struct zskiplistNode {
robj valObj; //节点数据
double score;
zskiplislLevel{
zskiplistNode forwards; // 连接指针
int span; //该层跨越的节点数量
}level[]
zskiplistNode* backward; // 回溯指针
}
结构图
最高有64层,数据结构保存了maxLevel,来优化查找过程,即从maxLevel层开始查找,不用浪费的去从最高层来查找
header节点的value值为null,score的值为Double.MIN_VALUE用来垫底
最底层是一个双向链表。方便以倒序方式获取一个范围内的元素
span值记录了该层级跨越的节点数,所以在计算排名
时,可以很方便的根据搜索路径层级上的span值叠加
来算得(header的所以层级的span值都为 0 )
listpack紧凑列表
listpack是ziplist的优化版,空间占用更加的紧凑,结构也更简单
数据结构上少了一个zltail_offset字段
数据结构
结构图
struct listpack {
int32 total_bytes; // 占用的总字节数
int16 size; // 元素个数
T[] entries; // 紧凑排列的元素列表
int8 end; // 同 zlend 一样,恒为 0xFF
}
节点数据结构
struct lpentry {
int encoding; //元素编码
optional byte[] content; //元素内容
int length; //当前元素的长度(不同于ziplist的prevlen是指上一个元素的长度)
}
ziplist通过zltail_offset来快速定位到尾部节点,用于逆序遍历
listpack的尾部节点位置可以通过total_bytes字段和最后一个元素的长度字段计算出来
Rax 基数树
它是一个有序字典树 (基数树 Radix Tree),按照 key 的字典序排列,
支持快速地定位、插入和删除操作
可以将一本英语字典看成一棵 radix tree,它所有的单词都是按照字典序进行排列,
每个词汇都会附带一个解释,这个解释就是 key 对应的 value。有了这棵树,你就
可以快速地检索单词,还可以查询以某个前缀开头的单词有哪些
数据结构
底层实现
String
INT 使用整数值实现的字符串对象
EMBSTR 使用 embstr 编码的简单动态字符串实现的字符串对象
长度不超过44
以 embstr 形式存储
embstr连续存储,raw分开存储
对象头和内容存在一起。只需要一次分配内存
对象头和内容存在一起。只需要一次分配内存
RAW 使用简单动态字符串实现的字符串对象
长度超过44
以 raw 形式存储
对象头和内容分开存储。需要 2 次分配内存
对象头和内容分开存储。需要 2 次分配内存
常用方法-时间复杂度
SET
O(1)
GET
O(1)
GETSET
O(1)
MSET
O(N)
MSETNX
O(N)
MGET
O(N)
List
ZIPLIST 使用压缩列表实现的列表对象
ziplist
优点
1.一整块内存,内存效率高
2.内存碎片少
缺点
1.正式因为一整块内存,所以不利于修改
2.每次数据变动都会引发一次内存的realloc
3.当ziplist长度很长的时候,一次realloc可能会导致大批量的数据拷贝,进一步降低性能
LINKEDLIST 使用双端链表实现的列表对象
linkedlist
优点
1.便于在表的两端进行push和pop操作
缺点
1.内存开销比较大
2.在每个节点上除了要保存数据之外,还要额外保存两个指针
3.双向链表的各个节点是单独的内存块,地址不连续,节点多了容易产生内存碎片
Set
INTSET 使用整数集合实现的集合对象
HT 使用字典实现的哈希对象
SortedSet
ZIPLIST 使用压缩列表实现的列表对象
SKIPLIST+hashtable
使用跳跃表和字典实现的有序集合对象
skipList实现score到val的映射,根据score的一个有序列表
hashtable实现val到score的映射
Hash
ZIPLIST 使用压缩列表实现的哈希对象
HT 使用字典实现的哈希对象
内存映射方式
1.虚拟内存
冷、热数据--把不经常访问的冷数据从内存交换到磁盘中
1.Redis的虚拟内存机制
1.OS的虚拟内存是以4k页为最小单位进行交换的。而redis的大多数对象都远小于4k
2.相比于os的交换方式。redis可以将被交换到磁盘的对象进行压缩,保存到磁盘的对象可以去除指针和对象元数据信息。
一般压缩后的对象会比内存中的对象小10倍。这样redis的vm会比os vm能少做很多io操作。
3.Redis只把Value置换到磁盘的swap文件中
2.Linux的虚拟内存机制
1.内存分为多个内存域(zone)
2.内存的最小操作单位--页,4KB大小
3.冷数据页,热数据页,用于内存置换(SWAP)
4.页表:内存寻址的辅助结构,层次化的页表有助于空间的访问
配置
vm-enabled yes #开启vm功能
vm-swap-file /tmp/redis.swap #交换出来的value保存的文件路径/tmp/redis.swap
vm-max-memory 1000000 #redis使用的最大内存上限,超过上限后redis开始交换value到磁盘文件中。
vm-page-size 32 #每个页面的大小32个字节
vm-pages 134217728 #最多使用在文件中使用多少页面,交换文件的大小 = vm-page-size * vm-pages
vm-max-threads
置换对象的线程数量
= 0:主线程来处理置换请求
(阻塞所有client)
换出
主线程定期检查发现内存超出最大上限后,会直接已阻塞的方式,将选中的对象保存到swap文件中,并释放对象占用的内存
换入
当有client请求的key对应的value被换出时(Redis只把values置换到磁盘的swap文件中)。
主线程会以 阻塞 的方式从文件中加载对应的value对象,加载时会 阻塞 所有client,然后再处理client的请求
> 0:新开线程处理置换请求
(只阻塞当前请求的client)
换出
当主线程检测到使用内存超过最大上限,会将选中的要交换的对象信息放到一个队列中交由工作线程后台处理,主线程会继续处理client请求。
换入
如果有client请求的key对应的value被换出了,主线程先阻塞发出命令的client,然后将加载对象的信息放到一个队列中,让工作线程去加载。
加载完毕后工作线程通知主线程。主线程再执行client的命令。
**这种方式只阻塞请求value被换出key的client**
2.diskstore
1.把key-value存储在磁盘上
2.cache-max-memory值将会严格支持,当超过该值后会造成等待
3.内存中保存着热数据,如果请求的key在内存中不存在,则去磁盘中加载,并缓存到内存中
4.数据的 写操作 全部在内存中,不直接操作磁盘。内存中的数据会被异步flush到磁盘
由于redis写操作是单线程的,当cache-flush-delay=0时,多个client同时写则需要排队。
如果内存容量超过cache-max-memory值,Redis会进入等待,造成调用方卡顿
5.异步flush到磁盘的频率可以设置。值为0时表示尽可能快的flush到磁盘上。
6.所以热数据和内存差不多或者小的时候会很快,但是当热数据大于内存的时候就会遭受到 IO瓶颈 了
3.内存映射文件(Memeory-Mapped File)
(MongDB的存储方式)
1.常规的读取文件的过程(2次拷贝过程)
磁[硬]盘-->系统内核空间(缓冲区)-->用户内核空间
2.于是出现了Memeory-MappedFile.(操作系统内存管理的一种机制)
实现 文件磁盘地址 和 进程虚拟地址空间 中一段虚拟地址的一一对映关系。
实现这样的映射关系后,进程就可以采用指针的方式直接读写操作这一段内存,
而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必
再调用read,write等系统调用函数。
MMAP详解
总而言之,常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。
而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。
说白了,mmap的关键点是实现了用户空间和内核空间的数据直接交互
而省去了空间不同数据不通的繁琐过程。因此mmap效率更高
过期原理
redis会将所有设置了过期时间的key放在一个单独的字典中,后续扫描都是从这个过期字典中扫描
从库不会进行过期扫描,从库的数据都是通过主库同步过来的,
主库中key过期时会在aof中插入一条del命令,
但由于主从同步是异步进行的,所以会存在数据不一致的情况[当使用主从架构时就得考虑不一致性的问题]
rdb文件在写入的时候会检查key是否过期,过期的key不会写入。从rdb文件恢复的时候,也会检查key是否过期,过期的不会恢复
惰性删除
每次获取key时,检查是否过期,若过期则删除
定期删除
每隔一段时间执行一次删除过期的key
默认100ms检查一次,但是并不是检查所有的key,而是随机抽取一部分进行检查删除
随机删除算法
随机选取一定数量的key进行检查删除
如果删除的key比例大于1/4.则在进行一次删除
为了避免循环过度,整个扫描时间默认不超过25ms
定时删除
在key设置过期的同时,为key设置一个定时器,定时器删除key
数据淘汰策略
LRU(最近使用时间)
当redis内存超过物理内存限制时,数据就会在内存和磁盘发生交换(Swap),性能急剧下降[正式环境应该不允许发生这种情况]
可以设置maxmemery来限制redis内存使用的最大值
RedisObj中的lru字段存储的是Redis 时钟server.lruclock。
当key被访问一次后则更新该值
Redis 时钟server.lruclock是一个 24bit 的整数,
默认是 Unix 时间戳对 2^24 取模的结果,大约 97 天清零一次
策略
noeviction:不允许淘汰缓存。内存不足时,新写入操作会报错
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key
allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key
volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key
volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key
volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除
LFU(数据热度-Redis4.0)
RedisObj中的lru字段存储2个值
ldt(last decrement time) - 占 16 个位,用来存储上一次 logc 的更新时间
logc(logistic counter) - 占 8 个 bit,用来存储访问频次
持久化
aof
命令日志备份
命令写到内存beffer中,定期flush到磁盘保存
默认是1秒flush一次
三种fsync配置
appendfsync no:不进行fsync,将flush文件的时机交给OS决定,速度最快
appendfsync always:每写入一条日志就进行一次fsync操作,数据安全性最高,但速度最慢
appendfsync everysec:折中的做法,交由后台线程每秒fsync一次
因为是将命令追加到aof文件后面,会触发bgrewriteaof,是fork一个子进程操作
rdb
叫快照备份,是一次全量备份
fork一个子进程进行快照保存,耗资源操作,可能影响服务卡顿
fork子进程的影响
fork子进程时会调用系统函数创建一个和主进程几乎完全一致的进行,所有资源都复制一份,包含内存占用,代码空间。
所以当主进程占用资源比较多的时候,在fork子进程可能导致卡顿,并且需要再分配一份资源,内存占用方面也会增倍,得益于Linux做了优化---COW(CopyOnWrite)
即fork子进程分配的内存并不是直接复制一份,而是在对数据进行修改的时候才去把这一部分内存复制一份
主从同步
redis的主从不同是异步进行的,fork一个进程来同步。数据遵循的是最终一致性
增量同步
同步的是指令流,redis将对数据产生改变的指令存在本地的内存buffer中,然后异步将buffer中的指令同步到从节点。
同时从节点向主节点反馈同步的偏移量
缓存命令的buffer是一个环形的数组,当环形数组满了,就会覆盖之前的命令。
所以需要注意,如果从库同步的太慢或buffer设置太小,主库中buffer被覆盖了,导致从库无法通过buffer中的指令进行同步,则会触发全量同步
全量同步
也叫快照同步,是个耗资源操作,先在主库上fork一个进程执行bgsave,将当前数据快照到磁盘。
然后将rdb文件传送给从节点,从节点进行一次全量加载。
全量加载也可能导致主库的buffer被覆盖,进而导致再次全量同步,出现死循环,所以注意buffer大小的设置
流程
哨兵模式
启动sentinel
1.启动redis-sentinel程序
redis-sentinel /path/to/sentinel.conf
配置文件
sentinel monitor mymaster 127.0.0.1 6379 2
配置监听主服务器mymaster 地址端口 至少2个sentinel投票失效(仍然要满足大多数投票)
sentinel down-after-milliseconds mymaster 60000
指定了 Sentinel 认为服务器已经断线所需的毫秒数
在给定的毫秒数内, 没有返回 Sentinel 的 PING 命令的回复, 或返回一个错误, 那么 Sentinel 将这个服务器标记为主观下线
主观下线并不是下线,只有足够数量的sentinel都标记该服务器为主管下线,则该服务器会被标记为客观下线,执行一次故障自动转移
sentinel failover-timeout mymaster 180000
failover过期时间,当failover开始后,在此时间内仍然没有触发任何failover操作, 当前sentinel将会认为此次failoer失败
sentinel parallel-syncs mymaster 1
在执行故障转移时, 最多可以有多少个从服务器同时对新的主服务器进行同步,
这个数字越小, 完成故障转移所需的时间就越短,
虽然从服务器的同步过程是异步的,但是在同步期间从服务器是不能对外提供服务的,应避免全部从服务器同时不能对外提供服务
2.将redis-server以sentinel模式启动
redis-server /path/to/sentinel.conf --sentinel
监控
Sentinel会不断检查主、从服务器是否正常
主观下线
单个sentinel对主服务器标记为下线状态
客观下线
1.多个sentinel都对主服务器标记为客观下线
2.sentinel相互询问 is-master-down-after-milliseconds 后,
达到故障转移条件后,该服务器则被标记为客观下线
只要某个sentinel将主服务器标记为主管下线状态,则该sentinel就会被推选出执行故障转移过程
你无需为每个sentinel设置其他sentinel的地址信息,因为所有sentinel会通过发布订阅功能,自动发现监听相同master的sentinel,然后他们可以互相交换信息
通知提醒
当某个服务器出现问题时,Sentinel可以通过API发送通知出来
故障自动转移
当一个主服务器不可用时,Sentinel会进行一次自动故障迁移,把某个从服务器提升为主服务器
配置信息也被重新写入
投票协议
一个Sentinel集群系统只有当大多数Sentinel投票成功才会发起一次故障转移。否则是不会转移的,
所以sentinel至少要有3个
选举新master算法
使用 Raft 算法来选举领头(leader) Sentinel
客户端Jedis
指定MasterName和sentinel集群ip列表,然后Jedis会自动从sentinel中获取到master的ip+port
同时在用master初始化pool时,会添加一个listener的监听线程,用于监听master的变化的
原理:通过jedis的sub命令来订阅master变化的消息
集群
代码层(Client)分片
中间件代理
Codis
twemproxy
server端集群
RedisCluster
使用数据分片(sharding)而非一致性哈希(consistency hashing)来实现
一个 Redis 集群包含 16384 个哈希槽(hash slot), 每个键都属于这 16384 个哈希槽的其中一个,
集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和
每个节点负责处理一部分hash槽
再配合 主从复制 达到每个节点的高可用
收缩方案
槽是 Redis 集群管理数据的基本单位,
集群伸缩就是槽和数据在节点之间的移动
扩容流程
准备新节点,"cluster meet"加入集群
"cluster setslot importing"设置新节点负责的槽
"cluster setslot migrating"原节点准备迁移的槽
原节点"migrate"批量迁移数据
向所有主节点"cluster setslot node"通知主节点新节点负责的槽
缩容
迁移中ing
前提知识
客户端在 本地 维护 slot 到 Redis 节点的映射关系
MOVED 命令重定向 负责协助客户端更新本地映射关系
迁移流程
客户端根据本地 slot 缓存发送命令到源节点,如果存在键对应则直接执行并返回结果给客户端
如果节点返回 MOVED 错误,更新本地的 slot 到 Redis 节点的映射关系,然后重新发起请求
如果数据正在迁移中,节点会回复 ASK 重定向异常。
格式如下: ( error ) ASK { slot } { targetIP } : { targetPort }
客户端从 ASK 重定向异常提取出目标节点信息,发送 asking 命令到目标节点打开客户端连接标识,再执行键命令。
MOVED和ASK
ASK 重定向说明集群正在进行 slot 数据迁移,客户端无法知道什么时候迁移完成,
因此只能是临时性的重定向,客户端不会更新 slot 到 Redis 节点的映射缓存
MOVED 重定向说明键对应的槽已经明确完成迁移到指定到的节点,所以客户端会更新slot 到redis节点的映射缓存
注意
参数 cluster-require-full-coverage
集群 16384 个槽 任何一个 没有指派到节点时整个集群不可用 的配置
不可用期间“执行任何键命令返回 CLUSTERDOWN Hash slot not served 命令
当持有槽的主节点下线时,从故障发现到自动完成转移期间整个集群是不可用状态,对于大多数业务无法忍受这情况,
因此建议将参数 cluster-require-full-coverage 配置为 no ,
当主节点故障时只影响它负责槽的相关命令执行,不会影响其他主节点的可用性。
java客户端-Jedis
RedisTemplate
引用JedisConnectionFactory
引用RedisClusterConfiguration
引用RedisNode(s)
集群节点信息
引用RedisNode(s)
在JedisCluster基础上实现,根据key类型使用具体的Jedis类与Redis进行交互
引用JedisPoolConfig
负责创建JedisCluster集群操作类,获取Redis连接对象
Redis集群信息操作类
JedisClusterConnectionHandler
一个抽象类,负责初始化、重建、重置Redis slot槽缓存
JedisSlotBasedConnectionHandler
JedisClusterConnectionHandler类的子类,负责根据key的slot值获取Redis连接
JedisClusterInfoCache
Redis slot缓存类,负责保存、重建和自动发现Redis slot槽与集群节点的关系
在初始化连接的过程中,从RedisNode中任意连接一个,
发送 cluster slots 命令来获取Redis集群节点的槽和Redis集群节点信信息,
并将相应信息保存到Map缓存中
127.0.0.1:6379> cluster slots
1) 1) (integer) 12288 起始槽
---2) (integer) 16383 结束槽
---3) 1) "127.0.0.1" 主节点ip
------2) (integer) 6382 主节点port
------3) "65aea5fc4485bc7c0c3c4425fb3f500c562ee243" 节点id
---4) 1) "127.0.0.1" 从节点ip
------2) (integer) 6386 从节点port
------3) "4061e306b094e707b6f4a7c8cd8e82bd61155060" 节点id
2) 1) (integer) 4096
---2) (integer) 8191
---3) 1) "127.0.0.1"
------2) (integer) 6380
------3) "c6e1b3691b968b009357dcac3349afbcd557fd8c"
---4) 1) "127.0.0.1"
------2) (integer) 6384
------3) "f915c7e6812a7d8fbe637c782ad261cd453022b2"
每一组结果由四个部分组成:起始槽节点、终止槽节点、主节点IP和端口加节点ID、从节点IP和端口加节点ID
客户端内部维护slots缓存表,并且针对每个节点维护连接池,
当集群规模非常大时,客户端会维护非常多的连接并消耗更多的内存
命令的执行
对于keys等多节点命令来说,不需要计算key的slot值,只需要遍历全部主节点然后执行命令即可
对于单一key和批量key命令操作来说,需要计算key的slot值,根据slot确定key所在的节点,然后在该节点上执行命令
常见的影响性能的因素
长耗时的命令
注意O(n)的命令
hgetall,lrange,smembers,zrange,sinter。
可以使用scan类的命令操作
用scan游标代替keys
注意list,set,sortedSet,hash的大小控制,一般控制在5000以内
开启Slow Log功能,进行监控
网络延迟
使用长连接或连接池,避免频繁创建销毁连接
批量数据操作,应使用Pipeline特性在一次交互中完成
持久化引发的延迟
rdb的频率
aof的rewrite
Swap引发的延迟
swap空间时,会阻塞Redis进程
正式环境应该避免这种情况。内存不够了就加内存
数据淘汰引发的延迟
大量key同时过期,应该错开key的失效时间
info的Redis信息
连接情况
info clients
connected_clients
连接数
blocked_clients
阻塞客户端数量
一般是执行了list数据类型的BLPOP或者BRPOP这种阻塞命令引起的
内存情况
info memory
used_memory
分配的内存总量,以字节(byte)为单位
used_memory_human
人类可读的格式返回 Redis 分配的内存总量
used_memory_rss
操作系统的角度,返回 Redis 已分配的内存总量
used_memory_peak
Redis 的内存消耗峰值(最大值)(以字节为单位)
不应超过maxmemory设置值,防止发生swap导致性能骤降
建议used_memory_peak的值与maxmemory的值有个安全区间,例如1G
used_memory_peak_human
used_memory_lua
Lua 引擎所使用的内存大小(以字节为单位)
mem_fragmentation_ratio
内存碎片率,可以重点关注下
=used_memory_rss / used_memory
在理想情况下, used_memory_rss 的值应该只比used_memory 稍微高一点儿
当 used > rss 时,表示发生内存Swap了
redis4.0之前的版本,这个问题除了重启也没什么很好的优化办法
redis4.0有一个主要特性就是优化内存碎片率问题(Memory de-fragmentation)
在redis.conf配置文件中有介绍即ACTIVE DEFRAGMENTATION:
碎片整理允许Redis压缩内存空间,从而回收内存。
这个特性默认是关闭的,
可以通过命令CONFIG SET activedefrag yes热启动这个特性。
mem_allocator
在编译时指定的, Redis 所使用的内存分配器。可以是 libc 、 jemalloc 或者 tcmalloc
持久化情况
info persistence
RDB 和 AOF 的相关信息
一般统计信息
info stats
信息
包括ops,key的命中率,fork阻塞时间deng
主从复制信息
info replication
各命令统计信息
info commandstats
库中key的统计信息
info keyspace
列出所有的统计信息
info all
列出默认的统计信息
info default
收藏
0 条评论
下一页