当前位置: 首页 > news >正文

Redis笔记

1、Redis为什么快?
答:
内存:读取速度快
单线程模型:redis 6.0之前是单线程,没有线程切换与上下文切换带来的性能开销
多路IO复用模型:不像传统的阻塞型IO,可以处理多个IO请求,提升IO效率。
高效的数据结构:hash、set、sorted set、list可以以O(1)的时间复杂度读取数据。
多线程:Redis6.0之后引入多线程,进一步提升并发性能。
2、redis常见的五种数据结构
答:
(1) String:key必须是String类型,实现了SDS(简单的动态字符串),底层是int和SDS,
底层的三种编码形式:int(ptr指针指向整数)、embstr(不超过32个字节,使用SDS存储,redis对象和SDS分在一个空间里)、raw(超过32字节,redis对象和SDS分在两个空间)
redis官方的目的:字符串可以灵活存储、操作便利。redis使用c语言编写出来的,c语言使用字符数组存储字符串,使用\0作为字符串的结尾,这样带来的问题:不能存储\0,这就做不到灵活存储、每次修改字符串得从头遍历去找\0才能知道在哪结尾,这就不遍历。redis的改动就是取消\0,给字符数组分配一个alloc(字符数组总长)和len(当前字符串长度)字段,这样就可以方便的定位字符串、灵活存储。
(2) Zset:在Set的基础之上每一个元素多了一个score属性,用于排序。
底层:压缩链表或者跳表实现,redis 7.0之后压缩链表变成listpack(紧凑链表)
压缩链表:特殊的双向链表,不是普通的双向链表一样前后节点通过两个指针相连,而是将Entry存储在连续数组上。格式:压缩链表总内存、最后一个entry偏移量、entry个数,entry、结束标识。
redis 7.0之后由跳表+hashmap组成,二者是同时存在同时维护的。
跳表:在单向链表的基础上增加了多级索引。查询、插入、删除的时间复杂度都是O(logN),适合范围查询和排序。这个索引层是随机的,就是插入一个节点之后,会有一个随机过程(抛硬币)决定这个节点能否升级为二级索引。
hashmap查询的时间复杂度是O(1),适合查找数据和判断数据是否存在。
压缩链表通过连续存储元素节省空间。
redis 5.0之前使用压缩链表、跳表
redis 5.0~7.0之间使用紧凑链表、调表
redis 7.0之后只有跳表
一般在元素少的时候使用压缩链表,标准就是:元素的个数小于128个 + 每个元素大小小于64字节 的时候使用压缩链表,否则使用跳表
应用:排行榜,使用ZADD、ZSCORE
(3) List:
底层:双向链表和压缩链表,有序可重复。
redis 3.2之前元素个数小于512,大小不超过64,就用压缩链表,否则使用双向链表。redis 3.2之后用quicklist。
应用:可以做消息队列(必须满足保序性、唯一性、可靠性),LPUSH、RPOP进行存储与获得消息,BRPOP阻塞式获取消息。但是不支持多个消费者重复消费
(4) Hash:
底层:压缩链表和Hash表实现。
元素的个数小于512个 + 每个元素大小小于64字节 的时候使用压缩链表,否则使用Hash表.
(5) Set:
底层:哈希表或者整数数组实现。无序不重复
标准就是:元素的个数小于512个使用整数集合,否则使用Hash表。
3、redis中的大key问题:
大key标准:value过大,对于String来说,内存超过5MB,对于Hash,元素个数不超过1000,其他类型的成员个数不超过10000。
如何识别大key:redis-cli命令
大key存在的问题:影响性能(读取内容、搜索内容很慢)、影响持久化、数据备份或者同步耗时、数据倾斜
如何处理大key:热度不高选择性的删除(unlink命令,不适用del命令,因为很慢)、设置缓存的TTL、拆分成小key(例如按照用户的券的类型拆分)、分散到不同的Clauster节点
4、redis数据倾斜问题
危害:
部分节点负载过高,响应时间过长,成为整个分布式系统的瓶颈;
部分节点可能任务处理失败,影响整个分布式事务。
数据倾斜类型:数据量倾斜、数据访问倾斜
数据量倾斜:HashTag使用不当、大key问题、slot分配不均匀。
数据访问倾斜:多副本冗余。将热点数据的副本复制多份放到不同节点上,访问的时候做一个负载均衡。
5、Redis使用过程中的缺陷?
答:
● 内存成本高
a. 业务不同阶段对 QPS 要求不同 比如游戏业务, 刚上线的新游戏特别火爆, 为了支持上千万同时在线, 需要不断的进行扩容增加机器。运营一段时间后, 游戏玩家可能变少, 访问频率(QPS)没那么高, 依然占用大量机器, 维护成本很高。
b. 需要为 Fork 预留内存 Redis 保存全量数据时, 需要 Fork 一个进程。Linux 的 fork 系统调用基于 Copy On Write 机制, 如果在此期间 Redis 有大量的写操作, 父子进程就需要各自维护一份内存。因此部署 Redis 的机器往往需要预留一半的内存。
● 缓存一致性的问题 对于 Redis + MySQL 的架构需要业务方花费大量的精力来维护缓存和数据库的一致性。
● 数据可靠性 Redis 本质上是一个内存数据库, 用户虽然可以使用 AOF 的 Always 来落盘保证数据可靠性, 但是会带来性能的大幅下降, 因此生产环境很少有使用。另外 不支持 回档, Master 故障后, 异步复制会造成数据的丢失。
● 异步复制 Redis 主备使用异步复制, 这个是异步复制固有的问题。主备使用异步复制, 响应延迟低, 性能高, 但是 Master 故障后, 会造成数据丢失。
6、为什么选择Redis?(分布式缓存选型)
答:Redis、Memcached、Dragonfly、KeyDB的对比
4、缓存读写策略?
答:旁路缓存模式、读写穿透、异步缓存写入
5、Redis事务
答:
redis事务就是保证一系列命令按顺序执行,中途不被打断,但是事务不支持回滚,因为redis设计初衷就是为了用缓存提升速度,回滚耗时耗性能。此外,redis cluster集群模式下不同key未必分布在同一个节点上,跨节点之间没法用事务!
redis事务用MUTI开启,EXEC结束,可以使用WATCH监听多个key在事务期间是否发生变动,如果发生改变就中止事务,将事务队列的所有命令全部删除。DISCARD命令会直接终止事务。
redis原子性在不同的部署方式下原子性是不同的。单机模式下,Redis拿到LUA脚本会将其放到脚本缓存队列里面,因为Redis是单线程和多路IO复用模型,在单机上肯定是能保证原子性,因此会串行读取LUA脚本中的命令。在主从复制下,所有LUA脚本写操作都在主节点上,从节点只会复制主节点的写操作,因此可以保证原子性。在Cluster集群部署下,如果LUA脚本操作的是同一个key,则可以保证原子性,但是如果操作的key不同,会hash到不同的slot,不一定能保证原子性。
6、为什么要LUA?
LUA脚本更快更简洁,LUA脚本能做事务能做的所有工作,而且事务队列的所有命令都要在EXEC命令执行才会被执行,多个命令之间存在依赖,不方便。
使用LUA脚本不能使用阻塞命令:BLPOP、BRPOP,这回导致阻塞。LUA脚本不宜过长,注意区分redis.call()和redis.pcall(),redis索引从1开始而不是0。
7、Redis集群
答:
(1)主从复制:一Master多Slave,不具备自动故障转移和恢复的功能,只能手动故障转移。
主从同步:全量同步(主节点执行bgsave,fork子线程生成RDB文件)、增量同步(根据offset(上一次同步的偏移量)拿对应的数据同步)
(2)哨兵模式:多个哨兵(Sentinel充当哨兵)监听一Master多Slave,向他们发送心跳信号,主节点没有回复则被哨兵标记为主观下线,多个哨兵标记为主观下线则从健康从节点选出一个主节点。可以自动故障转移保证高可用性(AP)
(3)Cluster集群模式:
Cluster集群模式中有多个Master节点(主节点之间通过心跳机制检测检测对方是否下线,就不需要Sentinel来监听整个集群),每个Master节点有多个从节点,这些从节点是其他Master节点的互备,通过数据冗余来提升可用性。
CRC16(请求的key)%16384(哈希槽个数)得到具体的哈希槽位置,每片区域的Mater节点负责写入数据,每个Master节点负责不同范围的哈希槽位置,请求直接发送到对应的Master节点就行。
每个分片里面都是一主多从模式,从节点复制其他主节点的数据作为互备。
当一个分片中的Master出现故障时,由于心跳机制,其他主节点会先主观认为它下线,直到多个主节点认为它下线,就是客观下线,于是会从它的从节点中选择一个主节点出来。
每个Master节点存的数据不相同,相当于把读请求分散了。

缺点:数据分片之后不方便批量操作(MSET、MGET方法)如果一定要用,可以用HashTag、资源隔离性差。
优点:分片多可以提升吞吐量与性能、可以水平拓展、更好的利用服务器资源,因为每个节点只处理一部分数据。

为什么是16384而不是65535?
答:(1)心跳数据包里面包含了节点配置,16384只需要2KB、65535需要8KB比较占内存
(2)官方的解释是一般来说出于负载均衡和迁移成本,主节点不会超过1000个,取16384不会让主节点下的slot过多,也可以使得取模运算更快。
8、redis延时队列
答:消息延时队列(ZSet,过期时间作为分数)、消息顺序队列(list,顺序存储)、消息目标队列(阻塞队列,存储已到期的消息)
redisson发送命令给redis去创建三个队列zset、list、blockinglist。
第一阶段:
通过发布-订阅机制,客户端可以订阅与延时队列相关的频道中的消息。(其一,创建延时任务;其二,异步获取数据)
调用zrangebyscore命令将此前没有发送完的消息都发送出去,默认是100条
通过BLPOP命令阻塞 式的从blockingqueue中获取消息
第二阶段:
客户端调用delayQueue的offer方法加数据,底层就是把数据存到ZSET与list里面。
然后ZSET会根据ZSET里的valu对应的score进行排序,把最临近的数据的到期时间publish到指定频道。
第三阶段:
客户端根据频道中的到期时间过会开延时任务,到点了就去ZSET里面取数据,把ZSET中对应数据删掉,放到blockingqueue里面。
客户端使用BLPOP命令阻塞式的获取数据。
9、redis过期策略
按照范围划分可以分为所有key和设置了过期时间的key,按照淘汰策略可以分为lru和lfu。
10、redis分布式锁
项目里面使用redisson实现分布式锁,redisson加锁的方法有lock和trylock一种是阻塞获取锁,一种是获取锁失败就会立即返回。可以聊自己项目里面秒杀活动加锁的行为。
11、本地缓存
设计本地缓存需要考虑的几个方面:
1、对象上限:本地缓存的数据放在JVM堆内存,可能导致OOM问题
2、线程安全:因为存在JVM堆内存,所以要考虑线程安全问题
3、缓存清除策略:FIFO、LRU、LFU、SOFT、WEAK
4、过期时间:
使用Caffeine实现本地缓存:
不使用Guava的原因:
1、Caffeine支持异步和写入外部资源,很多工作交给线程池处理。
2、guava用LRU,Caffeine用Window TinyLFU。
Caffeine实现本地缓存步骤:
1、使用CacheManager来管理本地缓存,
bean初始化的时候初始化本地缓存,初始化的时候要三种参数,缓存内存、写后的过期时间、处理后的过期时间。
向缓存中put数据,先看有没有,没有再put。
从缓存中get数据,使用getIfPresent()
从缓存中del数据,使用invalidate()方法
@Override
public void afterPropertiesSet(){
localCache = Caffeine.newBuilder()
.expireAfterWrite(10,TimeUnit.SECONDS)
.expireAfterAccess(10,TimeUnit.SECONDS)
.maximumSize(1000)
.build();
}

12、对redis强依赖如何保证高可用?
本地缓存caffine+分布式缓存redis 形成多级缓存结构,在分布式redis宕机的时候兜底

1、更新redis缓存时,也同步更新本地内存;
2、在redis失效后,查询本地缓存作为兜底数据;
3、管理后台修改数据库数据的时候,MQ广播消息,刷新本地缓存;更新redis缓存;
4、通过分布式定时任务来定时更新本地缓存数据和redis缓存,达到最终一致性;
4、在应用启动的时候,将特定的数据预热到本地缓存
Guava是线程安全的吗?
为什么线程安全?
很多核心类【InmutableList、InmutableMap】一旦创建就不可变
很多并发集合【Multiset、Multimap】使用类似于ConcurrentHashMap的分段锁机制保证线程安全
双重检查锁定(DCL):在锁区再判断一次
使用FutureTask避免重复计算:当一个线程开始计算某个键的值,其他线程会等待计算完成
缓存失效回收策略:
基于容量回收,使用LRU算法淘汰缓存数据。
定时回收:基于写操作的回收(一定时间没有写访问就会过期,exprireAfterWrite)、基于访问操作的回收(一定时间没有访问就会过期exprireAfterAccess)
基于引用回收:把key设置为软引用和弱引用
显式清除:invalidate、invalidateAll、asMap
缓存清除操作执行时机:
一般是在写操作期间完成的

13、如何保证本地缓存与分布式缓存的一致性
首先,本地缓存就是牺牲一致性来换效率的,如果本地缓存能保证一致性,那就不用分布式缓存了。
所以,方案做了以后可能会损害本地缓存的优势。本质就是本地缓存更新或者失效的时候,别的机器也能感知到,并且可以做出处理。
方案:
首先要评估数据变化频率。本地缓存和分布式缓存肯定是各司其职,本地缓存存储的是变化不频繁的数据,变化频繁的数据就放到redis。
其次要评估业务能否接受不一致,如果可以,接受的不一致时长是多少。如果能接受的延时时间是2min,那就可以把缓存里过期时间设置为1min的样子,到期就失效,去redis读新数据。另外Caffeine有一个refresh策略可以定时刷新数据。
通用的方案:要加中间件,比较难维护
1、借助配置中心(ZK),机器本地缓存发生变更之后,向配置中心做一次配置变更,然后配置中心再把变更推到每一台机器上。
2、MQ,变更本地缓存的时候,广播消息去更新其他机器上的缓存。

14、什么是脑裂?如何解决脑裂?
脑裂:在哨兵模式下,分布式系统有两个子集,每个子集都有一个主节点
脑裂的发生:
网络分区:master与slave、哨兵分区,哨兵会选举一个新的master。
主节点出问题:主节点出问题期间哨兵选举新的主节点,但是后来主节点又恢复了
脑裂的危害:
数据不一致:
重复写入:
数据丢失:网络恢复之后,slave节点会同步新master节点数据,在此之前会清空自身数据,导致数据丢失
redis有两个配置项:
min-slave-to-write(主节点同步的最少从库数量)、min-slave-max-lag(主从复制时从库相应ACK最大延时时间),例如配置为1和10s,通过这两个配置来限制旧的master不能接受客户端数据写入。
15、redis持久化?
AOF和RDB混合持久化。RDB持久化丢数据比较严重,AOF恢复又比较慢。所以使用混合模式,AOF不是全量日志,而是RDB持久化开始到RDB持久化结束这段时间内的增量日志。
16、redis的pipeline于原生批量命令
原生批量命令是一条命令多个key,所以是原子的
pipeline是多条命令,不是原子性的
因为我们业务需求是查询用户的券,所以在查询完券的时候还需要查询券的详细信息,所以涉及到多条命令,使用pipeline。原生批量查询和pipeline在网络延时越高的时候越好。
17、redis中事务和LUA脚本不能跨节点如何处理
方案一:使用hashtag去干预hash之后的结果,键名中用{}囊括的部分用于计算hash,这样可以把相同逻辑的键放到同一个节点。
方案二:redis 7.0之后提供了一个allow-cross-slot-keys命令,可以对不同slot中的键处理。但是一个事务下的键必须在一个slot上。
18、redisson的redlock
客户端向多个redis的master节点加锁,如果有N/2 +1个节点成功获取锁则视为加锁成功,否则释放所有节点的锁。
加锁的时间:客户端对每个master加锁的时间应该远小于锁释放的时间,例如lease time=5s,则加锁时间应该是50ms,目的是为了fast fail,因为要对很多节点加锁。向N个节点获取锁结束之后如果获取锁的时间小于失效时间则加锁是有效的。
为什么master数目N推荐奇数:
1、2N+1和2N+2的容灾能力相同,总数为5和总数为6的容灾性都为2,使用奇数占用资源更少
2、偶数会出现歧义,两个客户端都加锁的时候,每个客户端都对一般节点加锁会导致都加锁失败
为什么redisson废弃redlock:
1、加锁和维护都得对多个实例操作,比较复杂
2、网络分区和节点故障对加锁影响大
19、redisson的watchdog机制
redisson实现的分布式锁在默认过期时间(30秒)会开启看门狗机制,watchdog基于netty的时间轮启动一个后台任务定期的向redis发送命令去重设锁的过期时间,这个定期的时间就是锁的有效时间过去三分之一,也就是一到20秒就自动续期到30秒。
20、redis的发布订阅模式
redis消息发送者在频道中发布信息,订阅了频道的客户端都会收到该信息。
优点:实时性和灵活性高
缺点:可靠性低,发布订阅是一个异步的过程,因为发布者不会等待订阅者接受消息,消息可能丢失。
21、缓存三剑客
缓存击穿:热点key失效,缓存预热+永不过期、分布式双重判定锁
缓存穿透:访问不存在的key,布隆过滤器+空值缓存+双重判定锁
缓存雪崩:大量key同时失效,过期时间随机化、高可用架构(redis哨兵、集群)、服务降级
22、SETNX指令
定义:SETNX KEY VALUE . 当key不存在的时候设置值,如果设置成功则返回(Integer)1,否则返回(Integer)0。
如何判断key是否存在:由于redis是一张全局hash表,所以先计算key的hash值得到key对应表中的索引位置,然后会遍历这个位置上的链表判断是否有相同的key,判断key是否相同用的是C语言中dictCompareKeys函数。
命令:
SETNX key value
SET key value NX EX seconds; SETNX等价于SET NX
SET key value NX PX milliseconds
23、redis如何测试延时:
基本延时测试:
redis-cli -h 127.0.0.1 -p 6379 --latency
延时的历史记录:
redis-cli -h 127.0.0.1 -p 6379 --latency-history -i 10
24、redis的读写策略
1、旁路缓存:一般使用缓存双删
读:缓存命中则返回,缓存未命中则查库,写redis再返回
写:写库,删缓存
2、读写穿透:一般不用,redis没有同步机制
读:缓存命中则返回,缓存未命中则查库,写redis再返回
写:更新缓存,缓存去同步库。
3、异步缓存写入:一般不用,无法保证强一致性
写:多次更新缓存,批量更新库。

http://www.hskmm.com/?act=detail&tid=9563

相关文章:

  • MYSQL 笔记
  • Java笔记
  • 分布式 笔记
  • Windows Server 2019 中文版、英文版下载 (2025 年 9 月更新)
  • Windows Server 2016 中文版、英文版下载 (2025 年 9 月更新)
  • Windows Server 2025 中文版、英文版下载 (2025 年 9 月更新)
  • 美联储降息 25 个基点,这事儿跟我们有多大关系?
  • Windows Server 2022 中文版、英文版下载 (2025 年 9 月更新)
  • 袋鼠云跻身榜单2025中国Data+AI创新企业榜Top15,入选“2025中国隐形独角兽500强”榜单等多项荣誉
  • k8s系列--前世今生
  • excel文本改为数据格式
  • 面向对象初步接触-学生信息管理系统
  • Numpy高维数组的索引()
  • 详细介绍:jQuery 操作指南:从 DOM 操作到 AJAX
  • 一个 Blazor/WinForm 开发者的 WPF 学习记:通往 Avalonia 的那条路
  • VulkanAPI细节梳理2
  • React 状态丢失:组件 key 用错引发的渲染异常 - 指南
  • 快速实现 Excel 表格转 SVG:Java 教程 - E
  • 绕过文件上传限制实现客户端路径遍历漏洞利用的技术解析
  • 事件总线之初步学习
  • Markdown Day04
  • C++中类的内存存储
  • PyTorch 优化器(Optimizer)
  • 实用指南:域名市场中,如何确认域名的价值
  • 初步了解Neo4j
  • 多模态和语音 AI 年度收官大会,把握 2026 技术风向标!
  • 做题
  • 解码C语言函数
  • SchemaStore
  • XSS攻击防御