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