<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Redis on Tequila's 学习笔记</title><link>https://latnx.github.io/docs/notes/d-205f3859abe469be/d-01bd224b3773410f/d-7cf3b59b7dee5a40/</link><description>Recent content in Redis on Tequila's 学习笔记</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>© 2026 Tequila</copyright><lastBuildDate>Wed, 01 Apr 2026 13:53:00 +0000</lastBuildDate><atom:link href="https://latnx.github.io/docs/notes/d-205f3859abe469be/d-01bd224b3773410f/d-7cf3b59b7dee5a40/index.xml" rel="self" type="application/rss+xml"/><item><title>01 Redis</title><link>https://latnx.github.io/docs/notes/d-205f3859abe469be/d-01bd224b3773410f/d-7cf3b59b7dee5a40/n-b084a12188175095/</link><pubDate>Wed, 01 Apr 2026 13:53:00 +0000</pubDate><guid>https://latnx.github.io/docs/notes/d-205f3859abe469be/d-01bd224b3773410f/d-7cf3b59b7dee5a40/n-b084a12188175095/</guid><description>typedef struct redisObject {​ unsigned type:4;​ unsigned encoding:4;​ unsigned lru:LRU_BITS;​ int refcount;​ void *ptr;​ } robj; Redis 单线程
1.多线程引入的复杂性是极大的​
首先，多线程引入之后，Redis原来的顺序执行特性就不复存在，为了支持事务的原子性、隔离性，Redis就不得不引入一些很复杂的实现；​ 其次，Redis的数据结构，可以说是极其高效，在单线程模式下做了很多特性的优化，如果引入多线程，那么所有底层数据结构都要改造为线程安全，这会是极其复杂的工作； 2.多线程带来额外的成本​ 除了引入复杂度，多线程还会带来额外的成本。包括：​
上下文切换成本，多线程调度需要切换线程上下文，这个操作先存储当前线程的本地数据、程序指针等，然后载入另一个线程数据，这种内核操作的成本不可忽视。​ 同步机制的开销，一些公共资源，在单线程模式下直接访问就行了，多线程需要通过加锁等方式去进行同步，这也是不可忽视的CPU开销；​ 一个线程本身也占据内存大小，对Redis这种内存数据库而言，内存非常珍贵，多线程本身带来的内存使用的成本也需要谨慎决策。 性能 第一，Redis 的大部分操作在内存上完成，内存操作本身就特别快；​ 第二，Redis追求极致，选择了很多高效的数据结构，并做了非常多的优化，比如ziplist, hash，跳表，有时候一种对象底层有几种实现以应对不同场景。​ 第三，Redis 采用了多路复用机制，使其在网络 IO 操作中能并发处理大量的客户端请求，实现高吞吐量。
内存淘汰策略 # LRU：采样
标准LRU需要维护双链表，内存成本巨大，所以Redis采用近似LRU采样来做淘汰，具体步骤是随机采样 n 个 key，这个采样个数默认为 5，然后根据时间戳淘汰掉最旧的那个 key，如果淘汰后内存还是不足，就继续随机采样来淘汰。​
在3.0之后，Redis还针对近似LRU算法做了淘汰池优化，也就是维护一个候选池，池中的数据根据访问时间进行排序。第一次随机选取的key都会放入池中，然后淘汰掉最久未访问的，比如第一次选了5个，淘汰了1个，剩下4个继续留在池子里。​
当池子装满了后，每次随机选取的key只有空闲时间大于 池子里当前空闲时间最小的key时，才会放入池中，并替换池子中空闲时间最小的 key , 然后将池中空闲时间最大的key“淘汰”掉；
LFU：使用带时间衰减的近似 LFU</description></item><item><title>02 数据类型</title><link>https://latnx.github.io/docs/notes/d-205f3859abe469be/d-01bd224b3773410f/d-7cf3b59b7dee5a40/n-f666c9abd8048756/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://latnx.github.io/docs/notes/d-205f3859abe469be/d-01bd224b3773410f/d-7cf3b59b7dee5a40/n-f666c9abd8048756/</guid><description>String # 实现 # 字符串对象的内部编码（encoding）有 3 种 ：int、raw和 embstr。
INT编码：这个很好理解，就是存一个整型，可以用long表示的整数就以这种编码存储 EMBSTR编码：如果字符串小于等于阈值字节，使用EMBSTR编码 RAW编码：字符串大于阈值字节，则用RAW编码 EMBSTR 和 RAW 都是由 redisObject 和 SDS 两个结构组成，它们的差异在于，EMBSTR 下redisObject 和 SDS 是连续的内存，RAW 编码下 redisObject 和 SDS 的内存是分开的。​
EMBSTR优点是redisObject和SDS两个结构可以一次性分配空间，缺点在于如果重新分配空间，整体都需要再分配，所以EMBSTR设计为只读，任何写操作之后EMBSTR都会变成RAW，理念是发生过修改的字符串通常会认为是易变的。
Redis这样做会有很多好处：
embstr编码将创建字符串对象所需的内存分配次数从 raw 编码的两次降低为一次； 释放 embstr 编码的字符串对象同样只需要调用一次内存释放函数； 因为 embstr 编码的字符串对象的所有数据都保存在一块连续的内存里面可以更好的利用 CPU 缓存提升性能。 embstr 也有缺点的： 如果字符串的长度增加需要重新分配内存时，整个redisObject和sds都需要重新分配空间，所以embstr编码的字符串对象实际上是只读的，redis没有为embstr编码的字符串对象编写任何相应的修改程序。当我们对embstr编码的字符串对象执行任何修改命令（例如append）时，程序会先将对象的编码从embstr转换成raw，然后再执行修改命令。
命令 # &amp;gt; SET key value &amp;gt; SETNX key value &amp;gt; GET key &amp;gt; MGET key [key ...] &amp;gt; EXISTS key &amp;gt; STRLEN key &amp;gt; DEL key &amp;gt; MSET k1 v1 k2 v2 &amp;gt; INCR key &amp;gt; DECR key &amp;gt; EXPIRE &amp;gt; TTL 应用 # 缓存对象：SET user:1 '{&amp;quot;name&amp;quot;:&amp;quot;xiaolin&amp;quot;, &amp;quot;age&amp;quot;:18}' 常规计数：INCR aritcle:readcount:1001 分布式锁：`SET lock_key unique_value NX PX 10000 共享session：分布式服务器使用同一个session Hash # 实现 # 3.</description></item><item><title>03 持久化</title><link>https://latnx.github.io/docs/notes/d-205f3859abe469be/d-01bd224b3773410f/d-7cf3b59b7dee5a40/n-0504e66dcc10cdbd/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://latnx.github.io/docs/notes/d-205f3859abe469be/d-01bd224b3773410f/d-7cf3b59b7dee5a40/n-0504e66dcc10cdbd/</guid><description>1. RDB 快照 # RDB（Redis Database Backup），记录Redis某个时刻的全部数据，这种方式本质就是二进制形式数据快照，直接保存二进制数据到磁盘，后续通过加载RDB文件恢复数据。
触发时机
RDB可以通过配置定时触发，触发时用的是后台持久化方式。​ 也可以save命令，bgsave命令主动触发，save底层用的是阻塞式持久化，bgsave用的是后台持久化。​ 最后，如果Redis正常关闭，是会执行阻塞式持久化的。 流程 首先，Fork出一个子进程来专门做RDB持久化​ 接着，子进程写数据到临时的RDB文件​ 最后，用新RDB文件替换旧的RDB文件 写时复制 复制页表，但指向同一空间 若主线程修改，物理内存就会被复制一份（键值对 A&amp;rsquo;），然后主线程在这个数据副本（键值对 A&amp;rsquo;）进行修改操作 混合持久化 当开启了混合持久化时，在 AOF 重写日志时，fork 出来的重写子进程会先将与主线程共享的内存数据以 RDB 方式写入到 AOF 文件，然后主线程处理的操作命令会被记录在重写缓冲区里，重写缓冲区里的增量命令会以 AOF 方式写入到 AOF 文件，写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。
2. AOF 日志 # AOF（Append Only File），记录执行的每条命令，重启之后通过重放命令来恢复数据，AOF本质是记录操作日志，后续通过日志重放恢复数据。
先执行后写日志， 好处：避免检查开销、不阻塞命令 风险：日志丢失、阻塞下一个命令
流程
写入server.aof_buf 缓冲区 write() 系统调用，拷贝到page cache 写入磁盘 写磁盘策略 Redis 提供了三种将 AOF 日志写回硬盘的策略，分别是 Always、Everysec 和 No，这三种策略在可靠性上是从高到低，而在性能上则是从低到高。
重写机制
随着执行的命令越多，AOF 文件的体积自然也会越来越大，为了避免日志文件过大， Redis 提供了 AOF 重写机制，它会直接扫描数据中所有的键值对数据，然后为每一个键值对生成一条写操作命令，接着将该命令写入到新的 AOF 文件，重写完成后，就替换掉现有的 AOF 日志。重写的过程是由后台子进程完成的，这样可以使得主进程可以继续正常处理命令。 重写流程：一次拷贝，两处缓冲 fork子进程 写入AOF缓冲 和 AOF 重写缓冲; AOF缓冲用于保证 此时发生宕机，原来的AOF日志也是完整的，可用于恢复。 AOF重写缓冲 用于保证 新的AOF文件 也不会丢失 最新的写入操作。 区别 # 体积方面：相同数据量下，RDB体积更小，因为RDB是记录的二进制紧凑型数据​ 恢复速度：RDB是数据快照，可以直接加载，而AOF文件恢复，相当于重放情况，RDB显然会更快​ 数据完整性：AOF记录了每条日志，RDB是间隔一段时间记录一次，用AOF恢复数据通常会更为完整。</description></item><item><title>04 主从集群</title><link>https://latnx.github.io/docs/notes/d-205f3859abe469be/d-01bd224b3773410f/d-7cf3b59b7dee5a40/n-5932b83835e87469/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://latnx.github.io/docs/notes/d-205f3859abe469be/d-01bd224b3773410f/d-7cf3b59b7dee5a40/n-5932b83835e87469/</guid><description>主从同步 # 全量复制（Full Sync） 和 增量复制（Partial Sync）
全量同步 # slave 节点请求增量同步 master节点判断 replid ，发现不一致，拒绝增量同步 master将完整内存生成rdb，将rdb发送到slave slave 清空本地数据，加载RDB文件 发送剩余未同步的缓冲命令 增量复制 # 从服务器在恢复网络后，会发送 psync 命令给主服务器，此时的 psync 命令里的 offset 参数不是 -1； 主服务器收到该命令后，然后用 CONTINUE 响应命令告诉从服务器接下来采用增量复制的方式同步数据； 然后主服务将主从服务器断线期间，所执行的写命令发送给从服务器，然后从服务器执行这些命令。 repl_backlog_buffer，是一个「环形」缓冲区，用于主从服务器断连后，从中找到差异的数据 replication offset，标记上面那个缓冲区的同步进度，主从服务器都有各自的偏移量 哨兵模式 # Redis 哨兵（Sentinel）是 Redis 官方提供的一种高可用解决方案，用于在主从复制架构中实现自动故障检测与自动切换（failover）。核心目标是：当主节点不可用时，系统可以自动选举新的主节点，并让客户端继续工作。
作用 # 监控主从节点是否正常运行；​ 通过发布订阅模式，将故障转移的结果通知给订阅者；​ 当主节点发生故障时，自动进行故障转移，选择一个最合适的从节点升级为主节点，并通知应用方更新配置。 流程 # 主观下线：当某一个哨兵认为主节点不可达（超过 down-after-milliseconds），就会标记 客观下线：当多个哨兵达成共识
Leader 哨兵的选举策略
哪个哨兵节点判断主节点为「客观下线」，这个哨兵节点就是候选者，所谓的候选者就是想当 Leader 的哨兵。 候选者会向其他哨兵发送命令，表明希望成为 Leader 来执行主从切换，并让所有其他哨兵对它进行投票。 每个哨兵只有一次投票机会，如果用完后就不能参与投票了，可以投给自己或投给别人，但是只有候选者才能把票投给自己。 如果某个哨兵节点获得了超过半数哨兵节点的选票，且大于 quorum 配置值，就会成为 Leader 哨兵，否则重新进行选举。 故障转移： 选择主节点 1. 判断slave的优先级 (默认值 100，取值范围 0-100，是0不参与选举) 2.</description></item><item><title>05 分片集群</title><link>https://latnx.github.io/docs/notes/d-205f3859abe469be/d-01bd224b3773410f/d-7cf3b59b7dee5a40/n-ee80873a09a494cc/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://latnx.github.io/docs/notes/d-205f3859abe469be/d-01bd224b3773410f/d-7cf3b59b7dee5a40/n-ee80873a09a494cc/</guid><description>散列插槽 # 多个master平分哈希槽
3.1 Moved 重定向 # 客户端给一个Redis实例发送数据读写操作时，如果计算出来的槽不是在该节点上，这时候它会返回MOVED重定向错误，MOVED重定向错误中，会将哈希槽所在的新实例的IP和port端口带回去。这就是Redis Cluster的MOVED重定向机制。
3.2 ASK 重定向 # Ask重定向一般发生于集群伸缩的时候。集群伸缩会导致槽迁移，当我们去源节点访问时，此时数据已经可能已经迁移到了目标节点
Gossip协议 # meet消息：通知新节点加入。消息发送者通知接收者加入到当前集群，meet消息通信正常完成后，接收节点会加入到集群中并进行周期性的ping、pong消息交换。 ping消息：节点每秒会向集群中其他节点发送 ping 消息，消息中带有自己已知的两个节点的地址、槽、状态信息、最后一次通信时间等 pong消息：当接收到ping、meet消息时，作为响应消息回复给发送方确认消息正常通信。消息中同样带有自己已知的两个节点信息。 fail消息：当节点判定集群内另一个节点下线时，会向集群内广播一个fail消息，其他节点接收到fail消息之后把对应节点更新为下线状态。 故障转移 # Redis 分片集群（Redis Cluster）的故障转移（failover）机制，核心目标是在主节点（master）失效时，自动将对应的从节点（slave / replica）提升为新的主节点，从而保证集群可用性。 主观下线 客观下线
故障恢复
资格检查：检查从节点是否具备替换故障主节点的条件。 准备选举时间：资格检查通过后，更新触发故障选举时间。 发起选举：到了故障选举时间，进行选举。 选举投票：只有持有槽的主节点才有票，从节点收集到足够的选票（大于一半），触发替换主节点操 总结 是什么： Redis 集群将整个 key 空间分为 16384 个哈希槽（编号从 0 到 16383）。每个键根据其哈希值被映射到其中一个哈希槽中。Redis 使用 CRC16 算法计算键的哈希值，然后对 16384 取模，得到对应的哈希槽编号。​
数据怎么分布： 集群中的每个节点负责管理一定范围的哈希槽。例如，节点 A 可能负责管理哈希槽 0 到 5460，节点 B 负责管理哈希槽 5461 到 10922，节点 C 负责管理哈希槽 10923 到 16383。这样，键被分配到不同的节点上，从而实现数据的分布式存储。​</description></item><item><title>06 多级缓存</title><link>https://latnx.github.io/docs/notes/d-205f3859abe469be/d-01bd224b3773410f/d-7cf3b59b7dee5a40/n-d204b563d5f14db9/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://latnx.github.io/docs/notes/d-205f3859abe469be/d-01bd224b3773410f/d-7cf3b59b7dee5a40/n-d204b563d5f14db9/</guid><description> 本地-NGINX-Redis-TomCat-SQL # 缓存问题 # 缓存雪崩 # 当大量缓存数据在同一时间过期（失效）或者 Redis 故障宕机时，如果此时有大量的用户请求，都无法在 Redis 中处理，于是全部请求都直接访问数据库，从而导致数据库的压力骤增，严重的会造成数据库宕机，从而形成一系列连锁反应，造成整个系统崩溃，这就是缓存雪崩的问题。
缓存数据的过期时间设置随机，防止同一时间大量数据过期现象发生。​ 重建缓存加互斥锁，当线程拿到缓存发现缓存不存在就会尝试加锁，线程争抢锁，拿到锁的线程就会进行查询数据库，然后重建缓存，争抢锁失败的线程，你可以加一个睡眠然后循环重试 缓存击穿 # 如果缓存中的某个热点数据过期了，此时大量的请求访问了该热点数据，就无法从缓存中读取，直接访问数据库，数据库很容易就被高并发的请求冲垮，这就是缓存击穿的问题。
发现缓存失效，重建缓存加互斥锁，当线程查询缓存发现缓存不存在就会尝试加锁，线程争抢锁，拿到锁的线程就会进行查询数据库，然后重建缓存 不给热点数据设置过期时间，由后台异步更新缓存，或者在热点数据准备要过期前，提前通知后台线程更新缓存以及重新设置过期时间； 缓存穿透 # 既不在缓存中，也不在数据库中，导致请求在访问缓存时，发现缓存缺失，再去访问数据库时，发现数据库中也没有要访问的数据，没办法构建缓存数据，来服务后续的请求。那么当有大量这样的请求到来时，数据库的压力骤增，这就是缓存穿透的问题。
第一种方案，非法请求的限制； 第二种方案，缓存空值或者默认值； 第三种方案，使用布隆过滤器快速判断数据是否存在，避免通过查询数据库来判断数据是否存在；查询布隆过滤器说数据存在，并不一定证明数据库中存在这个数据，但是查询到数据不存在，数据库中一定就不存在这个数据 缓存一致性 # 更新MySQL即可，不管Redis，完全以过期时间兜底​ 更新MySQL之后，操作Redis，当然要考虑到Redis更新操作可能会因为网络、进程重启等各种原因失败，所以过期时间兜底还是少不了。​ 异步将MySQL的更新刷入到Redis，比如先更新mysql，通过订阅mysql的binlog记录来异步执行更新redis的操作</description></item><item><title>07 场景题</title><link>https://latnx.github.io/docs/notes/d-205f3859abe469be/d-01bd224b3773410f/d-7cf3b59b7dee5a40/n-5506ab4555e98ff9/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://latnx.github.io/docs/notes/d-205f3859abe469be/d-01bd224b3773410f/d-7cf3b59b7dee5a40/n-5506ab4555e98ff9/</guid><description> 分布式锁 # 分布式锁，顾名思义，分布式锁就是分布式场景下的锁，比如多台不同机器上的进程，去竞争同一项资源，就是分布式锁。
setnx key value如果key不存在，则会将key设置为value，并返回1；如果key存在，不会有任务影响，返回0。
set key value nx ex secondsnx表示具备setnx特定，ex表示增加了过期时间，最后一个参数就是过期时间的值。
分布式锁需要满足谁申请谁释放原则，不能释放别人的锁，也就是说，分布式锁，是要有归属的。
Redis➕Lua，可以说是专门为解决原子问题而生。有了Lua的特性，Redis才真正在分布式锁、秒杀等场景
RedLock
消息队列 # List，不需要ACK，不需要消费组，可用​
PUB/SUB，不需要ACK，不需要持久化，可用​
Stream，需要ACK，需要消费组，需要持久化，可用​
Stream功能最全，但是相对完备的消息队列中间件比如Kafka，可靠性还是很大差距，不支持至少一次语意，因为Redis本身的数据持久化都是有时间空隙的，如果对数据的可靠要求比较强，还是需要用完整的消息中间件。​
Redis这三种，是三种不同功能要求下的消息传递手段，Stream相对来说在轻量级里相对完善。
秒杀 # 第一点 高并发：海量请求，服务要能扛住。 我们可以先将库存名额预加载到Redis，然后在Redis中进行扣减，扣减成功的再通过消息队列，传递到MySQL做真正的订单生成。
第二点，不能超卖。 第一步，判断库存名额是否充足；​ 第二步，减少库存名额，扣减成功就是抢到。 如果第一步判断的时候还有库存，但是由于是并发操作，实际调用的时候，可能已经没有库存了，这样就会造成超卖。使用lua
第三点，避免少卖。 1. 上面提到的，减少库存操作超时，但实际是成功的，因为超时并不会进入生成订单流程​ 2. 在Redis操作成功，但是向Kafka发送消息失败，这种情况也会白白消耗Redis中的库存。 3. 在投递Kafka失败的情况下，增加渐进式重试；
第四点，保证触达到用户而不是黄牛。
为了性能，我们还是将限制逻辑加入到Redis中，所以我们的Lua脚本中，第一步查询库存，第二步扣减库存，需要优化为第一步查询库存，第二步查询用户已购买个数，第三步扣减库存，第四步记录用户购买数。 限流器 # 算法 核心思想 固定窗口 每段时间计数 滑动窗口 最近一段时间计数 漏桶 固定速度处理请求 令牌桶 固定速度发放通行证</description></item></channel></rss>