Redis 5.0中的 Stream是什么?

典型回答 Redis Stream 是 Redis 5.0 版本新增加的数据结构,主要用于处理有序的、可追溯的消息流。 Stream数据结构可以被视为一个日志或消息队列,其中每个消息都有一个唯一的ID,并且按照添加的顺序进行排序。开发人员可以向Stream中添加消息、读取消息、删除消息以及订阅消息。Stream数据结构还支持消费者组,可以让多个消费者并发地处理消息流。 在Redis 5.0之前,通过Redis的发布订阅 (pub/sub) 可以实现消息队列的功能,但它有个缺点就是消息无法持久化,如果出现网络断开、Redis 宕机等,消息就会被丢弃。 而 Redis Stream 提供了消息的持久化和主备复制功能,可以让任何客户端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失。 他的特点是支持持久化、消息具有有序性,并且支持分组。主要可以用来做消息队列、日志收集、实时数据处理和聊天室应用等。 有序性:消息可以按照发布时间排序,消费者可以按照消息发布的时间顺序进行消费。 多消费者支持:多个消费者可以订阅同一个Stream并独立消费消息,支持竞争式消费和共享式消费两种消费模式。 持久化:Stream支持消息持久化,即使Redis服务器重启或崩溃,之前的消息仍然可以恢复。 消息分组:Stream支持消息分组功能,可以将消息分配到不同的消费组中,从而实现更灵活的消息消费。 扩展知识 实现原理 Stream底层采用了类似于日志的数据结构,每个Stream都是由一个或多个日志实现的。每个日志包含多个消息,每个消息包含一个唯一的ID和一些附加的字段,如消息体、时间戳等。 Redis提供了一系列的Stream命令,用于创建Stream、发布/消费消息。 当一个消息被发布到Stream中时,它将被写入Stream日志中,每个消息都有一个唯一的ID和一些可选的字段,如时间戳、消息体等。消息ID是自动分配的,可以使用自然数或UUID作为ID。 当一个消费者订阅了一个Stream并开始消费消息时,它可以使用XREAD命令从Stream中读取消息。消费者可以使用XGROUP命令将自己加入到一个消费组中,并使用XREADGROUP命令从该组中读取消息。消费者可以使用XACK命令来确认已经消费的消息,避免消息被重复消费。 他还支持消息分组的功能,当一个消费组被创建时,它将与一个Stream相关联,并包含一个消费组名称和一个偏移量。消费组可以有多个消费者,消费者可以独立地从Stream中读取消息。当一个消费者开始消费消息时,它将记录当前的消费偏移量,并在处理完所有消息后将其更新到消费组中。消费组可以使用XPENDING命令查找未确认的消息,从而处理消息的超时和未确认情况。

March 22, 2026 · 1 min · santu

Redisson 中为什么要废弃 RedLock,该用啥?

典型回答 ✅什么是RedLock,他解决了什么问题? RedLock 算法是一种基于 Redis 实现的分布式锁的方法,主要用来解决 Redis 的单点问题,详细算法细节见上文。 在我们常用的 Redisson 中是支持 RedLock 的,但是在后续的版本中已经被废弃(Deprecated)了: https://github.com/redisson/redisson/blob/master/redisson/src/main/java/org/redisson/RedissonRedLock.java#L26 导致 Redisson 废弃 RedLock ,可能有以下几个原因: 缺乏官方认证:尽管 RedLock 算法由 Redis 的创始人提出的,他后来指出,这种算法并没有经过彻底的检验,并且他不推荐在需要严格一致性的分布式系统中使用它。 安全性和可靠性的担忧:在某些情况下,RedLock 算法可能无法保证互斥性,特别是在网络分区和节点故障的情况下。这种不确定性可能导致算法在分布式环境中的锁定行为不是完全可靠的。 维护和操作复杂性:实现和维护 RedLock 需要对多个 Redis 实例进行操作,这不仅增加了部署的复杂性,也增加了出错的可能性。 Martin Kleppmann 的批评:分布式系统领域的知名研究者和作者Martin Kleppmann ,在他的分析中指出,RedLock 算法存在几个关键问题,可能导致它在某些故障模式下不能正确地提供锁服务。 当然,以上只是一些猜测,主要其实就是官方不认可,并且作者表示也不承担责任,所以在 Redisson 这种框架中就不再支持他了。 替代方案 那么,在 RedLock 不再被建议使用之后,到底有什么方案来解决这种集群中的一致性问题导致的重复加锁呢? 这个在业内并没有公认的方案。在实践中,可以有以下几个方案: 1、这个方案我说出来可能会被喷,但是还是要提一下,因为他确实有效。那就是使用单实例的 Redis 锁,虽然这样的方案会面临单点故障的问题,但是在一些可以容忍短暂的服务中断的场景,用的还是比较多的。 2、还是用普通的锁,如 SETNX、redisson 等,但是在业务逻辑中做好幂等控制及状态及校验(比如数据库的唯一性约束),以及各种对账。这样就可以避免真的出现因为Redis 的集群一致性出现问题导致重复加锁时,导致业务出现问题。主要业务没问题,其实就不算问题了。 3、使用那种强一致性的组件来实现分布式锁,如 ZooKeeper、etcd 或 Consul,这些系统提供了更强大的一致性保证,适用于需要严格一致性的分布式应用。这些系统使用类似于 Raft 或 Paxos 的一致性算法来保证数据的一致性和分布式锁的安全性。

March 22, 2026 · 1 min · santu

Redisson如何保证解锁的线程一定是加锁的线程?

典型回答 在分布式锁的实现中,有一个比较重要的问题就是误解锁的问题,如何有效的避免A线程加的锁被B线程给解锁是非常重要的。 Redisson作为一个分布式锁用的比较多的框架,他是如何实现的这个功能呢?我们通过源码来深入展开介绍一下。 以下是 lock 方法,也就是加锁的入口, 1 2 3 4 5 6 7 8 @Override public void lock(long leaseTime, TimeUnit unit) { try { lock(leaseTime, unit, false); } catch (InterruptedException e) { throw new IllegalStateException(); } } 这里调用了一个<font style="color:#080808;background-color:#ffffff;">lock(long leaseTime, TimeUnit unit, boolean interruptibly) </font>方法,放方法的大致内容是: 1 2 3 4 private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException { long threadId = Thread.currentThread().getId(); Long ttl = tryAcquire(-1, leaseTime, unit, threadId); } � ...

March 22, 2026 · 3 min · santu

Redisson的watchdog机制是怎么样的?

典型回答 为了避免Redis实现的分布式锁超时,Redisson中引入了watch dog的机制,他可以帮助我们在Redisson实例被关闭前,不断的延长锁的有效期。 自动续租:当一个Redisson客户端实例获取到一个分布式锁时,如果没有指定锁的超时时间,Watchdog会基于Netty的时间轮启动一个后台任务,定期向Redis发送命令,重新设置锁的过期时间,通常是锁的租约时间的1/3。这确保了即使客户端处理时间较长,所持有的锁也不会过期。 续期时长:默认情况下,每10s钟做一次续期,续期时长是30s。 停止续期:当锁被释放或者客户端实例被关闭时,Watchdog会自动停止对应锁的续租任务。 核心流程图如下: 扩展知识 实现原理 那么,它是如何实现的呢? 在Redisson中,watch dog的主要实现在scheduleExpirationRenewal方法中: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 protected void scheduleExpirationRenewal(long threadId) { ExpirationEntry entry = new ExpirationEntry(); ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry); if (oldEntry != null) { oldEntry.addThreadId(threadId); } else { entry.addThreadId(threadId); try { renewExpiration(); } finally { if (Thread.currentThread().isInterrupted()) { cancelExpirationRenewal(threadId); } } } } //定时任务执行续期 private void renewExpiration() { ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (ee == null) { return; } Timeout task = getServiceManager().newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (ent == null) { return; } Long threadId = ent.getFirstThreadId(); if (threadId == null) { return; } CompletionStage<Boolean> future = renewExpirationAsync(threadId); future.whenComplete((res, e) -> { if (e != null) { log.error("Can't update lock {} expiration", getRawName(), e); EXPIRATION_RENEWAL_MAP.remove(getEntryName()); return; } if (res) { // reschedule itself renewExpiration(); } else { cancelExpirationRenewal(null); } }); } }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); ee.setTimeout(task); } //使用LUA脚本,进行续期 protected CompletionStage<Boolean> renewExpirationAsync(long threadId) { return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return 1; " + "end; " + "return 0;", Collections.singletonList(getRawName()), internalLockLeaseTime, getLockName(threadId)); } 可以看到,上面的代码的主要逻辑就是用了一个TimerTask来实现了一个定时任务,设置了internalLockLeaseTime / 3的时长进行一次锁续期。默认的超时时长是30s,那么他会每10s进行一次续期,通过LUA脚本进行续期,再续30s ...

March 22, 2026 · 4 min · santu

Redis中key过期了一定会立即删除吗

典型回答 我们都知道,Redis的Key是可以设置过期时间的,那么,过期了一定会立即删除吗? 回答这个问题之前,我们先看下Redis是如何实现的Key的过期。 以下是官网中关于过期的实现的描述(https://redis.io/commands/expire/ ): 也就是说Redis的键有两种过期方式:一种是被动过期,另一种是主动过期。 被动过期指的是当某个客户端尝试访问一个键,发现该键已经超时,那么它会被从Redis中删除。 当然,仅仅依靠被动过期还不够,因为有些过期的键可能永远不会再被访问。这些键应该被及时删除,因此Redis会定期随机检查一些带有过期时间的键。所有已经过期的键都会从键空间中删除。 具体来说,Redis每秒会执行以下操作10次: 从带有过期时间的键集合中随机选择20个键。 删除所有已经过期的键。 如果已经过期的键占比超过25%,则重新从步骤1开始。 直到过期Key的比例下降到 25% 或者这次任务的执行耗时超过了25毫秒,才会退出循环 所以,Redis其实是并不保证Key在过期的时候就能被立即删除的。因为一方面惰性删除中需要下次访问才会删除,即使是主动删除,也是通过轮询的方式来实现的。如果要过期的key很多的话,就会带来延迟的情况。 扩展知识 主动删除 vs 被动删除 主动删除的优点: 及时释放内存:主动删除能够及时地释放过期键占用的内存,避免内存空间被长时间占用,从而降低了内存使用率。 主动删除的缺点: 增加系统开销:定期扫描和删除操作会增加系统的开销,特别是在有大量键需要处理时,可能会导致Redis的性能下降。 可能导致访问延迟:当大量键同时过期并在访问时触发删除操作时,可能会导致读写操作的延迟。 被动删除的优点: 减少系统开销:被动删除不会定期地进行扫描和删除操作,因此可以减少系统的开销,节省计算资源。 避免写操作延迟:由于过期键被定期删除,不会导致过多的过期键在访问时触发删除操作,因此可以减少读写操作的延迟。 被动删除的缺点: 可能导致内存占用高:被动删除可能导致过期键长时间占用内存,直到被访问时才被删除,这可能会导致内存占用率较高。 Redis的被动删除策略,不需要额外配置。当你设置键的过期时间(TTL)时,Redis会自动处理被动删除。 要使用主动删除策略,需要在Redis配置文件中设置过期键检查的频率。你可以通过设置以下配置参数来调整主动删除的行为: hz(每秒执行的定时器频率):增加该值可以提高主动删除的频率。 maxmemory(Redis的最大内存限制):设置合适的最大内存限制,以确保Redis在内存不足时触发主动删除。 例如,在Redis配置文件中可以设置: 1 2 maxmemory 1gb hz 10

March 22, 2026 · 1 min · santu

Redis中的ListPack是如何解决级联更新问题的?

典型回答 ✅介绍下Redis中的ZipList和他的级联更新问题 上面的文章中,介绍过ZipList的级联更新问题。为了解决这个问题,Redis在5.0中推出了一个新的数据结构,那就是我们要介绍的ListPack了。 我们知道,ZipList之所以会出现级联更新,主要是因为他的Entry中有个prevlen字段,记录的是上一个Entry的长度,并且在长度不同的情况下可能会用1个或者5个字节表示。 那么ListPack为了解决这个问题,其实只要把prevlen干掉就行了,他也正是这么干的, 看下ListPack的结构: 主要改动是Entry这里,废弃了原来的prevlen,而是改用了新的方式,用一个backlen来记录整个Entry的字节数。并且位于元素末尾。他同样采用变成存储,可以用1-5个字节来存储。 就这样,因为ListPack的每个数据项都只会记录自己的长度,而不是再记录上一个节点的长度了,那么,在ListPack中新增或者删除元素,只会影响到这个元素自己,而不需要其他的元素进行级联更新了。也就解决了级联更新的问题了。

March 22, 2026 · 1 min · santu

Redis中的Zset是怎么实现的?

典型回答 ZSet(也称为Sorted Set)是Redis中的一种特殊的数据结构,它内部维护了一个有序的字典,这个字典的元素中既包括了一个成员(member),也包括了一个double类型的分值(score)。这个结构可以帮助用户实现记分类型的排行榜数据,比如游戏分数排行榜,网站流行度排行等。 Redis中的ZSet在实现中,有多种结构,大类的话有两种,分别是ziplist(压缩列表)和skiplist(跳跃表),但是这只是以前,在Redis 5.0中新增了一个listpack(紧凑列表)的数据结构,这种数据结构就是为了替代ziplist的,而在之后Redis 7.0的发布中,在Zset的实现中,已经彻底不再使用zipList了。 当ZSet的元素数量比较少时,Redis会采用ZipList(ListPack)来存储ZSet的数据。ZipList(ListPack)是一种紧凑的列表结构,它通过连续存储元素来节约内存空间。当ZSet的元素数量增多时,Redis会自动将ZipList(ListPack)转换为SkipList,以保持元素的有序性和支持范围查询操作。 ✅ZSet为什么在数据量少的时候用ZipList,而在数据量大的时候转成SkipList? 在这个过程中,Redis会遍历ZipList(ListPack)中的所有元素,按照元素的分数值依次将它们插入到SkipList中,这样就可以保持元素的有序性。 在Redis的ZSET具体实现中,SkipList的这种实现,不仅用到了跳表,还会用到dict(字典) 其中,SkipList用来实现有序集合,其中每个元素按照其分值大小在跳表中进行排序。跳表的插入、删除和查找操作的时间复杂度都是 O(log n),可以保证较好的性能。 dict用来实现元素到分值的映射关系,其中元素作为键,分值作为值。哈希表的插入、删除和查找操作的时间复杂度都是 O(1),具有非常高的性能。 扩展知识 ZipList和ListPack区别 ✅Redis的ZipList、SkipList和ListPack之间有什么区别? 何时转换 ZipList(ListPack)和SkipList之间是什么时候进行转换的呢? 在 Redis 中,ZSET在特定条件下会使用 ziplist作为其内部表示。这通常发生在有序集合较小的时候,具体条件如下: 元素数量少:集合中的元素数量必须小于某个阈值(zset-max-ziplist-entries)。 元素大小小:集合中的每个元素(包括值和分数)的大小必须小于指定的最大值(zset-max-ziplist-value)。 默认情况下,zset-max-ziplist-entries是128,zset-max-ziplist-value是64。 总的来说就是,当元素数量少于128,每个元素的长度都小于64字节的时候,使用ZipList(ListPack),否则,使用SkipList! 跳表 跳表也是一个有序链表,如下面这个数据结构: 在这个链表中,我们想要查找一个数,需要从头结点开始向后依次遍历和匹配,直到查到为止,这个过程是比较耗费时间的,他的时间复杂度是0(n)。 当我们想要向这个链表中插入一个数的时候,过程和查找类似,先需要从头开始遍历找到合适的为止,然后再插入,他的时间复杂度也是 O(n)。 那么,怎么能提升遍历速度呢,有一个办法,那就是我们对链表进行改造,先对链表中每两个节点建立第一级索引,如下图所示: 有了我们创建的这个索引之后,我们查询元素12,我们先从一级索引6 -> 9 -> 17 -> 26中查找,发现12介于9和17之间,然后,转移到下一层进行搜索,即9 -> 12 -> 17,即可找到12这个节点了。 ...

March 22, 2026 · 1 min · santu

Redis为什么这么快?

典型回答 Redis 之所以如此快,主要有以下几个方面的原因: 基于内存:Redis 是一种基于内存的数据库,数据存储在内存中,数据的读写速度非常快,因为内存访问速度比硬盘访问速度快得多。 单线程模型:Redis 使用单线程模型,这意味着它的所有操作都是在一个线程内完成的,不需要进行线程切换和上下文切换。这大大提高了 Redis 的运行效率和响应速度。 多路复用 I/O 模型:Redis 在单线程的基础上,采用了I/O 多路复用技术,实现了单个线程同时处理多个客户端连接的能力,从而提高了 Redis 的并发性能。 高效的数据结构:Redis 提供了多种高效的数据结构,如哈希表、有序集合、列表等,这些数据结构都被实现得非常高效,能够在 O(1) 的时间复杂度内完成数据读写操作,这也是 Redis 能够快速处理数据请求的重要因素之一。 多线程的引入:在Redis 6.0中,为了进一步提升IO的性能,引入了多线程的机制。采用多线程,使得网络处理的请求并发进行,就可以大大的提升性能。多线程除了可以减少由于网络 I/O 等待造成的影响,还可以充分利用 CPU 的多核优势。 扩展知识 ✅为什么Redis设计成单线程也能这么快? ✅为什么Redis 6.0引入了多线程?

March 22, 2026 · 1 min · santu

Redis如何实现发布_订阅?

典型回答 Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。 (在Stream推出之后,越来越多人会采用Stream来实现这样的功能了。) ✅Redis 5.0中的 Stream是什么? 即在Redis中定义频道,客户端可以订阅一个或多个频道并接收它们所发布的消息。发布者向一个或多个频道发布消息,所有订阅该频道的客户端都会收到该消息。 Redis的发布/订阅模式一般用于实时消息传递和事件驱动的应用程序中,例如: 即时通讯:发布/订阅模式可以用于实现即时消息传递应用程序,例如聊天室或社交媒体应用程序。订阅者可以订阅特定频道以接收他们感兴趣的消息,并能够实时更新。 日志处理:发布/订阅模式可以用于日志处理应用程序,例如日志聚合或日志监控系统。订阅者可以订阅特定频道以接收他们感兴趣的日志消息,例如错误或异常消息,并能够实时更新。 实时数据更新:发布/订阅模式可以用于实时数据更新应用程序,例如股票市场或在线游戏。订阅者可以订阅特定频道以接收他们感兴趣的实时数据更新,并能够实时更新。 缓存刷新:发布/订阅模式可以用于缓存刷新应用程序,例如缓存的数据过期时自动更新。当数据被更新时,发布者将消息发布到特定频道,订阅者将接收到消息并更新其本地缓存。 扩展知识 优缺点 Redis的发布/订阅模式有以下优点和缺点: 优点: 实时性高:发布/订阅模式可以实现实时消息传递,能够提高应用程序的实时性和响应速度。 灵活性高:发布/订阅模式可以根据需要订阅特定频道,订阅者只会接收他们感兴趣的消息,从而提高了灵活性。 可扩展性高:发布/订阅模式能够支持多个订阅者同时订阅特定频道,从而提高了可扩展性。 缺点: 可靠性低:发布/订阅模式是一种异步通信方式,发布者不会等待订阅者接收到消息,因此消息的可靠性可能会受到影响。 可靠性难以保证:发布/订阅模式在传输过程中可能会出现消息丢失的情况,尤其是在高负载情况下。 不适合高频次的请求:在高频次的请求场景下,发布/订阅模式可能会对性能造成影响,因为每个订阅者都需要对每个发布的消息进行处理。 用法 具体的实现过程如下: 1、创建并发布消息到一个Redis频道: 1 redis-cli> PUBLISH channel1 "Hello, world!" 2、客户端订阅渠道的消息 1 redis-cli> SUBSCRIBE channel1 订阅后,客户端将一直保持订阅状态,直到手动取消订阅或者连接断开。 可以通过在不同的客户端上运行相同的订阅命令来实现多个订阅者,它们都会接收到频道中发布的消息。 Redis发布/订阅模式是异步的,即发布者不会等待订阅者接收到消息。此外,Redis还提供了许多其他功能,例如模式匹配和非阻塞订阅等。

March 22, 2026 · 1 min · santu

Redis是AP的还是CP的?

典型回答 Redis是一个开源的内存数据库,那么他到底是AP的还是CP的呢? 有人说:单机的Redis是CP的,而集群的Redis是AP的? 但是我不这么认为,我觉得Redis就是AP的,虽然在单机Redis中,因为只有一个实例,他的一致性是有保障的,而一旦这个节点挂了,那么就没有可用性可言了。这么看上去好像是个CP系统。 但是,CAP是分布式场景中的理论,如果单机Redis,那就没啥分布式可言了。P都没有了,还谈什么AP、CP呢? ✅什么是CAP理论,为什么不能同时满足? 那么,我们来说说,为啥Redis是AP的呢? Redis的设计目标是高性能、高可扩展性和高可用性,Redis的一致性模型是最终一致性,即在某个时间点读取的数据可能并不是最新的,但最终会达到一致的状态。 Redis没办法保证强一致性的主要原因是,因为它的分布式设计中采用的是异步复制,这导致在节点之间存在数据同步延迟和不一致的可能性。 也就是说,当某个节点上的数据发生改变时,Redis会将这个修改操作发送给其他节点进行同步,但由于网络传输的延迟等原因,这些操作不一定会立即被其他节点接收到和执行,这就可能导致节点之间存在数据不一致的情况。 除此之外,Redis的一致性还受到了节点故障的影响。当一个节点宕机时,这个节点上的数据可能无法同步到其他节点,这就可能导致数据在节点之间的不一致。虽然Redis通过复制和哨兵等机制可以提高系统的可用性和容错性,但是这些机制并不能完全解决数据一致性问题。 如果用同步复制的方式呢? 会不会就是CP了呢?并不会,这一点在Redis的官网中自己明确的说了: 也就是说。客户端可以使用 WAIT 命令请求对特定数据进行同步复制。然而,WAIT 仅能确保数据在 Redis 实例中有指定数量的副本中被确认,它并不能将一组 Redis 实例转变为具有强一致性的 CP 系统:在故障转移期间,已确认的写操作仍然可能会丢失,这取决于 Redis 持久化的具体配置。然而,使用 WAIT 后,在发生故障事件时丢失写操作的概率大大降低,只在某些难以触发的故障模式下才会发生。

March 22, 2026 · 1 min · santu

留言给博主