Redis
2023-05-10 03:57:29 1 举报
AI智能生成
涵盖Redis大量知识点总结
作者其他创作
大纲/内容
数据结构
String
应用场景
单值缓存 set key value
对象缓存 set key json
对象属性缓存(批量set string) mset key value key value key value
分布式锁
设置锁 setnx key value
删除锁 del key
设置可配置锁 set key value EX 10 NX
EX seconds – 设置键key的过期时间,单位时秒
PX milliseconds – 设置键key的过期时间,单位时毫秒
NX – 只有键key不存在的时候才会设置key的值
XX – 只有键key存在的时候才会设置key的值
KEEPTTL -- 获取 key 的过期时间
GET -- 返回 key 存储的值,如果 key 不存在返回空
计数器
记录文章浏览量
设置文章数据 set key value
value++ incr key(如果key不存在则默认添加并生成value为1)
获取文章数据 get key
Web集群Session共享
分布式系统全局唯一序列号
Hash
应用场景
对象缓存 hmset key field value field value field value
电商购物车
添加商品到购物车 hset key field value(value是商品的数量)
修改商品在购物车中的数量 hincrby key field number (number可以是正数也可以是负数,正数代表加,负数代表减)
查看购物车中的商品总数 hlen key
删除购物车中的商品 hdel key field
获取购物车中全部商品 hgetall key
购物车示例
优劣势
优势
同比String类型去存储对象属性,Hash存储的更加规整
同比String占用的内存、cpu消耗更少
同比String更加节省存储空间
劣势
Redis集群架构下不适合大规模使用
过期时间只能设置到key上而不能设置到field上
List
应用场景
栈 Stack FILO
队列 Queue FIFO
阻塞队列 Blocking Queue
订阅号
PUSH方案
石衫架构笔记发布了最新的文章
订阅公众号的用户都执行该命令 新增一条文章id的数据 LPUSH userId articleId
macrozheng发布了最新的文章
订阅公众号的用户都执行该命令 新增一条文章id的数据 LPUSH userId articleId
用户查看自己的订阅号 LRANGE userId start stop(可以根据start、stop来实现分页)
思考点:如果是超大数据量,例如几千几万个用户需要接收到文章,该如何优化?
方案一:暂时只推送在线的用户接收到最新文章到订阅号,后续在后台执行其他用户的数据更新
补充方案一:如果用户登录需要去更新自己的订阅号,并且后台执行其他用户的数据更新要实时判断命令是否已执行,可以考虑都从一个队列中取,避免重复执行导致用户看到重复的订阅号内容
数据结构
Set
应用场景
抽奖
添加用户id到set集合中:SADD key value
随机取出count个元素并在集合中删除 :SPOP key count
随机取出count个元素不删除:SRANDMEMBER key count
查看集合中的全部元素:SMEMBERS key
点赞、收藏、标签
点赞 SADD {msg:id} {user:id}
取消点赞 SREM {msg:id} {user:id}
用户是否点赞 SISMEBER {msg:id} {user:id}
点赞数量 SCARD {msg:id}
点赞用户列表 SMEBERS {msg:id}
微博关注模型
场景描述:假设你查看了某一个博主的主页可能存在以下内容
共同关注 SINTER 查看交集
我关注的人也关注了他 SISMEBERS 查看集合中是否存在该用户,该命令需要逐条去执行,可以考虑分页
可能认识的人 SDIFF 查看差集
电商产品的筛选
搜索栏展示内容
存储数据命令
华为手机的型号:SADD phone phone:huawei:p40 phone:huawei:p50
CPU:SADD cpu:inter phone:huawei:P40
CPU:SADD cpu:haisi phone:huawei:P50
运行内存4G:SADD memory:4G phone:huawei:P40
运行内存8G:SADD memory:8G phone:huawei:P50
查询命令(假设查询华为手机且CPU是inter且运行内存8G的商品)
SINTER phone cpu:inter memory:8G
返回结果:{}
查询命令(假设查看华为手机且CPU是inter且运行内存4G的商品)
SINTER phone cpu:inter memory:4G
返回结果:{phone:huawei:P40}
高级操作
交集:SINTER key1 key2 ...keyn
并集:SUNION key1 key2 ...keyn
差集:SDIFF key1 key2 ...keyn
ZSet:有序集合(按权重排序)
应用场景
文章排行榜
热搜
七日内热搜
命令手册
单线程和高性能
单线程
严格意义上来说Redis并不是单线程,它分两种情况
1、网络IO,键值对读写是通过单线程的方式
2、持久化、集群数据同步、异步删除等是由额外的线程来完成
单线程如何保证高性能
Redis的所有数据都存储在内存当中,运算是内存级别的运算
单线程避免了切换线程所造成的性能损耗(正因为Redis是单线程,线上一定要避免执行时间过长的命令,例如keys,会阻塞其他命令,造成Redis卡顿,谨慎使用)
IO多路复用
持久化
RDB(默认)
持久化文件:dump.rdb(二进制文件)
save
直接生成持久化文件,每次执行都会重新生成持久化文件
同步执行,可能会阻塞redis的其他命令
bgsave(background save)
直接生成持久化文件,每次执行都会重新生成持久化文件
异步生成持久化文件,通过copy on write(cow)写时复制,不会阻塞redis的其他命令
工作流程简述:
1、主线程fork一个bgsave的子进程,并且共享内存数据
2、假设主线程持久化过程中对内存数据都是读的操作,不会影响到bgsave子进程持久化内存数据
3、如果主线程修改内存数据,那么会复制一份生成副本,bgsave会根据副本内容写入到持久化文件中
二者对比(save、bgsave)
IO类型
save 同步
bgsave 异步
优势
save 不需要多余的内存开销
bgsave 不会阻塞redis的其他命令
劣势
save 阻塞redis的其他命令
bgsave 开销相对较大
复杂度
O(n)
缺陷:可能会造成数据丢失数据
场景1:通常我们的生成持久化策略是根据时间n,操作了m次数据就生成持久化,如果在没达到配置策略的条件下redis宕机,就会造成数据丢失
AOF(append-only file)
模式
always
每条命令都写入aof文件
慢,安全不容易造成数据丢失
everysec(默认)
每秒钟写入一次
快,宕机时会丢失1s的数据
no
关闭aof
AOF重写:bgrewriteaof
aop中记录了大量可省略的命令,可以通过重写精简文件内的命令
例如:set key value | set key value1 | set key value2,同一个key,AOF文件只需要保存最后一条命令即可
配置
auto‐aof‐rewrite‐min‐size 64mb //aof文件至少要达到64M才会自动重写,文件太小恢复速度本来就很快,重写的意义不大
auto‐aof‐rewrite‐percentage 100 //aof文件自上一次重写后文件大小增长了100%则再次触发重写
缺陷
与rdb相并它并非二进制文件,所以恢复速度慢
是否会丢失数据是根据策略决定
RDB与AOF对比
体积
rdb 小
aop 大
效率
rdb 恢复更快
aop 相对较慢
可靠性
rdb 可能会造成数据丢失
aop 根据策略配置
启动优先级
rdb 低
aof 高
混合持久化机制(最佳持久化方式)
配置:aof-use-rdb-preamble yes(前置条件必须开启aof)
说明:实际上rdb文件容易造成数据丢失,通常我们会以aof持久化的数据来恢复文件,但是往往aof恢复的太慢了,redis在4.0之后增加了混合持久化机制,它可以更快的恢复我们的内存数据。
混合持久化是基于AOF重写的策略。如果没有开启混合持久化,AOF重写是压缩命令,而开启后则是压缩完命令并转成二进制,后续AOF存储的命令还是字符的形式,直到下一次压缩
Redis数据备份
定时任务 - 多目录备份(过旧的文件删除)
定时任务 - 多机备份(过旧的文件删除)
定时任务策略:每小时、每天
高级操作
管道(Pipelined)
简介:客户端一次性向服务端发送打包多条命令,无需等待服务器端逐条响应
优劣势
优势:减少网络开销,提高命令执行效率
劣势
并非原子性,失败感知能力较弱,服务端全部执行完后客户端才能拿到结果
Redis必须在管道提交的命令执行完之前将结果缓存起来(消耗内存),并不是提交的命令越多越好,要考虑到服务端到压力
Lua脚本
简介:lua是一种脚本语言,redis在2.6版本推出了脚本语言,开发者可以使用lua脚本来执行redis命令
优劣势
优势
打包命令减少网络开销(与管道类似)
原子操作:将一次脚本视为一个整体,如果失败就全部不执行
替代redis事物:原生事物并不支持报错回滚,而redis的lua脚本基本实现了全部常规事物(例如支持报错回滚),官方也更提倡这种方式开启事物
劣势
lua脚本如果出现死循环或耗时运算,则会阻塞服务端的命令
示例(服务端命令):eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
高可用
主从架构
简介
当单机无法满足系统的使用时,可以通过主从的方式去扩充多个节点,它是有一个主节点,多个从节点
主节点用来向从节点同步数据
建立连接(全量复制)
1、Master、Slave都启动后,Slave会向Master发送一个psync命令并建立socket连接
2、Master收到psync后,通过bgsave来生成最新持久化文件
fork的子进程会不断的记录操作内存数据的命令并生成副本
3、Master发送持久化完成的rdb文件
4、Slave接收到Master发送的rbd文件,清空内存数据重新同步内存数据
5、Matster发送子进程记录的持久化rbd文件
6、Slave接收rbd文件并同步数据
7、Master与Slave保持socket长连接并实时同步数据,保证数据一致性
数据复制
1、Master检测到与Slave的长连接超时(断开)
2、Master将断开后操作内存数据的命令生成副本repl backlog buffer
3、Slave重新建立连接并发送psync(offset)命令,表示我需要接收丢失的数据
offset代表偏移量
4、Master将repl backlog buffer的文件内容发送给Slave
可根据配置指定buffer的大小,例如1m,如果长时间断开或超过预置大小会走全量复制
5、Slave同步数据
6、Master与Slave保持socket长连接并实时同步数据
主从复制风暴
多个从节点申请向主节点同步数据,主节点可能会压力过大
为了避免Master同步压力过大,可以使用这样的方式同步数据
注意事项
主从复制的模式是一个主节点负责写入数据,多个从节点负责复制主节点数据,假设客户端向服务端发送命令,需要区分命令是写还是读,如果是写要将命令发送到主节点
如果Master节点宕机,运维需要手动修改Slave作为Master节点,包括服务的连接地址
哨兵模式
说明
sentinel哨兵,它是独立的进程,不负责Redis的读写操作,用来检测Redis的Master是否存活,如果检测到Master宕机,开启Slave选举机制,保证Redis的高可用性
核心问题
选举机制
根据sentinel配置文件中的quorum数量来确定,有多少个sentinel节点认为redis主节点宕机才开启选举机制
值一般为:sentinel总数 / 2 +1
选举完成后sentienl会自动更新配置文件中的master ip port
脑裂
假设我们是主从架构,一个Master,两个Slave,突然出现网络抖动,哨兵认为Master宕机了,发起选举机制,让Slave开启选举。
但是因为网络抖动的原因很快原Master又恢复了正常,那么就出现了两个Master节点。
但是因为网络抖动的原因很快原Master又恢复了正常,那么就出现了两个Master节点。
如何解决:我们可以通过设置半数以上哨兵认为Master宕机再是实行选举机制,其次可以设置通信时间,减少快速通信造成宕机的错觉
如何通信
哨兵与Master如何通信
哨兵会根据配置文件声明的Master节点不停的发送Ping(默认1/1s),如果Master长时间没有回复Pong就认为它宕机了
哨兵与哨兵之间如何通信
借助Redis的PUBLISH/MESSAGE来实现消息广播,每个哨兵都需要知道其他哨兵的地址,用来异步通讯
优劣势
优势
自动故障转移(选举)
高可用性
灵活性与扩展性,动态的修改Redis或哨兵的实例
劣势
单点故障(不可配置单一哨兵、哨兵本身也可能发生故障
配置复杂
延迟(主节点宕机后,哨兵监控到再选举完成这一过程需要一定的时间
Cluster
说明
Cluster其实是对主从和哨兵的一种新的架构,其中实现了哨兵对不同主节点的监控,每一个主节点都有自己的从节点,Redis会自动分片以保证数据不会大量倾斜到某一个主节点
创建Cluster的过程中Redis会自动配置主从的分配是在不同的机器交替通信,避免服务器宕机导致主从节点全部宕机
最大支持1w个主从集群,官方推荐不超过1000,因为主从之间是需要相互通信的,大量的主从集群会导致性能下降
核心问题
脑裂数据丢失
参考主从脑裂描述
造成原因:脑裂后有两个Master节点,那么根据Redis的机制早期的Master会降级会Slave,从新的Master同步数据;
Redis主从同步原理中,Slave会清空自己的持久化文件,也就是说早期的Master数据会丢失;
Redis主从同步原理中,Slave会清空自己的持久化文件,也就是说早期的Master数据会丢失;
如何解决:可以设置服务端响应客户端的命令,如果是write数据必须同步到1台或以上Slave实例再返回成功,这样就可以保证Master至少与一台Slave的数据是相同的
数据分片
Cluster模式下会存在一个逻辑的hash slot,它的长度是16384
假设我们有3个主从组成的Cluster,那么3个Master就会根据slot的长度平均分配可以存储哪些范围的hash
客户端发送的命令例如get、set需要提前计算存在于哪个实例,否则会造成not found data的情况(可以参考jedis是如何实现的)
跳转重定位
假设客户端发送一条get命令到服务端,服务端发现该元素并非在自己的hash slot内,会返回元素的slot。
客户端收到后会纠正自己记录的错误hash slot信息,重新获取元素
客户端收到后会纠正自己记录的错误hash slot信息,重新获取元素
网络抖动
为了避免脑裂造成数据丢失,监听实例的心跳通信时间可以实当延长,减小因为网络抖动造成假宕机的可能
集群是否完整才能对外提供服务
可以根据配置来确定如果某主从宕机集群还对外提供完整服务,还是只提供当前健全的服务
选举原理
1.slave监测到自己的master节点fail,会向全体master节点发送广播FAILOVER
2.其他master接收到后会去验证master是否已经fail,如果是就返回ack
3.slave的currentEpoch会根据master返回的ack去+1,当超过预期设置的选举阈值就成为新的master
4.发送广播通知其他节点,slave转换成新的master节点
补充:其他master收到failover后并不是立即对master节点尝试通信,而是有计算公式
延迟计算公式:DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
•SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新
这种方式下,持有最新数据的slave将会首先发起选举(理论上)
水平扩展
增加新的主从节点到集群
需要重新分片hash slot
数据倾斜
数据平均分配
分布式锁
Redisson-Lock流程图
核心问题
如何保证原子性?
lua脚本
如何保证业务未执行完前,即使到过期时间也不释放锁,给锁续时?
监听器+watchdog
常见问题
大Key
危害
导致redis阻塞线程,响应慢
网络拥挤
过期删除,如果没有异步删除就可能导致无法响应客户端的请求
避免一次性取出全部数据
如何解决
分段、分片存储
big list、big hash
常见缓存问题
缓存击穿
简述:大量的缓存数据在同一时间过期,导致大量请求直接请求到数据库层
如何解决
1、固定过期时间 + 随机过期时间
缓存穿透
简述:缓存热点数据,查询不存在的key导致请求直接访问到数据库层
如何解决
1、缓存空对象,避免查询不存在的key没有缓存(必须设置过期时间
2、布隆过滤器
缓存雪崩
简述:缓存服务宕机(压力过大、异常情况),导致大量请求访问数据库层,数据库层也宕机导致其他服务也受到影响
如何解决
1、缓存服务高可用(哨兵、主从、cluster)
2、超出峰值tps限流、熔断、降级
热点缓存key重建优化
简述:非热点数据(冷数据),并没有被缓存,某一原因导致突然访问量飙升,直接访问数据库层
如何解决
1、分布式锁,db操作成功后缓存到redis
缓存与数据库双写不一致
简述:数据被缓存后,并发情况下用户对数据进行修改,db可能与缓存的信息不一致
核心关注点:分什么样的场景,要满足多大程度的强一致性,要求一致性越高性能和复杂度都会有所不同
如何解决
1、非强一致性、并发量低,例如:我的购物车、我的主页信息,可以更新db后直接删除缓存,后续根据系统的缓存逻辑自动重新缓存
2、强一致性(可接受并发情况下容错),先更新db,后更新缓存并设置过期时间
3、延迟双删,防止并发情况下删除缓存的线程被阻塞,其他线程更新缓存导致数据不一致,可以先sleep一段时间后再删除缓存
4、阿里开源的canal监听mysql的binlog日志及时修改缓存,但是引入新的数据库中间件增加系统的复杂度
4、分布式锁(读写锁
过期策略
1.被动删除(惰性删除
读数据时验证数据是否过期,如果过期则直接删除这个key
2.主动删除
惰性删除无法满足清理大量冷数据时,redis会主动定期淘汰一批已过期的key
3.内存达到maxmemory执行主动清理策略
4.0之后增加了一些新的策略
volatile-ttl
volatile-random
allkeys-random
volatile-lru(最近最少使用
allkeys-lru
volatile-lfu(最不经常使用
allkeys-lfu
noeviction
内存满后提示客户端无法写入数据“OOM”
分布式系统全局序列号
使用redission可以轻松实现
概述
分布式系统全局序列号是指在分布式系统中,为了保证各个节点之间的数据同步,而对数据进行编号的一种机制。
特点
1.全局唯一性
全局序列号必须在整个分布式系统中是唯一的,不能重复。
2.递增性
全局序列号必须是递增的,每次生成的序列号都要比上一次生成的序列号大。
3.有序性
全局序列号必须是有序的,保证数据在分布式系统中的顺序性。
4.可比较性
全局序列号必须是可比较的,可以通过比较序列号的大小来判断数据的先后顺序。
5.易于生成
全局序列号的生成不能过于复杂,否则会影响系统的性能。
6.易于传输
全局序列号的传输必须是高效可靠的,否则会影响系统的性能。
7.易于存储
全局序列号的存储必须是高效可靠的,否则会影响系统的性能。
应用场景
1.分布式事务
在分布式系统中,为了保证各个节点之间的数据同步,需要使用全局序列号来对事务进行编号。
2.分布式锁
在分布式系统中,为了保证各个节点之间的数据同步,需要使用全局序列号来对锁进行编号。
3.分布式消息队列
在分布式系统中,为了保证消息的顺序性,需要使用全局序列号来对消息进行编号。
4.分布式日志
在分布式系统中,为了保证日志的顺序性,需要使用全局序列号来对日志进行编号。
5.分布式缓存
在分布式系统中,为了保证缓存的一致性,需要使用全局序列号来对缓存进行编号。
优秀Redis实战文章推荐
0 条评论
下一页