目录
Redis为什么这么快
1)基于内存;
2)单线程减少上下文切换,同时保证原子性;
(48条消息) 为什么NIO比BIO效率高_Fairy要carry的博客-CSDN博客
(48条消息) NIO学习_Fairy要carry的博客-CSDN博客
3)IO多路复用;
I/O多路复用,I/O就是指的我们网络I/O, 多路指多个TCP连接(或多个Channel),复用指复用一个或少量线程。串起来理解就是很多个网络I/O复用一个或少量的线程来处理这些连接。——>类似于之前NIO的原理
(48条消息) 什么是IO多路复用,理解IO多路复用_black bean的博客-CSDN博客_io多路复用
4)高级数据结构(如 SDS、Hash以及跳表等)。
(48条消息) 跳表(Skip List)_hakusai22的博客-CSDN博客_跳表
为何使用单线程
因为Redis是基于内存操作,所以说CPU这块不是很重要,所以用单线程
1)不需要各种锁的性能消耗
Redis 的数据结构并不全是简单的 Key-Value,还有 List,Hash 等复杂的结构,这些结构有可能会进行很细粒度的操作,比如在很长的列表后面添加一个元素,在hash当中添加或者删除一个对象。这些操作可能就需要加非常多的锁,导致的结果是同步开销大大增加(什么是同步开销——>就是一个一个来嘛,毕竟上了锁)。
2)单线程多进程集群方案
单线程的威力实际上非常强大,每核心效率也非常高,多线程自然是可以比单线程有更高的性能上限,但是在今天的计算环境中,即使是单机多线程的上限也往往不能满足需要了,需要进一步摸索的是多服务器集群化的方案,这些方案中多线程的技术照样是用不上的。
解决:
可以考虑多起几个Redis进程,Redis是key-value数据库,不是关系数据库,数据之间没有约束。只要客户端分清哪些key放在哪个Redis。
(40条消息) 为什么Redis是单线程的?_NABOAN的博客-CSDN博客_redis为什么是单线程
缓存三大问题解决方案
(48条消息) Redis常见问题-缓存击穿、缓存穿透、雪崩(白中白简单介绍版本)_Fairy要carry的博客-CSDN博客
缓存穿透:查询数据不存在
1)缓存空值
2)key 值校验,如布隆筛选器 ref 分布式布隆过滤器(Bloom Filter)详解(初版)
缓存击穿:缓存过期,伴随大量对该 key 的请求
1)互斥锁
2)热点数据永不过期
3)熔断降级——>可以利用Sentinel进行熔断降级处理
(48条消息) Sentinel_Fairy要carry的博客-CSDN博客_sentinel降级规则
缓存雪崩:同一时间大批量的 key 过期
1)热点数据不过期
2)随机分散过期时间
还可以使用超时处理,熔断降级等操作进行处理
先删后写还是先写后删
- 先删缓存后写 DB
产生脏数据的概率较大(若出现脏数据,则意味着再不更新的情况下,查询得到的数据均为旧的数据)。
比如两个并发操作,一个是更新操作,另一个是查询操作,更新操作删除缓存后,查询操作没有命中缓存,先把老数据读出来后放到缓存中,然后更新操作更新了数据库。于是,在缓存中的数据还是老的数据,导致缓存中的数据是脏的,而且还一直这样脏下去了。
解决:——>用1.canal保证数据一致性;2.或者我们可以用java代码更新sql后更新缓存(代码侵入性较强);3.还可以用mq,每次更新数据就发送消息给到我们的队列,然后查询服务进行更新进而更新我们的缓存数据
(48条消息) 缓存同步-Canal_Fairy要carry的博客-CSDN博客_canal 缓存
- 先写 DB 再删缓存
产生脏数据的概率较小,但是会出现一致性的问题;若更新操作的时候,同时进行查询操作并命中,则查询得到的数据是旧的数据。但是不会影响后面的查询。——>先写DB,再删缓存,这样我们缓存未命中就读取DB中内容,保证一致性
比如一个是读操作,但是没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后之前的那个读操作再把老的数据放进去,所以会造成脏数据。
如何保证Redis高并发
Redis 通过主从加集群架构,实现读写分离,主节点负责写,并将数据同步给其他从节点,从节点负责读,从而实现高并发。
(47条消息) Redis分片集群_Fairy要carry的博客-CSDN博客_redis集群分片
(47条消息) 分布式Redis_Fairy要carry的博客-CSDN博客
Redis如何保证原子性的
首先,Redis是单线程的,所以Redis提供的APi就是原子操作
但是我们再业务中并发情况下会出现数据不一致的情况下
(47条消息) 线程安全问题_Fairy要carry的博客-CSDN博客
解决:
1)使用 incr
、decr
、setnx
等原子操作;
2)客户端加锁;——>非常关键
3)使用 Lua 脚本实现 CAS 操作。
Redis集中数据类型对于的场景
)String:缓存、限流、分布式锁、计数器、分布式 Session 等。
2)Hash:用户信息、用户主页访问量、组合查询等。
3)List:简单队列、关注列表时间轴。
4)Set:赞、踩、标签等。
5)ZSet:排行榜、好友关系链表。
String类型的底层实现
String 为例,其底层实现就可以分为 int、embstr 以及 raw 这三种类型。这些特定的底层实现在 Redis 中被称为编码(Encoding),可以通过 OBJECT ENCODING [key]
命令查看
Redis 中所有的 key 都是字符串,这些字符串是通过一个名为简单动态字符串(SDS) 的抽象数据类型实现的。
struct sdshdr{
//记录buf数组中已使用字节的数量
//等于 SDS 保存字符串的长度
int len;
//记录 buf 数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
}
说说 SDS 带来的好处?
我们知道 Redis 是使用 C 语言写的,那么相比使用 C 语言中的字符串(即以空字符 \0
结尾的字符数组),自己实现一个 SDS 的好处是什么?
1)常数复杂度获取字符串长度
由于 len 属性的存在,我们获取 SDS 字符串的长度只需要读取 len 属性,时间复杂度为 O(1)。
2)杜绝缓冲区溢出
3)减少修改字符串的内存重新分配次数
4)二进制安全
5)兼容部分 C 字符串函数
演示数据类型的底层
String
通过OBJECT ENCODING key命令可以发现string数据类型的数据结构有embstr和int
再比如 list 数据类型:
我们说过Redis是根据C写的,但是对于Redis字符串就不是像C(即以空字符’\0’结尾的字符数组),它是自己构建了一种名为 简单动态字符串(simple dynamic string,SDS)的抽象类型,并将 SDS 作为 Redis的默认字符串表示
struct sdshdr{
//记录buf数组中已使用字节的数量
//等于 SDS 保存字符串的长度
int len;
//记录 buf 数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
}
①、常数复杂度获取字符串长度
由于 len 属性的存在,我们获取 SDS 字符串的长度只需要读取 len 属性,时间复杂度为 O(1)。而对于 C 语言,获取字符串的长度通常是经过遍历计数来实现的,时间复杂度为 O(n)。通过 strlen key 命令可以获取 key 的字符串长度。
②、杜绝缓冲区溢出
我们知道在 C 语言中使用 strcat 函数来进行两个字符串的拼接,一旦没有分配足够长度的内存空间,就会造成缓冲区溢出。而对于 SDS 数据类型,在进行字符修改的时候,会首先根据记录的 len 属性检查内存空间是否满足需求,如果不满足,——>会进行相应的空间扩展,然后在进行修改操作,所以不会出现缓冲区溢出。——>总结就是会先利用len进行判断,是否空间扩展
③、减少修改字符串的内存重新分配次数
C语言由于不记录字符串的长度,所以如果要修改字符串,必须要重新分配内存(先释放再申请),因为如果没有重新分配,字符串长度增大时会造成内存缓冲区溢出,字符串长度减小时会造成内存泄露。
而对于SDS,由于len属性和free属性的存在,对于修改字符串SDS实现了空间预分配和惰性空间释放两种策略:
1、空间预分配:对字符串进行空间扩展的时候,扩展的内存比实际需要的多,这样可以减少连续执行字符串增长操作所需的内存重分配次数。
2、惰性空间释放:对字符串进行缩短操作时,程序不立即使用内存重新分配来回收缩短后多余的字节,而是使用 free 属性将这些字节的数量记录下来,等待后续使用。(当然SDS也提供了相应的API,当我们有需要时,也可以手动释放这些未使用的空间。)
④、二进制安全
因为C字符串以空字符作为字符串结束的标识,而对于一些二进制文件(如图片等),内容可能包括空字符串,因此C字符串无法正确存取;而所有 SDS 的API 都是以处理二进制的方式来处理 buf 里面的元素,并且 SDS 不是以空字符串来判断是否结束,而是以 len 属性表示的长度来判断字符串是否结束。
一般来说,SDS 除了保存数据库中的字符串值以外,SDS 还可以作为缓冲区(buffer):包括 AOF 模块中的AOF缓冲区以及客户端状态中的输入缓冲区。后面在介绍Redis的持久化时会进行介绍。
链表
typedef struct listNode{
//前置节点
struct listNode *prev;
//后置节点
struct listNode *next;
//节点的值
void *value;
}listNode
通过多个 listNode 结构就可以组成链表,这是一个双向链表,Redis还提供了操作链表的数据结构:
typedef struct list{
//表头节点
listNode *head;
//表尾节点
listNode *tail;
//链表所包含的节点数量
unsigned long len;
//节点值复制函数
void (*free) (void *ptr);
//节点值释放函数
void (*free) (void *ptr);
//节点值对比函数
int (*match) (void *ptr,void *key);
}list;
Redis链表特性:
①、双端:链表具有前置节点和后置节点的引用,获取这两个节点时间复杂度都为O(1)。
②、无环:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL,对链表的访问都是以 NULL 结束。
③、带链表长度计数器:通过 len 属性获取链表长度的时间复杂度为 O(1)。
④、多态:链表节点使用 void* 指针来保存节点值,可以保存各种不同类型的值。
哈希字典
字典又称为符号表或者关联数组、或映射(map),是一种用于保存键值对的抽象数据结构。字典中的每一个键 key 都是唯一的,通过 key 可以对值来进行查找或修改。C 语言中没有内置这种数据结构的实现,所以字典依然是 Redis自己构建的。
Redis 的字典使用哈希表作为底层实现,关于哈希表的详细讲解可以参考我这篇博客。
Java数据结构和算法(十三)——哈希表 - YSOcean - 博客园 (cnblogs.com)
typedef struct dictht{
//哈希表数组
dictEntry **table;
//哈希表大小
unsigned long size;
//哈希表大小掩码,用于计算索引值
//总是等于 size-1
unsigned long sizemask;
//该哈希表已有节点的数量
unsigned long used;
}dictht
哈希表是由数组 table 组成,table 中每个元素都是指向 dict.h/dictEntry 结构,dictEntry 结构定义如下,table 中每个元素都是指向 dict.h/dictEntry 结构,dictEntry 结构定义如下
ypedef struct dictEntry{
//键
void *key;
//值
union{
void *val;
uint64_tu64;
int64_ts64;
}v;
//指向下一个哈希表节点,形成链表
struct dictEntry *next;
}dictEntry
key 用来保存键,val 属性用来保存值,值可以是一个指针,也可以是uint64_t整数,也可以是int64_t整数。
注意这里还有一个指向下一个哈希表节点的指针,我们知道哈希表最大的问题是存在哈希冲突,如何解决哈希冲突,有开放地址法和链地址法。这里采用的便是链地址法——>通过next这个指针可以将多个哈希值相同的键值对连接在一起(链表),用来解决哈希冲突。
②、解决哈希冲突:
这个问题上面我们介绍了,方法是链地址法。通过字典里面的 *next 指针指向下一个具有相同索引值的哈希表节点。
③、扩容和收缩:
当哈希表保存的键值对太多或者太少时,就要通过 rerehash(重新散列)来对哈希表进行相应的扩展或者收缩。具体步骤:
1、如果执行扩展操作,会基于原哈希表创建一个大小等于 ht[0].used*2n 的哈希表(也就是每次扩展都是根据原哈希表已使用的空间扩大一倍创建另一个哈希表)。相反如果执行的是收缩操作,每次收缩是根据已使用空间缩小一倍创建一个新的哈希表。
2、重新利用上面的哈希算法,计算索引值,然后将键值对放到新的哈希表位置上。
3、所有键值对都迁徙完毕后,释放原哈希表的内存空间。
什么是渐近式 rehash?
也就是说扩容和收缩操作不是一次性、集中式完成的,而是分多次、渐进式完成的。 如果保存在 Redis 中的键值对只有几个几十个,那么 rehash 操作可以瞬间完成,但是如果键值对有几百万,几千万甚至几亿,那么要一次性的进行 rehash,势必会造成 Redis 一段时间内不能进行别的操作。所以 Redis 采用渐进式 rehash。
跳表(zSet有序集合的底层实现之一)
跳表是一种随机化的数据结构,基于并联的链表,实现简单,插入、删除、查找的复杂度均为 O(logN)
简单来说就是层逐级递增,如果当前层有所需节点,那么下面的层也一定含有该节点,通过比较节点大小确定层数
①、搜索:从最高层的链表节点开始,如果比当前节点要大和比当前层的下一个节点要小——>那么则往下找,也就是和当前层的下一层的节点的下一个节点进行比较,以此类推,一直找到最底层的最后一个节点,如果找到则返回,反之则返回空。
②、插入:首先确定插入的层数,有一种方法是假设抛一枚硬币,如果是正面就累加,直到遇见反面为止,最后记录正面的次数作为插入的层数。当确定插入的层数k后,则需要将新元素插入到从底层到k层。
③、删除:在各个层中找到包含指定值的节点,然后将节点从链表中删除即可,如果删除以后只剩下头尾两个节点,则删除这一层。
zset 要支持随机的插入和删除,所以它不好使用数组来表示。我们先看一个普通的链表结构。
我们需要这个链表按照 score 值进行排序。这意味着当有新元素需要插入时,要定位到特定位置的插入点,这样才可以继续保证链表是有序的。通常我们会通过二分查找来找到插入点,但是二分查找的对象必须是数组,只有数组才可以支持快速位置定位,链表做不到,那该怎么办?
想想一个创业公司,刚开始只有几个人,团队成员之间人人平等,都是联合创始人。随着公司的成长,人数渐渐变多,团队沟通成本随之增加。这时候就会引入组长制,对团队进行划分。每个团队会有一个组长。开会的时候分团队进行,多个组长之间还会有自己的会议安排。公司规模进一步扩展,需要再增加一个层级 —— 部门,每个部门会从组长列表中推选出一个代表来作为部长。部长们之间还会有自己的高层会议安排。
跳跃列表就是类似于这种层级制,最下面一层所有的元素都会串起来。然后每隔几个元素挑选出一个代表来,再将这几个代表使用另外一级指针串起来。然后在这些代表里再挑出二级代表,再串起来。最终就形成了金字塔结构。 想想你老家在世界地图中的位置:亚洲- ->中国->安徽省->安庆市->枞阳县->汤沟镇->田间村->xxxx 号,也是这样一个类似的结构。
定位插入点时,先在顶层进行定位,然后下潜到下一级定位,一直下潜到最底层找到合适的位置,将新元素插进去。你也许会问,那新插入的元素如何才有机会「身兼数职」呢?跳跃列表采取一个随机策略来决定新元素可以兼职到第几层。
首先 L0 层肯定是 100% 了,L1 层只有 50% 的概率,L2 层只有 25% 的概率,L3 层只有 12.5% 的概率,一直随机到最顶层 L31 层。绝大多数元素都过不了几层,只有极少数元素可以深入到顶层。列表中的元素越多,能够深入的层次就越深,能进入到顶层的概率就会越大。
Set和Zset的区别
set 无需不可重复集合,应用去重、抽奖、共同关注、好友推荐等
zset 在set 集和基础上增加了一个分值(权重),用于排序、统计、区间查找
(40条消息) redis数据类型-set/zset_Ta来了Ta来了的博客-CSDN博客_set zset
总结
大多数情况下,Redis使用简单字符串SDS作为字符串的表示,相对于C语言字符串,SDS具有常数复杂度获取字符串长度,杜绝了缓存区的溢出,减少了修改字符串长度时所需的内存重分配次数,以及二进制安全能存储各种类型的文件,并且还兼容部分C函数。
通过为链表设置不同类型的特定函数,Redis链表可以保存各种不同类型的值,除了用作列表键,还在发布与订阅、慢查询、监视器等方面发挥作用(后面会介绍)。
Redis的字典底层使用哈希表实现,每个字典通常有两个哈希表,一个平时使用,另一个用于rehash时使用,使用链地址法解决哈希冲突。
跳跃表通常是有序集合的底层实现之一,表中的节点按照分值大小进行排序。
整数集合是集合键的底层实现之一,底层由数组构成,升级特性能尽可能的节省内存。
压缩列表是Redis为节省内存而开发的顺序型数据结构,通常作为列表键和哈希键的底层实现之一。
什么是RedisObject
我们知道,Redis 底层实现了很多高级数据结构,如简单动态字符串、双端链表、字典、压缩列表、跳跃表、整数集合等。然而 Redis 并没有直接使用这些数据结构来实现键值对的数据库,而是在这些数据结构之上又包装了一层 RedisObject(对象),也就是我们常说的五种数据结构:字符串对象、列表对象、哈希对象、集合对象和有序集合对象。
typedef struct redisObject {
// 类型
unsigned type:4;
// 编码,即对象的底层实现数据结构
unsigned encoding:4;
// 对象最后一次被访问的时间
unsigned lru:REDIS_LRU_BITS;
// 引用计数
int refcount;
// 指向实际值的指针
void *ptr;
} robj;
好处:
1.通过不同类型的对象,Redis 可以在执行命令之前,根据对象的类型来判断一个对象是否可以执行该的命令。
2.可以针对不同的使用场景,为对象设置不同的实现,从而优化内存或查询速度。
五种数据类型分别对应的实现
String
字符串对象的 encoding 有三种,分别是:int、raw、embstr。
1)如果一个字符串对象保存的是整数值,并且这个整数值可以用 long 类型标识,那么字符串对象会讲整数值保存在 ptr 属性中,并将 encoding 设置为 int。比如 set number 10086 命令。
2)如果字符串对象保存的是一个字符串值,并且这个字符串的长度大于 44 字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值,并将对象的编码设置为 raw。
3)如果字符串对象保存的是一个字符串值,并且这个字符串的长度小于等于 44 字节,那么字符串对象将使用 embstr 编码的方式来保存这个字符串。
(48条消息) Redis小于等于44个字节的字符串是embstr编码、大于44个字节是raw编码_henry_2016的博客-CSDN博客_embstr
embstr编码方式的优点
embstr 存储形式是这样一种存储形式,它将 RedisObject 对象头和 SDS 对象连续存在一起,使用 malloc 方法一次分配。embstr 最小占用空间为 19(16+3),而 64-19-1(结尾的\0)=44,所以 embstr 只能容纳 44 字节。
1)embstr 编码将创建字符串对象所需的内存分配次数从 raw 编码的两次降低为一次。
2)释放 embstr 编码的字符串对象只需要调用一次内存释放函数,而释放 raw 编码的字符串对象需要调用两次内存释放函数。
3)因为 embstr 编码的字符串对象的所有数据都保存在一块连续的内存里面,所以这种编码的字符串对象比起 raw ,编码的字符串对象能够更好地利用缓存带来的优势。
哈希对象(hash)
哈希对象的编码有 ziplist 和 hashtable 两种(数组+链表)。当哈希对象保存的键值对数量小于 512,并且所有键值对的长度都小于 64 字节时,使用压缩列表存储;否则使用 hashtable 存储。
列表对象(list)
列表对象的编码有 ziplist 和 linkedlist 两种。当列表的长度小于 512,并且所有元素的长度都小于 64 字节时,使用压缩列表存储,否则使用 linkedlist 存储。
集合对象(set)
列表对象的编码有 intset 和 hashtable 两种。当集合的长度小于 512,并且所有元素都是整数时,使用整数集合存储;否则使用 hashtable 存储。
有序集合对象(sort set)
有序集合对象的编码有 ziplist 和 skiplist 两种。当有序集合的长度小于 128,并且所有元素的长度都小于 64 字节时,使用压缩列表存储;否则使用 skiplist 存储。
intset(整数集合)和 ziplist(压缩列表)主要是为节省内存而设计的内存结构,它的优点就是节省内存,但缺点就是比其他结构要消耗更多的时间,所以 Redis 在数据量小的时候使用整数集合存储。
Redis的数据过期策略
如何设置 Redis 中数据的过期时间?
1)expire key time (以秒为单位)–这是最常用的方式
2)setex(String key, int seconds, String value) --字符串独有的方式策略
-
常见的过期策略
1.定时删除
在设置 key 的过期时间的同时,为该 key 创建一个定时器,让定时器在 key 的过期时间来临时,对 key 进行删除。
优点:——>对内存友好,是我们主动删除,内存进行释放
缺点:——>1.对CPU不友好,再过期键较多时候,我们主动去删除会耗费CPU时间,对服务器的响应时间和吞吐量造成影响。
2.定时器的创建耗时,若为每一个设置过期时间的key创建一个定时器(将会有大量的定时器产生),性能影响严重
(48条消息) 系统性能相关的几个指标——QPS、吞吐量详解_程序员资料站的博客-CSDN博客_qps 吞吐量
2.定期删除 (主动)
每隔一段时间执行一次删除(在 redis.conf 配置文件设置,1s 刷新的频率)过期 key 操作
优点:限制了删除执行的频率一次减少删除操作对CPU的影响
缺点:难以确定删除操作的时长,另外,在获取某个键时,如果某个键的过期时间已经到了,但是还没执行定期删除(类似超时商品过期不删除),那么就会返回这个键的值,这是业务不能忍受的。
2、惰性删除【被动】
key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null。(不会删除过期key,而是获取它的时候判断是否过期再删除)
优点:对CPU友好,只会在使用该键的时候进行过期检查,对CPU时间的占用是比较少的。
缺点:对内存不友好,若大量的键已经过期了,但是没有被获取过,那么这些键永远不会删除,可能造成内存泄漏。
Redis使用的过期删除策略
redis的过期删除策略就是:惰性删除和定期删除两种策略配合使用。
惰性删除:在进行get或 setnx等操作时,先检查 key是否过期,若过期,删除key,然后执行键不存在的操作;未过期则不操作,继续执行原有的命令。
定期:遍历每个数据库,(就是redis.conf配置的database 数量,默认为16),从库中随机取出20个键检查是否过期,若没有一个过期键,继续遍历下一个库,若存在键过期,则删除该键。
定时删除的运行频率:在redis 2.6版本中,规定每秒运行10次,大概100ms运行一次。在Redis2.8版本后,可以通过修改配置文件redis.conf 的 hz 选项来调整这个次数。(建议不要超过100,否则对CPU压力大)
持久化文件的处理
(48条消息) 分布式Redis_Fairy要carry的博客-CSDN博客
Redis中的内存淘汰机制
场景:当内存空间不足的时候,为了保证命中率,就会和我们的操作系统中的页面置换算法类似,选择一种数据淘汰策略
1.参数设置
我们的redis数据库的最大缓存、主键失效、淘汰机制等参数都是通过配置文件来配置的。这个文件是我们的redis.config文件,我们的redis装在了/usr/local/redis目录下,所以配置文件也在这里。首先说明一下我使用的redis是5。也是目前最新的版本。
关键的配置就在最下面,我们可以设置多少个字节。默认是关闭的。
2.内存淘汰策略
(1)volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。
(2)volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。
(3)volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。
(4)volatile-lfu:从已设置过期时间的数据集挑选使用频率最低的数据淘汰。
(5)allkeys-lru:从数据集中挑选最近最少使用的数据淘汰
(6)allkeys-lfu:从数据集中挑选使用频率最低的数据淘汰。
(7)allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
(8) no-enviction(驱逐):意思是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用 no-enviction 策略可以保证数据不被丢失。
3.淘汰机制的实现
1、删除失效主键
既然是淘汰,那就需要把这些数据给删除,然后保存新的。Redis 删除失效主键的方法主要有两种:
(1)消极方法(passive way)——>在主键被访问时->如果发现它已经失效,那么就删除它。redis在实现GET、MGET、HGET、LRANGE等所有涉及到读取数据的命令时都会调用 expireIfNeeded,它存在的意义就是在读取数据之前先检查一下它有没有失效,如果失效了就删除它。
(2)积极方法(active way),周期性地探测->发现失效就删除。消极方法的缺点是,如果key 迟迟不被访问,就会占用很多内存空间,所以才有积极方式。
像消极方法的话,如果不去访问key,那就永远删不了
(3)主动删除:当内存超过maxmemory限定时,触发主动清理策略,该策略由启动参数的配置决定
Redis6为何引入多线程?
Redis集群模式
-
主从模式(没有监控master的功能)
(40条消息) Redis主从复制_Fairy要carry的博客-CSDN博客
和 MySQL 需要主从复制的原因一样,Redis 虽然读写速度非常快,但是也会产生性能瓶颈,特别是在读压力上,为了分担压力,Redis 支持主从复制。Redis 的主从结构一主一从,一主多从或级联结构,复制类型可以根据是否是全量而分为全量同步和增量同步。
-
哨兵模式
- (40条消息) Redis集群(初学)_Fairy要carry的博客-CSDN博客
- (40条消息) 手把手一步一步的搭建Redis集群(超级详细)_涟漪海洋的博客-CSDN博客_redis集群搭建
在主从复制实现之后,如果想对 master 进行监控,Redis 提供了一种哨兵机制,哨兵的含义就是监控 Redis 系统的运行状态,通过投票机制——>从 slave 中选举出新的 master 以保证集群正常运行。
还可以启用多个哨兵进行监控以保证集群足够稳健,这种情况下,哨兵不仅监控主从服务,哨兵之间也会相互监控。
然后我们这个Sentinel哨兵会充当Redis客户端发现服务来源,当集群发生故障转移时,哨兵会讲最新消息推送给Redis客户端
缺点:复制延迟
因为写操作都是在之前的老主机上,要同步数据到新的主机上会有一定的时间消耗;
-
Cluster 集群模式(分片集群)
(40条消息) Redis分片集群_Fairy要carry的博客-CSDN博客_redis 分组存储
如何优化热点key
热 key 带来的问题:请求到的分片过于集中,超过单台 Server 的性能极限。
解决方案:
1)服务端缓存:即将热点数据缓存至服务端的内存中;
2)备份热点Key:即将热点Key+随机数,随机分配至 Redis 其它节点中。