Redis
2025-01-10 11:06:11 1 举报
AI智能生成
Redis
作者其他创作
大纲/内容
缓存
旁路型缓存
服务先到缓存中读取数据,存在直接返回
如果缓存中没有数据,就去数据库中读取
服务再将从数据库中读取到的数据同步给缓存
过期数据的删除策略
定时删除
当key设置过期时间到达,就删除key
优点:节约内存
缺点:对cpu实时处理压力影响,对redis执行效率有影响
惰性删除
数据到达过期时间,先不做删除,直到下次访问该数据时,在做删除
优点:节约cpu资源
缺点:内存占用过大
定期删除
每个一段时间就对一些设置了过期时间的键进行检查没删除其中过期的(周期性轮询redis库中的时效性数据,采取随机抽取的策略,利用过期数据占比方式控制删除频度),该策略是惰性删除和定时删除的一个这种,既避免了占用大量cpu资源又避免了出现大量过期键不被清楚占用大量内存情况
Redis 选择「惰性删除+定期删除」这两种策略配和使用,默认每10秒抽取一次,一次抽取20个,如果过期数大于25%,则会重复抽取,默认不会超过 25ms
持久化机制
aof
过期删除之后,会向AOF文件中追加一个(append)的一条del命令,来显式的记录该key已被删除
RDB
生成时候过期的key不不保存到rdb中、主节点载入时检查过期的key不载入、从节点不检查
内存淘汰策略
默认情况下,如果不进行内存淘汰,一但内存被写满了,redis直接报错(淘汰的都是在过期范围内的数据)
设置过期时间的数据
valotitle-ttl:在进行筛选时,根据过期时间先后顺序进行一个删除,越早过期的越先被删除
valotitle-random:在设置了过期时间的键值对中,进行随机删除
valotitle-LRU:会使用LRU算法筛选设置了过期的键值对
valotitle-LFU:会使用LFU算法筛选设置了过期的键值对
所有数据淘汰策略
allkeys-random:从所有键值对中随机筛选并删除
allkeys-LRU:从所有键值对中采用LRU算法进行筛选删除
allkeys-LFU:从所有键值对中采用LFU算法进行筛选删除
LRU算法
多久没用
LFU算法
用了多少次
缓存存在问题
缓存雪崩
产生原因:大量的请求在redis缓存中读取不到数据,全部请求到数据库,导致数据库压力激增,数据库崩溃
大量的key 同时过期
大量的key 同时过期
原因1∶缓存中有大量数据同时过期
1.页面静态化处理数据:对于不经常更换的数据,生成静态页
⒉避免大量数据同时过期:为商品过期时间追加一个随机数,在一个较小的范围内(1~3分钟)。
3.构建多级缓存架构: redis缓存+nginx缓存+ehcache缓存
4.延长或取消热度超高的数据过期时间
5.服务降级
不同的数据采取不同的处理方式。
原因2: redis实例故障
灾难预警:监控redis服务器性能指标,包括数据库服务器性能指标,CPU、内存、平均响应时间、线程
数等
数等
限流处理
缓存击穿
对某个访问频繁热点数据的请求。主要发生在热点数据失效
预先设定:电商双11,商铺设定几款是主打商品,延长过期时间
实时监控:监控访问量,避免访问量激增
定时任务:启动任务调度器,后台刷新数据有效期
分布式锁:可防止缓存击穿,但会有性能问题
缓存穿透
要访问的数据在redis中不存在,在数据库中也不存在。
1.缓存空值或缺省值
2.使用布隆过滤器,快速判断数据是否存在
3.在请求入口前端进行请求检测
4.实时监控
5.key加密
Bloom 过滤器
- 通过K个哈希函数计算该数据,返回K个计算出的hash值
- 这些K个hash值映射到对应的K个二进制的数组下标
- 将K个下标对应的二进制数据改成1。
优点
- 时间复杂度低,增加和查询元素的时间复杂为O(N),(N为哈希函数的个数,通常情况比较小)
- 保密性强,布隆过滤器不存储元素本身
- 存储空间小,如果允许存在一定的误判,布隆过滤器是非常节省空间的(相比其他数据结构如Set集合)
缺点
- 通过K个哈希函数计算该数据,返回K个计算出的hash值
- 这些K个hash值映射到对应的K个二进制的数组下标
- 将K个下标对应的二进制数据改成1。存在误判
通过误判率来减少误判
- 误判率可以通过fpp参数进行调节
- fpp越小,需要的内存空间就越大:0.01需要900多万位数,0.03需要700多万位数。
- fpp越小,集合添加数据时,就需要更多的hash函数运算更多的hash值,去存储到对应的数组下标里。(忘了去看上面的布隆过滤存入数据的过程)
布隆过滤器虽然可以快速判断元素可能存在
在Redis中使用布隆过滤器来快速判断元素的可能存在性,如果布隆过滤器判断元素可能存在,则再查询哈希表(或红黑树)获取更高准确性。
调整过滤器参数
调整过滤器参数
结合其他数据结构
动态调整过滤器
在理论上,布隆过滤器无法完全避免误判。这是因为布隆过滤器的设计目标是牺牲一定的准确性来换取高效的存储和查询速度,它使用多个哈希函数将元素映射到位数组中,并允许哈希冲突的存在。
redis数据类型
Sting
普通的 k-v 可以存储字符串、整数或浮点数
商品编号、点赞数、储存配置项、计数器
nx
setIfAbsent 方法用于创建新的键值对,只有在键不存在的情况下才会进行设置操作。
如果键已经存在,不会进行任何操作,如果值为空会set
如果键不存在则新增
如果键不存在则新增
redisTemplate.opsForValue().setIfAbsent("name", "John");
过期时间
px 设置单位为毫秒
set key value px 10000
ex 设置单位为秒
set key value ex10
xx
setIfPresent 方法用于更新键的值,只有在键已经存在的情况下才会进行更新操作。
如果键已经存在,那么会将键的值更新为指定的新值。
如果键不存在,那么不会进行任何操作,也不会设置新的键值对。
如果键不存在,那么不会进行任何操作,也不会设置新的键值对。
redisTemplate.opsForValue().setIfPresent("name", "John");
简单动态字符串(SDS)
编码格式为 embstr 或者 raw ,embstr分配一次内存和redisobject是连续的空间,但是只能读不能更改,如果更改的话会将编码格式改为raw,raw的存储也是sds但是和redisobject不是连续的空间
valueOperations
this.valueOperations = redisTemplate.opsForValue();
设置分布式锁 + 过期时间
set key value nx ex 10
Hash(hash 压缩列表)
是一种键值对的集合,其中的键和值都是字符串类型
对存储结构化数据的支持
可以将多个字段和对应的值存储在一个Hash键下
可以将多个字段和对应的值存储在一个Hash键下
# 设置用户信息
HSET user:1 name "Alice"
HSET user:1 age 25
HSET user:1 gender "female"
HSET user:1 name "Alice"
HSET user:1 age 25
HSET user:1 gender "female"
存储用户对象、缓存数据、存储对象的属性等。
一般对象用String+json存储,频繁变化的属性可以考虑抽出来用hash类型存储
底层使用哈希表实现,哈希表是一种键值对的散列表,用于存储字段和值的映射关系。
压缩列表:哈希类型元素小于512,每个元素的大小小于64字节,使用压缩列表 7.0废弃交给listpack
HashOperations
this.hashOperations = redisTemplate.opsForHash();
List(压缩列表 双向链表)
是一个有序的可重复的集合,其遵循FIFO原则先进先出
有序列表
例如:某微博大V粉丝、文章的评论列表
消息队列(为了解决不清楚是否有数据来不停的循环获取,引入BRPOP和BLPOP,为了确保不处理重复信息
首先在创建的时候就会在value加上全局唯一id,之后将处理过的信息记录下来,判断要处理的信息是否是已
经处理过的,为了保证消息可靠性,避免拿去数据后消费者故障或宕机不知道消息是否处理完成,提供了BRPOPLPUSH
让消费者拿数据的同时,再将数据插入到另一个list中做备份
缺点:不支持多个消费者消费同一条数据,不支持消费组
首先在创建的时候就会在value加上全局唯一id,之后将处理过的信息记录下来,判断要处理的信息是否是已
经处理过的,为了保证消息可靠性,避免拿去数据后消费者故障或宕机不知道消息是否处理完成,提供了BRPOPLPUSH
让消费者拿数据的同时,再将数据插入到另一个list中做备份
缺点:不支持多个消费者消费同一条数据,不支持消费组
压缩列表
元素个数小于512,每个元素值小于64使用压缩列表,可以设置参数
双向链表实现
支持正向 反向双重查找
3.2之后只使用quicklist,替代压缩列表和双向链表
通过lrange 命令,就是从某个元素开始读取多少个元素,可以基于List实现分页查询
ListOperations
this.listOperations = redisTemplate.opsForList()
Set(hash 整数集合)
无序的天然去重的集合,K-Set 提供了交集并集等一系列直接操作集合的方法
对于求共同好友、共同关注等功能实现特别方便
SetOperations
setOperations.intersect(key1, key2);
并集是.union
并集是.union
this.setOperations = redisTemplate.opsForSet();
底层使用哈希表实现,与 Hash 类型类似,但 Set 只存储键,不存储值。
当集合元素都是整数并且数量小于512,会使用整数集合作为底层数据结构
SetOperations
this.setOperations = redisTemplate.opsForSet();
SortedSet(跳表 压缩列表)
类似于java中的TreeSet 是set的排序版 此外还支持优先排序,维护一个score的参数来实现
排行榜、实时热门数据、带权重的任务队列
底层使用跳跃表和哈希表实现,跳跃表用于保持元素的有序性,哈希表用于存储元素和分数的映射关系。
底层由压缩列表或跳表实现,如果集合元素小于128,并且每个元素的大小小于64字节,采用压缩列表,否则采用跳表 7.0将压缩列表废除,交给listpack
ZSetOperations
this.setOperations = redisTemplate.opsForZSet()
bitmap
用于处理位图,二进制数据,可以用来统计是与否的情况,例如:是否签到,是否打卡等
值为0或1,一个值占1bit位,1byte=8bit
底层是String
常用命令:
setbit
getbit
bitcount
例子:
HyperLogLog
2.8.9新增的数据类型,用于统计基数的数据集合类型(集合中不重复的个数),但是是基于概率完成的,标准误算率是0.81%
优点:可以通过很小的空间完成很大的基数计算,12k内存就可以计算将近2^64个不同的基数
应用场景:百万级网页uv计数,但是可能会有误差
GEO
3.2新增 基于位置信息服务的应用,提供了经纬度设置、查询、范围查询、距离查询、经纬度hash等操作
直接使用了sorted set 集合类型
操作指令
geoadd< key>< longitude><latitude>< member> [longitude latitude member...]︰添加地理位置(经度纬度名称)
geopos < key>< member>[member...]:获取指定的位置坐标值
geodist<key>< member1>< member2>:获取两个位置之间的直线距离。单位:m米km千米ft英尺mi英里
georadius< key><longitude><latitue> radius [m | km / fm| mi],以经定的经纬度做为中心,找出给定半径内的位置
Stream
5.0新增,专门为消息队列设计的数据类型
会在创建的时候自动生成全局唯一的id,可以设置消费组,设置同样的开始读取位置,就可以实现不同消费组读取同一数据,内部使用 PENDING List 自动保存消息,使用 XPENDING 命令查看消费组已经读取但是未被确认的消息,消费者使用 XACK 确认消息
redis数据结构
redisobject
type
标志是何种类型的对象
encoding
使用了哪种编码
prt
只想底层数据结构的指针
SDS
len 字符串长度
alloc分配的空间长度
在修改字符串时会计算剩余的空间大小,判断空间是否满足修改需求,如果不满足会自动将 SDS 的空间扩展至所需的大小,然后才执行实际的修改操作,所以不会缓冲区溢出
flags sds类型
buf[]字节数组
压缩列表
由连续内存块组成的顺序型数据结构,类似于数组
哈希表
键值对(key-value)的数据结构
链式
渐进式rehash
有两个hash表,第二个表一般是第一个的二倍,数据线存进表一,
当达到rehash条件时会逐步的将数据转移到表二中,将表二变为表一,再重新分配表二
当达到rehash条件时会逐步的将数据转移到表二中,将表二变为表一,再重新分配表二
触发 rehash
当负载因子大于等于 1 ,并且没有执行 RDB 快照或没有进行 AOF 重写的时候,就会进行 rehash 操作
当负载因子大于等于 5 时,会强制进行 rehash 操作
整数集合
连续的内存空间,但是有数据位数限制
整数集合的升级操作
例如要在int16_t类型的数组添加int32_t的数据,就会先将内存扩容,将数组改为int32_t类型,
将数据重新分配内存,保持有序,之后在执行添加操作,整数整合不支持降级
将数据重新分配内存,保持有序,之后在执行添加操作,整数整合不支持降级
跳表
优势,可以支持平均O(logN)复杂度查找
quicklist
双向链表+压缩列表
在向 quicklist 添加一个元素的时候,不会像普通的链表那样,直接新建一个链表节点。而是会检查插入位置的压缩列表是否能容纳该元素,如果能容纳就直接保存到 quicklistNode 结构里的压缩列表,如果不能容纳,才会新建一个新的 quicklistNode 结构
listpack
连续内存,但是不记录上一个元素的大小,解决压缩列表的连锁更新
持久化
持久化的意义
数据恢复,保证数据不丢失
RDB
工作原理:
对redis中的数据周期性持久化,生成redis内存中的一份完整快照
命令
每次命令执行都会将所有redis内存快照到一个新的rdb文件里,并覆盖原有rdb快照文件。
自动
配置文件
save 900 1
如果 900s 之内对value 做一次修改操作 就会生成dump.rdb文件
save 300 10
save 60 1000
关闭RDB只需要将所有的save保存策略注释掉即可
手动
save
线程阻塞
阻塞客户端命令
bgsave(写时复制(COW)机制)
子进程
异步fork
需要fork子进程,消耗内存
配置自动生成rdb文件后台使用的是bgsave方式。
打开是数据的二进制文件
优点
RDB会生成多个数据文件,每个文件都代表了某一时刻的redis数据。多个数据文件的方式 非常适合冷备
通过写时复制的机制,影响非常小,保持高性能,
由于是压缩的数据二进制文件,所以重启和恢复都比较快,
在重启的时候可以对rdb文件做备份,然后直接复制到重启后的机器
在重启的时候可以对rdb文件做备份,然后直接复制到重启后的机器
RDB是有磁盘寻址的
缺点
发生故障的时候,因为RDB是点时性存储的,这时候一旦redis宕机会丢失一个单位时间间隔的的数据
如果数据比较大 那么在fork的时候,可能会影响客户端提供的服务暂停数毫秒
AOF
工作原理
对每一条写入的命令作为日志,以append-only 的模式(先执行redis指令,把数据写到内存,然后去记录日志,最后写入数据库),
在redis重启的时候,可以通过回放AOF日志中写入的指令来重构数据
在redis重启的时候,可以通过回放AOF日志中写入的指令来重构数据
为什么使用写后日志?
避免检查开销,向AOF中记录日志是不做检查的,如果写前记录,很有可能将错误的指令记录,redis恢复日志时,就会出错
命令
appendonly yes
appendfsync always:每次有新命令追加到 AOF 文件时就执行一次 fsync ,非常慢,也非常安全。
appendfsync everysec:每秒 fsync 一次,足够快,并且在故障时只会丢失 1 秒钟的数据。
appendfsync no:从不 fsync ,将数据交给操作系统来处理。更快,也更不安全的选择。
推荐(并且也是默认)的措施为每秒 fsync 一次, 这种 fsync 策略可以兼顾速度和安全性。
如果要是多次set 一样的数据
auto-aof-rewrite-min-size 64mb //aof文件至少要达到64M才会自动重写,文件太小恢复速度本来就很快,重写的意义不大
#auto-aof-rewrite-percentage 100 //aof文件自上一次重写后文件大小增长了100%则再次触发重写
手动 bgrewriteaof重写AOF
AOF重写redis会fork出一个子进程去做(与bgsave命令类似),不会对redis正常命令处理有太多影响
bgrewriteaof触发重写,判断当前是否有重写进行,如果有,则等待重写结束后执行
主进程fork出一个子进程,执行重写操作,保证主进程不阻塞,可以继续执行命令
子进程循环遍历redis内存中的所有数据到临时文件,客户端的写请求同时写入aof缓冲区和aof重写缓冲区。保证原AOF文件完整以及新的AOF文件生成期间的新的数据修改操作不会丢失
子进程写完新的AOF文件后,向主进程发送信号,主进程更新统计信息
主进程把aof重写缓冲区中的数据写入到新的AOF文件
用新的AOF文件覆盖掉旧的AOF文件,完成AOF重写
优点
AOF 会更好的保护数据丢失,默认是每隔一秒通过后台的线程
(对客户端没有影响,相当于fork)执行一次Fsync 操作,最多丢失1秒的数据,
(对客户端没有影响,相当于fork)执行一次Fsync 操作,最多丢失1秒的数据,
append-only的模式写入,所以对任何的磁盘寻址没有开销,写入的性能极高,文件不易破损,即使文件破损也好恢复
数据过大也不会影响客户端的读写,因为在rewrite log的时候,会对其中的指导进行压缩 创建一个需要恢复数据的最小日志出来
AOF日志文件的命令可读性较高,比如 不小心flushall了可以立即的copy一个aof文件,
然后删除最后的一条,然后再把AOF放回去,就会自动恢复了
然后删除最后的一条,然后再把AOF放回去,就会自动恢复了
缺点
对于同一份数据来说的话,aof文件比rdb的要大
AOF开启后 写入的QPS会比RDB时期降低,因为默认fsync一秒一次
由于是通过Merge的方式回会放的,数据恢复的方式相比于RDB不是绝对的安全,容易出现bug ,所以AOF为了避免一定的bug 每次rewrite 并不是基于旧指令日志进行merge 而是基于当时的内存中的数据进行指令重新构建
如何选择?
redis启动时如果既有rdb文件又有aof文件则优先选择aof文件恢复数据,因为aof一般来说数据更全一点。
Redis 4.0 混合持久化
必须先开启aof
aof-use-rdb-preamble yes
AOF在重写时,不再是单纯将内存数据转换为RESP命令写入AOF文件:
- 而是将重写这一刻之前的内存做RDB快照处理,并且将RDB快照内容和增量的AOF修改内存数据的命令存在一起,都写入新的AOF文件,新的文件一开始不叫appendonly.aof,等到重写完新的AOF文件才会进行改名,
- 覆盖原有的AOF文件,完成新旧两个AOF文件的替换。
- 于是在 Redis 重启的时候,可以先加载 RDB 的内容,
- 然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,因此重启效率大幅得到提升。
- 写crontab定时调度脚本,每小时都copy一份rdb或aof的备份到一个目录中去,仅仅保留最近48小时的备份
- 每天都保留一份当日的数据备份到一个目录中去,可以保留最近1个月的备份
- 每次copy备份的时候,都把太旧的备份给删了
- 每天晚上将当前机器上的备份复制一份到其他机器上,以防机器损坏
Redis实现高并发
集群
概念
多个主从节点结构的集合
特点
集群中可以有多个master,每个master可以保存不同数据
每个master都可以有多个从节点
master之间通过ping监测彼此之间的健康状态
客户端请求可以访问集群任意节点,最终会被穿法到正确节点
解决问题
海量数据存储
高并发写
数据存储方式
散列插槽
Redis会把每一个master接待你映射到0~16383共16384个插槽(hash slot)上,查看集群信息时就能看到
数据的key不是与节点绑定的,而是与插槽绑定的,redis会根据key 通过CRC16哈希算法计算插槽值
为什么不绑定节点上呢?
因为,如果节点宕机了,那么值就没了,绑定在插槽上,即使节点宕机了,还可以把插槽分配给其他节点
如何计算key的插槽值?
key中包含“{}”,且“{}”中至少包含一个字符,那么“{}”中的为有效部分
key中不包含“{}”的,整个key都是有效部分
如何控制同一类数据在同一Redis节点呢?
可以保证key中的有效部分相同,例如:“{aaa}”
故障迁移
情况:当有master节点需要维护,同时需要选一个新的master节点,这就需要手动数据迁移
步骤
在从节点执行cluster failover命令,此时从节点会告诉主节点拒绝任何客户端请求
master节点返回当前数据的offset给从节点
从节点等待数据offset与master一致
开始故障转移
从节点标记自己为master并广播
哨兵
作用
监控集群的健康状态
自动故障恢复,将一个从节点升级为主节点
通知:当集群实例发生故障时,会将最新消息推送给Redis客户端,也就是Redis不是直接访问集群的,而是通过哨兵找到集群的地址
检查集群是否掉线
主观掉线:如果某sentinel节点发现某实例在规定时间未响应,则认为主管下线
客观下线:若超过指定数量的sentinel都认为该实例主管下线,则该实例客观下线。指定数量最好超过sentinel实例数量的一半
选举新的主节点
首先判断从节点与主节点断开时间长短,超过指定值则会排除该从节点
然后判断从节点的slave-priority值,越小优先级越高,如果是0则永远不参与选举(默认都是一样的,没啥用)
如果slave-priority一样,则判断从节点的offset值(最关键),越大说明数据越新,优先级越高
最后是判断从节点的运行id大小(创建时自动生成)越小优先级越高
实现主从切换
选中slave1为新的主节点后,,会让他执行 slaveof no one命令,让该节点成为master
哨兵会给其他所有从节点发送命令,让其执行 slaveof 192.168.... 7002(地址-端口号)命令,让其成为7002的从节点,开始从新的主节点上同步数据
最后,哨兵会将故障的主节点标记为从节点,当故障的主节点恢复后会自动成为新的master的slave
主从同步机制
全量同步
从节点首先向主节点请求数据(执行replicaof命令,建立连接)
主节点首先判断是否是第一次请求
是第一次请求,返回主节点的全部数据(数据版信息)
从节点保存版本信息
主节点执行bgsave,生成RDB文件
主节点向从节点发送RDB文件
从节点清空本地数据,加载RDB文件
在记录RDB期间,新生成的命令将会放在一个repl_baklog缓冲区中
将缓冲区中命令发送给从节点,从节点执行接收到的命令
如何判断从节点是否第一次同步
Replication Id:简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的repld,slave则会继承mster节点的repld
offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新
因此,从节点做数据同步,必须向主节点生命自己的Replication Id和offset,主节点方可判断如何同步数据
增量同步
从节点提交自己的offset到主节点
主节点获取repl_backlog中从offset之后的命令给从节点,达到增量同步
注意:repl_backlog是一块环形区域,大小有限,如果从节点掉线太久,repl_backlog已经执行了一周那么,主节点将无法找到offset后的命令,所以会做全量同步。
0 条评论
下一页