Skip to content

Redis 笔记 ​

Redis 中常见的数据类型有哪些? ​

基础的数据类型就是:String,List,Hash,Set,ZSet

  • String:经典数据类型,存储文本,数字,二进制,单个最大为 512 MB,缓存用户会话,计数器都可用
  • List:有序的,字符串列表,底层是双端队列,可以用于消息队列场景
  • Hash:键值对集合,适合存储热点对象属性,类似于商品信息之类的数据可以使用
  • Set:无序,不重复的集合,适合做需要去重的场景
  • Zset:和 Set 差不多,区别是带有一个 分数 元素来排序,可以实现排行榜的操作

除此之外还有高级数据类型

  • BitMap:在 2.2 版本引入,用来存储位数据,每个 bit 表示一个哈希槽,只存 01,适用于存储用户是否签到之类的。用 SETBIT 设置状态,GETBIT 读取状态:
  • HyperLogLog:2.8 版本引入,是概率性的数据结构,用来估算基数的,无论多少数据,只占用 12 kB 的内存,缺点是不是百分百准确的,适合统计 UV 这种精度要求不高的场景
  • GEO: 地理信息,支持经纬度存储和空间查询,底层是用 ZSet 实现的,可以用来查询 附近的人 的操作
  • Stream: 5.0 版本引入,专门用于消息队列,支持了消费组的设定,还支持唯一消费 ID,还可以持久化,即使消费者挂了重启也可以重新消费

Redis 为什么这么快? ​

  • 数据保存在内存中,除了持久化,基本不和硬盘打交道,内存比硬盘快太多了
  • 单线程 + IO 多路复用:单线程意味着不会发生抢占,也不会因为上下文切换产生性能损耗,网络 IO 使用 epoll 多路复用,一个线程处理几万个 socket 链接,哪个链接有数据就处理哪个
  • 高效的数据结构,Redis 每个数据结构都经过精挑细选,String 底层使用 SDS,Hash 数据小的时候使用 zipList,数据大的时候使用 HashTable 保证查询 O(1),ZSet 使用跳表,插入,查询都是 O(Log N)

为什么 Redis 设计为单线程?6.0 版本为何引入多线程? ​

单线程意味着 Redis 的性能瓶颈不在 CPU,而是网络和内存

单线程的优势很明显,代码简单,不容易出 bug,无线程间竞争,没有锁和上下文切换的开销,一个线程盯着十几万个 socket,单机就能处理 十万+ 的QPS,大多数场景都够了

至于 6.0 为什么引入多线程,现代业务并发需求大,瓶颈出现在网络 IO 了,解析 Socket 的数据成了压力,所以就引入了 Threads 来处理网络 IO,指令还是有由一条主线程发送处理的,

另外 4.0 引入了后台线程,处理大 Key 的删除,AOF 刷盘这些耗时操作,防止主线程阻塞

Redis 在生成 RDB 文件时如何处理请求? ​

RDB 产生自主线程发送的 bgsave 由fork出的子线程执行,主线程会将内存页表复制给子线程一份,子线程通过这个复制品来制作 RDB,写入磁盘,主线程仍然处理用户请求,增量使用写时复制技术来保证子进程数据不变

生成 RDB 文件的时候如何保证一致性? ​

主线程在 fork 子线程的时候并不是去复制一份完整的内存数据给子线程,这样太浪费空间而且太慢,主线程会复制一份页表给子线程,子线程通过这个页表去遍历内存,还是共享一块内存

但是这样,主线程改,子线程读,还是会被污染,Redis 采用了写时复制技术,如果有写入操作,就复制一份当前内存页的副本出来,主线程在副本上更改,子线程去读旧的内存页,这样效率高,而且也能保证数据就是 fork 那一瞬间的数据,不会被后续操作污染

高峰期使用 RDB 有什么问题?如何优化? ​

  • 首先 RDB 期间,主线程采用写时复制进行写入,极端情况下会造成内存翻倍占用
  • 子线程需要将 RDB 文件写入磁盘,如果缓存数据集很大,写入磁盘的时间相当客观,坏情况数分钟不止,磁盘IO跑满,会影响其他的AOF 和其他业务
  • fork 子线程的时候主线程也会阻塞,虽然是复制的内存页表,但是在数据量很大的情况下,内存页的大小也相当可观,阻塞时间可能得几十毫秒甚至上百毫秒,这个时候,主线程是不能去工作的

所以一般会去设置 RDB 的时间为低峰时段跑,或者用从节点跑RDB,不耽误主节点处理请求

fork 的时候为什么会阻塞主进程?不是说只复制页表吗? ​

复制页表也是需要时间的,fork 期间会被阻塞,Redis 使用虚拟内存,每个内存页通常有 4kb,如果一个有 20G 的实例去做 RDB,主线程需要去复制一个差不多几十MB的页表,差不多需要消耗100毫秒了

如果 RDB 生成到一半机器挂了怎么办? ​

挂了就挂了,并不会造成很大的损失,RDB 操作是先写一份临时的 RDB 文件,然后再由 rename 操作转正为 dump.rdb,rename 是原子性的,如果失败了,就还是去恢复上一次的 RDB 文件。

bgsave 和 save 有什么区别?生产上用哪个? ​

save 是同步操作,主线程去做备份,期间完全阻塞,bgsave 是交给子线程的,异步操作,生产要用 bgsave,主线程阻塞带来的服务关停是不可承受的

Redis 中跳表的实现原理是什么? ​

跳表是一种多层链表,底层的链表存储数据,按照一定规则排序,上层的链表负责索引,上层是下层的子集,跳表相对于链表可以将查询操作优化到 O(N)

查找过程:首先从最上层的链表开始,往左找,如果左侧节点比查找值大,就下沉到下一层节点,然后一直循环到最底层找到数据或者数据不存在为止。

插入过程:首先通过查找的方法定位到数据层的插入位置,然后通过随机算法来确定要增设几层索引,Redis 使用百分之二十五的概率增加一层

删除过程:也是先找到节点,然后在每一层更改前后的引用,就跟普通的链表一样

Redis 的小巧思:在最底层的链表增设了回退指针,因为 ZSet 提供了反向查询的命令,可以倒序查数据

Redis 的小巧思:随机索引层的概率算法,Redis使用了随机数来确定当前插入的数据应该建立几层的索引,具体来说,Redis 规定了最多只能存在 32 层索引,然后在一个循环中取这随机数,看看是否小于32 的 百分之二十五,如果是的话就层数+1,不是就退出,最大是 32,通过最后计算出的层数来决定增加几层索引

Redis 的 hash 是什么? ​

Hash 是键值对的集合,可以将一堆键值对放在一个 key 下面,可以用于存储对象数据,每个字段相互独立,不像 Json 得全部覆盖,编码使用两种数据结构:总大小不超过 512 字节或者单个字段不超过 64 字节的状态使用 listpack,反之使用 hashtable 存储,查询效率为 O(1)

其实在 Redis 7 之前,Hash 不是使用的 Listpack,而是 ZIPList,但是 ZIPList 虽然也是紧凑型的数据结构,但是他维护了一个前一个节点长度的数据,这会导致级联更新问题,更新一个数据,就有可能要同步更新后面所有的数据,listpack 只存储当前节点的长度,避免了这个问题

Redis Zset 的实现原理是什么? ​

ZSet 的底层是跳表 + 哈希表配合实现的,跳表使用 score 进行排序,支持 O(Log N)的插入,删除,范围查询,哈希表存储 member 到 score 的映射,用来保证 O(1) 的查询

显而易见,两者各有所长,哈希表保证了 O(1) 的快速查询,效率极高,跳表保证了高效的范围查询,Redis 的跳表数据层是双向链表,支持正向 / 负向的范围查询

更新数据的时候,需要同时维护这两张表,同时更新其数据结构

当元素比较少的时候(总元素少于 128 个,且,每个元素不超过 64 字节)使用 Listpack 来存储,反之使用 跳表 + 哈希表

Redis 中如何保证缓存与数据库的数据一致性? ​

数据源同步问题没有很好的解决方案,只能说是有取舍

  • 先更新缓存,再更新数据库:这种方法很容易出问题,两个请求同时更新,A 先更新缓存,B更新缓存,B 更新数据库,A 更新数据库,就会出现缓存和数据库不一致的情况
  • 先更新数据库,再更新缓存:和前者一样,由于执行顺序不同,会出现缓存和数据库不一致的问题
  • 先删缓存,再更新数据库:也很常见,A 删除了缓存,B 来查询,发现查不到,去数据库拿了旧数据,A 更新缓存,B 更新缓存
  • 先更新数据库,再删缓存:缺点就是有很短的时间数据可能不一致
  • 缓存双删:先删除缓存,然后写数据库,然后延迟后再删除缓存,可以避免多任务写入旧数据带来的不一致问题
  • Binlog 异步更新:Canal 监听 MYSQL Binlog,通过消息异步同步到缓存,一致性最好,也不用操心缓存,自动更新,就是要做幂等这种操作而已

如果实在是不能容忍任何的不一致,比如金融场景,就只能进行加锁操作了,读写互斥,读操作不互斥

Redis 中的缓存穿透、缓存击穿和缓存雪崩是什么? ​

  • 缓存穿透:大量请求访问不存在于缓存中的值,请求打到数据库,这种常见于攻击行为,攻击者随便找一个不存在的 ID,进行查询,消耗数据库资源

    • 对请求的参数进行合法校验,防止拿着错误的参数进行查询
    • 使用布隆过滤器,加载需要的 Key,进行过滤操作,不存在就是不存在
    • 如果数据库查询之后,就在 Redis 中放置空值,设置过期时间,防止大量数据打入
  • 缓存击穿:某个热点 key 过期之后,大量请求被打到数据库,数据库压力暴增

    • 过期之后,可以只让一个任务去数据库查询,其他的等待更新就可以
    • 还可以在后台起一个线程用来专门刷新热点数据
  • 缓存雪崩:大量热点 key 在同一时间过期,或者 Redis 直接就是挂了,请求全部走到了数据库,直接爆炸

    • 如果是集体过期,就设置浮动值来避免大量 Key 直接过期
    • 如果是 Redis 崩了,就要考虑架构的问题了,可以设置 Redis 集群,主从 + 哨兵的机制让 Redis 即使挂了一个节点也没问题,也可以使用 Guava Cache 做一层本地缓存,先顶一会,看看 Redis 能不能重启成功,或者也可以直接熔断,防止产生雪崩

Redis String 类型的底层实现是什么?(SDS) ​

SDS 是 Redis 对 C++ char 数组进行的改进,原来的 char[] 数组获得长度是 O(n) 的时间复杂度,并且自动检测 \n 字符,而且扩容还得自己搞,

SDS 维护了除了数据区 buf 以外,还维护两个额外的区域:len 和 alloc,前者用于记录数据长度,后者用于记录分配区域大小,获取长度操作简化为了 O(1),插入时使用 alloc - len 进行空间检测,不够就扩容,buf 末尾保留 \n 用于兼容 C

另外,其还采用了不同的编码方式应对不同的状况,

  • 如果能解析成整数,直接使用 int 编码。直接放在指针位置,不用 SDS
  • 如果字符串长度小于 44,使用 embstr,redisObject 和 SDS 放到一起,一次 malloc 就可以
  • 超过 44 的话,使用 raw 编码,分开存储

至于为什么是 44,Redis 的内存分配器以 64 字节为一个分配单元,RedisObject 占用 16 字节,sdshdr8 头部 3 字节,末尾 \0 一字节,一共 二十字节,剩下 44 字节

Redis 中如何实现分布式锁? ​

其实很简单,Redis 使用 SET key value EX seconds NX 就可以进行创建锁,解锁可以使用 Lua 脚本校验删除,

加锁的时候先检测这个 key 是否存在,如果存在就不设置,说明拿不到锁,返回失败,如果不存在,就设置成自己的唯一识别码,然后返回成功

解锁的时候使用 lua 脚本先获取锁的 Value,检查是否是自己的唯一识别码,如果是的话就解锁,返回成功,如果不是的话就返回失败,

注意,分布式锁必须设有过期时间,可以防止拿到锁的服务挂掉,锁永不释放的情况出现

Lua 脚本可以让检查 Value 和删除的过程绑定为原子操作,防止分开执行中间被人插一脚

唯一的问题是单点下,Redis 挂了,锁就没了,这个对生产环境产生的打击是致命的,主从也容易出现不一致的情况,解决方案就是 RedLock,

RedLock 就是让多个 Redis 通过多数成功的机制加锁,解决了分布试下锁容易随着主节点崩溃而消失的问题,缺点就是需要多个节点同步,效率低

Redis 的 Red Lock 是什么?你了解吗? ​

红锁 - RedLock,Redis 使用的一种分布式锁,用于解决分布式环境下,Redis 集群锁丢失的问题

普通的 Redis 做分布式锁有丢失的风险,就是主节点挂了之后,从节点没来得及同步锁的消息,就直接晋升成为主节点了,锁就消失了

RedLock 采用 多数投票机制,拿到大多数节点锁的操作才算成功,所以即使主节点爆炸,从节点也能保证带有锁

RedLock 加锁流程:

  • 客户端记录当前的 时间戳 T1
  • 依次向多个 Redis 实例发送请求,请求授权锁,如果这个请求超时就跳过
  • 最后轮训完一遍,拿到这个时候的时间戳 T2
  • 统计成功的请求数目,统计请求成功的节点数,统计总耗时 T3 = T2 - T1
  • 如果通过的实例数 >= 总实例数 / 2 向上取整,并且 T3 < 锁过期时间就说明加锁成功
  • 失败就向所有实例发送解锁请求,表示加锁失败,撤了吧

其实这玩意也不是绝对安全的,首先就是:

  • 线程1请求到了锁,然后发生 GC了,进程暂停,锁过期
  • 线程2拿到了锁,美美执行任务
  • 线程1复活,美美执行任务
  • 两个线程美美共用同一个临界区的资源

除了这个,由于每个锁都是在本地执行的,发生时钟跳变也会影响锁的唯一性

一般情况下,Redisson 的看门狗就能解决这个问题

Redis 实现分布式锁时可能遇到的问题有哪些? ​

如果使用简单的锁的话遇到的问题也不少:

  • 锁在业务执行前自动过期,数据自然就乱了,可以使用续约机制,到期之前检测业务是否正常进行,如果是的话,直接续约
  • 线程发生 CG,锁过期,其他线程持有了锁,线程醒过来又给删了,解决方案一般是设置 唯一设别码,用来判断是不是自己的锁
  • 如果单点 Redis 爆炸,锁就爆了,如果主从同步,主节点爆了,从节点还没有同步到,锁也会丢失,可以使用 RedLock
  • 锁不可重入,解决办法是设置重入次数,重入一次 +1,释放一次 -1

Redis 的持久化机制有哪些? ​

主要有两种持久化的机制:RDB 和 AOF

RDB:快照持久化技术,通过记录这一瞬间的数据快照然后通过 dump 保存内存中的数据,优点是加载速度快,数据直接加载到内存中,缺点就是备份间隙的数据容易丢,比如五分钟一次备份,最多就丢五分钟数据

AOF:日志持久化技术,将指令追加到 AOF 日志中,安全性最高,每条指令都备份,缺点就是一直备份,备份文件体积大,恢复的时候需要全量执行指令,速度慢

两者各有优缺点,可以结合起来,4.0 引入了混合持久化,AOF 重写的时候先用 RDB 保存快照,快照间隙产生的增量用 AOF 追加到文件末尾。兼顾恢复速度和安全性

说说 AOF? ​

AOF 是Redis 提供的一种日志式的持久化操作,通过维护一个 AOF 文件,将每条指令都追加到 AOF 文件的末尾,因此,这玩意也比 RDB 安全,最多丢掉一秒钟的指令。但是由于每条指令都追加,这玩意的文件大小也大,恢复速度也慢,

由于 AOF 日志存放在磁盘中,如果每次指令都需要写回磁盘的话,压力太大,Redis 提供了好几种方案用于 AOF 日志的写回

  • always;每条指令都追加到 AOF 中,最安全,性能最低,因为要等待磁盘 IO
  • everysec:每秒刷一次,时间和效率的妥协,默认就是这个,毕竟,一秒能存什么牛逼的数据呢,对吧?
  • no:不主动存,让操作系统判断什么时候该存,性能最好,但是最容易丢

即使是 always 模式下,也容易丢,因为 Redis 执行指令和 AOF 写回并不是一个原子操作,如果中间断了,就直接丢了,所以 Redis 并不能做到完全不丢数据,毕竟性能才是 Redis 的卖点

AOF 由于一直追加会导致日志文件爆炸大,而且一条数据被 Set 一千次,只有最后一条是有用的,这就有了 AOF 重写机制: 重写操作进行的时候 fork 出子进程,子进程根据内存快照重新制定一份 AOF 文件,在这中间产生的增量同时追加到旧 AOF 和 重写缓冲区,然后子进程追加重写缓冲区的数据到新 AOF,然后用新 AOF 替换掉旧 AOF

重写可以通过自动触发也可以手动

Redis 主从复制的实现原理是什么? ​

主从复制就是 Redis 集群用于同步数据的方案,主要是主节点向子节点同步数据,增加负载

主要分为三步:

  • 建立全量同步:子节点链接的时候,找到主节点,发送一个 PSYNC 命令,表示自己第一次来,想要跟着大哥混,索要数据,主节点就会进行一次全量同步,主节点会 fork 一个子线程用于生成一份 RDB 快照发送给从节点,从节点卸载掉自己的旧数据,加载得到的 RDB,在这个过程中,主节点并不会停止服务,这个过程中产生的增量会保存到 Replication Buffer 中,从节点同步完成之后,再发送这个缓冲区的数据,保证数据不丢
  • 数据同步:主节点每收到一个命令之后,就会异步发送给从节点,从节点执行这些命令并且互相发送心跳保证存货
  • 重连恢复:如果子节点挂了,重连的话,全量同步太费时间了,而且消耗资源,所以就有了增量同步这一说,Redis 在 2.8 之后维护了一个 repl_backlog_buffer 用于存储近期数据,这个缓冲区是环形存储的,存储满了就从最老的节点进行覆盖,子节点重连之后,会发送自己的偏移量,主节点检测是否存在这个数据,如果存在就同步,不存在就全量

Redis 数据过期后的删除策略是什么? ​

Redis 提供了两种删除方法

  • 惰性删除,过期的 Key 不会马上被清除出去,而是会在被读取的时候顺手检测一下,如果过期就删掉并且返回空,好处就是不会占用太多 CPU 去扫描,坏处就是冷数据不会走
  • 定期删除其实也不是完全删除,而是进行抽样检测,发现过期就清理,避免 CPU 全在干清理的活

如何解决 Redis 中的热点 key 问题? ​

热 Key 就是被高频访问的 Key,比如大促热门商品等,一秒几十万的请求打到一个 Key 上面,Redis 单线程处理命令,大量的请求可能直接占满 CPU 打掉 Redis,主要解决思路就是分散压力

  • 可以在 Redis 前面加一层本地缓存,比如 Cache,让 JVM 直接承担热 Key 的响应,如果能命中百分之九十的热 Key,Redis 压力直接少一个数量级
  • 读写分离:Redis 提供了主从节点来改善这种情况,从节点负责读取,这样就不会都打到一个 Redis 了
  • 限流:在网关层限制对热 Key 的访问,超过阈值直接快速失败,与其让用户看到 活动过于火爆,也不能让 Redis 崩溃造成雪崩

关于这个 热点Key,如果一个 8 核的 Redis 实例,整体的 QPS 能到十万,如果一个 key 的 QPS 到了五千就可以准备报警了,超过一万就说明是热点 key 了

Redis 集群的实现原理是什么? ​

Redis 集群的设计目的是解决单机瓶颈问题,将数据分成不同的块,节点之间通过 gossip 协议通讯,每个 Redis 负责不同的块,降低单机压力

首先,Redis 将所有的数据分成了 16384 个槽位,每个节点负责一部分,也就是哈希槽,数据过来之后,计算出 CRC16 值,然后对着 16384 取模,就可以知道这个归谁管了,直接送到对应的 Redis 中,好处就是增加 Redis 节点之后,只需要重新分管哈希槽就可以知道数据存在什么节点了,而不需要重新计算哈希值。因为槽位是固定的

节点之间通讯使用 gossip 协议通信,大概来说,每个节点随机选择几个节点,和这些节点进行信息交换,主要有分管哈希槽的信息,心跳等,这样去中心化也能保证在很短的时间内节点的一致性,也能知道整体的拓扑结构

至于客户端如何获取集群的数据,这个就很简单了,客户端首先随便链接集群中的一个节点,然后去拿着 key 找数据,如果恰巧这个 key 所在的哈希槽就归这个 Redis 分管,那么就可以直接获取数据,如果不在,Redis 会查看这个哈希槽归谁管,然后返回这个节点的连接信息

Redis 中的 Big Key 问题是什么?如何解决? ​

Big key 就是指比较大的 key,会产生问题:

  • 大 key 占用空间很大,集群模式下,哈希桶是平均分配的,大 key 可能集中在某一个实例中,影响查询效率
  • Redis 是单线程处理的,处理大 key 消耗的资源可能会影响其他任务的进行,比如内存资源,网络资源,可能会导致其他任务的超时

解决大 key:

  • 可以改变存储方式,可以拆分大 key,或者可以对数据做压缩,Json 数据可以用 Hash 存储
  • 尽量只存储必要数据,从业务层减小 key 的体积
  • 使用 Redis Cluster 部署,减少单片压力

Redis 通常应用于哪些场景? ​

  • 缓存:Redis 将数据存放在内存之中,速度很快,天然适合做缓存,用于将一些热点数据存储在 Redis 中,防止所有的请求打到数据库,提升系统性能
  • 分布式锁:Redis 指令都是由一个线程完成的,天然保持原子性,适合做锁
  • 高频计数器:Redis 基于内存,性能高,配合 incr 做自增,不会算错
  • 排序业务:Redis 自带一个 ZSet 的数据结构,天然排序,速度很快,适合做实时大榜之类的场景
  • 轻量消息队列:如果不想用 kafka 这种重型消息队列,可以使用 Redis 自带的 List 来实现

如何使用 Redis 快速实现排行榜? ​

Redis 提供 Sorted Set,底层使用跳表实现,插入,删除,查询的时间复杂度都是 O(logN),Sorted Set 根据 score 进行排序

Sorted Set 支持范围查询,查询某个用户位与多少名,加分,减分都可以

如果 score 相同,就按照 member 的字典序排序,如果想按照时间排序,就可以将时间戳写入到 score 中,这样就可以根据时间排序了

百万级的排名可以,但是再往上压力就大了,可以进行分片:

  • 比如可以按时间进行分片,这种可以针对多个排行榜的场景,比如月榜,日榜之类的
  • 可以根据分数的大小进行分片。比如 0~1000 分一个榜,1001 ~ 2000 一个榜,在业务层进行聚合
  • 对用户取模进行分片,适合不关心全局,只关心附近的场景

如果排行榜需要支持实时更新,怎么保证一致性? ​

Redis ZADD 本身就是原子操作,不用考虑单条数据的并发问题,如果是多个用户去更新,可以用事务或者脚本的方式进行操作,也能保证原子性,如果是分布式场景,使用分布式锁也能保证避免覆盖问题

排行榜数据量特别大,比如上亿用户,怎么优化? ​

将排行榜进行分层,活跃的用户放到 Redis,不活跃的放到库中,查询的时候先查缓存,没查到再查库

也可以做分片,将用户按照 ID 进行分片存储,查询的时候再进行聚合

分数相同怎么处理?比如游戏里先达到的应该排前面 ​

分数相同默认按照字典序进行排序,如果想要按照时间的话,可以将时间写到分数后面,这样就会按照时间进行排序了

为什么 Redis Zset 用跳表实现而不是红黑树?B+树? ​

  • 跳表实现简单,本质就是几层链表叠到一起,没有红黑树的左旋右旋,没有 B+ 树的分裂合并
  • 范围查询简单有效,跳表底层就是完整的数据,想要范围查询直接定位到最底层然后遍历就行,红黑树还得中序遍历,很麻烦,B+ 倒是可以
  • B+ 树设计是为了减少磁盘 IO 设计的,矮胖的设计可以减少巡道次数,适合将数据存放到硬盘的类型,Redis 数据在内存,用不上这个

Redisson 看门狗(watch dog)机制了解吗? ​

看门狗是为了解决 Redis 分布式锁的一个常见问题:业务申请锁,正在执行,但是锁过期了,业务还在跑,别的线程进来获取锁,一同进入临界区,这个时候业务就乱了

原理很简单,当业务去申请锁,但是没有设置 leaseTime 的时候,Redisson 就会自动开启一个定时任务,没过十秒就检测一遍业务是不是还在跑,如果是的话就向 Redis 发送一个续约的请求,将锁的超时时间重新设定成 30 秒,这样就可以防止业务没跑完,锁却到期的问题

锁被释放的时候,定时任务会取消,如果客户端宕机,定时任务也会一起挂掉,所以并不会出现无限续约的情况,三十秒后就自动释放了

最后更新时间:2026/10/17

Contact me: 1943284256@qq.com