简单来说,为 Redis 大 Key(Big Key)增加一个二级缓存(通常是本地进程内缓存),核心目标是用内存换取CPU和网络资源,以数量级的方式减少对 Redis 的访问压力,并避免大 Key 带来的潜在风险。

下面我们来详细拆解这个问题。

首先,为什么 Redis 大 Key 是个问题?

“大 Key” 通常指一个 Key 对应的 Value 非常大(例如超过 100KB),或者一个 Key 包含的元素非常多(例如一个有数百万成员的 Hash 或 ZSET)。

大 Key 会带来一系列严重问题:

  1. 网络IO瓶颈:一次 GET 一个 5MB 的大 Key,就会在网卡上产生 5MB 的流量。在高并发下,这会迅速占满服务器带宽,导致网络延迟剧增,影响所有其他服务。
  2. Redis 阻塞风险:Redis 的命令处理是单线程的。当 Redis 处理一个大 Key 时(例如对其进行序列化、反序列化、网络传输),会消耗更长的时间。在这期间,其他所有请求都必须排队等待,导致 Redis 的 QPS (每秒查询率)急剧下降,甚至出现服务“卡死”的现象。特别是删除一个大 Key (DEL a_big_key),可能会造成秒级的阻塞。
  3. 内存分配不均:在 Redis Cluster 模式下,一个大 Key 会导致某个特定节点的内存使用量远超其他节点,造成数据倾斜和资源分配不均,难以扩容。
  4. 缓存淘汰效率低:当内存不足需要淘汰数据时,如果淘汰了一个大 Key,会瞬间释放大量内存,但下一次请求这个 Key时,又会造成一次成本极高的“缓存穿透”,直接打到数据库上。

什么是二级缓存(本地缓存)?

在这里,“二级缓存”特指位于应用程序进程内部的缓存(In-Process Cache)。

  • 一级缓存:远程的分布式缓存,如 Redis、Memcached。

  • 二级缓存:本地内存中的缓存,如 Java 的 Caffeine/Guava Cache,Go 的 sync.Map 或 freecache,Python 的 functools.lru_cache 或 dict。

    这个二级缓存的特点是:

  • 速度极快:直接在内存中访问,没有任何网络开销,速度是访问 Redis 的百倍甚至千倍。

  • 容量有限:受限于应用服务器的内存,容量远小于 Redis。

为什么用二级缓存优化大 Key 效果拔群?

现在,我们将大 Key 的问题和二级缓存的优势结合起来看,答案就显而易见了。

假设我们有一个 5MB 的大 Key,它存储了某个不常变化但读取频繁的配置信息。

优化前的工作流程:

  1. 客户端请求数据。
  2. 应用服务向 Redis 发送 GET big_key 命令。
  3. Redis 读取 5MB 数据,通过网络发送给应用服务。
  4. 应用服务接收 5MB 数据,处理后返回给客户端。
  • 问题:每次请求都要消耗 Redis 的 CPU 和大量的网络带宽。

优化后的工作流程:

  1. 客户端请求数据。
  2. 应用服务首先检查本地缓存中是否存在 big_key。
  3. 缓存命中 (Hit):
    • 直接从本地内存中读取数据(速度极快,无网络IO)。
    • 处理后返回给客户端。
    • 效果:整个过程完全不涉及 Redis,Redis 和网络带宽的压力被彻底消除。
  4. 缓存未命中 (Miss):
    • 本地缓存没有数据,此时才向 Redis 发送 GET big_key 命令。
    • Redis 读取 5MB 数据,通过网络发送给应用服务。
    • 应用服务接收到数据后,先将数据写入本地缓存(并设置一个较短的过期时间,如1分钟)。
    • 处理后返回给客户端。
    • 效果:只有在第一次请求或本地缓存过期后的第一次请求,才会访问 Redis。后续大量请求都由本地缓存响应。

优势:

  • 极大降低网络开销:避免了每次请求都传输大 Key 的数据。只有在缓存失效时才会有一次网络传输。
  • 极大降低 Redis 负载:绝大多数读请求被本地缓存拦截,Redis 不再需要为这些请求消耗 CPU 进行数据查找和序列化。
  • 避免 Redis 阻塞:由于访问 Redis 的频率大大降低,由大 Key 引起的阻塞风险也随之降低。
  • 提升应用性能和响应速度:从本地内存读取数据的速度远快于远程读取,用户感受到的延迟更低。

关键的注意事项:数据一致性

引入二级缓存带来的最大挑战是数据一致性。当大 Key 的源数据在数据库中被更新后,你需要一种机制来同时或先后地让 Redis 和所有应用服务器的本地缓存都失效。

  • Redis 中的数据如何更新?
    • 通常采用“缓存旁路模式”(Cache-Aside Pattern):更新数据库后,直接 DEL Redis 中的 Key。下次请求时会重新从数据库加载并写入 Redis。
  • 所有服务器的本地缓存如何更新?
    • 这是一个难题,因为一个应用通常部署在多个服务器上。
    • 常用解决方案:使用消息队列(如 RabbitMQ, Kafka, Redis Pub/Sub)。
      1. 当数据在数据库中被更新时。
      2. 发送一个“缓存失效”消息到消息队列的特定主题(Topic)。
      3. 所有订阅了该主题的应用服务实例都会收到这个消息。
      4. 收到消息后,每个应用服务实例都删除自己本地缓存中的对应 Key。

总结

为大 Key 添加二级(本地)缓存是一种典型的空间换时间和分级缓存思想的应用。它通过在应用端牺牲一小部分内存,来保护下游更宝贵、更容易成为瓶颈的分布式缓存和网络资源。

这种模式尤其适用于“读多写少”的大 Key 场景,例如:

  • 商品详情页的完整信息。
  • 系统的基础配置。
  • 一个热点新闻的完整内容。

对于频繁更新的数据,使用二级缓存需要非常谨慎地处理一致性问题,成本会更高。

Have you received a 100% off promo code for a complimentary subscription? Complete a short form below to redeem your coupon. Once your order is confirmed, you will receive a confirmation email and further instructions on how to activate your Product.

free

兑换码:DataGrip2025

兑换地址: https://www.jetbrains.com/store/redeem/

如果已经有订阅了会续订一年

兑换码已失效,兑换完毕,额度用完(最后兑换时间 2025-07-01 12:00 左右)

原文

本文讨论了 Go 语言中 `[]Item` 类型的参数如何传递给函数,阐明了是使用副本还是指向同一数据的指针。当一个 `[]Item` 切片传递给函数时,切片的头部信息(包含指针、长度和容量)会被复制,但底层数组不会。这意味着在函数内部修改切片的元素会影响原始切片,因为它们共享同一个底层数组。但是,如果函数追加元素时超过了切片的容量,将会创建一个新的底层数组,而原始切片保持不变。文章提供示例代码来演示这些概念,展示了元素修改和在容量范围内追加如何影响原始切片,而超出容量的追加则不会。总结建议在将切片传递给函数时要小心,并建议创建切片副本以避免修改原始切片。This Go article discusses how `[]Item` type parameters are passed to functions, clarifying whether a copy or a pointer to the same data is used. When a `[]Item` slice is passed to a function, the slice header (containing a pointer, length, and capacity) is copied, but the underlying array is not. This means modifications to the slice's elements within the function will affect the original slice because they share the same underlying array. However, if the function appends elements exceeding the slice's capacity, a new underlying array is created, and the original slice remains unchanged. The article provides example code to demonstrate these concepts, showing how element modification and appending within capacity affect the original slice, while appending beyond capacity does not. It concludes by advising caution when passing slices to functions and suggests creating a slice copy if modifications are to be avoided.

阅读全文 »

手慢无,免费领Perplexity年卡会员 网址:https://www.perplexity.ai/join/p/redeem

验证码:PPLXLIUMBLHOTVAJ2QI

记得用美国的节点。

免费领AI搜索鼻祖perplexity的年卡会员,价值200美刀。

推荐订阅

推荐并获得专业版$10的折扣,推荐给一个朋友,你们俩都可以获得折扣。赠送$10,得到$10。
每推荐一位订阅者,你的下一个计费周期可以减免$10,他们也可以减免$10。你可以推荐尽可能多的人,折扣会在每个计费周期内自动获取一次。

This Go blog post discusses the history and reasons behind the Go team's decision to stop pursuing syntactic language changes for error handling.Go 语言的错误处理,与众不同,报错会单独返回一个值,这导致代码冗余。多年来,使用者一直要求有更好的写法,Go 团队想了很多办法,效果都不好。现在,官方终于宣布,不再寻求更好的写法,保持现状。

阅读全文 »

NLWeb creates a standard protocol that turns any website into a conversational interface that both humans and AI agents can query naturally. It leverages Schema.org structured data and includes built-in support for MCP.NLWeb 是微软新提出的一个接口协议,类似于 MCP 协议,是网站跟 AI 大模型的对话协议,方便大模型从网站获取数据。本文是这个协议的简介。

阅读全文 »