Redis原理Redis性能优化面试题集群
2022-09-25 18:48:22 4 举报
AI智能生成
jk时间redis原理学习笔记脑图整理redis集群redis同步原理
作者其他创作
大纲/内容
基础篇
Redis基本数据类型
String
存储字符串,整数,浮点数
操作命令
get、set、mset、mget
setnx 如果key不存在则设置key,返回1 否则什么都不做,返回0
set key value [expiration EX seconds|PX milliseconds][NX|XX] 如set lock1 1 EX 10 NX
incr、incrby、decr、decrby、incrbyfloat
strlen、append
存储原理
redisObject
SDS
应用场景
缓存 热点数据缓存
分布式数据共享 如:分布式session
分布式锁 基于setnx命令实现
全局ID 基于incrby命令实现
计数器 基于incr命令实现
限流 基于incr命令实现
位统计
Hash
存储类型
value只能是字符串(字符串、整数、浮点数)
操作命令
hget、hset、hmget、hmset
存储原理
ziplist
hashtable
应用场景
String的应用场景、Hash都可以做
购物车
List
存储类型
有序的字符串、可重复
操作命令
lpush、rpush、lpop、rpop
blpop 带阻塞
lindex queue 0
lrange queue 0 -1
lrange queue 0 -1
存储原理
quicklist
应用场景
用户消息时间线
消息队列
Set
存储类型
无序集合 最大40亿左右
操作命令
sadd、smembers myset、scard myset、spop、sismember
存储原理
intset或hashtable
应用场景
抽奖
点赞、签到、打卡
商品标签
商品筛选
sdiff(差集)、sinter(交集)、sunion(并集)
用户关注模型
ZSet
存储类型
有序set 每个元素都有score
操作命令
zadd myzset 10
zrange myzset 0 -1 withscores 获取全部元素
zrangebyscore myzset 20 30 根据分值区间获取元素
zrem myzset php cpp 移除元素
zcard myzset 统计元素个数
zincrby myzset 5 python 分值递增
zcount myzset 20 60 根据分值统计个数
zsocre myzset java 获取元素 score
存储原理
ziplist
skiplist+dict
应用场景
排行榜
其他数据类型
BitMaps
Hyperloglogs
Streams
redis高性能查找数据的过程
通过全局哈希表,找到对应entry,然后entry保存的是value存放的内存地址
流程图
dictEntry
RedisObject
结构图
元数据
type:值类型,涵盖前面学习的5大基本类型
encoding:值编码方式,例如SDS、压缩列表、哈希表、跳表
lru:记录对象最后一次被访问的时间,用于淘汰过期键值对。
refcount:记录对象引用计数
ptr
指向数据的指针。
redis使用rehash解决哈希表哈希碰撞问题
rehash过程
读取值时再进行rehash的过程
rehash时机
redis中的数据结构
动态字符串SDS(Simple Dynamic String)
数据结构
buf:保存实际值,以\0表示结尾。会额外占用1字节
alloc:占4字节,表示buf可用长度(类似于可以用多少钱,已经用了多少钱)
len:占4字节,表示buf已用长度
alloc:占4字节,表示buf可用长度(类似于可以用多少钱,已经用了多少钱)
len:占4字节,表示buf已用长度
redis对于SDS的优化
双向链表
压缩列表
数据结构
zlbytes:列表长度
zltail:列表偏移量
zllen:列表entry个数
zlend:列表结束
prev_len:前一个entry的长度,一般两种取值,1或者5字节,1字节时只能0-254
encoding:编码方式,1字节
len:自身长度,4字节
key(content):保存实际数据。
entry紧密放在内存中,不需要额外的指针指向,所以可以省空间。
zltail:列表偏移量
zllen:列表entry个数
zlend:列表结束
prev_len:前一个entry的长度,一般两种取值,1或者5字节,1字节时只能0-254
encoding:编码方式,1字节
len:自身长度,4字节
key(content):保存实际数据。
entry紧密放在内存中,不需要额外的指针指向,所以可以省空间。
总结:查找第一个最后一个元素效率最快。查找其他就是O(N)
哈希表
跳表
数据结构
子主题
整数数组
总结
所有数据结构时间复杂度
基础数据类型
基础数据类型
set
数据结构:整数数组 当容量变大之后升级为 哈希表 所有类型的升级是不可逆的
场景:求共同好友 并集 交集等操作 去重
sort set
数据结构:压缩列表 容量大之后变跳表
场景:带有排序功能的set 分数等
特殊场景:滑动窗口 用来动态收集窗口内的数据集合 判断数据是否超过阈值
hash
数据结构:压缩列表 升级 哈希表
子主题
list
数据结构:压缩列表 升级双向链表
场景:双端队列性能最高
string
其他数据类型
Bitmap
使用场景:布隆过滤器 统计打卡 01 表示有无
HyperLogLog
使用场景:大数据量的基数统计 结果并不精准,错误率大概在0.81% PV UV
GEO
使用场景:位置信息服务 打车
原理:底层使用sort set数据结构,通过geohash计算权重值。
geohash:对经纬度进行二分,在左分区则取0 右分区取1 然后奇偶交叉,偶数位放纬度
总结
原理篇
Redis事务
事务用法
multi(开启事务)
exec(执行事务)从队列取出提交的操作命令,进行执行
discard(取消事务)清空命令队列
watch(监视)事务要修改的值在事务执行期间是否发生变化,是就放弃执行
watch命令
CAS乐观锁
事务特性
原子性(A)
将执行语句放入队列中,当提交事务exec时,一起执行 执行过程出错则不保证原子性
一致性(C)
命令执行前后,redis状态不发生改变。由于redis是非关系型数据库,所以这里跟mysql的一致性是不一样的
redis执行过程中,如果某一天语句报错了,那么并不会回滚整个,而是成功的就成功,失败的就失败,redis认为一致性并没有被破坏
redis执行过程中,如果某一天语句报错了,那么并不会回滚整个,而是成功的就成功,失败的就失败,redis认为一致性并没有被破坏
隔离性(I)
命令放入不同队列中,相互隔离,并使用watch监听是否被修改,被修改则取消事务
持久性(D)
RDB以及AOF实现持久性
事务可能遇到的问题
在执行exec之前发生错误:之前发生错误,则放弃事务
在执行exec之后发生错误:事务不会回滚,执行成功的则成功不会回滚,失败的值不变。破坏了原子性。所以Redis要确保语句执行过程不报错才能保证事务
内存回收
过期策略
定时过期(主动淘汰)
惰性过期(被动淘汰)
淘汰策略
配置参数
maxmemory
maxmemory_policy
配置项
noeviction(默认):一旦缓存满了,不再对写请求提供服务。不淘汰数据,不腾出新的空间。
volatile-ttl:按照过期时间升序删除,淘汰准备过期的
allkeys-random:所有key中随机删除
volatile-random:找设置了ttl的key,并随机删除
allkeys-lfu:所有key中删除最近时间少使用的
volatile-lfu:所有设置了ttl的key中,删除最近时间少使用的
allkeys-lru LRU算法 :删除所有key使用次数最少的
volatile-lru LRU算法
maxmemory_samples
LRU淘汰原理
LFU淘汰原理
持久化机制
RDB(Redis Database)
原理:Redis使用写时复制技术(COW)copy on write 。 主线程fork一个子线程,然后子线程访问数据并保存,为了保证快照一致性,在生成快照的时候,数据是不可修改的了,但是又为了不影响主线程,所以使用写时复制技术(COW)copy on write 当数据要修改时,生成要写入的副本,然后修改副本的值。当完成RDB后,将新的RDB文件覆盖旧的RDB文件。
概念:某一时刻将内存数据写到磁盘文件上。AOF是执行命令,RDB是将数据保存,RDB恢复数据比AOF快。
触发时机:
手动触发
flushall 需要配置 save (自动快照)
save:在主线程执行,会阻塞处理请求。生产环境慎用
bgsave :创建一个子进程,专门写入RDB文件,避免主线程阻塞。redis默认的快照生成方式。
自动触发
配置文件参数(自动快照) save 900 1 表示900秒或者有一个以上的键修改则进行快照。
shutdown
AOF(Append Only File)
原理:先执行命令,执行成功再记录执行语句到日志中 与mysql不同,mysql是记录完日志才执行语句。
为什么MYSQL是先写日志后执行与redis不同
AOF重写机制
AOF重写时机
手动执行 bgrewriteaof 触发 AOF 重写
redis.conf 文件配置重写的条件:
auto-aof-rewrite-min-size 64MB // 当文件小于64M时不进行重写
auto-aof-rewrite-min-percenrage 100 // 当文件比上次重写后的文件大100%时进行重写
auto-aof-rewrite-min-size 64MB // 当文件小于64M时不进行重写
auto-aof-rewrite-min-percenrage 100 // 当文件比上次重写后的文件大100%时进行重写
AOF重写流程
redis会拷贝出一个线程,然后将数据的内存页的引用拷贝一份,通过访问拷贝的内存页往新的AOF中写,主线程正常处理请求,并将产生的AOF分别记录到新老AOF中,等重写结束后,新的AOF文件覆盖掉旧的AOF即可。
内存页引用:就是所有的entry集合,他们不保存数据,只保存数据的物理内存地址
AOF文件格式:set testkey testvalue -> *3 $3 set $7 testkey $9 testvalue
AOF回写策略
appendfsync=Always
appendfsync=Everysec
appendfsync=No
两种方案使用
为什么要混用:Redis4.0提出混合使用AOF RDB 为了避免频繁RDB影响性能,所以RDB设置为1.5分钟执行一次,但是这1.5分钟内如果宕机那会丢失1.5分钟数据,所以AOF可以很好的解决这个问题。如果1.5分钟内宕机,那么从AOF恢复。如果1.5分钟内没有宕机则清空AOF 继续
使用场景:
1.数据不能丢失,那么混用最好
2.如果允许丢失分钟内的数据,则只用RDB就好
3.AOF设置为每秒刷一次性能最好,建议用这个
1.数据不能丢失,那么混用最好
2.如果允许丢失分钟内的数据,则只用RDB就好
3.AOF设置为每秒刷一次性能最好,建议用这个
高性能IO模型(IO多路复用)+Reactor模式
实现方式
select
使用方式:使用位图bitmap数据结构,长度默认时1024,当事件就绪时,主线程会遍历位图找到就绪的事件,并将数据进行拷贝
缺点
poll
使用方式:使用数组作为数据结构,没有最大连接数限制
缺点:跟select差不多
epoll
使用方式:使用红黑树添加epoll需要监控的socket,所有添加到epoll的socket都会与网卡驱动程序建立回调关系,当触发回调函数时,会将发生的事件保存在双线链表中。epoll还提供了epoll wait 函数,来读取链表,判断是否为空,不为空说明有事件发生了。
优点:1.没有最大连接数限制,1G内存能监听10W个端口
2.效率高于select poll
3.内存拷贝机制 使用mmap减少复制开销
2.效率高于select poll
3.内存拷贝机制 使用mmap减少复制开销
缺点:只能linux使用
与BIO流程的区别
IO多路复用比同步IO多了一次数据读取过程,
同步IO调用之后,会一直阻塞直到数据拷贝到用户态。
但是IO多路复用用于处理多个socket的场景,所以处理单个socket时,IO多路复用性能比不上同步阻塞
同步IO调用之后,会一直阻塞直到数据拷贝到用户态。
但是IO多路复用用于处理多个socket的场景,所以处理单个socket时,IO多路复用性能比不上同步阻塞
与两种IO的差异
BIO
会同步阻塞,等到数据拷贝到用户态,如果要处理多个socket,就要多线程,IO多路复用一个线程就可以处理多个socket
NIO
NIO可以一个线程处理多个socket,但是会一直轮询socket是否就绪,耗费CPU资源,而IO多路复用select时,会阻塞,等待通知,通知到了才会扫描哪个socket就绪。
reactor
redis的处理流程,通过文件事件处理器,不同的event 不同的处理器处理
分布式篇
Redis主从复制
环境搭建
同步原理
第一次同步
不是第一次同步
性能优化
主从级联模式
从数据库持久化
无硬盘复制
增量复制
主库在写入过程中,写入replication buffer,还会写入repl_backlog_buffer 缓冲区。
重连的时候,主库会收到从库的offset 然后与repl_backlog_buffer比较,找出与主库的差距,然后完成断连期间的同步。
重连的时候,主库会收到从库的offset 然后与repl_backlog_buffer比较,找出与主库的差距,然后完成断连期间的同步。
repl_backlog_buffer 是一个环形的缓冲区,主库记录自己写到的位置,从库记录自己读到的位置。
合理设置repl_backlog_buffer 一般为2倍缓存空间 缓存空间 = 主库写入速度* 操作大小 - 主从传输速度 * 操作大小
复制风暴
sentinel哨兵
哨兵搭建
哨兵是什么
本身就是一个redis应用,只不过它不提供服务。
哨兵启动参数:sentinel monitor <master-name> <ip> <redis-port> <quorum>
通过哨兵获取集群状态:订阅哨兵的频道,获取集群状态。
SUBSCRIBE +odown
哨兵原理
哨兵建立连接流程
1.哨兵之间互相发现
哨兵通过sentinel monitor语句,底层会在主库上创建一个 __sentinel__:hello 的频道,所有的哨兵都把自己的信息发到频道上,然后互相能知道其他哨兵的ip 端口 然后可以建立连接。
2.哨兵获取集群从节点信息
哨兵通过INFO命令,从主库中获取从库列表,然后建立连接
哨兵通过sentinel monitor语句,底层会在主库上创建一个 __sentinel__:hello 的频道,所有的哨兵都把自己的信息发到频道上,然后互相能知道其他哨兵的ip 端口 然后可以建立连接。
2.哨兵获取集群从节点信息
哨兵通过INFO命令,从主库中获取从库列表,然后建立连接
哨兵故障切换流程
判断主库下线
主观下线:主观表示哨兵自己认为下线了,其他人没发现或大家还没达成一致。
客观下线:客观下线是大家达成一致,这个节点下线已经是客观事实。
选举哨兵Leader
Raft
选举(leader election)
节点状态
follower:
1.在选举过程中当发现了新的leader 则从Candidate转化为follower
2.当当前节点是Leader,发现更高的纪元存在,则自己转化为follower
1.在选举过程中当发现了新的leader 则从Candidate转化为follower
2.当当前节点是Leader,发现更高的纪元存在,则自己转化为follower
candidate:
1.follower->candidate:当主节点挂掉或者心跳超时,那么从节点会开启一个选举,follower变为candidate
2.candidate->candidate : 如果选举超时,还没选出leader 则随机事件,继续选举
1.follower->candidate:当主节点挂掉或者心跳超时,那么从节点会开启一个选举,follower变为candidate
2.candidate->candidate : 如果选举超时,还没选出leader 则随机事件,继续选举
leader:
1.candidate->leader:选举成功 拿到大多数的票
1.candidate->leader:选举成功 拿到大多数的票
状态转换流程图
term:纪元 任期,每次选举或者选举失败,重新选举,term都会自增。
每一个term 都是递增的
选举过程
日志复制(log replication)
正常同步情况:主节点接收到请求,会等到绝大多数的从节点持久化才响应客户端,其他没响应的,最终都会通过状态机实现完全同步。
选举完同步:选举过程中会优先选择日志最完整的做为leader. leader维护每一个从节点的nextIndex 当刚选举出来时,nextIndex = leader的日志长度+1 从节点如果没这个长度,则长度递减,直至有,当长度满足之后,则进行内容匹配,内容匹配失败,则递减继续匹配,直至匹配成功,然后删除这些内容不一样的部分,开心同步leader的,最终和leader一致。
redis选举流程
选主库
筛选:筛选从库的在线状态,判断它之前的网络连接状态,如果网络不好,则筛选不通过
确定优先级
redis切片集群
切片集群
优点
缺点
切片槽分配
自动分配
手动指定
客户端定位数据
未发生迁移
迁移进行中
性能优化篇
内部阻塞操作
客户端交互的阻塞
CPU核心与NUMA架构
多CPU多核(NUMA)
概念:在多 CPU 架构下,一个应用程序访问所在 Socket 的本地内存和访问远端内存的延迟并不一致,所以,我们也把这个架构称为非统一内存访问架构(Non-Uniform Memory Access,NUMA 架构)。
远端内存访问:多CPU,每个CPU通过总线互通消息,如果不进行绑定,当CPU1上运行一段时间,切换到其他CPU2上运行,那么CPU2要通过消息总线访问CPU1获取内存信息。并且如果网络中断处理程序跟CPU绑定不在同一个核上,还是会出现远端内存访问
解决方法:1.绑定物理核到redis 跟单CPU一样,并且将网络中断程序处理也绑定到同一个物理核
单CPU多核
单CPU架构:每个CPU会有多个物理核,每个物理核有自己的L1(L1分为i Cache 和 d Cache)、L2缓存,以及逻辑核,
CPU上的多个物理核共用一个L3缓存
CPU上的多个物理核共用一个L3缓存
优化方案:绑定cpu的核让redis只用某个核
查看服务器物理核命令:lscpu
关键系统配置
内存碎片
产生原因:
操作系统原因:一次性分配是2的整数倍,会比需要的空间还要大
redis原因:在进行删除修改时,会清空占用,造成碎片
判断内存碎片:
清理内存碎片
1.重启redis实例(数据会丢失)
2.redis4.0提供自动清理方法
缓冲区
客户端输入输出缓冲区
输入缓冲区溢出
查看输入缓冲区使用情况
输出缓冲区溢出
1.服务端返回bigkey
2.Monitor监控一直开启
3.缓冲区设置不合理
主从集群缓冲区
复制缓冲区溢出
1.控制主节点保存数据量大小
2.合理设置缓冲区大小
总结:为了避免复制缓冲区溢出,
1.控制主节点保存数据量大小(RDB大小) 小的话,同步起来快。
2.主节点下不要挂太多从节点。
1.控制主节点保存数据量大小(RDB大小) 小的话,同步起来快。
2.主节点下不要挂太多从节点。
复制积压缓冲区溢出
关闭大页
Linux2.6支持大页
关闭大页
查看大页状态
大页的影响:当RDB写时复制时,大页会造成拷贝一页数据,此时拷贝的数据很多,性能不好
实战篇
数据一致性
一致性
缓存与数据库一致性
缓存写回策略
双写一致性解决方案
先更新缓存再更新数据库
先删缓存再更新数据库
先更新数据库再更新缓存
先更新数据库再删缓存(用这个)
高并发问题
缓存击穿
场景
解决方案
缓存雪崩
场景:大量缓存在同一个时间点过期,然后所有的请求都落到数据库进行查询。存储层压力过大,造成系统雪崩。
解决方案
缓存穿透
场景:查询一个根本不存在的值,缓存没用命中,去数据库进行查询,增加数据库的压力。
解决方案:
Redis客户端
Jedis
Jedis Pool
Sentinel获取连接原理
Cluster获取连接原理
Luttece
Redisson
本质
特点
实现分布式锁
分布式锁
redis
加锁方式
使用reddison 原子命令执行加锁
使用redsson命令原子释放锁
坑
枷锁时非原子操作
使用原子性加锁命令 设置过期时间
忘了释放锁
加入超时时间,并且try catch finally 释放锁
超时时间过短,业务没执行完
redission的看门狗 自动续约
释放了别人的锁
在加锁时,value设置一个线程id 然后解锁的时候,校验线程id是否匹配,匹配才能释放锁
锁重入问题
reddison支持可重入
大量失败请求
自旋获取
主从复制 主节点挂掉,从节点未同步问题
Redisson 红锁 就是要部署N套redis 然后 半数加锁成功,才算成功
主从同步是异步的 不能要求强一致啊 面试的时候别当kafka一样,说啥保存主要等保存完从才返回成功
zk
加锁方式
创建临时节点,多线程竞争的情况下用顺序节点保证公平性。后来的线程watch 前一个节点,如果前一个节点消失了,就可以自己获取锁了。
坑
锁无法释放
临时节点,客户端当掉,则自动释放锁
非阻塞锁
顺序节点+watch前一个节点保证非阻塞
不可重入
将锁信息写入节点信息中,加锁时判断一下信息是否一致一致则可重入
公平问题
顺序节点
一致性问题
对比
使用场景 如果高可用用redis 如果强一致性 zk
redis性能高于zk redis基于内存
数据丢失
1.异步复制导致的数据丢失
2.系统脑裂导致数据丢失
2.系统脑裂导致数据丢失
解决方案
min-slaves-to-write x
min-slaves-max-lag y
min-slaves-max-lag y
异步复制导致的数据丢失
这几个参数可以确保slave于master之间失联太久就拒绝写入
脑裂数据丢失
兜底方案就是写入redis失败 我们可以往mq写 然后注意幂等性就行
主从同步是异步的 不存在从写入再ack,所以此方案只是为了减少数据丢失并不是完全解决数据丢失的
redis运维
运维工具
基础命令:INFO
监控平台:Prometheus
数据迁移:Redis-shake
集群管理:CacheCloud
统计基准性能:
什么是基线性能:一个系统在低压力,无干扰的基本性能,性能只由软硬件配置决定。
统计网络性能:iPerf
Redis6.0新特性
多线程处理网络请求
主线程阻塞等待IO(多线程)读取socket
主线程阻塞等待 IO 线程(多线程)将数据回写 socket 完毕
总的来说就是多线程进行IO操作,读socket以及写socket
客户端缓存
普通模式
开启方式
广播模式
开启方式:
RESP3
RESP2
RESP3协议(REdis Serialization Protocol)
RESP3支持多种数据类型,区分编码,包括空值,浮点数,布尔值,有序字典集合,无序集合。
RESP2 仅支持字符串等其他数据类型
细粒度权限控制
面试
为什么说redis是AP的?
Redis 的大 Key 对持久化有什么影响?
0 条评论
下一页