Redis
2021-11-06 12:17:57 0 举报
AI智能生成
基于Redis相关的思维导图,平常自己收集的笔记。
作者其他创作
大纲/内容
Redis
数据结构
基础数据结构
容器型数据结构的通用规则
如果容器不存在,那么就创建一个,在进行操作
如果容器里元素没有了,那么立即删除元素,释放内存。
String
当字符串长度小于1M前,成倍扩容,大于1M后,每次按照1M扩容,最大为512M
每次修改后会使过期时间失效
sds[Simple Dynamic String] 简单动态字符串
len
记录buf中数组已经使用字节的数量,等于SDS所保存字符串的长度
free
记录buf数组中未使用字节的数量
char buf[]
字节数组,用于保存字符串
优势
1. 记录len的值,在查询的时候效率会更高。
2. 惰性释放空间,当申请的空间不再被使用的时候,不是立刻释放空间,而是在SDS中的free属性将这些字节记录下来,等待将来使用。
https://www.jianshu.com/p/c111d2a87ec4
超链接
内部编码
int
8个字节的长整形
embstr
小于等于39个字节的字符串
raw
大于39个字节的字符串
List
List类型是一个链表结构集合,并且是有序的集合,其值是可重复的
List类型类似数组,所以有下标,可以根据指定下标操作数据
既可以做[栈-前进后出]使用,也可以做队列【先进先出】使用,所以常用来做异步队列使用
Hash
Hash类型是String的key,value的映射表。
hash可以对用户结构中的每个字段单独存储。这样当我们需要获取用户信息时可以进行部分获取
但是hash结构的存储消耗要高于单个字符串
ziplist
当hash类型元素个数小于hash-max-ziplist-entries时(默认512),同时所有值都小于hash-max-ziplist-value配置时(默认64字节)。
hashtable
当hash类型无法满足ziplist的条件时,会采用hashtable作为hash内部实现。因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)
Set
相当于Java中的HashSet,它内部的键值对是无序的唯一值,可以用来做去重处理
可以对Set实施交集(inter--InterStore)、并集(Union-UnionStore)和差集(Diff-DiffStore)的操作
Zset
Zset是一个有序集合,一方面它是一个set,保证了内部value的唯一性,另一方面它可以给每个Value赋予一个Score,代表这个value的排序权重。
场景
根据score,可以排序分值(学生成绩)
时间(心跳在线时间或者用户关注时间)
作为延时队列
限流
字符串
HyperLogLog
HyperLogLog 提供不精确的去重计数方案(统计UV),标准误差为81%
pfmerge : 用于将多个pf计数值累加在一起形成一个新的pf值(合并多个页面的UV值)
无法知道某一个值是不是已经在HyperLogLog结构里面(仅提供添加、统计,合并三个操作)
布隆过滤器
可以理解为一个不怎么精确的Set结构,通过add前用bf.reserver指令显示创建,设定参数提高准确率
initial_size参数表示预计放入的元素数量,initial_size参数设置错误率,错误率越低,需要的空间越大。
特性
如果结果说某个值存在时,这个值可能不存在;
但是当它说不存在时,那就肯定不存在。
使用场景
解决去重问题
看新闻,推荐新内容,不能包含老内容。
缓存穿透
GeoHash
地理坐标计算
附件的人
stream
redis 5.0 版本新出现的数据结构;可以支持广播模式的可持久化的消息队列
应用层面场景
分布式锁
安全性
任何时刻只有一个客户端可以获取到锁[排他性]
避免死锁
即使客户端崩溃,不会影响锁的释放
容错性
只要redis集群的大部分节点存活,客户端就可以进行加锁解锁操作
使用setnx ex来获取锁,并需要设置过期时间来防止业务代码异常,锁没有释放的情况
set liukx "axs" NX EX 5
其中V最好是全局唯一值,这个随机数在释放锁时保证操作的安全性
避免极端情况:客户端A抢到锁,但超时的时候,业务没有执行完,客户端B抢到锁,客户端A执行完业务,释放锁的时候,释放的是客户端B的锁。
1. 分布式锁的过期时间到期了,但是业务没有执行完的问题
redission
watch dog 自动延期机制
客户端获取到锁,启动一个线程watch dog,比如默认的时间是30秒,在30-10=20秒的时候,就会进行一次续期,将锁的超时时间延长
客户端宕机
30秒之后,自动解开了。
正常情况
业务处理完成,自行del掉
方法设置
internalLockLeaseTime
配置文件
lockWatchdogTimeout
2. 主从异步复制导致的问题
客户端1对某个redis Master实例,写入了锁,此时会异步复制给对应的Master Slave实例
如果这个过程中Master宕机,主备切换,redis slave变为了Master,锁丢了失效了。
客户端1还在执行业务
客户端2来尝试获取锁,获取成功了。
参考redLock机制
https://www.jianshu.com/p/fba7dd6dcef5
5主
抢锁的时候,必须一半以上都成功,锁才成功。
如果客户端1抢到3台,那么其他的客户端也抢不到了
如果抢到的3台挂了一台,那么还剩2台。一共4台,其他线程抢锁的时候也会失败。
抢到2台,但是另外3台被抢了,也算失败,释放锁重新竞争
如果没有抢到锁,则设置一个随机时间,再试,避免竞争。
计数器
分库分表的自增编号
INCR指令实现简单限流
缓存
热点数据
报表
复杂SQL
访问量
异步队列
list结构作为队列
入队
rpush
后入
lpush
前入
出队
缺点
无法做到有值就消费,类似于BlockQueue一样阻塞。需要通过sleep降低请求频率
lpop
前出
rpop
后出
blpop
阻塞直到队列有消息或者超时
阻塞久了会导致连接空闲问题,服务器会主动断开,抛出异常,客户端需要捕获处理或重试,并且只能提供一个消费者消费。
pub/sub消费订阅模式
实现1:N的消息队列
消费者下线之后,此后的消息会丢失;当redis宕机的时候,pub/sub的数据不会持久化
reids 5.0 提供 : 解决了消息不能持久化的问题
https://blog.csdn.net/enmotech/article/details/81230531
延时队列
使用zset结构,将消息序列化成一个字符串作为zset的value,过期时间为score
用多个线程轮训zset获取到期的任务进行处理,多个线程是为了保证可用性,玩意挂了,还有其他线程可以继续处理。
分布式数据共享
session 共享
缓存场景
缓存雪崩
同一时间大面积的缓存失效,或者redis宕机,瞬间所有查询打入DB,导致DB崩溃
解决方案
分散key的过期时间,随机设置
集群、主备
通过搭建缓存的高可用,避免缓存挂掉之后无法提供服务的情况
本地缓存
使用本地缓存,即使分布式缓存挂了,本地缓存作为失败的容错方案,避免后续请求打到DB中
查询一个肯定不存在的key,缓存不命中时,就落到DB上。这种请求量一旦过多也会出现雪崩的状况。
被动写
即使查不到也会缓存该Key,并且设置过期时间。
一旦DB有该数据了,同步到缓存中。
其他层面
1. 网关层做限制,每秒超出一定阀值的ip拉黑处理
2. 缓存空对象,并设置过期时间
3. 使用布隆过滤器。在缓存服务的基础上,构建BloomFilter数据结构,在BloomFilter中存储对应的key是否存在,如果存在说明该key不为空。但需要提前将已存在的数据放在布隆过滤器中。
1. 根据key查询【BloomFilter缓存】,如果不存在则直接返回;如果存在则流程继续。
2. 根据KEY查询在【数据缓存】的值,如果存在值,直接返回;不存在则直接向下
3. 查询DB对应的值,如果存在则更新到缓存,并返回该值。
缓存击穿
某个极度热点的数据过期了,恰好这个时间点对这个key有大量请求并发过来,DB猝。
1. 使用互斥锁,请求发现缓存不存在之后,去查询DB前使用分布式锁,保证有且只有一个线程去查询DB,并更新到缓存。
1. 获取分布式锁,知道成功或超时。如果超时,则抛出异常,返回。如果成功,继续向下执行。
2. 获取缓存.如果存在值,则直接返回;如果不存在,则继续往下执行。因为或得到锁,可能已经被“那个”线程去查询过DB,并更新到缓存中了。
3、查询DB,并更新到缓存中,返回值
2. 手动过期,缓存上不设置过期时间,功能上将过期时间存在的key对应到Value里
1. 获取缓存,通过Value的过期时间,判断是否过期,未过期直接返回,过期了往下执行
2. 通过一个后台的异步线程进行缓存的构建,也就是手动过期,通过后台异步线程保证有且只有一个线程去查询DB
3、同时,虽然VALUE已经过期,还是直接返回。通过这样的方式,保证服务的可用性,虽然损失了时效性
数据不一致
缓存和DB的操作不在一个事务中,可能DB更新成功了,缓存更新失败了。
1. 将缓存可能存在的并行写,实现串行写。
在写请求时,先淘汰缓存之前,先获取该分布式锁。
在读请求时,发现缓存不存在是,先获取该分布式锁。
2. 先淘汰缓存,在写数据库因为先淘汰缓存,所以数据的最终一致性不会受到影响。即使下一次请求是空的,最终还是会落到DB,带来的影响就是多读一次数据库。
可以对一些重要数据、并发高的更新采用数据订阅的方式
过期策略
主动过期
redis会将所有设置过期时间的key放在一个独立的字典里面,以后肯定会定时遍历这个字典来删除到期的key
1. 默认会每秒进行十次过期扫描
2. 从过期字典中随机20个key,删除这个20个key中已过期的key。
3. 如果过期的key比率超过1/4,那就重复步骤1
惰性过期
就是指在客户端访问这个key的时候,redis对key的过期时间进行检查,过期了就删除。
淘汰机制
当redis内存超出物理内存的限制时,内存的数据会开始和磁盘产生频繁的交换。
交换会让redis的性能急剧下降,对于访问量比较频繁的Redis来说,这样龟速的存取效率几乎等于不可用。
为了限制最大使用内存,redis提供了配置参数maxmemory来限制内存超出期望大小,超出后执行淘汰策略。
淘汰策略
noeviction
不淘汰key【默认】
volatile-lru
淘汰过期集合的key中最少使用的
volatile-ttl
淘汰过期集合的key中,剩余寿命最短
volatile-random
随机淘汰过期集合的key
allkeys-lru【常用】
淘汰全部key中最少使用
使用频率最低的key
allkeys-random
随机淘汰全部key
过期时间类型
持久化
RDB【周期性】
RDB快照,对redis中的数据执行周期性的全量持久化到磁盘,恢复快。
RDB生成快照可自动触发,也可以使用命令手动触发,以下是reids触发执行快照条件
客户端执行命令save和bgsave会生成快照
save操作阻塞主线程,会导致服务一段时间不可用。
bgsave是fork子线程来执行save操作,仅在fork子线程的时候发生阻塞。
根据配置文件save m n规则进行自动快照
主从复制时,从库全量复制同步主库数据,此时主库会执行bgsave命令进行快照
客户端执行数据库清空命令flushall时候,触发快照
客户端执行shutdown关闭redis时,触发快照
持久化的工作流程
1. fork一个子进程出来,子进程创建后,父子进程共享数据段,父进程继续提供写服务,写脏的页面数据会逐渐和子进程分离开。
2. 子进程尝试将数据dump到临时的rdb快照文件中
3. 完成rdb快照文件的生成之后,就替换之前的旧的快照文件。
优点
灵活设置备份频率和周期
非常适合冷备份,对于灾难恢复来说,RDB是非常不错的选择。我们可以非常轻松的讲一个单独的文件压缩后再转义到其他存储介质上。
性能最大化。对于redis服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,极大避免了服务进程进行IO操作。
相比于AOF机制,RDB恢复速度快。
如果想要在redis故障时,尽可能少的丢失数据,那么RDB没有AOF好。一般来说,RDB数据快照文件都是每隔一段时间生成一次。
RDB在每次fork子进程来执行RDB快照数据文件生成的时候,如果文件特别大,可能会导致客户端提供的暂停数毫秒甚至数秒。
分析工具
RDR
RDR: redis data reveal
AOF【实时性】
AOF追加更新指令到AOF本地文件,达到一定的量后会对文件进行瘦身。
AOF持久化配置
always
每次写入一条数据,同步追加到aof文件,性能非常非常差,吞吐量第,但实时性高、数据肯定不会丢失。
eversec
每秒将os cache中的数据追加到aof文件,默认的配置
no
仅仅redis负责将数据写入os cache就撒手不管了,然后后面os自己会时不时有自己的策略将数据刷入磁盘,不可控制(即redis不负责更新aof文件,何时更新由宿主机自己决定,linux64位大概是30秒一次)
AOF重写
aof文件是追加的形式增量递增的,因为内存的容量是有限的,且redis的有些数据会过期的概念和淘汰机制的存在,会导致一开始存储的指令没有实际对应的数据。
重写流程
redis fork一个子进程,这个子进程是异步的。
触发的时机(2s)
子进程基于当前父进程内存中的数据,构建日志,开始往一个新的临时的aof文件中写入日志
如果此时父进程有写入数据的操作,由于进程之间是隔离的,子进程是看不到的,父进程会将使用一个内存副本来修改数据,并将修改的数据写入到AOF重写缓冲区中。当子进程拷贝完毕之后,会将AOF重写缓冲区也就是最新变动过的数据重新追加到临时的AOF重写日志中。(这里需要注意的是如果是设置了过期时间并且过期了的数据将会丢弃)
1. 数据的安全性,即数据的持久性。redis提供了3中同步策略
1. 每秒同步
Everysec
2. 每修改同步
Always
3. 不同步
日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。
AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flashall命令给删了,然后再讲该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据。
对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大。
linux对文件的大小有限制,理论上文件越大追加的速度也就越慢。
根据同步策略的不同,AOF在运行效率上会慢于RDB
持久化的意义
故障的恢复
持久化恢复数据的时候
AOF
存放的指令日志,做恢复的时候,其实是回放和执行所有的指令日志,来恢复出来内存中的所有数据。
RDB
就是一份数据文件,恢复的时候,直接加载到内存中即可。
持久化方案
主从的时候,主节点制作AOF(避免AOF重写?)从节点使用AOF+RDB混合持久化。
主从同步
主从架构的核心原理
1. 增量同步
redis同步的是指令流,主节点将那些对自己的状态产生修改性影响的指令记录在本地的内存Buffer中,然后异步将Buffer中的指令同步到从节点,从节点一边执行同步的指令流来达到和主节点一样的状态,一边向主节点反馈自己同步到哪里了(偏移量)
因为内存的Buffer是有限的,所以Redis主库不能讲所有的指令都记录在内存Buffer中
redis的复制内存Buffer是一个定长的环形数组,如果数组内容满了,就会从头开始覆盖前面的内容。
2. 快照同步
快照同步是一个非常耗费资源你的操作,它首先需要在主库上进行一次bgsave将当前内存的数据全部快照到磁盘文件中,然后再将快照文件的内容全部传送到从节点
从节点将快照文件接收完毕后,立即执行一次全量加载,加载之前先要将当前内存的数据清空。加载完毕后通知主节点继续进行增量同步。
在整个快照同步进行的过程中,主节点的复制buffer还在不停的往前移动,如果快照同步的时间过长或者复制buffer太小,都会导致同步期间的增量指令在赋值buffer中被覆盖。
这样就会导致快照同步完成后无法进行增量复制,然后会再次发起快照同步,如此极有可能会陷入快照同步的死循环
当从节点刚刚加入到集群时,它必须先要进行一次快照同步,同步完成后再继续进行增量同步
从节点过期key处理
slave不会过期key,只会等待master过期key、。如果master过期了一个key,或者通过LRU淘汰了一个key,那么会模拟一条del命令发送给slave。
哨兵机制
哨兵+主从实现redis集群高可用
集群监控
负责监控Redis master和slave是否正常进行工作
消息通知
如果某个redis实例挂了,那么哨兵会发送通知给系统管理员
故障转移
如果Master node 挂了 ,会自动转移到slave node上
配置中心
如果故障转移发生,通知客户端最新的master node的地址
sentinel 无法保证消息完全不丢失,但也尽可能保证消息少丢失,它有两个选项可以限制主从延迟过大
1. min-slaves-to-write 1 表示主节点必须至少有一个节点在进行正常复制,否则就停止对外写服务,丧失可用性。
2. min-slave-max-lag 10 表示如果10s没有收到从节点的反馈,就意味着同步不正常。
性能描述
单机
1. 业务复杂性
2. 环境性能
慢查询
命令
slowlog get {n}
查询最近的n条慢命令
原因
1. 不合理的命令或者数据结构
比如对一个包含上万个元素的hash结构执行hgetall操作,由于数据量比较大且命令算法复杂度是O(n),这条命令执行速度必然很慢。
避免在大对象上执行算法复杂度超过O(n)的命令,对于键值较多的hash结构可以使用scan系列命令来逐步遍历,而不是直接使用hgetall来全部获取
2. 持久化阻塞
1. fork 阻塞
2. AOF刷盘阻塞
3. 内存交换
QPS大概1W到几万不等
面试相关问题
1. 为什么数据明明过期了,内存还是没释放?
1. 定时轮训删除的时候,可能没有扫描到。
2. 数据没有被访问,没有被触发到惰性删除。
总的来说还是它的过期策略。
2. 为什么说redis是单线程的?
因为Redis内部使用文件事件处理器file event handler,这个文件事件处理器是单线程的,所以redis才叫做单线程模型
它采用IO多路复用机制同时监听多个Socket,根据Socket上的事件来选择对应的事件处理器进行处理。
多个Socket可能会并发产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个Socket
会将Socket产生的事件放入队列中排队,事件分派器没从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。
为什么使用单线程?
1. 减少并发访问控制问题
为什么6.0又改用了多线程?
redis的瓶颈不在CPU,而是在内存和网络.
但redis的网络IO的读写占用了部分CPU的时间,如果可以把网络处理改成多线程,性能会有很大提升。
1. 充分利用服务器的多核资源
2. 多线程分摊redis同步IO的读写负荷
处理网络数据读写用到了多线程,但是执行命令还是单线程顺序执行.
3. 单线程模型为什么快?
单线程带来的问题
1. 非阻塞的IO模型
2. 基于纯内存的快速处理操作
3. 单线程避免了多线程上下文的操作
4. 如何解决脑裂导致的数据丢失
上面的配置就确保了如果跟任何一个slave丢了连接,10秒后发现没有slave给自己ack,那么就拒绝client的写请求,因此在脑裂场景下,最多丢失10秒的数据。
min-slaves-to-write [减少异步复制的数据丢失]
配置描述
要求至少有一个slave,数据复制和同步的延时不能超过10秒
一旦slave复制数据和ack延时太长,就认为可能master宕机后损失的数据太多了,那么久拒绝写请求,这样可以吧master宕机时由于部分数据未同步到slave导致的数据丢失降低的可控范围内。
min-slaves-max-lag[减少脑裂的数据丢失]
如果说一旦所有的slave,数据复制和同步的延迟都超过了10秒钟,那么这个时候,master将不会接受任何写请求。
如果一个master出现了脑裂,跟其他的slave丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的slave发送数据,而且slave超过10秒没有给自己ack消息,那么就直接拒绝客户端的写请求,这样脑裂后的旧master就不会接受client的新数据,也就避免了数据的丢失
与memcache对比
1. redis 数据丰富
2. redis 支持持久化操作,通过aof和rdb数据持久化到磁盘,但是mem不支持。
3. 对于热点、bigkey支持度的优化程度
2. memcahce是多线程,影响的程度较小。
4. 高可用方面
1. redis 支持节点之间的数据复制,主从同步可以实时进行数据的同步复制。支持增量、多级复制
2. memcache无法进行数据同步.
非常关键的因素,线程模型
redis 是单线程请求,所有命令串行执行,并发情况下不需要考虑数据一致性;性能受限于CPU,单实例QPS在 4-6W
Memcache是多线程,可以利用多核优势,单实例在正常情况下,可以达到写入60-80W,读80-100W
来源地址
造成延时抖动的原因有哪些
1. 数据同步
2. big key
3. 使用复杂度过高的命令
4. 大量key集中过期
5. 淘汰策略的选择
6.
redis的处理事件流程
1. 网络处理
1. 绑定监听端口
bind/listen
2. 接收请求事件
accept
3. 读取请求事件
recv
4. 解析请求事件
parse
2. 键值对读写
get
set
3. 网络IO处理
send
redis的优化有哪些?
1. 多擅长利用批量操作去处理,减少交互次数。
当redis的内存已经成为瓶颈的时候?
将内存转化为磁盘读写,参考 https://github.com/pika/pika
redis 是如何实现高可用的?
主从
多节点
哨兵
事务操作
mulit
开启事务
exec
提交事务
span style=\
主动放弃事务
watch
检测一个或多个键的值在事务执行期间是否发生变化,如果发生变化,那么当前事务放弃执行
并不是真正意义上的 原子性
0 条评论
回复 删除
下一页