企业级NoSQL数据库Redis

news2025/1/20 21:52:33

1.浏览器缓存过期机制

1.1 最后修改时间 last-modified

浏览器缓存机制是优化网页加载速度和减少服务器负载的重要手段。以下是关于浏览器缓存过期机制、Last-ModifiedETag 的详细讲解:

一、Last-Modified 头部

  • 定义Last-Modified 表示服务器上资源的最后修改时间。

  • 作用:用于资源的条件请求,帮助浏览器判断缓存的资源是否是最新的。

  • 工作流程

    1. 浏览器第一次请求资源时,服务器返回资源内容和 Last-Modified 时间。

    2. 下次请求同一资源时,浏览器发送 If-Modified-Since 头部,值为之前的 Last-Modified 时间。

    3. 服务器比较资源的当前修改时间与 If-Modified-Since的值:

      • 如果资源未修改,返回 304 Not Modified,浏览器继续使用缓存。
      • 如果资源已修改,返回新的资源内容和更新后的 Last-Modified 时间。
  • 示例

Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT

二、ETag 头部

  • 定义ETag(Entity Tag)是服务器为资源生成的唯一标识符,通常是资源内容的哈希值或版本号。

  • 作用:比 Last-Modified 更加精确,用于验证资源是否变化。

  • 工作流程

    1. 浏览器第一次请求资源时,服务器返回资源内容和 ETag 值。

    2. 下次请求同一资源时,浏览器发送 If-None-Match 头部,值为之前的 ETag

    3. 服务器比较当前资源的 ETagIf-None-Match的值:

      • 如果 ETag 未变化,返回 304 Not Modified,浏览器继续使用缓存。
    • 如果 ETag 变化,返回新的资源内容和新的 ETag 值。
  • 示例

ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"

五、Last-Modified vs ETag

  • 精确度
    • Last-Modified 仅记录最后修改时间,可能无法检测到在同一秒内的多次修改。
    • ETag 通常基于内容的哈希值,能够更精确地检测到任何变化。
  • 性能
  • 生成 ETag 可能需要更多的计算资源,尤其是在大规模资源或高频请求的情况下。
  • Last-Modified 相对简单,性能开销较小。
  • 使用场景
    • 对于静态资源,ETag 更加适用。
    • 对于动态资源,可以结合 Last-Modified 和其他缓存策略使用。

六、最佳实践

  1. 合理设置缓存策略
    • 对于不经常变化的静态资源,设置较长的 max-age 以充分利用缓存。
    • 对于经常变化的资源,使用较短的 max-age 或结合验证机制。
  2. 使用 ETagLast-Modified
    • 同时使用两者可以提供更可靠的缓存验证,但需注意服务器的性能开销。
  • 如果服务器性能有限,可以选择只使用其中一个。
  1. 版本化资源
    • 通过在资源URL中包含版本号(如 style.v1.css),可以在资源更新时强制浏览器下载新版本,避免缓存问题。

七、总结

浏览器缓存机制通过多种HTTP头部字段控制资源的缓存和过期,Last-ModifiedETag 是其中重要的验证手段。合理配置这些头部字段,可以显著提升网页性能,优化用户体验,同时有效管理服务器资源。

第一次访问

image-20241216144110944

第二次访问

image-20241216144252162

1.2 Etag 标记

但是如果访问的时间一样的,怎么办?If-Modefied精确到的是秒,要知道的是,计算机一秒中可以干好多事的,比如一秒中修改上千次的图片

# 使用 touch 模拟访问时间是一样的(移走1.jpeg,在重新上传一张图片,重命名为1.jpeg)
[root@Rocky9.4 html]#touch -t 202407020931.48 1.jpeg

第一次访问

第一次访问是由于我将时间设置成一样了,但是因为服务器返回的Etag是新的,而浏览器保存的还是旧的,所以Etag不一致,所以返回状态码是200

image-20241216143352281

第二次访问

第二次访问,Etag也统一了,所以返回了状态码304

image-20241216145149331

1.3 过期时间 expires 和 Cache-Control

一、浏览器缓存机制概述

浏览器缓存通过在本地存储网页资源(如HTML、CSS、JavaScript、图片等),避免每次访问网页时都从服务器重新下载这些资源,从而加快页面加载速度,提高用户体验,同时减少服务器带宽的使用。

明白了,你希望更详细地了解浏览器缓存中的 ExpiresCache-Control 头部,以及它们之间的关系和具体应用。以下是更深入的讲解:

二、Expires 头部

1. 定义与作用
  • Expires 是一个HTTP响应头,用于指定资源的绝对过期时间。它告诉浏览器在指定的时间之前,可以直接从缓存中使用该资源,而无需向服务器重新请求。
2. 格式
  • Expires 的值是一个绝对的HTTP日期和时间,格式为:Wdy, DD Mon YYYY HH:MM:SS GMT

示例

Expires: Wed, 21 Oct 2025 07:28:00 GMT
3. 使用场景
  • 适用于静态资源,如图片、CSS、JavaScript文件,这些资源不经常变化。
  • 适合设置较长的缓存时间,减少浏览器对服务器的请求频率,提升加载速度。
4. 缺点
  • 使用绝对时间,可能受客户端和服务器时间不同步的影响。
  • 当资源更新时,若不改变 Expires,可能导致浏览器继续使用过期的缓存,出现内容不一致的问题。

三、Cache-Control 头部

1. 定义与作用
  • Cache-Control 是一个更为灵活和强大的HTTP响应头,用于控制缓存策略。它可以替代或补充 Expires 头部,提供更精确的缓存控制。
2. 常用指令
  • max-age=秒数:指定资源在多少秒内被认为是新鲜的。max-age 的优先级高于 Expires

示例

Cache-Control: max-age=3600
  • no-cache:资源必须在使用前重新验证(即使资源没有过期)。

示例

Cache-Control: no-cache
  • no-store:禁止任何形式的缓存,既不存储请求信息,也不存储响应信息。

示例

Cache-Control: no-store
  • public:响应可被任何缓存区缓存,包括浏览器和中间缓存(如CDN)。

示例

Cache-Control: public
  • private:响应仅为单个用户缓存,不能被共享缓存(如CDN)缓存。

示例

Cache-Control: private
  • must-revalidate:一旦资源过期,必须向服务器验证其有效性。

示例

Cache-Control: must-revalidate
  • proxy-revalidate:与 must-revalidate 类似,但仅适用于共享缓存。

示例

Cache-Control: proxy-revalidate
3. 使用场景
  • 动态资源:可以灵活设置缓存策略,如需要频繁更新但又希望利用缓存提升性能的资源。
  • 细粒度控制:通过组合多个指令,实现更复杂的缓存策略。
4. 与 Expires 的关系
  • 优先级:当同时存在 Cache-Control: max-ageExpires 时,Cache-Control 优先级更高。
  • 推荐使用:现代浏览器和服务器更推荐使用 Cache-Control,因为它更灵活且不依赖绝对时间。

四、ExpiresCache-Control 的对比

特性ExpiresCache-Control
类型绝对时间相对时间及其他缓存指令
格式HTTP日期格式指令列表
优先级低于 Cache-Control高于 Expires
灵活性较低,只有一个绝对过期时间高,可以组合多种指令控制缓存行为
推荐使用场景主要用于向后兼容旧浏览器现代Web应用的首选缓存控制方式

五、实际应用示例

1. 设置长时间缓存(适用于不经常变化的静态资源)
Cache-Control: public, max-age=31536000
Expires: Wed, 21 Oct 2025 07:28:00 GMT
  • 解释:资源可以被公共缓存(如CDN)缓存,且在1年内(31536000秒)不需要重新验证。
2. 设置短时间缓存,需重新验证(适用于可能会频繁更新的资源)
Cache-Control: no-cache
  • 解释:浏览器每次使用缓存前必须向服务器验证资源是否有更新。
3. 禁止缓存(适用于敏感数据)
Cache-Control: no-store
  • 解释:禁止任何形式的缓存,确保每次请求都从服务器获取最新数据。

六、结合 ETagLast-Modified 使用缓存验证

即使设置了 Cache-ControlExpires,浏览器在某些情况下仍可能需要验证缓存资源的有效性。此时,ETagLast-Modified 提供了有效的验证机制:

  • ETag:提供资源的唯一标识符,确保缓存的资源与服务器上的一致。
  • Last-Modified:记录资源的最后修改时间,供浏览器进行条件请求。

示例

Cache-Control: max-age=3600, must-revalidate
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT

七、最佳实践

  1. 优先使用 Cache-Control
  • 由于其灵活性和优先级,现代Web开发中应优先配置 Cache-Control 头部。
  1. 合理设置 max-age
  • 根据资源的更新频率,合理设置缓存时间。静态资源可以设置较长时间,动态资源设置较短时间或不缓存。
  1. 结合使用 ETagLast-Modified
  • 提供双重验证机制,确保缓存的资源始终是最新的。
  1. 版本化静态资源
  • 通过在资源URL中添加版本号(如 style.v1.css),确保资源更新时浏览器能够获取到最新版本,避免缓存问题。
  1. 使用CDN
  • 配合缓存头部,利用内容分发网络(CDN)提升全球范围内的资源加载速度,并有效管理缓存策略。

八、总结

  • ExpiresCache-Control 都用于控制资源的缓存和过期,但 Cache-Control 提供了更高的灵活性和优先级。
  • ETagLast-Modified 是用于缓存验证的强大工具,确保浏览器使用最新的资源。
  • 最佳实践 是结合使用这些HTTP头部,合理设置缓存策略,提升Web应用的性能和用户体验。

1.4 CDN

CDN(内容分发网络,Content Delivery Network)是一种通过将内容复制并缓存到全球多个地理位置的服务器上,从而加速用户访问速度的技术。它主要的目的是提高网站或应用的性能、稳定性、可扩展性,同时减少服务器负载和带宽消耗。

一、CDN的工作原理

CDN的核心思想是将网站的静态资源(如HTML文件、CSS文件、JavaScript、图片、视频等)缓存到分布在全球的边缘服务器(Edge Servers)上。当用户请求访问某个资源时,CDN会根据用户的地理位置,选择距离用户最近的服务器提供资源,从而减少加载时间和提高访问速度。

1. 资源分发与缓存
  • 资源分发:当你将资源上传到CDN服务时,CDN提供商会将这些内容分发到位于世界各地的数据中心。
  • 缓存:CDN的服务器会将常用的静态内容缓存到本地存储中,当有新的请求时,如果内容已经存在并且没有过期,则直接返回缓存的内容。
2. 边缘服务器与原始服务器
  • 边缘服务器(Edge Server):这些是部署在全球各地的服务器,负责将资源提供给终端用户。用户访问时,通常会被路由到离他们最近的边缘服务器,以减少延迟。
  • 原始服务器(Origin Server):原始服务器是网站的源服务器,存储网站的所有内容。如果CDN的边缘服务器没有缓存某个请求的内容,它会从原始服务器获取并返回给用户。
3. 缓存策略

CDN通常会使用一些缓存策略来决定哪些内容需要缓存,以及缓存多久。常见的缓存策略包括:

  • 缓存时间(TTL,Time to Live):决定缓存的有效期。例如,静态资源如图片、CSS文件可能会缓存较长时间,而动态内容可能缓存较短时间。
  • 缓存控制(Cache-Control):通过设置HTTP头来控制缓存行为(如 max-ageno-cache)。
  • 动态内容缓存:CDN一般针对动态内容(如用户特定数据、实时信息)使用不同的缓存策略,可能会使用“按需缓存”或“低过期时间”的方式进行处理。
4. 智能路由与负载均衡

CDN通常会根据多个因素(如地理位置、网络负载、带宽等)选择最优的边缘服务器来响应用户请求。这一过程称为智能路由或负载均衡。通过此方式,CDN能够确保用户始终通过最快的路径获取到资源。

二、CDN的优势

  1. 提高加载速度
    • 减少延迟:通过将内容分发到全球多个节点,用户总是能够从离自己最近的节点获取资源,从而大幅减少延迟,提高加载速度。
    • 更高的可用性:通过分布式缓存,用户能够在多个服务器之间获取资源,即使某个服务器出现故障,也不会影响服务的可用性。
  2. 减轻原始服务器负载
    • CDN缓存了大量静态内容,减少了原始服务器的直接负担,降低了带宽使用和处理请求的压力。
  3. 提升网站的可扩展性
    • CDN帮助网站应对流量激增,能够在不同地区和时段自动调整资源的分配和流量管理,提供更好的扩展性。
  4. 增强网站的安全性
    • DDoS防护:许多CDN提供DDoS攻击防护,能够通过分布式架构分担攻击流量,从而减轻原始服务器的压力。
    • SSL加密:CDN服务提供SSL证书支持,帮助加密数据传输,提升安全性。
  5. 节省带宽成本
    • 通过减少从原始服务器到客户端的流量,CDN有助于降低带宽费用,尤其是对于全球性网站。
  6. 高可用性和容错性
    • CDN通过将资源缓存到多个节点,提升了资源的冗余度。在某个节点出现故障时,流量可以被自动引导到其他正常工作的节点,保证网站的高可用性。

三、CDN的类型

  1. 静态内容CDN
    • 主要缓存静态内容,如图片、JavaScript文件、CSS文件等。通过将这些内容缓存到多个位置,能够加速资源加载速度。
  2. 动态内容CDN
    • 动态内容指的是根据用户请求生成的内容,比如数据库查询结果或用户个性化信息。动态内容通常不缓存,但现代CDN提供商提供了对动态内容的优化方案,通过智能缓存策略加速动态内容的加载。
  3. 直播和视频流CDN
    • 专门用于视频流、直播视频内容的传输,优化了大带宽视频数据的分发和传输。常见的技术包括流媒体协议如 HLS(HTTP Live Streaming)和 DASH(Dynamic Adaptive Streaming over HTTP)。
  4. 边缘计算CDN
    • 这种类型的CDN不仅提供缓存功能,还支持在边缘服务器上执行计算任务。它能够在靠近用户的地方处理请求,提高性能和降低延迟。

四、CDN的工作流程

  1. 资源上传到CDN
    • 将网站的静态资源上传到CDN供应商的服务器。资源可能会分发到多个全球节点进行缓存。
  2. 用户请求访问资源
    • 用户访问网页时,浏览器向CDN发起请求。CDN会根据用户的地理位置,智能选择离用户最近的服务器响应请求。
  3. 缓存命中与未命中
    • 如果边缘服务器已缓存该资源(缓存命中),CDN直接返回缓存的内容。
    • 如果缓存过期或没有缓存该资源(缓存未命中),CDN会向原始服务器请求资源,并将返回的资源缓存起来供后续用户使用。
  4. 返回资源给用户
    • 一旦缓存的资源通过CDN的边缘节点返回给用户,用户的浏览器会在本地缓存该资源,下次访问时,直接从浏览器本地获取。

五、CDN的服务提供商

目前,全球有多个主要的CDN服务提供商,最知名的包括:

  1. Cloudflare
    • 提供免费和收费的CDN服务,支持全球分布的边缘节点,提供DDoS防护和Web应用防火墙(WAF)。
  2. Akamai
    • 全球领先的CDN供应商,服务覆盖范围广,适用于大规模企业和高流量网站,提供强大的内容加速和安全功能。
  3. Amazon CloudFront
    • AWS提供的CDN服务,能够与AWS的其他服务(如S3、EC2等)无缝集成,提供高可扩展性和灵活性。
  4. Fastly
    • 以高性能为特点,支持即时缓存清除和高效的动态内容传输,适用于对延迟要求极高的应用。
  5. KeyCDN
    • 提供较为简单和成本效益高的CDN解决方案,适用于中小型网站。

六、CDN的优化策略

  1. 合理设置缓存过期时间
    • 根据内容的更新频率,合理设置缓存过期时间(TTL),避免缓存过期导致频繁访问原始服务器。
  2. 使用分布式缓存
    • 利用CDN的全球节点分布,将内容缓存到多个节点,从而提供更好的负载均衡和冗余。
  3. 压缩和优化内容
    • 对资源进行压缩(如图片、CSS、JavaScript等),减少传输的数据量,提高加载速度。
  4. 结合HTTPS加密
    • 使用CDN的SSL证书加密功能,为网站提供HTTPS支持,提升数据传输的安全性。

七、总结

CDN是一种通过将网站内容分发到全球多个节点,减少延迟、提高加载速度、减轻服务器负载的技术。它不仅能加速资源的交付,还能提高网站的安全性、可用性和可扩展性。随着互联网应用的增长,CDN已成为优化网站性能和提供全球用户良好体验的重要工具。

1.4.1 用户请求CDN流程

用户请求CDN资源的流程可以分为几个步骤。这个流程涉及到用户如何向CDN发起请求,CDN如何决定从哪个服务器提供资源,以及缓存如何影响响应时间。以下是详细的用户请求CD能资源的流程:

一、请求流程概述

  1. 用户发起请求:用户的浏览器或应用程序向服务器请求某个资源(如图片、CSS、JavaScript文件等)。
  2. DNS解析:请求首先通过DNS解析,将资源的域名解析为CDN的IP地址。
  3. 路由到CDN边缘节点:用户的请求被路由到距离用户最近的CDN边缘节点。
  4. 边缘节点缓存检查:CDN的边缘节点检查缓存中是否已有该资源。
  5. 缓存命中或未命中:根据缓存的情况,决定是直接返回缓存的内容,还是从源服务器获取最新的资源。
  6. 返回资源给用户:资源通过边缘节点传输给用户,用户的浏览器接收并展示。

二、详细步骤

1. 用户发起请求

用户在浏览器中输入网址或点击链接时,浏览器会发起HTTP请求来请求某个资源。这些资源通常是静态文件,如HTML、CSS、JavaScript文件,或者图片、视频等媒体文件。

例如,用户请求资源:https://www.example.com/images/logo.png

2. DNS解析

用户请求的域名(如 www.example.com)会通过DNS解析,转化为一个IP地址。通常,这个域名已经指向CDN提供商的域名解析系统。

  • 传统方式:直接访问原始服务器的IP。
  • CDN方式:DNS解析返回的是CDN边缘服务器的IP,而不是源服务器的IP。

CDN提供商通常会在多个地理位置部署多个边缘节点(edge node),当请求发起时,DNS会返回离用户最近的CDN边缘节点的IP地址,确保请求被路由到最近的服务器。

3. 请求被路由到CDN边缘节点

DNS解析完成后,浏览器向CDN的边缘节点发送请求。CDN边缘节点是部署在全球各地的服务器,它们缓存了资源内容,能够快速响应用户请求。

CDN边缘节点的选择通常由以下因素决定:

  • 地理位置:用户的IP地址与边缘节点的地理位置之间的距离,尽可能选择距离用户最近的节点。
  • 网络负载:当前边缘节点的负载情况。如果某个节点过载,CDN会选择其他负载较低的节点。
4. 边缘节点缓存检查

边缘节点收到请求后,会检查缓存中是否已有该资源。这一步称为缓存命中检查

  • 缓存命中:如果边缘节点缓存中已经存在该资源,并且资源没有过期,则直接从缓存中读取并返回给用户。
  • 缓存未命中:如果缓存中没有该资源,或者资源已经过期,则会将请求转发给源服务器(origin server)。
5. 缓存命中或未命中
  • 缓存命中:如果资源已经存在并且有效,CDN会直接将缓存的资源返回给用户。这是加速访问的关键步骤,因为用户不需要访问源服务器,节省了时间和带宽。

    例如,若用户请求 https://www.example.com/images/logo.png,CDN的边缘节点可能已经缓存了这个文件,且TTL(过期时间)没有到期,此时CDN直接返回文件。

  • 缓存未命中:如果缓存中没有该资源,或者缓存的资源已经过期,CDN会向源服务器发起请求以获取资源。

6. 从源服务器获取资源

当缓存未命中时,CDN边缘节点会向原始服务器(origin server)请求该资源。此时,源服务器会根据请求返回最新的资源,并且将该资源缓存到边缘节点,以供下次请求使用。

  • 资源返回后,CDN会缓存到边缘节点并设置适当的缓存过期时间(TTL)。这意味着下一次请求时,边缘节点可以直接返回缓存的内容,而不需要再访问源服务器。
7. 返回资源给用户

无论是缓存命中还是从源服务器获取资源,最终,CDN的边缘节点会把响应数据返回给用户的浏览器。用户的浏览器从CDN边缘节点接收到资源,并进行展示。

8. 浏览器缓存

在资源返回给浏览器后,浏览器也会根据响应头(如 Cache-ControlExpires 等)进行本地缓存,以便在下一次访问时直接从本地缓存中获取资源,而不再发送请求到CDN或源服务器。

三、缓存策略与内容更新

CDN中的缓存策略非常关键,它决定了缓存内容的过期时间、更新方式以及缓存策略的灵活性。

  1. TTL(Time to Live,生存时间)
    • 每个缓存的资源都会设置一个TTL,TTL指定了该资源在CDN边缘节点缓存的有效期。TTL过期后,缓存的内容会被认为是过期的,需要重新向源服务器请求内容。
  2. 缓存清除
    • 主动清除:CDN提供商允许通过管理控制台或API来主动清除缓存中的某些资源。这对于资源更新频繁或紧急更新的情况非常重要。
    • 自动清除:当资源的TTL到期时,CDN会自动清除缓存并向源服务器请求新的内容。
  3. 缓存验证
    • 使用 ETagLast-Modified 等HTTP头部字段,CDN可以验证缓存是否有效。即使TTL未到期,CDN也可以通过向源服务器发送条件请求(If-None-MatchIf-Modified-Since)来判断缓存是否需要更新。

四、CDN的优势

  1. 减少延迟:用户总是能从离自己最近的边缘服务器获取资源,减少了传输延迟。
  2. 提高可用性:即使源服务器宕机,CDN仍可以从其他节点提供缓存的内容,保持服务可用。
  3. 减轻源服务器负担:通过缓存大量请求,CDN能够减轻源服务器的负载,减少带宽消耗。
  4. 提高网站性能:加速资源加载,提升用户体验,尤其是对于全球用户。

五、CDN请求流程示意图

 用户请求 --> DNS解析 --> CDN边缘节点 --> 缓存检查 -->
     |                          |                        |
  缓存命中                     缓存未命中                  |
     |                          |                        |
 返回缓存的资源             从源服务器请求资源            |
     |                          |                        |
返回给用户的资源             缓存资源并返回给用户        |

六、总结

  1. CDN工作流程:CDN通过将资源分发到多个边缘节点,利用智能路由、缓存和负载均衡技术,将资源快速交付给用户,减少延迟,提高网站性能。
  2. 缓存命中与未命中:CDN根据缓存策略决定是否直接返回缓存的内容,或者向源服务器请求更新内容。
  3. 浏览器与CDN缓存:浏览器本地缓存和CDN的缓存共同工作,确保资源加载更快,减少重复请求。

CDN在提高网站性能、增强网站可用性、降低带宽消耗等方面发挥了重要作用,是现代Web应用不可或缺的组成部分。

image-20241217113705693

1.4.2 CDN分层缓存

CDN(Content Delivery Network,内容分发网络)的分层缓存(Layered Caching)是指通过多级缓存架构有效提升内容分发效率的一种策略。在CDN中,请求的内容通常会经过多个层级的缓存节点,以实现更佳的性能和资源利用率。整个流程通常可以分为以下几个层次:

  1. L1 边缘节点缓存(Edge Cache)
    这是离用户最近的一层缓存节点。当用户向CDN请求内容时,边缘节点首先检查本地缓存是否已存有该内容。若存在并未过期,便直接从该节点返回内容给用户,降低传输延迟,提高用户体验;若缓存中无此内容或内容已过期,则向上层的缓存节点或源站请求。
  2. L2 区域或中间层缓存(Mid-Tier/Regional Cache)
    当边缘节点未能在本地拿到所需内容时,会将请求向上层的区域缓存节点发出。区域缓存通常位于更靠近源站的核心网络,储存那些在一定时间窗口内被多个边缘节点重复请求的内容。通过在此层进行缓存,CDN减少了向源站多次重复请求同一内容的频率。这一层有助于将热门内容在更广的地理范围内进行共享,降低源站负载,并减少跨区域的回源请求延迟。
  3. 源站(Origin Server)
    当所有中间层缓存与边缘缓存均无请求内容时,才会到达最终的源站。源站是内容的原始出处,CDN会从这里获取最新版本的内容,然后将其分发给请求用户,并在适当的层级缓存节点中储存副本,以便满足未来类似请求。

分层缓存的工作原理

以下是一个典型的用户请求过程:

  1. 用户访问网站,请求某个资源(例如一张图片)。
  2. 用户的DNS解析请求将用户导向离他最近的L1边缘节点。
  3. L1节点检查自身是否缓存了该资源。
    • 如果,则直接将资源返回给用户,请求结束。这称为“缓存命中”。
    • 如果没有,则L1节点向其上层的L2区域节点发起请求。
  4. L2节点执行相同的检查,查看自身是否缓存了该资源。
    • 如果,则将资源返回给L1节点,L1节点再将其返回给用户。同时,L1节点也会缓存该资源,以便下次相同的请求可以直接命中。
    • 如果没有,则L2节点继续向上,向源站发起请求。
  5. 源站将资源返回给L2节点,L2节点再返回给L1节点,L1节点最终返回给用户。L1和L2节点都会缓存该资源。

分层缓存的优势

  • 减轻源站压力: 通过多层缓存,大部分用户请求都可以在L1或L2节点得到满足,大大减少了回源站的请求数量,从而减轻了源站的负载。
  • 提高缓存命中率: 分层结构使得更常用的内容可以缓存在更靠近用户的L1节点上,从而提高整体的缓存命中率,减少用户访问延迟。
  • 降低网络拥塞: 由于大量请求在CDN内部完成,减少了跨区域和跨运营商的网络传输,有助于缓解网络拥塞。
  • 更好的可扩展性: 分层结构使得CDN系统更容易扩展,可以通过增加L1和L2节点来应对不断增长的用户访问量。

分片缓存(Chunked Caching)

在某些情况下,CDN还会使用分片缓存技术,将大文件(例如视频文件)分割成多个小片段(chunks),然后分别缓存这些片段。当用户请求文件时,CDN只需传输用户需要的片段,而不是整个文件。这对于提高大文件传输效率和支持流媒体播放非常有用。

总结

CDN分层缓存是一种有效的提高网站性能和用户体验的技术。通过合理地组织和管理多层缓存节点,CDN可以更好地分配资源,提高缓存命中率,并减轻源站的压力。

2.Redis 安装及连接

Redis简介
Redis(Remote Dictionary Server)是一个开源的内存数据结构存储系统,可以用作数据库、缓存和消息队列中间件。它以Key-Value形式存储数据,提供多种数据结构和丰富的功能特性。Redis的核心价值在于高速访问、简单的数据操作模型以及丰富的数据结构支持,使其在需要快速读写、实时计算和高并发的场景中表现突出。

Redis的主要特性

  1. 内存存储
    Redis将数据存储在内存中,从而达到非常高的访问速度(读写操作通常在微秒级别)。这使其在对实时性要求高的场景(如会话存储、实时排行、实时计数器等)表现优异。

  2. 多种数据结构支持
    相较于传统的Key-Value存储仅支持字符串,Redis支持多种丰富的数据结构类型,这些数据结构以简单命令即可操作:

    • String(字符串):最基础的数据结构,可存储普通字符串、数字、二进制数据。
    • Hash(哈希):类似于Key-Value映射的集合,可方便存储对象属性并对属性进行增删改操作。
    • List(列表):双端链表实现,支持从头尾插入、弹出元素,可用来实现消息队列、任务列表等功能。
    • Set(集合):无序集合结构,支持求交集、并集和差集等集合运算,常用于去重、标签管理。
    • Sorted Set(有序集合):每个元素会关联一个分数(score),Redis会根据分数对元素进行排序,可用于排行榜、延时队列等场景。
    • Bitmap(位图)、HyperLogLogGeo(地理位置)等特殊数据类型:满足统计计数、地理位置查询等特殊需求。
  3. 持久化能力
    虽然Redis是内存数据库,但它并非易失性存储。Redis提供两种持久化机制,让数据在断电后仍能恢复:

    • RDB(Redis Database Backup):定时生成内存快照并持久化到磁盘,恢复速度快,数据略有延迟。
    • AOF(Append Only File):将每次写操作以日志的形式追加到文件中,数据恢复更为完整,可根据策略对AOF文件进行定期重写压缩。

    可以根据业务需求选择合适的持久化方案,或同时开启RDB和AOF实现数据安全与高效率的折中。

  4. 高可用与分布式
    Redis提供主从复制(Master-Slave Replication)实现数据的多份冗余,主节点负责写操作,从节点同步主节点的数据,提供读取分流和故障切换。当主节点出现故障时,可手动或借助Redis Sentinel(哨兵)实现自动故障转移。
    对于更大规模的数据集与访问压力,Redis Cluster可以将数据分片至多个节点,提升整体存储能力和吞吐性能。

  5. 事务支持
    Redis提供简单的事务机制(MULTI/EXEC命令),可以将一组操作打包,保证这些操作的顺序性和原子性。虽然不支持复杂的回滚功能,但事务可以确保一组命令要么都执行要么都不执行。

  6. Lua脚本扩展
    Redis内置了Lua解释器,用户可以在Redis内原子执行Lua脚本,对数据进行复杂操作,而无需在客户端与Redis之间多次往返,提高复杂操作的性能和一致性。

  7. 丰富的使用场景
    凭借高性能和多数据结构支持,Redis可广泛应用于各种场景:

    • 缓存热点数据(例如:热门商品信息、用户会话数据、应用程序配置)
    • 消息队列与任务调度(利用List或Stream)
    • 实时统计(计数器、排行榜、实时分析)
    • 分布式锁(利用SetNx命令实现简单的分布式锁机制)
  8. 简单易用的命令行与客户端支持
    Redis提供简洁直观的命令行客户端和与主流编程语言(如Java、Python、Go、C#等)兼容的客户端库,降低学习成本与集成难度。


总结
Redis作为一个内存数据存储系统,具有高性能、丰富的数据类型、灵活的持久化策略以及高可用性架构支持。它在高并发、低延迟与实时处理场景中得到广泛应用,已成为构建现代互联网应用的重要基础组件。

image-20241218093552202

2.1 dnf 安装 Redis

# Rocky 9.4 由系统源提供
[root@redis1.xyy.org ~]#dnf info redis
Name         : redis
Version      : 6.2.7
Release      : 1.el9
Architecture : x86_64
Size         : 1.3 M
Source       : redis-6.2.7-1.el9.src.rpm
Repository   : appstream
Summary      : A persistent key-value database
URL          : https://redis.io
License      : BSD and MIT
Description  : Redis is an advanced key-value store. It is often referred to as a data
             : structure server since keys can contain strings, hashes, lists, sets and
             : sorted sets.
             :
             : You can run atomic operations on these types, like appending to a string;
             : incrementing the value in a hash; pushing to a list; computing set
             : intersection, union and difference; or getting the member with highest
             : ranking in a sorted set.
             :
             : In order to achieve its outstanding performance, Redis works with an
             : in-memory dataset. Depending on your use case, you can persist it either
             : by dumping the dataset to disk every once in a while, or by appending
             : each command to a log.
             :
             : Redis also supports trivial-to-setup master-slave replication, with very
             : fast non-blocking first synchronization, auto-reconnection on net split
             : and so forth.
             :
             : Other features include Transactions, Pub/Sub, Lua scripting, Keys with a
             : limited time-to-live, and configuration settings to make Redis behave like
             : a cache.
             :
             : You can use Redis from most programming languages also.

[root@Rocky9.4 ~]#

# CentOS 7由 epel 源提供
[root@CentOS7 ~]#yum info redis
Name        : redis
Arch        : x86_64
Version     : 3.2.12
Release     : 2.el7
Size        : 1.4 M
Repo        : installed
From repo   : epel
Summary     : A persistent key-value database
URL         : http://redis.io
License     : BSD

[root@redis1.xyy.org ~]#dnf install redis
[root@redis1.xyy.org ~]#systemctl enable --now redis
[root@redis1.xyy.org ~]#pstree -p | grep redis
           |-redis-server(4237)-+-{redis-server}(4238)
           |                    |-{redis-server}(4239)
           |                    |-{redis-server}(4240)
           |                    `-{redis-server}(4241)
[root@redis1.xyy.org ~]#
[root@redis1.xyy.org ~]#redis-cl
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> INFO Server
# Server
redis_version:6.2.7
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:ec192bdd77ecd321
redis_mode:standalone
os:Linux 5.14.0-427.13.1.el9_4.x86_64 x86_64
arch_bits:64
monotonic_clock:POSIX clock_gettime
multiplexing_api:epoll
atomicvar_api:c11-builtin
gcc_version:11.3.1
process_id:4237
process_supervised:systemd
run_id:37144e0c3a2930dac6148605d26afae8ee4d38ba
tcp_port:6379
server_time_usec:1734486571682241
uptime_in_seconds:37314
uptime_in_days:0
hz:10
configured_hz:10
lru_clock:6433323
executable:/usr/bin/redis-server
config_file:/etc/redis/redis.conf
io_threads_active:0
127.0.0.1:6379>

2.2 编译安装 Redis

从 Redis 官方下载地址 获取稳定版压缩包,如 redis-7.4.0.tar.gz

image-20241218102907079
# 1.创建 Redis 用户(不需要登录权限,只是用于运行 Redis 服务以提高安全性)
useradd -r -s /sbin/nologin redis

# 2.获取源码包
wget https://download.redis.io/releases/redis-7.4.0.tar.gz

# 3.解压并进入源码目录
tar xf redis-7.4.0.tar.gz
cd redis-7.4.0

# 4.开始编译(在某些发行版下可开启 USE_SYSTEMD=yes 选项,以生成可与 systemd 交互的可执行文件。)
make -j $(nproc) USE_SYSTEMD=yes

# 5.安装到指定位置
make PREFIX=/apps/redis install

# 6.建立软链接(方便在命令行中使用redis-server、redis-cli)
ln -s /apps/redis/bin/redis-* /usr/bin/

# 7.创建所需目录
mkdir -p /apps/redis/{etc,log,data,run}

# 8.拷贝源码目录中自带redis.conf,拷贝到配置目录:
cp redis.conf /apps/redis/etc/

# 9.redis.conf:修改关键配置
#bind:改为 0.0.0.0 或保留默认看实际需要;
#requirepass:设置 Redis 密码,如 requirepass 123456;
#dir:RDB/快照文件存放目录,一般设为 /apps/redis/data;
#logfile:日志文件路径,如 /apps/redis/log/redis-6379.log;
#pidfile:pid 文件路径,如 /apps/redis/run/redis-6379.pid;
sed -i -e 's/bind 127.0.0.1/bind 0.0.0.0/' \
       -e "/# requirepass/a requirepass 123456" \
       -e "/^dir .*/c dir /apps/redis/data/" \
       -e "/^logfile .*/c logfile /apps/redis/log/redis-6379.log" \
       -e "/^pidfile .*/c pidfile /apps/redis/run/redis-6379.pid" \
       /apps/redis/etc/redis.conf

# 10.设置文件权限
chown -R redis:redis /apps/redis

# 11.内核与系统参数优化(不优化会有告警)
	# 11.1 调整内核参数
vim /etc/sysctl.conf
net.core.somaxconn = 1024
vm.overcommit_memory = 1

sysctl -p

	# 11.2 禁用透明大页(THP)
echo never > /sys/kernel/mm/transparent_hugepage/enabled
# 可以写入启动脚本(如 /etc/rc.local 或 /etc/rc.d/rc.local)以在重启后继续生效。

# 12.创建Systemd服务并启动
# CentOS/Rocky:/usr/lib/systemd/system/redis.service
# Ubuntu:/lib/systemd/system/redis.service(或 /etc/systemd/system/redis.service)
[Unit]
Description=Redis persistent key-value database
After=network.target

[Service]
ExecStart=/apps/redis/bin/redis-server /apps/redis/etc/redis.conf --supervised systemd
ExecStop=/bin/kill -s QUIT $MAINPID
# 启动notify一定要编译了 USE_SYSTEMD=yes,否则启动服务有问题
Type=notify
User=redis
Group=redis
RuntimeDirectory=redis
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target

# 13.刷新并启动服务
systemctl daemon-reload
systemctl enable --now redis
systemctl status redis

# 14.查看Redis版本或者信息
redis-server -v
# 查看服务信息
redis-cli -a 123456 INFO Server
# 测试插入和查询数据
redis-cli -a 123456 set mykey "Hello World"
redis-cli -a 123456 get mykey

#! /bin/bash
#-----------------------------------------------------
#Author:            XingYuyu
#Date:              2024-08-12
#Blog:              http://8.141.4.74
#Filename:          install_redis.sh
#Description:       [Online Install Redis for Rocky Linux ,Ubuntu,CentOS ]
#-----------------------------------------------------
VERSION=redis-7.4.0
PASSWORD=123456
INSTALL_DIR=/apps/redis

os_type() {
  awk -F'[ "]' '/^NAME/{print $2}' /etc/os-release
}

color() {
  RES_COL=80
  MOVE_TO_COL="echo -en \e[${RES_COL}G"
  SETCOLOR_SUCCESS="echo -en \e[1;32m"
  SETCOLOR_FAILURE="echo -en \e[1;31m"
  SETCOLOR_WARNING="echo -en \e[1;33m"
  SETCOLOR_NORMAL="echo -en \e[0m"
  echo -n "$1" && $MOVE_TO_COL
  echo -n "["
  if [ $2 = "success" -o $2 = "0" ]; then
    ${SETCOLOR_SUCCESS}
    echo -n $"  OK  "
  elif [ $2 = "failure" -o $2 = "1" ]; then
    ${SETCOLOR_FAILURE}
    echo -n $"FAILED"
  else
    ${SETCOLOR_WARNING}
    echo -n $"WARNING"
  fi
  ${SETCOLOR_NORMAL}
  echo -n $"]"
  echo
}

install_redis() {
  wget https://download.redis.io/releases/${VERSION}.tar.gz || {
    color "Redis 源码下载失败" 1
    exit
  }
  tar xf ${VERSION}.tar.gz
  cd ${VERSION}
  CPUS=lscpu | awk '/^CPU\(s\)/{print $2}'
  make -j $CPUS USE_SYSTEMD=yes PREFIX=${INSTALL_DIR} install && color "Redis 编译安装完成" 0 || {
    color "Redis 编译安装失败" 1
    exit
  }
  ln -s ${INSTALL_DIR}/bin/redis-* /usr/bin/
  mkdir -p ${INSTALL_DIR}/{etc,log,data,run}
  cp redis.conf ${INSTALL_DIR}/etc/
  sed -i -e 's/bind 127.0.0.1/bind 0.0.0.0/' -e "/# requirepass/a requirepass $PASSWORD" -e "/^dir .*/c dir ${INSTALL_DIR}/data/" -e "/^logfile .*/c logfile ${INSTALL_DIR}/log/redis-6379.log" -e "/^pidfile .*/c pidfile ${INSTALL_DIR}/run/redis-6379.pid" ${INSTALL_DIR}/etc/redis.conf
  if id redis &>/dev/null; then
    color "Redis 用户已经存在,无需创建" 0
  else
    useradd -r -s /sbin/nologin redis
    color "Redis 用户创建成功" 0
  fi
  chown -R redis.redis ${INSTALL_DIR}
  cat >>/etc/sysctl.conf <<EOF
net.core.somaxconn = 1024
vm.overcommit_memory = 1
EOF
  sysctl -p
  if [ `os_type` == "Ubuntu" ];then
      cat >> /lib/systemd/system/rc-local.service <<EOF
[Install]
WantedBy=multi-user.target
EOF
      echo '#!/bin/bash' > /etc/rc.local
      echo 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' >>/etc/rc.local
      chmod +x /etc/rc.local
      /etc/rc.local
      # Ubuntu 的service文件放在/lib/systemd/system/下或者/etc/systemd/system/下不能放在/usr/lib/下
      cat > /lib/systemd/system/redis.service <<EOF
[Unit]
Description=Redis persistent key-value database
After=network.target

[Service]
ExecStart=${INSTALL_DIR}/bin/redis-server ${INSTALL_DIR}/etc/redis.conf --supervised systemd
ExecStop=/bin/kill -s QUIT \$MAINPID
Type=notify
User=redis
Group=redis
RuntimeDirectory=redis
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target
EOF
  else
      echo 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' >>/etc/rc.d/rc.local
      chmod +x /etc/rc.d/rc.local
      /etc/rc.d/rc.local
      cat > /usr/lib/systemd/system/redis.service <<EOF
[Unit]
Description=Redis persistent key-value database
After=network.target

[Service]
ExecStart=${INSTALL_DIR}/bin/redis-server ${INSTALL_DIR}/etc/redis.conf --supervised systemd
ExecStop=/bin/kill -s QUIT \$MAINPID
Type=notify
User=redis
Group=redis
RuntimeDirectory=redis
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target

EOF
  fi

  systemctl daemon-reload
  systemctl enable --now redis &>/dev/null
  systemctl is-active redis &> /dev/null && color "Redis 服务启动成功,Redis信息如下:" 3 || { color "Redis 启动失败" 1 ;exit; }
  #sleep 5
  redis-cli -a $PASSWORD INFO Server 2>/dev/null
}

install_CentOS7() {
  . /etc/init.d/functions
  # jemalloc-devel依赖于epel源
  yum -y install epel-release && yum -y install gcc jemalloc-devel systemd-devel || {
    color "安装软件包失败,请检查网络配置" 1
    exit
  }
  rpm -q wget &>/dev/null || yum -y install wget &>/dev/null
  wget https://download.redis.io/releases/${VERSION}.tar.gz || {
    action "Redis 源码下载失败" false
    exit
  }
  tar xf ${VERSION}.tar.gz
  cd ${VERSION}
  CPUS=lscpu | awk '/^CPU\(s\)/{print $2}'
  make -j $CPUS USE_SYSTEMD=yes PREFIX=${INSTALL_DIR} install && action "Redis 编译安装完成" || {
    action "Redis 编译安装失败" false
    exit
  }
  ln -s ${INSTALL_DIR}/bin/redis-* /usr/bin/
  mkdir -p ${INSTALL_DIR}/{etc,log,data,run}
  cp redis.conf ${INSTALL_DIR}/etc/
  sed -i -e 's/bind 127.0.0.1/bind 0.0.0.0/' -e "/# requirepass/a requirepass $PASSWORD" -e "/^dir .*/c dir ${INSTALL_DIR}/data/" -e "/^logfile .*/c logfile ${INSTALL_DIR}/log/redis-6379.log" -e "/^pidfile .*/c pidfile ${INSTALL_DIR}/run/redis-6379.pid" ${INSTALL_DIR}/etc/redis.conf
  if id redis &>/dev/null; then
    action "Redis 用户已经存在" false
  else
    useradd -r -s /sbin/nologin redis
  fi
  chown -R redis.redis ${INSTALL_DIR}
  cat >>/etc/sysctl.conf <<EOF
net.core.somaxconn = 1024
vm.overcommit_memory = 1
EOF
  sysctl -p
  echo 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' >>/etc/rc.d/rc.local
  chmod +x /etc/rc.d/rc.local
  /etc/rc.d/rc.local

  cat >/usr/lib/systemd/system/redis.service <<EOF
[Unit]
Description=Redis persistent key-value database
After=network.target

[Service]
ExecStart=${INSTALL_DIR}/bin/redis-server ${INSTALL_DIR}/etc/redis.conf --supervised systemd
ExecReload=/bin/kill -s HUP \$MAINPID
ExecStop=/bin/kill -s QUIT \$MAINPID
Type=notify
User=redis
Group=redis
RuntimeDirectory=redis
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target

EOF
  systemctl daemon-reload
  systemctl enable --now redis &>/dev/null
  systemctl is-active redis &> /dev/null && ${COLOR}"Redis 服务启动成功,Redis信息如下:"${END} || { ${COLOR}"Redis 启动失败"${END};exit; }
  #sleep 5
  redis-cli -a $PASSWORD INFO Server 2>/dev/null
}

install_Ubuntu() {
 apt -y install make gcc libjemalloc-dev libsystemd-dev || {
    color "安装软件包失败,请检查网络配置" 1
    exit
  }
  install_redis
}

install_Rocky() {
  # jemalloc-devel依赖于epel源
  yum -y install epel-release && yum -y install gcc jemalloc-devel systemd-devel || {
    color "安装软件包失败,请检查网络配置" 1
    exit
  }
  rpm -q wget &>/dev/null || yum -y install wget &>/dev/null
  install_redis
}

if [ $(os_type) == 'CentOS' ]; then
  install_CentOS7
elif [ $(os_type) == 'Rocky' ]; then
  install_Rocky
elif [ $(os_type) == 'Ubuntu' ]; then
  install_Ubuntu
else
  color "未识别的操作系统" 1
fi

2.3 连接到 Redis

2.3.1 客户端连接到 Redis

1.本机无密码连接

redis-cli

2.跨主机无密码连接

redis-cli -h HOSTNAME/IP -p PORT

3.跨主机密码连接

redis-cli -h HOSTNAME/IP -p PORT -a PASSWORD

2.3.2 程序连接 Redis

redis 支持多种开发语言访问

https://redis.io/docs/latest/develop/clients/

image-20241218152549416

shell 脚本写入数据到 Redis

#!/bin/bash

NUM=100
PASS=123456

for i in `seq $NUM`; do
    redis-cli -h 127.0.0.1 -a "$PASS" --no-auth-warning set key${i} value${i}
    echo "key${i} value${i} 写入完成"
done

echo "$NUM 个key写入到Redis完成"

3.Redis 的多实例

在生产环境中,为了更好地利用资源、实现多租户隔离或分离不同业务的数据与配置,运维人员往往会在一台服务器上运行多个 Redis 实例。Redis 的多实例部署并非Redis内建的特性,而是通过为每个实例指定独立的配置文件、独立的运行端口与数据目录来实现的。以下是关于Redis多实例的详细讲解:

为什么需要多实例

  1. 资源隔离与多租户支持
    在某些场景下,不同的业务线或不同的用户需要独立的Redis服务,以免数据和性能相互影响。多实例可以为每个业务运行独立的Redis,保证数据和访问流量的隔离。
  2. 不同的配置要求
    某些业务可能需要不同的持久化策略(RDB或AOF)、内存管理策略或安全设置。多实例部署允许针对每个实例使用单独的配置文件,从而灵活定制每个实例的行为。
  3. 更好地利用硬件资源
    一台物理机/虚拟机的CPU、内存、网络资源较为充裕时,可以在同一台机器上运行多个Redis实例,充分利用硬件资源。尤其在内存较大时,不同实例分别作为缓存、队列、会话存储使用,可以最大化硬件利用率。

配置多实例的关键点

  1. 独立的配置文件
    每个实例都需要一个独立的配置文件(例如 redis-6379.conf, redis-6380.conf)。
    在配置文件中需要注意如下参数:

    • port:每个实例必须使用不同的端口,如6379、6380、6381等。
    • pidfile:每个实例需要独立的PID文件,如 /var/run/redis_6379.pid/var/run/redis_6380.pid
    • logfile:为每个实例指定独立的日志文件,如 /var/log/redis_6379.log/var/log/redis_6380.log
    • dir:为每个实例指定独立的数据目录,如 /var/lib/redis/6379//var/lib/redis/6380/,确保RDB或AOF文件不冲突。
    • daemonize yes:通常在生产中,多实例都以守护进程方式后台运行。利用 systemd 的进程监督能力,即使用 --supervised systemd 参数时,必须将 daemonize 设为 no。如果将 daemonize 设为 yes,则与 systemd 的监督模式相矛盾,导致 Redis 无法正常通过 systemd 进行管理和监控。
  2. 独立的启动命令
    启动时为每个实例指定相应的配置文件。常用命令形式:

    redis-server /path/to/redis-6379.conf
    redis-server /path/to/redis-6380.conf
    

    确保每个实例正常监听自己的端口并使用自己的配置。

  3. 服务管理与守护进程
    为每个实例创建单独的systemd服务文件或init脚本,方便运维管理。如在systemd中创建 /etc/systemd/system/redis@6379.serviceredis@6380.service 等文件,然后通过 systemctl start redis@6379 启动指定实例。

  4. 安全与访问控制
    确保为每个实例设置合理的访问控制,如 bind参数、protected-mode设置、requirepass或ACL策略。多实例运行时应确保不同实例的数据和访问策略独立,避免安全隐患。

  5. 监控与报警
    多实例运行时需要对每个实例分别进行监控,收集其内存使用、连接数、QPS、延迟、慢查询等指标,并对异常情况及时报警。

举例:多实例文件组织形式

/etc/redis/
├─ redis-6379.conf
├─ redis-6380.conf
└─ redis-6381.conf

/var/lib/redis/
├─ 6379/
│  ├─ dump.rdb
│  └─ appendonly.aof
├─ 6380/
│  ├─ dump.rdb
│  └─ appendonly.aof
└─ 6381/
   ├─ dump.rdb
   └─ appendonly.aof

/var/log/
├─ redis_6379.log
├─ redis_6380.log
└─ redis_6381.log

总结

Redis多实例部署是通过为每个实例提供独立的端口、独立的配置文件以及数据和日志目录来实现的。这种方式在同一台服务器上实现了灵活的资源分配和多租户支持。通过精心配置和管理,运维人员能够同时运行多个Redis实例,为不同应用提供高效、独立而又经济实惠的内存数据存储服务。

案例:以编译安装为例实现 Redis 多实例

# 生成的文件列表
[root@Rocky9.4 ~]#ll /apps/redis/
total 0
drwxr-xr-x 2 redis redis 134 Dec 17 23:22 bin
drwxr-xr-x 2 redis redis  22 Dec 18 20:04 data
drwxr-xr-x 2 redis redis  24 Dec 18 20:04 etc
drwxr-xr-x 2 redis redis  28 Dec 17 23:22 log
drwxr-xr-x 2 redis redis  28 Dec 18 20:04 run

[root@Rocky9.4 redis]#tree /apps/redis/
/apps/redis/
├── bin
│   ├── redis-benchmark
│   ├── redis-check-aof -> redis-server
│   ├── redis-check-rdb -> redis-server
│   ├── redis-cli
│   ├── redis-sentinel -> redis-server
│   └── redis-server
├── data
│   ├── dump-6379.rdb
│   ├── dump-6380.rdb
│   └── dump-6381.rdb
├── etc
│   ├── redis_6379.conf
│   ├── redis_6380.conf
│   ├── redis_6381.conf
│   └── redis.conf
├── log
│   ├── redis-6379.log
│   ├── redis-6380.log
│   └── redis-6381.log
└── run
    ├── redis-6379.pid
    ├── redis-6380.pid
    └── redis-6381.pid

5 directories, 19 files



# 配置文件需要修改的地方
vim /apps/redis/etc/redis_6379.conf
bind 0.0.0.0 -::1
port 6379
daemonize no
pidfile /apps/redis/run/redis-6379.pid
logfile /apps/redis/log/redis-6379.log
# 写入数据的时候,并且满足save才会生产dump-6379.rdb这个文件
dbfilename dump-6379.rdb
dir /apps/redis/data/
# 3600秒,写一次数据 300秒,100次数据,60秒,10000次数据 满足就会备份,为了更快的看到效果可以更改,例如:save 60 1
save 3600 1 300 100 60 10000
appendfilename "appendonly-6379.aof"

vim /apps/redis/etc/redis_6380.conf
bind 0.0.0.0 -::1
port 6380
daemonize no
pidfile /apps/redis/run/redis-6380.pid
logfile /apps/redis/log/redis-6380.log
dbfilename dump-6380.rdb
dir /apps/redis/data/
save 3600 1 300 100 60 10000
appendfilename "appendonly-6380.aof"

vim /apps/redis/etc/redis_6381.conf
bind 0.0.0.0 -::1
port 6381
daemonize no
pidfile /apps/redis/run/redis-6381.pid
logfile /apps/redis/log/redis-6381.log
dbfilename dump-6381.rdb
dir /apps/redis/data/
save 3600 1 300 100 60 10000
appendfilename "appendonly-6381.aof"

# 创建service文件
# 1./usr/lib/systemd/system/redis6379.service
[Unit]
Description=Redis persistent key-value database
After=network.target

[Service]
ExecStart=/apps/redis/bin/redis-server /apps/redis/etc/redis_6379.conf --supervised systemd
ExecStop=/bin/kill -s QUIT $MAINPID
Type=notify
User=redis
Group=redis
RuntimeDirectory=redis
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target

[root@Rocky9.4 ~]#

# 2./usr/lib/systemd/system/redis6380.service
[Unit]
Description=Redis persistent key-value database
After=network.target

[Service]
ExecStart=/apps/redis/bin/redis-server /apps/redis/etc/redis_6380.conf --supervised systemd
ExecStop=/bin/kill -s QUIT $MAINPID
#Type=notify
User=redis
Group=redis
RuntimeDirectory=redis
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target

[root@Rocky9.4 ~]#

# 3./usr/lib/systemd/system/redis6381.service
[Unit]
Description=Redis persistent key-value database
After=network.target

[Service]
ExecStart=/apps/redis/bin/redis-server /apps/redis/etc/redis_6381.conf --supervised systemd
ExecStop=/bin/kill -s QUIT $MAINPID
#Type=notify
User=redis
Group=redis
RuntimeDirectory=redis
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target
[root@Rocky9.4 ~]#


systemctl daemon-reload
systemctl enable --now redis6379.service redis6380.service redis6381.service
# 这里有个问题,通过二进制安装好的Redis,start的时候用tab键无法补全

4.Redis 持久化

Redis 是一个基于内存的数据结构存储系统,但它提供了多种持久化机制,可以将内存中的数据保存到磁盘中,从而在 Redis 重启或服务器宕机后依然能够恢复数据。Redis 主要提供了两种持久化方式:RDB(Redis Database)AOF(Append Only File)。这两种方式可以单独使用,也可以配合使用,具体选择取决于业务需求(对数据一致性、写入性能、磁盘空间等的不同要求)。

4.1 RDB(Redis Database)

RDB 方式是 Redis 最早的持久化模式,即在某个时间点对内存数据做快照,并保存到一个 .rdb 文件中

4.1.1 RDB 的工作机制

方法1:

SAVE 命令是“阻塞式”保存,Redis 不会创建子进程,而是直接由主进程把内存数据写到 RDB 文件里。

[root@Rocky9.4 redis]#( redis-cli -a 123456 save & );pstree -p | grep redis-server;ls /apps/redis/data/ -lh
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
           |-redis-server(28847)-+-{redis-server}(28854)
           |                     |-{redis-server}(28855)
           |                     |-{redis-server}(28856)
           |                     |-{redis-server}(28857)
           |                     `-{redis-server}(28858)
total 180M
-rw-r--r-- 1 redis redis 180M Dec 23 18:48 dump_6379.rdb
-rw-r--r-- 1 redis redis  48K Dec 23 21:45 temp-28847.rdb

使用 python 脚本存入一千万条数据,再进行备份看到下面的现象

# 这个需要使用pip install redis来安装redis包
import redis

pool=redis.ConnectionPool(host="10.0.0.41",port=6379,password="123456")
r=redis.Redis(connection_pool=pool)
for i in range(10000000):
    r.set("k%d" % i,"v%d" % i)
    data=r.get("k%d" % i)
    print(data)

image-20241223215647425

方法2:

BGSAVE 才是“后台”保存,Redis 会 fork 一个子进程来完成 RDB 持久化,主进程继续对外提供服务。

[root@Rocky9.4 data]#redis-cli -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> bgsave
Background saving started
127.0.0.1:6379>

# 生产临时文件,fork 子进程 pid是920,从temp-920.rdb也可以看出是进程920在备份
[root@Rocky9.4 data]#pstree -p | grep redis-server;ls /apps/redis/data/ -lh
           |-redis-server(638)-+-redis-server(920)
           |                   |-{redis-server}(666)
           |                   |-{redis-server}(667)
           |                   |-{redis-server}(668)
           |                   |-{redis-server}(669)
           |                   `-{redis-server}(671)
total 128M
-rw-r--r-- 1 redis redis 67M Dec 24 14:43 temp-920.rdb
# 备份结束以后,将文件重命名
[root@Rocky9.4 data]#pstree -p | grep redis-server;ls /apps/redis/data/ -lh
           |-redis-server(638)-+-{redis-server}(666)
           |                   |-{redis-server}(667)
           |                   |-{redis-server}(668)
           |                   |-{redis-server}(669)
           |                   `-{redis-server}(671)
total 180M
-rw-r--r-- 1 redis redis 180M Dec 24 14:43 dump_6379.rdb

# 也可以查看日志
[root@Rocky9.4 data]#tail -f ../log/redis-6379.log
# bgsave的日志,会显示出具体的子进程编号
638:M 24 Dec 2024 15:15:14.746 * Background saving started by pid 1037
1037:C 24 Dec 2024 15:15:22.016 * DB saved on disk
1037:C 24 Dec 2024 15:15:22.026 * Fork CoW for RDB: current 0 MB, peak 0 MB, average 0 MB
638:M 24 Dec 2024 15:15:22.095 * Background saving terminated with success

# save的日志
638:M 24 Dec 2024 15:20:09.364 * DB saved on disk

image-20241224150259770

方法3:

Redis 会在配置文件中设置触发 RDB 生成快照的条件,典型配置示例:

save 900 1   # 在900秒内(15分钟)至少有1个键发生变动,则触发保存快照
save 300 10  # 在300秒内(5分钟)至少有10个键发生变动,则触发保存快照
save 60 10000# 在60秒内(1分钟)至少有10000个键发生变动,则触发保存快照

# 上面是之前老版本的写法,写在新版本的写法:
save 3600 1 300 100 60 10000

触发快照后,Redis 通过fork 出一个子进程,子进程负责将内存数据写入临时的 RDB 文件;父进程继续处理客户端请求,因此在快照生成的过程中,Redis 仍然能够服务读写请求。

当子进程将数据写完后,会原子性地用临时文件替换原先的 RDB 文件,以确保在替换前的 RDB 文件依然可用。这个和bgsave的模式是一样的.

4.1.2 RDB 的优点

  1. 性能开销小:由于生成快照时是通过 fork 子进程来执行,主进程只需做少量工作,对性能影响较小。
  2. 适合做冷备:如果业务允许一定程度的数据丢失(因为 RDB 只能反映生成快照时的数据状态),那么 RDB 非常简洁且容易做冷备份与全量备份。
  3. 启动速度快:从 RDB 文件进行数据恢复时,因为只是加载一个快照文件,启动速度通常比较快。

4.1.3 RDB 的缺点

  1. 可能丢失数据:快照通常并不会很频繁地生成(除非你把 save 指令配置得极短,这会带来极大的性能损耗),所以在两次快照之间发生的数据写操作可能会丢失。
  2. fork 开销:在大数据量时执行 fork 操作需要分配子进程的内存页表,会有一定系统开销,且写入 .rdb 文件时也会消耗 I/O 资源。

4.2 AOF(Append Only File)

从 Redis 7.0.0开始以及之后的版本中,AOF(Append Only File)机制经过优化,引入了基础 RDB 文件和增量 AOF 文件的组合方式。这种设计有助于提高 AOF 的管理效率和数据恢复的速度。().

AOF 是另一种持久化方式,它会将每次写操作以命令的形式追加到一个文件中(默认叫 appendonly.aof),从而实现数据的保存。

4.2.1 AOF 的工作机制

  • 写命令追加:Redis 会把收到的每条写命令,用 Redis 协议格式(Redis Serialization Protocol)记录到 AOF 文件的末尾。
  • AOF 刷盘策略:Redis 提供了多种 AOF 同步策略(即何时将命令真正写到磁盘),通过 appendfsync参数控制:
    1. appendfsync always:每次有写操作时都同步写入磁盘,最安全但最慢。
    2. appendfsync everysec:每秒将缓存中的写命令同步写到磁盘,默认配置,在系统断电时最多丢失1秒的数据
    3. appendfsync no:由操作系统决定何时同步写到磁盘,性能最高,安全性最低。
  • AOF 重写(Rewrite):随着大量写操作的发生,AOF 文件会越来越大,因此需要对 AOF 文件进行“重写压缩”。
    • Redis 会 fork 出子进程,把内存中的数据以最精简的命令集合重新写到一个新文件中。
    • 重写过程中,主进程持续将新的写操作命令追加到一个缓冲区,待子进程重写完成后,再将这些命令同步到新文件末尾,最后原子地替换旧 AOF 文件。

4.2.2 AOF 的优点

  1. 数据安全:AOF 可以配置成每次写操作都写入磁盘(always),或者至少每秒写一次(everysec),相比 RDB,数据丢失的风险会小得多。
  2. 日志记录:AOF 文件是按命令记录的文本文件,人为可读,并且在出现紧急情况时可以对其进行分析或修复(比如手动删除错误指令)。

4.2.3 AOF 的缺点

  1. 文件体积大:和 RDB 相比,AOF 文件会更大,尤其是在没有做 AOF 重写的情况下。
  2. 写性能影响:如果采用最安全的 appendfsync always 模式,那么每次写操作都要同步到磁盘,会带来明显的性能损耗。
  3. 恢复速度:AOF 重放所有写命令来恢复数据,可能比载入一个完整的 RDB 文件更慢。

4.3 如何选择 RDB 和 AOF

  1. 只用 RDB
    • 对数据一致性要求不高,能容忍几分钟的数据丢失,且更倾向于更好的写性能。
    • 能够定期手动备份 RDB 文件,或者通过复制等方式冗余数据。
  2. 只用 AOF
    • 对数据安全性要求更高,不能容忍太多数据丢失,希望可以在秒级甚至实时上落盘。
    • 愿意投入更多的磁盘性能和空间成本,接受 AOF 重放带来的恢复速度影响。
  3. RDB + AOF 同时使用(较推荐)
    • 大多数生产环境下,往往两者结合使用,Redis 启动时优先载入 AOF 文件(更完整),如果 AOF 文件不存在或不可用才载入 RDB 文件。
    • 可以在保证数据安全的同时,也能定期生成快照,便于快速恢复或冷备份。

4.4 AOF相关配置

[root@Rocky9.4 etc]#vim /apps/redis/etc/redis_6379.conf
# 启用 AOF 持久化,通过config命令开启,防止数据清空 config set appendonly yes
appendonly yes
# AOF 文件的名称
appendfilename "appendonly-6379.aof"
# 新版本专门为aof增加了一个目录,这个目录是在$dir下创建的
appenddirname "appendonlydir"

# AOF 同步策略
# always: 每个写命令都同步到磁盘
# everysec: 每秒同步一次
# no: 让操作系统决定何时同步
appendfsync everysec

# 数据目录
dir /apps/redis/data/

# AOF 重写的策略
# 例如,当 AOF 文件大小增长到上一个重写后的大小的 100% 时触发重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 查看备份的目录
[root@Rocky9.4 data]#tree
.
├── appendonlydir
│   ├── appendonly-6379.aof.1.base.rdb
│   ├── appendonly-6379.aof.1.incr.aof
│   └── appendonly-6379.aof.manifest
├── dump-6379.rdb
├── dump-6380.rdb
└── dump-6381.rdb

1 directory, 6 files
[root@Rocky9.4 data]#pwd
/apps/redis/data
[root@Rocky9.4 data]#

根据业务需求选择合适的同步策略:

  • always:适用于对数据安全性要求极高的场景,但性能开销较大。
  • everysec:默认策略,适用于大多数场景,平衡了性能和数据安全性。
  • no:适用于对性能要求极高且可以容忍数据丢失的场景。

在 Redis 7.4 中,AOF(Append Only File)持久化机制引入了更为复杂和高效的文件结构,以提高数据持久性和恢复速度。您在 appendonlydir 目录下看到的以下几个文件:

基础 RDB 文件: appendonly-6379.aof.1.base.rdb      180M
增量 AOF 文件: appendonly-6379.aof.1.incr.aof      56
清单文件: 	   appendonly-6379.aof.manifest        98

这些文件分别代表了 Redis 7.4 中 AOF 持久化机制的新特性和结构。下面将详细解释每个文件的含义及其作用。

1. appendonly-6379.aof.1.base.rdb(180M)

作用

  • 这是一个 基础 RDB 快照 文件。它包含了在某个时间点上 Redis 数据库的完整状态。
  • 作为 AOF 持久化的一部分,Redis 7.4 结合了 RDB 和 AOF 的优点,通过基础 RDB 文件和增量 AOF 文件来实现高效的数据持久化和恢复。

优点

  • 快速恢复:通过加载 RDB 快照,Redis 可以快速恢复到某个时间点的状态,而无需重放所有 AOF 命令。
  • 减少文件大小:基础 RDB 文件存储了数据的全量快照,后续的增量 AOF 文件只记录自快照以来的变化,避免了 AOF 文件过大的问题。

2. appendonly-6379.aof.1.incr.aof(56)

作用

  • 这是一个 增量 AOF 文件,记录了自基础 RDB 快照以来的所有写操作命令(如 SET, HSET, LPUSH 等)。
  • 增量 AOF 文件用于补充基础 RDB 快照,确保在恢复时可以通过重放这些命令来达到最新的数据状态。

优点

  • 高效写入:相比传统 AOF 记录所有写操作,增量 AOF 只记录快照之后的变化,减少了磁盘写入量。
  • 灵活管理:可以定期生成新的基础 RDB 快照,并清理旧的增量 AOF 文件,优化存储空间。

3. appendonly-6379.aof.manifest(98)

作用

  • 这是一个 清单文件(Manifest File),用于管理和跟踪基础 RDB 文件与对应的增量 AOF 文件之间的关系。
  • 该文件记录了哪些增量 AOF 文件对应于哪个基础 RDB 文件,确保在数据恢复时能够正确地加载和重放命令。

优点

  • 数据一致性:通过清单文件,Redis 可以准确地知道需要加载哪些文件来恢复数据,避免数据不一致的问题。
  • 自动管理:清单文件帮助 Redis 自动管理文件的生命周期,如删除过期的增量 AOF 文件,维护持久化目录的整洁。

二、Redis 7.4 AOF 持久化机制的改进

Redis 7.4 引入了 混合持久化(Hybrid Persistence) 机制,将 RDB 和 AOF 结合起来,以充分利用两者的优势:

  1. 基础 RDB + 增量 AOF
    • 定期生成基础 RDB 快照,作为持久化的基准点。
    • 记录基础 RDB 之后的所有写操作到增量 AOF 文件中,确保数据的实时性和持久性。
  2. 高效恢复
    • 在恢复数据时,Redis 首先加载基础 RDB 文件,快速恢复到某个时间点的状态。
    • 然后重放对应的增量 AOF 文件,达到最新的数据状态。
  3. 优化存储和性能
    • 通过将持久化过程分为全量快照和增量记录,减少了 AOF 文件的大小和重写开销。
    • 提高了持久化和恢复的效率,降低了对系统性能的影响。

三、如何管理这些文件

1. 自动管理

Redis 7.4 会自动生成和管理这些文件,包括:

  • 生成基础 RDB 文件:根据配置的策略(如 AOF 重写触发条件),定期生成新的基础 RDB 文件。
  • 记录增量 AOF:在基础 RDB 文件生成后,开始记录新的写操作到增量 AOF 文件中。
  • 更新清单文件:确保清单文件准确反映当前的持久化文件结构。

2. 手动管理

虽然 Redis 会自动管理这些文件,但您仍可以进行一些手动操作以优化或排查问题:

  • 触发 AOF 重写:可以使用 BGREWRITEAOF 命令手动触发 AOF 重写,生成新的基础 RDB 文件和增量 AOF 文件。
  • 备份持久化文件:定期备份 appendonlydir 目录下的所有持久化文件(包括 .rdb, .aof, .manifest)以防止数据丢失。
  • 监控文件大小:监控各类持久化文件的大小,确保磁盘空间充足,并根据需要调整持久化策略。

四、配置示例

在 Redis 配置文件 (redis.conf) 中,相关配置可能如下:

# 启用 AOF 持久化
appendonly yes

# AOF 文件的名称
appendfilename "appendonly.aof"

# AOF 同步策略
appendfsync everysec

# 混合持久化配置
# 具体配置项可能因 Redis 版本而异,请参考官方文档

注意:Redis 7.4 的混合持久化机制可能引入了新的配置选项,请务必参考 Redis 官方文档 以获取最新和详细的配置说明。

五、总结

Redis 7.4 在 AOF 持久化机制上引入了基础 RDB 文件、增量 AOF 文件和清单文件的结构,通过混合持久化机制,结合了 RDB 和 AOF 的优势,实现了高效、可靠的数据持久化和快速恢复。这些文件的存在确保了 Redis 在高负载和大数据量的场景下,能够保持数据的完整性和系统的高可用性。

理解和正确管理这些持久化文件,对于保障 Redis 数据的安全性和系统的稳定性至关重要。建议定期备份持久化文件,并监控文件的大小和系统性能,以确保 Redis 实例的健康运行。

4.5 AOF rewrite 重写

appendonly-6379.aof.1.base.rdb:基础 RDB 快照文件。

appendonly-6379.aof.1.incr.aof:增量 AOF 文件,记录自基础快照以来的所有写命令。

appendonly-6379.aof.manifest:清单文件,管理基础 RDB 文件与增量 AOF 文件的关系。

4.5.1 基础 RDB 文件与增量 AOF 文件的工作机制

  1. 基础 RDB 文件 (appendonly-6379.aof.1.base.rdb)
  • 作用:保存某一时间点的数据库完整状态,相当于一个 RDB 快照。
  • 更新条件:当进行 AOF 重写(AOF Rewrite)操作时,Redis 会生成一个新的基础 RDB 文件。此操作可以自动触发,也可以手动执行。
  1. 增量 AOF 文件 (appendonly-6379.aof.1.incr.aof)
  • 作用:记录自上一个基础 RDB 快照以来的所有写命令(增量操作)。
  • 更新方式:在 Redis 运行过程中,所有写操作都会被追加到当前的增量 AOF 文件中。
  1. 清单文件 (appendonly-6379.aof.manifest)
  • 作用:跟踪和管理基础 RDB 文件与增量 AOF 文件之间的关系,确保在数据恢复时能够正确加载基础快照并应用增量命令。

4.5.2 增量备份的工作原理与配置

增量备份主要依赖于基础 RDB 文件和增量 AOF 文件的组合。通过这种方式,你可以在保持高效的同时,实现数据的持续备份。

增量备份的工作流程

  1. 基础快照生成:
    • 当执行 AOF 重写操作时,Redis 会生成一个新的基础 RDB 文件,记录当前数据库的完整状态。
  2. 记录增量操作:
    • 在基础快照生成后,所有新的写操作会被记录到新的增量 AOF 文件中。
  3. 管理文件关系:
    • 清单文件 (appendonly-6379.aof.manifest) 记录了当前使用的基础 RDB 文件和对应的增量 AOF 文件,确保数据恢复时能够正确加载。

4.5.3 参数配置

[root@redis.xyy.org ~]#vim /apps/redis/etc/redis.conf
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble yes
aof-timestamp-enabled no

以下是这些参数的详细解释及其在增量备份中的作用:

appendonly yes:启用 AOF 持久化。

appendfilename "appendonly.aof":指定 AOF 文件名。

appendfsync everysec:每秒执行一次 FSYNC 操作,平衡性能与持久性。

  1. no-appendfsync-on-rewrite no
    • 解释:当设置为 yes 时,Redis 在执行 AOF 重写期间会停止执行 FSYNC 操作,从而提高性能;设置为 no 则不会停止 FSYNC
    • 建议:默认情况下建议保持 no,确保数据的持久性,尤其在对数据一致性要求较高的场景。
  2. auto-aof-rewrite-percentage 100
    • 解释:定义 AOF 文件增长的比例,达到此比例后触发 AOF 重写。100 表示当当前 AOF 文件大小是上一次重写后的大小的 2 倍时触发重写(增长了 100%)。
    • 建议:根据实际数据写入量和系统性能调整此值。较低的比例会更频繁地进行重写,但可能影响性能;较高的比例则减少重写频率,但可能导致 AOF 文件过大。
  3. auto-aof-rewrite-min-size 64mb
    • 解释:设置 AOF 重写的最小触发文件大小。只有当 AOF 文件大小超过 64MB 且增长比例达到 auto-aof-rewrite-percentage 时,才会触发重写。
    • 建议:确保设置一个合理的最小值,以避免频繁的小规模重写,影响性能。
  4. aof-load-truncated yes
    • 解释:当 AOF 文件不完整或被截断时,是否允许 Redis 加载这些文件。yes 表示允许加载,并尽可能恢复数据。
    • 建议:在生产环境中建议设置为 no,以避免加载损坏的数据。如果设置为 yes,需要确保有其他数据恢复机制,以防止数据丢失。
  5. aof-use-rdb-preamble yes
    • 解释:在 AOF 文件开头包含一个 RDB 快照的前导数据(preamble)。这有助于加快数据加载速度。
    • 建议:默认建议保持 yes,提高数据恢复的效率。
  6. aof-timestamp-enabled no
    • 解释:是否在 AOF 文件中记录命令的时间戳。no 表示不记录,yes 表示记录。
    • 建议:通常设置为 no,除非你有特定需求需要记录时间戳。
# 通过 bgrewriteaof 手动触发重写机制
[root@Rocky9.4 data]#redis-cli -a 123456 bgrewriteaof;pstree -p | grep redis ;ll appendonlydir/ -h
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
Background append only file rewriting started
           |-redis-server(22944)-+-redis-server(23325)
           |                     |-{redis-server}(22946)
           |                     |-{redis-server}(22947)
           |                     |-{redis-server}(22948)
           |                     |-{redis-server}(22949)
           |                     `-{redis-server}(22950)
total 180M
-rw-r--r-- 1 redis redis 180M Dec 28 23:25 appendonly.aof.1.base.rdb
-rw-r--r-- 1 redis redis 719K Dec 29 23:04 appendonly.aof.1.incr.aof
-rw-r--r-- 1 redis redis    0 Dec 29 23:08 appendonly.aof.2.incr.aof
-rw-r--r-- 1 redis redis  132 Dec 29 23:08 appendonly.aof.manifest

[root@Rocky9.4 data]#ll appendonlydir/
total 183420
-rw-r--r-- 1 redis redis 187817903 Dec 29 23:08 appendonly.aof.2.base.rdb
-rw-r--r-- 1 redis redis         0 Dec 29 23:08 appendonly.aof.2.incr.aof
-rw-r--r-- 1 redis redis        88 Dec 29 23:08 appendonly.aof.manifest
[root@Rocky9.4 data]#

5.Redis 常用命令

5.1 ACL 控制

user <username> [on|off] [>password] [~pattern] [+permissions] [-permissions]

命令权限(+ / - / ~ / allcommands / nocommands)

  • +<command>:允许执行某个命令(如 +get, +set
  • -<command>:禁止执行某个命令
  • allcommands:允许执行所有命令
  • nocommands:禁止执行所有命令
  • ~<pattern>:这是匹配命令子令(SUBCOMMANDS),也可以通过 +<command>|subcommand 的形式添加特定子命令权限

onoff:启用或禁用用户。

>password:设置用户的密码,可以有多个密码。

~pattern:指定用户可以访问的键的模式(可选)。

+permissions-permissions:授予或撤销用户的权限,可以使用命令类别或具体命令。

# 假设您希望创建一个名为 alice 的用户,设置密码为 123456,并且授予她所有权限但禁用 FLUSHALL 命令。可以按照以下方式配置:
[root@Rocky9.4 etc]#vim redis_6379.conf
user alice on >123456 ~* +@all -FLUSHALL -FLUSHDB
user default on >123456 ~* +@all +get +set -FLUSHALL -FLUSHDB -keys -config
# 重启服务
systemctl restart redis
# 连接到Redis
[root@Rocky9.4 etc]#redis-cli -u redis://alice:123456@127.0.0.1:6379
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> FLUSHALL
(error) NOPERM User alice has no permissions to run the 'flushall' command
127.0.0.1:6379> exit

Key 权限(~ / %)

  • ~<pattern>:允许对匹配 <pattern> 的 key 进行读写操作
  • &<pattern>:只读访问(7.0+ 里对 key 权限有进一步细分,比如读写分离,会使用 % 符号来区分写权限)
  • %<pattern>:只写访问(这是 Redis 7.0+ 扩展的语法,用于区分只写权限)
  • allkeys / nokeys:允许/禁止访问所有 key

解释:

  • user alice:定义用户名为 alice
  • on:启用该用户。
  • >123456:设置用户的密码为 123456
  • ~*:允许用户访问所有键。
  • +@all:授予用户所有命令权限。
  • -FLUSHALL -FLUSHDB:撤销 FLUSHALLFLUSHDB 命令的权限,防止用户执行这些危险命令。
# 默认用户去掉flushall 和 flushdb
user default on >123456 &logs:* ~* +@all -FLUSHALL -FLUSHDB

# 动态管理 ACL
# 1.ACL List(查看当前所有用户配置的详细信息,包括用户名称、密码哈希、权限列表、key patterns 等。)
[root@Rocky9.4 etc]#redis-cli -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> acl list
1) "user alice on sanitize-payload #87b9b7660e9fe77503b14eb6da0277151e031aad3a88a1673b798d8443af242b resetchannels -@all"
2) "user default on sanitize-payload #8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92 ~* resetchannels &logs:* +@all -flushall -flushdb"
127.0.0.1:6379>

# 2.ACL GETUSER <username>(获取指定用户的ACL信息)
127.0.0.1:6379> ACL GETUSER alice
 1) "flags"
 2) 1) "on"
    2) "sanitize-payload"
 3) "passwords"
 4) 1) "87b9b7660e9fe77503b14eb6da0277151e031aad3a88a1673b798d8443af242b"
 5) "commands"
 6) "-@all"
 7) "keys"
 8) ""
 9) "channels"
10) ""
11) "selectors"
12) (empty array)
127.0.0.1:6379>

# 3.ACL SETUSER <username> [规则 ...](更新(创建或修改)用户的权限规则)
ACL SETUSER bob on >bob_password +get +set -FLUSHALL -FLUSHDB  ~bob:*
ACL SETUSER bob on:启用 bob
>bob_password:设置 bob 的密码
+get +set:允许 bob 执行 get、set 命令
~bob:*:只允许 bob 访问前缀为 bob: 的 key

# 4.ACL DELUSER <username> [<username> ...](删除用户及其权限配置)
ACL DELUSER alice
ACL DELUSER bob

# 5.ACL SAVE(将内存中的 ACL 配置写回到 aclfile(如果在配置文件中指定了 aclfile 路径的话)中。默认不写入 redis.conf,如果希望保存到文件,需要先在 redis.conf 中指定:)
注意:在redis.conf中配置aclfile,就不能同时配置user alice...,必须要将配置写入到aclfile,并且还要将这个文件手动创建出来,服务才会重启成功
aclfile /path/to/aclfile.conf

5.2 INFO

作用:查看 Redis 服务器的各种统计信息和状态,例如内存使用情况、复制状态、连接数、持久化信息、keyspace 信息等。

使用示例

127.0.0.1:6379> INFO keyspace
# Keyspace
db0:keys=10000001,expires=0,avg_ttl=0,subexpiry=0
127.0.0.1:6379>

127.0.0.1:6379> info server
# Server
redis_version:7.4.1
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:a9a1c6875521b0ad
redis_mode:standalone
os:Linux 5.14.0-427.13.1.el9_4.x86_64 x86_64
arch_bits:64
monotonic_clock:POSIX clock_gettime
multiplexing_api:epoll
atomicvar_api:c11-builtin
gcc_version:11.5.0
process_id:9692
process_supervised:no
run_id:83d8b9655623d6edaf809f8a7456e68179e9de91
tcp_port:6379
server_time_usec:1735883510494144
uptime_in_seconds:6113
uptime_in_days:0
hz:10
configured_hz:10
lru_clock:7830262
executable:/apps/redis/bin/redis-server
config_file:/apps/redis/etc/redis_6379.conf
io_threads_active:0
listener0:name=tcp,bind=0.0.0.0,bind=-::1,port=6379
127.0.0.1:6379>

Redis 会返回一个多段文本,包含大量信息,可按模块划分(server、clients、memory、persistence、stats、replication、cpu、cluster、keyspace 等)。

常用操作

  • INFO memory:只查看和内存相关的信息。
  • INFO replication:只查看主从复制(replication)相关信息。
  • INFO server: 查看server的相关信息
  • INFO cluster: 查看集群的信息
  • INFO keyspace: 查看数据库的信息,有多少键.

5.3 SELECT

作用:切换 Redis 的逻辑数据库(DB)。Redis 默认有 16 个逻辑数据库,编号 0 到 15(可通过 databases 配置修改)。

使用示例

127.0.0.1:6379> SELECT 1
OK
127.0.0.1:6379[1]>    # 命令提示符会显示当前已在 DB1

注意点

  • Redis 集群模式下通常只能使用 DB 0,不支持多 DB 切换。
  • 切换数据库仅对当前连接有效,断开后重新连接默认仍进入 DB 0。

5.4 KEYS

作用:列出匹配给定模式(pattern)的所有 Key。常用模式如 KEYS user:*

使用示例

127.0.0.1:6379> KEYS *
1) "foo"
2) "bar"
3) "user:1001"

注意点

  • 不要在生产环境中频繁使用 KEYS 命令,因为它会对整个 keyspace 做遍历,耗时且阻塞服务器,容易导致性能问题,最好使用acl禁用keys*。
  • 生产中更常用 SCAN 命令以非阻塞(游标)方式遍历 key。

5.5 BGSAVE

作用:在后台(异步)执行一次 RDB 快照,将当前数据集保存到磁盘(默认文件名 dump.rdb,可在 redis.conf 中配置)。

使用示例

127.0.0.1:6379> BGSAVE
Background saving started

执行后会立即返回,Redis 会在后台完成 RDB 持久化。

常用场景

  • 手动触发一次快照,做数据备份。
  • 结合 save 配置自动触发也可以,但在现代 Redis 版本中,很多人更倾向于 AOF 或混合持久化。

5.6 DBSIZE

作用:返回当前所选数据库(DB)中 key 的数量。

使用示例

127.0.0.1:6379> DBSIZE
(integer) 10000001
127.0.0.1:6379>

表示当前 DB 里有 一千万 个 key。

注意点

  • DBSIZE 是一个简单计数,不涉及 keys 的遍历,因此非常高效。
  • 如果想知道所有 DB 的 key 数量,可以逐一 SELECT 0~15 执行 DBSIZE,或者用 INFO keyspace 查看。

5.7 FLUSHDB

作用:清空当前数据库(DB)的所有 Key(只影响您当前选择的 DB)。

使用示例

127.0.0.1:6379> FLUSHDB
OK

如果在 DB0 执行,会清空 DB0;在 DB1 执行会清空 DB1。

风险与注意

  • 不可逆,会删除当前 DB 中的全部数据!
  • 可在正式执行前先确认 SELECT 到了正确的 DB,避免误删。

5.8 FLUSHALL

作用:清空 所有 DB 的所有 Key(默认 16 个数据库都会被清空)。

使用示例

127.0.0.1:6379> FLUSHALL
OK

风险与注意

  • FLUSHDB 更危险,一旦执行,会导致 Redis 整个实例所有 DB 数据被清空。
  • 一般不建议在生产环境使用,务必谨慎操作!

5.9 SHUTDOWN

作用:关闭 Redis 服务器。执行后,会尝试做一次持久化(若配置了 RDB/AOF),然后退出进程。

使用示例

127.0.0.1:6379> SHUTDOWN

连接会立即断开,Redis 服务停止。

注意点

  • 如果想避免保存数据(即不再进行 RDB/AOF 落盘),可加选项 SHUTDOWN NOSAVE
  • 如果只想保存数据而不关闭,可以执行 SAVEBGSAVE

5.10 SCAN

在 Redis 中,SCAN 命令是一种基于游标(cursor)**的迭代查询方式,能够**分批且非阻塞地遍历数据库中的 Key(或集合、哈希、ZSet 等),避免像 KEYS * 这样一次性扫描全部 Key 导致大规模阻塞的问题。下面介绍一下 SCAN 的核心概念、使用方法以及与 KEYS 的区别。

一、为什么要用 SCAN?

  1. KEYS 的缺点
    • KEYS pattern 命令会一次性遍历所有 Key,并返回所有匹配的结果。
    • 在 Key 数量很大的情况下(几百万上千万),一次扫描会造成主线程阻塞,期间无法处理其他请求,导致服务卡顿甚至超时。
    • 因为 Redis 是单线程架构,这种大规模阻塞会严重影响线上业务。
  2. SCAN 的优点
    • 将大范围扫描拆分成多次小范围扫描,每次只返回一部分数据。
    • 采用“游标(Cursor)+ 增量遍历”的模式,每扫描一部分,Redis 就返回一个新的 Cursor,并在响应中包含本次扫描到的部分数据。
    • 用户可以根据返回的 Cursor 继续下一次扫描,直到 Cursor 回到 0 表示扫描结束。
    • 相比 KEYSSCAN 对服务器的阻塞时间更短,也更可控。

二、SCAN 的基本用法

2.1 命令格式

SCAN cursor [MATCH pattern] [COUNT count]
  • cursor:游标,初次调用时通常传 0,表示从头开始扫描。
  • MATCH pattern:可选,用于过滤匹配的 Key 模式(如 MATCH user:*)。如果不指定 MATCH,则返回的 Key 不做任何模式过滤(会返回所有 Key 的子集)。
  • COUNT count:可选,用于指定每次扫描希望返回的 Key 数量。并非严格保证返回固定数量,而是“期望值”,Redis 可能实际返回多于或少于这个数的 key。

初次调用

127.0.0.1:6379> SCAN 0 COUNT 10
1) "13107200"
2)  1) "k4533779"
    2) "k252114"
    3) "k933235"
    4) "k3676789"
    5) "k2576537"
    6) "k7573677"
    7) "k5285770"
    8) "k2267950"
    9) "k2473601"
   10) "k4433328"
  • 返回结果中的第一个元素 "13107200" 是新的游标值,下次扫描时要用它。
  • 第二个数组是本次扫描到的一批 Key(例如 5~10 个)。

后续调用

127.0.0.1:6379> SCAN 13107200 COUNT 10
1) "4456448"
2)  1) "k1450912"
    2) "k9102989"
    3) "k6829708"
    4) "k3410677"
    5) "k2513869"
    6) "k9564207"
    7) "k7683296"
    8) "k2951179"
    9) "k6113726"
   10) "k8041825"
127.0.0.1:6379>
  • 继续用上一次返回的游标 13107200 作为本次调用的游标;
  • Redis 返回游标 “4456448” 和新的 Key 列表。

三、SCAN 与其他相关命令

SCAN:遍历数据库中的 Key。

SSCAN:遍历 Set 中的元素。

HSCAN:遍历 Hash 中的 field-value 对。

ZSCAN:遍历 Sorted Set 中的 member-score 对。

它们用法相似,都是 SCAN cursor [MATCH pattern] [COUNT count] 的形式,只是操作的数据结构不同。例如:

HSCAN myhash 0 MATCH field:* COUNT 10

四、SCAN vs. KEYS

  • 性能:
    • KEYS 命令会阻塞 Redis 直到扫描完所有 Key;
    • SCAN 采用增量扫描,每次只处理一部分,能把阻塞时间分散到多个小的时间片,对线上性能影响更小。
  • 用法:
    • KEYS 适合在测试小规模场景下调试时使用,方便一次性获取所有匹配 key;
    • 生产环境中强烈推荐使用 SCAN,能避免大规模阻塞。
  • 一致性:
    • KEYS 在那一刻会返回快照式的所有 Key;
    • SCAN 可能会出现漏扫或重复,尤其当 Key 动态变化时。但大多数情况下,这种不完全一致性是能接受的(可额外在应用层做去重处理)。

6.Redis 数据类型

6.1 字符串(string)类

6.1.1 String 的存储特点

  • 存储内容:可以是文本(如 "Hello")、数字(如 "123")、二进制文件(如图片、音频等),只要单个值不超过 512 MB 即可。
  • 内存开销:小字符串采用 SDS(Simple Dynamic String) 结构存储,Redis 会根据实际值大小自动选择合适的底层结构,避免频繁的内存分配。
  • 常用场景:
    • 缓存网页内容、配置、令牌、session 信息、计数器等;
    • 存储对象的序列化结果,比如 JSON、Protobuf 等;
    • 计数统计,利用 INCR/DECR 等快速自增自减

6.1.2 常见的操作命令

6.1.2.1 设置与获取
6.1.2.1.1 SET
  • 作用:设置 key 的值。如果 key 已存在,会被覆盖;如果 key 不存在,则创建一个新的 key。

  • 基本语法:

    SET <key> <value>
    
  • 示例:

    SET user:1001 "Alice"
    GET user:1001  # 返回 "Alice"
    
  • 扩展参数:

    • EX seconds:设置过期时间(秒)。

    • PX milliseconds:设置过期时间(毫秒)。

    • NX:只有当 key 不存在时才执行设置。

    • XX:只有当 key 存在时才执行设置。

    • 例如:

      SET mykey "Hello" EX 10 NX
      

      表示仅当 mykey不存在时,才设置值为 "Hello"并自动在 10 秒后过期。

6.2.2.1.2 GET
  • 作用:获取 key 的字符串值。

  • 示例:

    GET mykey
    

    若 key 存在,则返回对应的值;若 key 不存在,返回 nil

6.2.2.1.3 MSET / MGET
  • MSET:同时设置多个 key-value 对。

    MSET k1 "v1" k2 "v2" k3 "v3"
    

    一次性写入多对数据,减少多次网络往返。

  • MGET:批量获取多个 key 的值。

    MGET k1 k2 k3
    

    返回一个数组,如 [v1, v2, v3],不存在的 key 会以 nil 对应。

6.2.2.1.4 SETNX / SETXX(或结合 SET 命令的 NX / XX 参数)
  • SETNX:Set if Not eXists,只在 key 不存在时设置成功。等价于 SET key value NX
  • SETXX:Set if eXists,只在 key 存在时设置成功。等价于 SET key value XX
6.1.2.2 数值操作

Redis 支持对字符串值进行数字自增自减操作(前提是该字符串能被解析为整数或浮点数)。

6.1.2.2.1 INCR / DECR
  • INCR :将 key 的值自增 1。如果 key 不存在,则先初始化为 0 再自增。

  • DECR :将 key 的值自减 1。

  • 示例:

    SET counter 10
    INCR counter      # counter = 11
    DECR counter      # counter = 10
    
  • 注意:若 value 不是一个整数字符串,例如 "Hello"1.5,执行 INCR/DECR 会报错 (ERR value is not an integer)。

6.1.2.2.2 INCRBY / DECRBY
  • 作用:一次性加/减指定数值。

    INCRBY <key> <increment>
    DECRBY <key> <decrement>
    
  • 示例:

    SET counter 100
    INCRBY counter 50   # counter = 150
    DECRBY counter 20   # counter = 130
    
6.1.2.2.3 INCRBYFLOAT
  • 作用:对浮点数进行加法操作。

    SET price 12.5
    INCRBYFLOAT price 0.7  # price = 13.2
    
  • 使用场景:计量或需要小数的场景,如金额、温度等。

6.1.2.3 部分字符串操作
6.1.2.3.1 APPEND
  • 作用:向指定 key 的现有值 追加 一段字符串。如果 key 不存在,就相当于 SET

  • 示例:

    SET greeting "Hello"
    APPEND greeting ", Redis!"
    GET greeting        # "Hello, Redis!"
    
6.1.2.3.2 GETRANGE (旧命令:SUBSTR)
  • 作用:获取字符串中指定区间(基于下标)的子串,区间含左右边界,支持负索引(-1 表示最后一个字符)。

  • 语法:

    GETRANGE <key> <start> <end>
    
  • 示例:

    SET greeting "Hello, Redis!"
    GETRANGE greeting 0 4    # 返回 "Hello"
    GETRANGE greeting -6 -2  # 返回 "Redis" 中的一部分, 要根据实际字符串判断
    

    注意字符串 "Hello, Redis!"的长度和下标分布,从 0 到 12。

6.1.2.3.3 SETRANGE
  • 作用:在指定偏移量开始处,用给定值覆盖或插入到现有字符串里。若偏移量超过当前字符串长度,中间会填充空字节(\x00)。

  • 示例:

    SET mykey "Hello World"
    SETRANGE mykey 6 "Redis"  # 原字符串第 6 位开始覆盖,"Hello Redis"
    GET mykey  # "Hello Redis"
    
6.1.2.3.4 STRLEN
  • 作用:返回字符串值的长度(字节数)。

  • 示例:

    SET name "Alice"
    STRLEN name    # 返回 5
    
6.1.2.4 过期与生命周期管理

尽管不是 String 专有命令,但实际中常和 String 配合使用:

  • EXPIRE :为 key 设置过期时间(单位秒)。到期后 key 会被自动删除。
  • TTL :查看 key 剩余存活时间。
  • PERSIST :移除 key 的过期时间,使 key 变为永久存在。

也可在 SET 命令时直接附带 EXPX 参数来设置过期时间。

6.1.2.5 高级应用:Bit 操作

Redis 还提供了对 String 值执行位操作(bitwise)的命令,如 SETBIT, GETBIT, BITCOUNT, BITOP 等,能在每个位上进行读写、计数或逻辑运算。

  • SETBIT :将字符串第 offset 位设置为 value (0 或 1)。
  • GETBIT :获取 offset 位置的位值。
  • BITCOUNT [start end]:统计字符串指定区间内值为 1 的位数。
  • BITOP AND/OR/XOR [… ]:对多个 key 对应的位进行逻辑运算,并将结果存储到 destkey

这些命令常用于**实现位图(bitmap)**功能,比如用户签到、活跃状态等布尔属性的存储。

6.1.2.6 小结 & Best Practices
  1. 最常用的操作
    • SET / GET:基础读写;
    • MSET / MGET:批量读写;
    • INCR / DECR / INCRBYFLOAT:实现计数器或数值操作;
    • APPEND / GETRANGE / SETRANGE:在字符串上做增改或截取;
    • EXPIRE / SET ... EX:配合过期管理。
  2. 注意字符串与数值
    • 只有当字符串能被解析为整数或浮点数时,INCR/DECR/INCRBYFLOAT 才能工作,否则会报错;
    • 若您频繁进行数值操作,不要在中间手动 GET 并转回数字,以免造成竞态。直接用原子操作(INCR…)更可靠。
  3. 内存与大小限制
    • Redis 默认可存储最大 512MB 的值,但太大的值会带来内存和网络传输负担,不建议把超大文件直接放 Redis。
    • 建议拆分或使用外部文件存储(OSS、S3、NFS),Redis 中只存元数据或指针。
  4. 使用过期策略
    • 若用 Redis 做缓存,通常会为 key 设置过期时间,避免数据无限堆积或过期失效。
  5. Bit 操作
    • 高级玩法,能有效利用 Redis 做位图统计,如用户行为打点、日活统计等,但要注意数据结构设计和偏移量计算。
# 设置值,并设置有效期(s)--10秒过期,也可以通过SETEX来直接设置
set title study ex 10 

# 存在title,不变,不存在设置成ceo
127.0.0.1:6379> SETNX title ceo
(integer) 1

# 批量设置--mset/mget
127.0.0.1:6379> mset name xingyuyu age 18 gender femal title cto
OK
127.0.0.1:6379> mget name age
1) "xingyuyu"
2) "18"
127.0.0.1:6379>

6.2 哈希(Hash)

在 Redis 中,Hash(哈希) 是一种将多个字段(field)映射到同一个 key 下的数据结构。换句话说,Redis 的 Hash 类似一个小型的 key-value 表,表里的 “行” 只有一条,但这条“行”有许多字段(field)。它非常适合用来存储和读取类似对象、用户资料、配置项等场景。下面将按照前面类似的步骤,讲解 Hash 的基本概念、常用操作以及使用场景与注意事项。


6.2.1 Redis Hash 的存储与特点

  1. 内部结构
    • 小规模的哈希对象(字段数量较少时),Redis 会采用一种紧凑的数据结构(ziplist 或 listpack)来节省内存。
    • 当字段数量或字段长度变大时,会转换成真正的哈希表(Hashtable)。
    • 这对我们是透明的,Redis 会自动处理。但它解释了为什么 Hash 往往在小规模场景下特别高效。
  2. 数据特征
    • Key:在 Redis 层面标识一个哈希对象的名称(如 user:1001)。
    • Fields 和 Values:在哈希对象内部,每个 field 都拥有一个独立的 value。不同 field 之间互不干扰,但都属于同一个 key 的管理之下。
    • 字段唯一:同一个哈希键下的 field 名称是不能重复的,若重复则会更新原有值。
  3. 常用场景
    • 存储用户信息(字段:username、email、age 等);
    • 缓存一条记录或配置,这些记录可有多个属性;
    • 减少 key 数量(相比将每个属性单独存为一个 String key,这时可以把多个属性存在一个哈希 key 中)。

6.2.2 Hash 的常见操作命令

6.2.2.1 基础增删改查
6.2.2.1.1 HSET / HGET
  • HSET

    • 作用:向 hash 里设置(或更新)一个字段的值。如果字段不存在,就创建;若存在,就覆盖。

    • 语法:

      HSET <key> <field> <value> [<field2> <value2> ...]
      
    • 示例:

      HSET user:1001 name "Alice" age "20"
      

      user:1001不存在,Redis 会创建一个新的哈希;并设置字段 name="Alice"age="20"

  • HGET

    • 作用:获取 hash 表中某个字段的值。

    • 语法:

      HGET <key> <field>
      
    • 示例:

      HGET user:1001 name
      # 返回 "Alice"
      
6.2.2.1.2 HMSET / HMGET
  • HMSET(在新版本 Redis 中 HSET <key> <field> <value> ... 已经可以替代,HMSET 旧命令仍兼容)

    • 作用:同时设置多个字段值,和 HSET 的多字段形式一致。

    • 示例:

      HMSET user:1001 city "Beijing" gender "female"
      
  • HMGET

    • 作用:一次性获取多个字段的值。

    • 示例:

      HMGET user:1001 name age city
      # 返回一个数组 ["Alice", "20", "Beijing"]
      
6.2.2.1.3 HGETALL / HKEYS / HVALS
  • HGETALL

    • 作用:获取 hash 中所有字段和对应值,以 field-value 对数组返回。

    • 示例:

      HGETALL user:1001
      # 可能返回 ["name","Alice","age","20","city","Beijing","gender","female"]
      
  • HKEYS

    • 作用:只返回所有字段(field)的列表。
  • HVALS

    • 作用:只返回所有字段的值列表。
  • 这三种命令在字段很多时,一次性返回也会占用较多资源,生产环境可考虑 HSCAN(分批扫描)以避免阻塞。

6.2.2.1.4 HDEL
  • 作用:删除哈希表中的一个或多个字段。

  • 语法:

    HDEL <key> <field1> [field2 ...]
    
  • 示例:

    HDEL user:1001 age
    # 移除 "age" 字段
    
6.2.2.1.5 HEXISTS
  • 作用:检查哈希表中指定字段是否存在。

  • 示例:

    HEXISTS user:1001 name
    # 若存在则返回 1,否则返回 0
    

6.2.2.2 数值操作(原子自增自减)

如果哈希字段的值可以解析为数字(整数或浮点数),Redis 提供了原子增量命令:

6.2.2.2.1 HINCRBY
  • 作用:对 hash 某个字段的值执行加法(整数)。

  • 语法:

    HINCRBY <key> <field> <increment>
    
  • 示例:

    HINCRBY user:1001 login_count 1
    # 将 user:1001 哈希表里 login_count 字段值 +1
    
  • 注意:若字段不存在,会先初始化为 0,再进行加法。如果原来是非数字值,会报错。

6.2.2.2.2 HINCRBYFLOAT
  • 作用:对 hash 字段的值执行浮点加法。

  • 语法:

    HINCRBYFLOAT <key> <field> <increment>
    
  • 示例:

    HINCRBYFLOAT product:2001 price 9.99
    # 如果 price="100.0",则更新后 price="109.99"
    

6.2.2.3 Hash 扫描(HSCAN)
  • HSCAN

    • SCAN 类似,用于分批遍历一个哈希表,避免 HGETALL 在字段太多时造成阻塞。

    • 用法:

      HSCAN <key> <cursor> [MATCH pattern] [COUNT count]
      
    • 示例(伪代码):

      cursor = 0
      do {
        response = HSCAN user:1001 cursor MATCH "f*"
        cursor = response[0]        # 新游标
        fields = response[1]        # 字段值对
        # ...处理 fields ...
      } while (cursor != 0)
      
    • 这样可以一口气分多次小范围返回,不会阻塞 Redis 主线程太久。


6.2.3 Hash 的使用场景

  1. 用户配置、信息
    • user:1001 存放 name、email、age、login_count 等多个字段;
    • 避免把每个字段都做一个单独的 String key。
  2. 数据对象缓存
    • 例如商品信息 product:2001,其中字段 price、title、stock 等;
    • 更新时只需改对应字段,不必覆盖整个对象;
    • 还能减小内存,因为小哈希在 Redis 内部可用紧凑结构存储。
  3. 记录统计量
    • 可以在 Hash 中存放各种计数器字段(点击量、点赞量、回复数等),通过 HINCRBY 实现原子自增。
    • 一个大对象的多种统计值都集中在同一个 hash key 下,管理更方便。
  4. 减少 key 的数量
    • 在某些业务场景中,为了避免 key 过多带来的管理压力,把多个相关字段汇总到同一个 Hash key 下会简化命名空间;
    • 但要注意单个哈希不宜过大,否则一次获取全部字段仍可能带来性能开销。

6.2.4 常见注意事项 & Best Practices

  1. 单个 Hash 不宜过度膨胀
    • 虽然 redis.conf 里会控制 ziplist/listpack 的一些大小阈值,但如果字段规模过大,一次性 HGETALL 会占用大量内存带宽;
    • 对于超大哈希(上百万字段),谨慎使用或考虑拆分、使用 HSCAN 分批遍历。
  2. 字段值只能是字符串
    • Redis Hash 的 value 最终以字符串形式存储(即使是数字,也保存为字符串);
    • 若要存储复杂对象,可以序列化后放进字段值,如 JSON 或 MsgPack,更新局部字段可能就需要再解析 JSON。
  3. 过期和生命周期
    • Redis 只能对整个 Hash key 设置过期时间(如 EXPIRE user:1001 3600),无法对 Hash 里某个字段单独设置过期。
    • 若只想让特定字段有失效时间,需要业务层逻辑配合。
  4. Hash vs. String
    • 如果只有一个字段,使用 String 即可;
    • 如果对象字段较多或需要一起管理,Hash 更方便,也可能更省内存(对小字段场景)。
  5. Hash vs. Set、ZSet
    • Hash 专注于“字段-值”映射的结构,字段之间无顺序,主要用于描述对象属性;
    • Set/ZSet 更侧重“集合”或“排序”语义;
    • 业务逻辑决定选择。
  6. HXxx 命令的性能
    • 在绝大多数情况下都为 O(1) 或 O(N)(N 为字段数)的操作,和内部哈希表实现相关。
    • 大量字段更新时,也要留意操作分散性,别在高并发下对同一个大哈希做高频更新——可能会增大锁竞争(Redis 单线程)。

6.2.5 小结

Redis Hash 提供了紧凑且灵活的 “字段-值” 结构,适合存储类似对象、配置、多字段记录等。它的核心命令可总结为:

  1. 增改
    • HSET / HMSET:设置或更新字段
    • HINCRBY / HINCRBYFLOAT:对数值字段原子自增
  2. 查询
    • HGET / HMGET:获取单个或多个字段值
    • HGETALL:获取全部字段和值
    • HKEYS / HVALS:只获取字段 / 值列表
    • HEXISTS:判断字段是否存在
    • HLEN:获取字段总数
  3. 删除
    • HDEL:删除一个或多个字段
  4. 扫描
    • HSCAN:分批遍历所有字段
  5. 其他
    • EXPIRE / TTL 等只能针对整个 key,不支持单字段到期

合理使用 Hash,可以有效减少 key 数量,提升空间利用率,且让数据结构更加接近业务对象形态。结合其他 Redis 数据类型(如 List、Set、ZSet)能够构建出更丰富的业务逻辑与功能。

  • HSET <key> <field> <value>:设置 hash 表中的 field 字段值。
  • HGET <key> <field>:获取 hash 表中 field 字段值。
  • HMGET / HMSET:批量操作多个字段。
  • HGETALL <key>:获取 hash 的所有字段和值。
  • HDEL <key> <field>:删除指定字段。

6.3 列表(List)

在 Redis 中,List(列表) 是一个非常常用的数据类型,用来存储一个按插入顺序排序的字符串列表。它本质上可被视作一个双端队列,允许在队列头(left)或队列尾(right)进行插入、弹出等操作。


6.3.1 List 的存储与特点

  1. 存储结构:在内部,Redis 为 List 使用了一种类似双端链表QuickList(在较新的版本中)来维护元素。
  2. 数据特征:
    • 有序:按插入顺序保存元素,可在头/尾插入或弹出。
    • 元素类型:每个元素都是一个字符串,可以是文本、JSON、二进制等,长度最大可达 512MB(与普通 String 的限制相同)。
  3. 常用场景:
    • 实现消息队列 / 任务队列(如生产者-消费者模型)。
    • 记录最新的操作日志或信息(用 LPUSH + LTRIM 维护一个固定长度)。
    • 需要按顺序遍历、插入、或弹出的场景。

6.3.2 List 的常见操作命令

Redis 提供了多种与 List 交互的命令,下面分门别类介绍。

6.3.2.1 插入与弹出
6.3.2.1.1 LPUSH / RPUSH
  • LPUSH [element …]:在列表头部插入一个或多个元素。新的元素会排在最前面。
  • RPUSH [element …]:在列表尾部插入一个或多个元素。新的元素会排在最后面。

示例

LPUSH mylist "world"
LPUSH mylist "hello"
# mylist = ["hello", "world"]

RPUSH mylist "!"
# mylist = ["hello", "world", "!"]

mylist 不存在时,会先创建一个空列表再插入。

6.3.2.1.2 LPOP / RPOP
  • LPOP :移除并返回列表头部的第一个元素。
  • RPOP :移除并返回列表尾部的最后一个元素。

示例

LPOP mylist    # 返回 "hello",mylist 变为 ["world", "!"]
RPOP mylist    # 返回 "!", mylist 变为 ["world"]
6.3.2.1.3 LINSERT
  • LINSERT BEFORE|AFTER
  • 作用:在列表中的某个“现有元素”前或后,插入一个新元素。
  • 如果 pivot 不存在,命令返回 -1;如果 key 不存在或列表为空,返回 0。

示例

# mylist = ["hello", "world"]
LINSERT mylist BEFORE "world" "there"
# mylist = ["hello", "there", "world"]

6.3.2.2 获取与查看列表内容
6.3.2.2.1 LRANGE
  • LRANGE :返回列表在指定区间内的所有元素,包含左右边界。

  • 下标可以是正数,0 表示第一个元素,1 表示正数第二个,以此类推。

  • 下标可以为负数,-1 表示最后一个元素,-2 表示倒数第二个,以此类推。

  • 示例:

    # mylist = ["hello", "world", "!"]
    LRANGE mylist 0 -1   # 返回所有元素 ["hello","world","!"]
    LRANGE mylist 0 1    # 返回 ["hello","world"]
    
6.3.2.2.2 LINDEX
  • LINDEX :根据索引获取列表中的单个元素。

  • index 可为正数(从头开始 0 表示第一个)或负数(-1 表示最后一个)。

  • 示例:

    # mylist = ["hello", "world", "!"]
    LINDEX mylist 0   # 返回 "hello"
    LINDEX mylist -1  # 返回 "!"
    
6.3.2.2.3 LLEN
  • LLEN :返回列表的长度(元素个数)。

  • 若 key 不存在,则返回 0;若 key 的类型不是 list,则报错。

  • 示例:

    # mylist = ["hello","world","!"]
    LLEN mylist   # 返回 3
    

6.3.2.3 修改与截取
6.3.2.3.1 LSET
  • LSET :将列表中下标为 index 的元素设置为新的 element

  • 若索引越界或列表不存在,会返回错误。

  • 示例:

    # mylist = ["hello","world","!"]
    LSET mylist 1 "Redis"
    # mylist 变为 ["hello", "Redis", "!"]
    
6.3.2.3.2 LTRIM
  • LTRIM :对列表进行修剪,只保留指定区间的元素,区间外的全部删除。

  • 注意截取的是保留区间,而非删除区间。

  • 示例:

    # mylist = ["hello","Redis","!"]
    LTRIM mylist 0 1
    # 只保留下标 0 到 1 的元素,mylist = ["hello","Redis"]
    
6.3.2.3.3 LREM
  • LREM :按 count 的值移除指定元素:

    • count > 0:从头到尾,删除最多 count 个等于 element 的元素;
    • count < 0:从尾到头,删除最多 |count| 个等于 element 的元素;
    • count = 0:删除所有等于 element 的元素。
  • 示例:

    # mylist = ["hello","world","hello","Redis"]
    LREM mylist 1 "hello"
    # 只从头到尾删除 1 个 "hello" -> mylist = ["world","hello","Redis"]
    LREM mylist 0 "hello"
    # 再把剩余的 "hello" 都删掉 -> mylist = ["world","Redis"]
    

6.3.2.4 队列 & 阻塞操作
6.3.2.4.1 RPOPLPUSH
  • RPOPLPUSH :

    • source 列表的尾部弹出一个元素,并将该元素插入到 destination 列表的头部;
    • 返回弹出的元素值。
  • 可把它当作右弹左推的队列操作,适用于在多个队列之间传递消息。

  • 示例:

    # list1 = ["job1","job2","job3"]
    # list2 = ["x","y"]
    RPOPLPUSH list1 list2 
    # -> 返回 "job3"
    # list1 = ["job1","job2"]
    # list2 = ["job3","x","y"]
    
6.3.2.4.2 BLPOP / BRPOP
  • BLPOP [key2 …] :

    • 从左侧弹出第一个非空列表的头元素;如果所有列表都为空,则阻塞等待,直到有新元素插入或达到超时。
    • 如果超时,返回 nil
  • BRPOP 类似,但从右侧弹出。

  • 常用场景:消息队列或任务队列中,消费者阻塞等待任务的到来。

  • 示例:

    BLPOP list1 list2 30
    # 优先检查 list1 是否有元素,如果空,再检查 list2
    # 如果都为空,则最多阻塞 30 秒,期间如果有新元素插入便立即返回
    

    LPOP/RPOP:如果列表为空,会立即返回 nil,不会阻塞。

    BLPOP/BRPOP:如果列表为空,会阻塞等待(直到超时或有新数据),适合任务队列实时消费场景。


6.3.3 List 的使用场景

  1. 消息/任务队列
    • 生产者使用 LPUSH/RPUSH 将任务入队;
    • 消费者使用 BLPOP/BRPOP 阻塞式弹出任务进行处理;
    • 实现简单可靠的异步队列机制。
  2. 最新消息/日志列表
    • 使用 LPUSH 不断往头部插入最新的消息;
    • 配合 LTRIM mylist 0 99 等截取操作,保留最近 100 条;
    • 方便快速获取最新信息,同时限制长度防止无限膨胀。
  3. 数据分页或流式读取
    • 可以用 List 存储一批数据,使用 LRANGE 分段获取;
    • 但要注意大规模数据可能导致内存占用多,可分而治之或考虑其他结构。
  4. Rotating Queue
    • 使用 RPOPLPUSH 在多个队列之间转移数据,实现轮转或多队列消费等策略。

6.3.4 常见注意事项 & Best Practices

  1. 避免存放超大量元素
    • 虽然 List 可存数百万个元素,但一次 LRANGE 全量拉取也会占用大量内存和带宽。
    • 如果只需要最新部分数据,建议使用 LTRIM 保留一定区间,或按业务逻辑拆分存储。
  2. 使用阻塞命令需注意
    • BLPOP/BRPOP 在等待时会占用一个连接。如果连接数有限或网络环境复杂,需要注意不要同时有太多阻塞请求。
  3. List 并非随机访问结构
    • 只能通过索引下标进行有限的随机访问(LINDEX),或是依赖 LRANGE 全面遍历。
    • 若需要频繁随机读写中间元素或多字段操作,或许 Hash/Set 等其他结构更合适。
  4. 使用场景决定操作端
    • 对于队列场景,一般约定一端入队另一端出队,避免混用增删端,以形成一致的 FIFO(或 LIFO)顺序。
    • 对于日志场景,通常是 LPUSH + LTRIM,永远从头部插入最新消息。
  5. Watch out for LRANGE large ranges
    • 尽管 Redis 的 List 操作性能较优,但一次性获取几百万条也会造成阻塞和内存压力。
    • 生产环境中可以 LRANGE key start end 分段获取,或设计合理的数据拆分。

6.3.5 小结

List 在 Redis 中是一个强大且灵活的数据结构,可满足多种顺序场景需求,包括消息队列最新列表双端队列等。其核心操作可以概括为:

  1. 头/尾插入LPUSHRPUSH
  2. 头/尾弹出LPOPRPOP
  3. 读取区间LRANGE、查看长度:LLEN
  4. 阻塞弹出BLPOPBRPOP(常用于队列)
  5. 部分修改LSETLTRIMLREM
  6. 多队列转移RPOPLPUSH

在实际业务中,通过合理地选用这些操作并结合过期策略或其他结构(例如存储 ID 在 List 中、实际数据放在 Hash 或 String 里),可以构建高效的队列模型、实时日志系统或其他顺序相关的功能。

6.4 集合(Set)

在 Redis 中,Set(集合) 类型用于存储一组不重复的字符串元素,和我们熟悉的数学集合很类似。Redis 提供了丰富的命令来操作集合,包括添加、删除、判断成员以及集合之间的交并差运算等。下面将按照类似前面介绍的风格,详细讲解 Set 的基本概念、常用命令以及使用场景和最佳实践。


6.4.1 Redis Set 的存储与特点

  1. 内部结构
    • Redis 使用类似哈希表(Hash Table)来存储 Set 中的元素,每个元素都是一个独立的字符串,并且不允许重复
    • 如果尝试将相同的元素多次添加到同一个 Set,只有第一次会生效,后续重复添加不会改变集合状态,也不会报错。
  2. 数据特征
    • 元素无序:Redis 不保证插入顺序,也不能通过索引下标来获取或遍历。
    • 元素唯一:同一个元素在集合中只会出现一次。
    • 高效查找:Set 中的元素查找、添加、删除通常是 O(1) 平均复杂度(哈希表机制)。
  3. 常用场景
    • 存储好友列表、标签列表、不重复的元素集合;
    • 取交集、并集、差集,做共同好友、共同兴趣、共同标签分析等;
    • 去重功能(快速判断某元素是否已经存在);
    • 随机获取、抽奖(SRANDMEMBERSPOP)等场景。

6.4.2 Set 的常见操作命令

6.4.2.1 添加、删除、判断成员
6.4.2.1.1 SADD
  • 作用:向集合添加一个或多个元素,如果元素已存在则跳过,不影响集合。

  • 语法:

    SADD <key> <member1> [member2 ...]
    
  • 返回值:实际被添加进集合的新元素数量。

  • 示例:

    SADD myset "apple"
    SADD myset "banana" "orange"
    # 如果 myset 之前不存在,会创建并插入元素。
    # 如果 "apple" 已存在,再次插入不会重复。
    
6.4.2.1.2 SREM
  • 作用:删除集合中指定的一个或多个元素。

  • 语法:

    SREM <key> <member1> [member2 ...]
    
  • 返回值:被成功移除的元素数量。

  • 示例:

    SREM myset "banana"  # 从 myset 移除 "banana"
    
6.4.2.1.3 SISMEMBER
  • 作用:判断某个元素是否为集合成员。

  • 语法:

    SISMEMBER <key> <member>
    
  • 返回值:1 表示是集合成员,0 表示不是或不存在。

  • 示例:

    SISMEMBER myset "apple"
    # 若 myset 包含 "apple" 则返回 1,否则返回 0
    

6.4.2.2 获取、查看和随机操作
6.4.2.2.1 SMEMBERS
  • 作用:返回集合中的所有元素。

  • 示例:

    SMEMBERS myset
    # 比如返回 ["apple","orange","grape"]
    
  • 注意:

    • 如果集合元素非常多,一次性返回也会占用较多内存和带宽;
    • 生产环境中若集合规模庞大,可以考虑使用 SSCAN 命令分批次扫描(类似于 SCAN)。
6.4.2.2.2 SCARD
  • 作用:返回集合中的元素数量。

  • 示例:

    SCARD myset  # 返回例如 3
    
6.4.2.2.3 SPOP
  • 作用:随机移除并返回集合中的一个或多个元素(默认 1 个)。

  • 语法:

    SPOP <key> [count]
    
  • 示例:

    SPOP myset
    # 随机弹出一个元素,并将其从 myset 中移除。
    SPOP myset 2
    # 随机弹出 2 个元素,返回数组,并同时从集合删除它们。
    
  • 应用场景:抽奖、随机发放奖品、随机推荐等。

6.4.2.2.4 SRANDMEMBER
  • 作用:随机获取集合中的一个或多个元素,但不会将其从集合中删除(和 SPOP 区别在于是否删除)。

  • 语法:

    SRANDMEMBER <key> [count]
    
  • 示例:

    SRANDMEMBER myset
    # 随机返回一个元素,不会删除
    SRANDMEMBER myset 2
    # 随机返回 2 个元素(可能包含重复,Redis < 3.2 不同版本实现略有区别)
    

    在 Redis 3.2+,如果 count 为正,则返回不重复的元素;如果 count 为负,则可能返回重复的元素。可查阅对应版本文档以确认。


6.4.2.3 集合运算(交集、并集、差集)

Redis 提供了三个重要的集合运算命令:SINTERSUNIONSDIFF。配合这些命令,能快速做聚合分析,如“共同好友”、“共同兴趣标签”等场景。

6.4.2.3.1 SINTER / SINTERSTORE
  • SINTER:求一个或多个集合的交集。

    SINTER <key1> [key2 ... keyN]
    

    返回所有在这些集合中都存在的元素。

  • SINTERSTORE:将交集结果直接存储到另一个集合。

    SINTERSTORE <destination> <key1> [key2 ... keyN]
    
  • 示例:

    # set1 = {"apple","banana","orange"}
    # set2 = {"banana","orange","grape"}
    SINTER set1 set2
    # 返回 {"banana","orange"}
    
    SINTERSTORE set3 set1 set2
    # set3 将变成 {"banana","orange"}
    
6.4.2.3.2 SUNION / SUNIONSTORE
  • SUNION:求并集,返回任意集合里出现过的所有元素(去重)。

    SUNION <key1> [key2 ... keyN]
    
  • SUNIONSTORE:把并集结果存储到 destination

  • 示例:

    SUNION set1 set2
    # 返回 {"apple","banana","orange","grape"}
    SUNIONSTORE set4 set1 set2
    # set4 = {"apple","banana","orange","grape"}
    
6.4.2.3.3 SDIFF / SDIFFSTORE
  • SDIFF:求差集,返回在第一个集合里而不在其他集合中的元素。

    SDIFF <key1> [key2 ... keyN]
    
  • SDIFFSTORE:将差集结果存储到 destination

  • 示例:

    SDIFF set1 set2
    # 返回 {"apple"} (set1 - set2)
    SDIFFSTORE set5 set1 set2
    # set5 = {"apple"}
    

6.4.2.4 其他常用或补充命令
  1. MOVE (Redis 6.2 以前版本仅对 key 生效,对 Set 结构可以使用 SPOP + SADD 或 SRANDMEMBER + SADD 的方式来“移动”元素到另一个集合)。
  2. RENAME (对整个 key 进行重命名,不仅是 Set 类型才有,不过仅限 key 级别操作,不是移动元素)。
  3. SSCAN (类似 SCAN,可分批扫描大集合,语法:SSCAN key cursor [MATCH pattern] [COUNT count]),在集合规模很大的情况下比一次性 SMEMBERS 友好,避免阻塞。

6.4.3 使用场景

  1. 去重功能
    • 将数据插入 Set 并检查返回值或 SISMEMBER 判断重复。
    • 例如:用户签到(只需保存用户 ID,保证每个用户只签到一次)。
  2. 社交场景
    • 用户关注列表、好友列表都可以用 Set 表示;
    • 求共同好友:SINTER userA:friends userB:friends
    • 计算关注量:SCARD user:followers,判断是否互相关注:SISMEMBER user:followers otherUserID 等。
  3. 标签或权限管理
    • 每个用户的标签是一个 Set 或每个标签对应一个 Set。可通过集合运算来灵活地求交集或并集。
  4. 随机抽取、抽奖
    • SRANDMEMBER 随机选取;如需“抽完就移除”,可用 SPOP
  5. 去除重复消息 / 数据过滤
    • 在大量数据处理中,可把已处理过的标记存进 Set,若再次出现,表明是重复,跳过即可。

6.4.4 常见注意事项 & Best Practices

  1. Set 元素数量过多
    • SMEMBERS 会把所有元素一次性返回,可能导致内存和带宽的压力;
    • 建议使用 SSCAN 分批遍历;或在设计上拆分成多个小集合,避免单个 Set 过大。
  2. 大小限制
    • 单个 Set 理论上可存放多达数亿个元素(只要内存足够);但操作如交集并集也会消耗相应资源,需评估性能和内存。
  3. 不要滥用 Set 做有序需求
    • Set 是无序的,若需要按照分数、排名等有序排序,应该考虑使用 Sorted Set (ZSet)
    • 若需要按插入顺序遍历或常做 FIFO/LIFO,List 或 Stream 更适合。
  4. 集合运算开销
    • SINTER, SUNION, SDIFF 等运算在集合很大时也可能比较耗时,尤其当参与运算的集合规模都非常庞大时。
    • 若是在线查询,需要注意别在关键路径上做大规模集合运算,以免阻塞 Redis 主线程。可以在业务层缓存结果,或在后端服务分层处理。
  5. 过期策略
    • Redis 不支持对 Set 中的单个元素设置过期,但可以对整个 key 设置过期时间(EXPIRE key seconds)。
    • 若需要对其中的部分元素做时间控制,可能需要别的结构或自行管理。

6.4.5 小结

Set 在 Redis 中是一个非常基础且强大的数据结构,最显著的特点是:

  • 元素唯一无序快速判重
  • 提供交集、并集、差集等集合运算;
  • 适合去重、好友列表、标签管理、抽奖等常见场景。

其核心命令可以简要归纳为:

  1. 添加 / 删除:
    • SADD / SREM
  2. 查询:
    • SMEMBERS(全量),SISMEMBER(单一判断),SCARD(数量)
    • SSCAN(分批扫描)
  3. 随机:
    • SPOP(随机弹出并删除),SRANDMEMBER(随机获取不删除)
  4. 集合运算:
    • SINTER, SUNION, SDIFF 以及对应的 STORE 版本
  5. 其他:
    • SMOVE(已弃用命令,Redis 版本里没有 SMOVE 可以用 SPOP+SADD 替代),LTRIM 不适用于 Set,SSCAN 用于分批遍历等。

只要掌握这些命令和组合应用,就能应对大部分业务中需要“去重、随机、集合运算”的需求。在实际开发中,还要结合 Redis 的内存管理、过期策略及安全控制,来达成高性能又可靠的 Set 使用方案。

6.5 有序集合(ZSet)

在 Redis 中,有序集合(Sorted Set)是一种在集合基础上,为每个成员额外关联一个分数(score)的数据结构,常被缩写为 ZSet。它的特点是:元素不允许重复,但可以按照分数进行排序。Redis 提供了一系列针对有序集合的操作命令,方便我们在排行榜、排名查询、区间筛选、限时排序等场景中使用。


6.5.1 ZSet 的存储与特点

  1. 内部结构
    • Redis 使用一种结合了 跳表(Skiplist) + 哈希表(Hashtable) 的结构来存储 ZSet。
    • 跳表可以让我们在 O(log N) 的复杂度下进行有序操作(如范围查找、排序等),哈希表负责快速定位元素。
  2. 数据特征
    • 元素唯一:和 Set 一样,每个 ZSet 里元素(member)是唯一的。
    • 分数(score)可以重复:不同的 member 可以拥有相同的分数,但是 member 自身不能重复。
    • 按分数排序:Redis 可以根据 score 值对元素进行从小到大的排序,也可以支持倒序操作。
  3. 常用场景
    • 排行榜:比如按积分、热度、时间戳等排序;
    • 时间序列:score 代表时间戳,按时间顺序进行查询或截取;
    • 带权重的列表:以 score 作为优先级或权重值,随时能快速获取指定排名或分数区间的元素。

6.5.2 有序集合 ZSet 的常见命令

6.5.2.1 添加、更新、删除元素
6.5.2.1.1 ZADD
  • 作用:向有序集合中添加一个或多个 member,并设置它们的分数 score。若 member 已存在则更新其 score。

  • 语法:

    ZADD <key> [NX|XX] [CH] [INCR] <score1> <member1> [<score2> <member2> ...]
    
  • 常用选项:

    • NX:仅当 member 不存在时才添加;
    • XX:仅当 member 已存在时才更新;
    • CH:返回被修改的成员数量(包括 score 发生变动的情况);
    • INCR:对给定 member 的分数执行加法操作,而不是直接设置分数。
  • 示例:

    ZADD leaderboard 100 "playerA"
    ZADD leaderboard 200 "playerB" 150 "playerC"
    # 如果 playerA 已存在,则其 score 会被更新为 100
    
6.5.2.1.2 ZREM
  • 作用:从有序集合中删除指定元素,支持删除多个。

  • 语法:

    ZREM <key> <member1> [member2 ...]
    
  • 示例:

    ZREM leaderboard "playerC"
    # 删除 "playerC" 这个 member
    
6.5.2.1.3 ZINCRBY
  • 作用:对指定 member 的分数进行增量操作(加上给定的数值)。

  • 语法:

    ZINCRBY <key> <increment> <member>
    
  • 示例:

    ZINCRBY leaderboard 50 "playerA"
    # playerA 的分数在原来的基础上 +50
    
  • 注意:如果 member 不存在,会先以 score=0 创建该 member,再加上 increment。


6.5.2.2 按序查询、获取排名
6.5.2.2.1 ZRANGE / ZREVRANGE
  • 作用:按 score 升序(ZRANGE)或降序(ZREVRANGE)获取指定索引区间内的元素。

  • 语法:

    ZRANGE <key> <start> <stop> [WITHSCORES]
    ZREVRANGE <key> <start> <stop> [WITHSCORES]
    
  • 示例:

    ZRANGE leaderboard 0 -1        # 返回所有成员,按分数升序
    ZRANGE leaderboard 0 2 WITHSCORES  # 返回前三名,同时显示分数
    ZREVRANGE leaderboard 0 2 WITHSCORES # 返回分数最高的三名
    
  • 索引区间:

    • 0 表示第一个元素(最小或最大的视升序/降序而定),-1 表示最后一个元素;
    • 依次类推,startstop 可以是正负索引。
6.5.2.2.2 ZRANGEBYSCORE / ZREVRANGEBYSCORE
  • 作用:按 score 值进行范围查询,返回分数在 [min, max](或 [max, min])区间内的 member 列表。

  • 语法:

    ZRANGEBYSCORE <key> <min> <max> [WITHSCORES] [LIMIT offset count]
    ZREVRANGEBYSCORE <key> <max> <min> [WITHSCORES] [LIMIT offset count]
    
  • 说明:

    • ZRANGEBYSCORE leaderboard 100 200:获取 score 处于 100~200 范围内的所有成员,按升序。
    • ZREVRANGEBYSCORE leaderboard 200 100:同样的分数区间,但按分数从高到低排序。
    • LIMIT offset count:可对结果做分页或截取。
    • +inf-inf:可用于表示无穷大和无穷小;(100 表示大于 100 而不是大于等于(带括号表示排除)。
6.5.2.2.3 ZRANGEBYLEX / ZRANGEBYDICT(Redis 6.2+ 支持),与按 Member 字典序相关
  • 在某些场景下可以按 Member 的字典序,而非 score 进行范围查询;这是比较少用的操作,了解即可。
6.5.2.2.4 ZRANK / ZREVRANK
  • 作用:获取某个 member 在有序集合中的排名(下标)。

  • 语法:

    ZRANK <key> <member>
    ZREVRANK <key> <member>
    
  • 返回值:

    • 若 member 存在,返回其以 0 开始的排名;
    • 若不存在,返回 nil
  • 举例:

    ZRANK leaderboard "playerA"
    # 返回 playerA 在升序下的名次(0 表示 score 最小的那个,数值越大表示分数更高的排名)
    ZREVRANK leaderboard "playerA"
    # 返回 playerA 在降序下的名次(0 表示分数最高的)
    
6.5.2.2.5 ZCARD / ZCOUNT
  • ZCARD:返回有序集合的基数(元素总数)。

    ZCARD <key>
    
  • ZCOUNT:统计指定 score 区间内的元素数量。

    ZCOUNT <key> <min> <max>
    

    例如:ZCOUNT leaderboard 100 200 返回分数在 [100,200] 的元素数。


6.5.2.3 删除和范围操作
6.5.2.3.1 ZREM、ZREM_RANGE_BY_SCORE、ZREM_RANGE_BY_RANK
  • ZREM:前面提过,按 member 删除指定元素。

  • ZREMRANGEBYSCORE:删除score在给定区间内的所有元素。

    ZREMRANGEBYSCORE <key> <min> <max>
    # 如 ZREMRANGEBYSCORE leaderboard 0 100  -> 删除 score <= 100 的所有元素
    
  • ZREMRANGEBYRANK:按排名下标区间删除元素。

    ZREMRANGEBYRANK <key> <start> <stop>
    # 如 ZREMRANGEBYRANK leaderboard 0 9  -> 删除排在前 10 名的元素
    
6.5.2.3.2 ZINTERSTORE / ZUNIONSTORE / ZDIFFSTORE (Redis 6.2+)

和普通 Set 类似,Redis 也提供了对 ZSet 的交集、并集、差集运算,但是会对 score 做相应的合并或计算。常见的是 ZINTERSTOREZUNIONSTORE

  • ZINTERSTORE:计算给定的一个或多个有序集合的交集,将结果存储到新集合中,并根据每个元素在各集合中的 score 值执行相应的加权或求和。
  • ZUNIONSTORE:并集操作,score 同样可以加权或求和。

示例:

ZADD zset1 1 "a" 2 "b"
ZADD zset2 2 "a" 3 "c"

ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3
# 这里 2 表示有 2 个集合,WEIGHTS 2 3 分别表示 zset1 的 score 乘以2, zset2 的 score 乘以3
# "a" 在 zset1 score=1, zset2 score=2
# 计算交集则 "a" 存在于两个集合中,score = 1*2 + 2*3 = 2 + 6 = 8
# "b" 只在 zset1, "c" 只在 zset2,故它们不在交集里
# out = {"a":8}

注意:Redis 7.0+ 也引入了 ZDIFFSTORE(差集),不过在实际中用得相对少一些。


6.5.3 常见使用场景

  1. 排行榜
    • 例如游戏积分榜、电商热卖榜、点赞数排行等;
    • 可以通过 ZADD 不断更新用户的分数,然后使用 ZREVRANGEZREVRANK 获取某人排行或前 N 名;
    • ZINCRBY 用于积分累加,ZRANK / ZREVRANK 用于查询当前名次。
  2. 实时排序
    • 每次用户操作(点赞、查看、消费等)都可以更新其相关分数;
    • 通过按分数的正序或倒序可实现某些实时刷新排名的功能,比如直播间送礼排行榜、产品热度排名等。
  3. 延时队列 / 时间调度
    • 把 future task 存成 (score=执行时间, member=任务ID),利用 ZRANGEBYSCORE 查询到期任务,进行处理后删除;
    • 可以替代一些简单的定时任务逻辑。
  4. 时间序列
    • 以时间戳作为 score,member 是事件或数据 ID;
    • 使用 ZRANGEBYSCOREZREVRANGEBYSCORE 在一定时间范围内获取数据。

6.5.4 注意事项 & Best Practices

  1. score 的精度
    • Redis score 本质是一个双精度浮点数(double),尽量避免过于依赖小数精度(有时可能出现浮点误差)。
    • 如果分数是整数,推荐直接用整数(如 123456)或用一个长整型来表达时间戳/分值以减小误差。
  2. ZADD 频繁更新
    • 更新分数只需再次执行 ZADD <key> <score> <member>,若 member 存在则覆盖;或 ZINCRBY 增量更新。
    • 频繁更新大规模 ZSet 时要注意是否会带来内存或操作耗时,毕竟跳表需要维护顺序。
  3. 排行榜前 N vs. 全量遍历
    • ZRANGE key 0 9ZREVRANGE key 0 9:获取前 10 名或后 10 名非常高效;
    • 如果要获取所有成员(百万级)并排序,返回时就会产生大流量,占用 Redis 线程,对性能影响较大。
    • 可以考虑分页或分段查询:ZREVRANGE key start end,或者用 ZRANGEBYSCORE + LIMIT 进行分页,不过也要注意性能。
  4. 删除操作
    • ZREM 只针对指定 member;
    • 对于区间删除(score 范围或排名范围)可使用 ZREMRANGEBYSCOREZREMRANGEBYRANK
    • 这些批量删除操作会在一定程度上阻塞 Redis 主线程,规模很大时也需注意分拆或异步管理。
  5. 内存占用
    • ZSet 同时维持哈希表 + 跳表结构,会比普通 Set 消耗更多内存;
    • 大量 ZSet 时需考虑内存大小、持久化和主从复制对性能的影响。
  6. 过期策略
    • Redis 不支持对 ZSet 的单个 member设置过期,只能对整个 key 设置过期;
    • 如果需要对某些分数或元素做定期清理,可以定时执行 ZREMRANGEBYSCORE 或业务逻辑判断后删除。

6.5.5 小结

有序集合(ZSet)是 Redis 中一个非常重要的高级数据结构,结合了“集合去重”与“分数排序”的优势,能在很多排行榜调度排序场景下大显身手。

其核心命令可简要概括为:

  1. 增、删、改、查
    • ZADD:添加/更新元素
    • ZREM:删除指定元素
    • ZINCRBY:分数自增
    • ZRANK / ZREVRANK:查看排名(索引位置)
    • ZCARD:ZSet 大小
  2. 获取数据
    • ZRANGE / ZREVRANGE:按索引区间获取排序结果
    • ZRANGEBYSCORE / ZREVRANGEBYSCORE:按分数区间获取
    • ZCOUNT:分数区间计数
  3. 范围删除
    • ZREMRANGEBYSCOREZREMRANGEBYRANK
  4. 交并集操作
    • ZINTERSTORE / ZUNIONSTORE:对多个 ZSet 做交集 / 并集,score 可以加权合并

掌握了这些命令及应用思路,就能灵活地在Redis中构建实时排行榜、延时队列、时间序列、计分系统等功能。配合其他类型(String、Hash、Set 等)的混合使用,可以构建出更丰富、更高效的 Redis 数据模型来满足不同业务需求。

6.7 时间复杂度

在 Redis 中(以及更多计算机科学领域),我们常用 Big-O 表示法(如 O(1)、O(log N)、O(N) 等)来衡量算法或操作的时间复杂度,它代表了输入规模增大时,操作耗时的增长趋势。下面我们分几个层面来理解:

6.7.1 什么是 O(1) 和 O(log N)?

  1. O(1)(常数时间):
    • 表示无论数据规模 N N N 多大,操作所需的时间都近似于一个常数,与 N N N 无关。
    • 例如,用哈希表(Hash Table)查询一个元素的平均时间复杂度是 O(1):查找过程可在常数时间内完成。
    • 当然这是平均情况。哈希表在最坏情况下(冲突严重)可能退化到 O(N);但在良好散列和低冲突的场景下可视为 O(1)。
  2. O(log N)(对数时间):
    • 表示操作所需时间随数据规模 N N N 的增加按照对数规律增长。对数函数增长速度比线性函数慢得多,所以当 N N N 很大时,O(log N) 算法仍然比较高效。
    • 例如,用平衡二叉搜索树跳表(Skiplist)B+树等结构进行插入、查找、删除,通常都能在 O(log N) 时间内完成。
    • Redis 的**有序集合(ZSet)**内部默认用“跳表 + 哈希表”来存储:
      • 跳表部分提供按分数排序、区间查询等有序操作的能力(O(log N));
      • 哈希表部分用来快速定位元素是否存在(O(1) 平均情况)。

6.7.2 为何跳表的有序操作是 O(log N)?

跳表(Skiplist) 是一种随机化的数据结构,结构上类似多层链表,每一层都跳过一些节点,以加速查找、插入和删除。

  • 在理想状态下,每向上走一层,节点数量就减少一半;
  • 因此要找到某个元素或一个分数区间时,只需从高层往低层“逐渐逼近”,访问的节点数大约是 log ⁡ 2 N \log_2{N} log2N量级;
  • 整体上插入、删除、查找等操作都能保持在 O(log N) 水平。

举个形象的例子:

  • 在一个排好序的很长的链表中,如果只能一格一格往后找,那么查找就是 O(N)。
  • 但如果你先在“顶层链表”里,隔几个节点就有一个索引,可以快速跳过若干节点;再往下一层,更密集的索引帮助你再定位……如此层层逼近目标,就能极大缩短“跳”的次数,这就是为什么能够得到 O(log N) 的时间复杂度。

6.7.3 为何哈希表的操作是 O(1)(平均情况)?

哈希表(Hash Table) 通过哈希函数把键(Key)映射到一个桶(bucket)或槽(slot)里,理论上可以直接通过哈希函数一次定位到存储位置。

  • 在冲突不多、散列均匀的情况下,查找、插入、删除操作只需要常数步(O(1))就能完成;
  • 这是因为无论有多少数据(N 很大),哈希函数的计算和一次到位的寻址,都不太依赖 N 的规模;
  • 不过,如果哈希冲突非常多或散列不均,最坏情况可能要在某个桶里的链表挨个遍历(O(N)),但平均来说,好的哈希设计能让大部分操作维持在 O(1)。

6.7.4 Redis 中具体体现

  1. ZSet 的跳表部分
    • 当我们需要“根据分数范围取某些元素”或“得到某个分数在有序集合中的排名”时,就需要依赖有序结构(跳表)来进行区间遍历或排名查找,复杂度约为 O(log N)(有序查找、插入、删除)或 O(log N + M)(找出 M 个结果)。
    • 例如命令 ZRANGEBYSCOREZREVRANGEBYSCORE 就是利用跳表快速定位到指定分数区间的起始位置,然后顺序扫描出结果。
  2. ZSet 的哈希表部分
    • 当我们执行 “给某个元素设置分数” 或 “查一个元素在不在这个 ZSet 里” 时,可先通过哈希表在 O(1) 平均时间内找到该元素;如果更新分数,则跳到跳表对应位置更新节点;
    • 这样就兼顾了快速定位(哈希表)和有序操作(跳表)。
  3. Hash(哈希)类型SetHashMap 等其他结构:
    • 也都基于哈希表,插入、查询、删除的平均复杂度是 O(1)。
  4. ListStream
    • 大部分情况下是 O(1) 地在头尾插入或弹出(LPUSH、LPOP、RPUSH、RPOP),但随机访问或索引查找时就需要 O(N)(链表或类似结构需遍历)。

6.7.5 如何理解这些复杂度?

  • Big-O 并非绝对耗时:它只描述增长趋势,并不意味着一旦是 O(log N) 就一定比 O(1) 慢,或者 O(N) 就一定比 O(log N) 慢;在小规模数据时,常数和常数因子也会很重要。
  • 平均 vs. 最坏:哈希表的 O(1) 通常指平均复杂度;跳表/树型结构的 O(log N) 指大多数情况下都会如此,但在极端情况下也可能退化。
  • 选择合适的数据结构:如果你需要有序查询范围操作排名等功能,就需要跳表/树这样支持排序的结构(ZSet)。如果只是key -> value 快速查询(比如 Set/Hash),哈希表就足够好,简单粗暴且平均 O(1)。

6.7.6 小结

  • O(1):操作耗时不随着数据规模变化而显著增长,是平均情况下哈希表操作最有吸引力的特征。
  • O(log N):跳表、平衡树等结构能实现高效的有序操作。随着 NN 增大,耗时按照 级别增长,比 O(N) 好很多,但比 O(1) 大。
  • Redis ZSet 内部“跳表 + 哈希表”的组合可以理解为:
    • 哈希表提供快速定位(O(1) 平均)
    • 跳表提供有序操作(O(log N))
    • 二者互补,使得 ZSet 在“既需要有序,又要能快速查某元素”的场景中表现优异。

这些就是常见的时间复杂度概念和在 Redis 内部的具体落地。当在应用层设计数据结构或分析 Redis 性能时,理解这些复杂度有助于我们选择合适的操作提前预估可能的开销

6.7 消息队列

在消息队列或者消息传递领域,一般会提到 两种常见的消息模式

  1. 生产者-消费者(Producer-Consumer)
  2. 发布-订阅(Publisher-Subscriber,Pub/Sub)

6.7.1 生产者-消费者(Producer-Consumer)

6.7.1.1 模式概念
  • 生产者(Producer):负责发送消息或任务到队列(Queue)中。
  • 消费者(Consumer):从队列中取出消息或任务并进行处理。

常见的形态是“消息队列”:生产者把消息投递到队列,消费者从队列里取出并消费,最终队列中存储的是待处理的消息。

6.7.1.2 工作流程
  1. 生产者将消息发送到消息队列;
  2. 消息队列负责临时存储这些消息,充当缓冲区;
  3. 消费者从队列中轮询或阻塞获取消息(通常是 FIFO,先进先出 first in,first out),然后执行处理;
  4. 处理完毕后,消息即被标记为消费完成或从队列移除。
6.7.1.3 主要特点
  1. 解耦:生产者和消费者可以异步运行,生产者发送后就可以继续处理其他任务,不用等待消费者处理完成。
  2. 缓冲:消息队列在生产者和消费者之间提供了一个缓冲池,能应对突发的高并发或流量不均衡,避免直接把压力传递给消费者。
  3. 可靠性(可选):大多数消息队列中间件(RabbitMQ、Kafka、RocketMQ 等)会提供持久化、确认(ACK)机制来保证消息不丢失。
  4. 竞争消费:通常一个队列可被多个消费者并行消费,每条消息只会被其中一个消费者处理一次,适合“任务分发”或“分布式工作池”模式。
6.7.1.4 典型应用场景
  • 异步任务处理:如发送短信、邮件、图像处理,把耗时任务扔进队列中,再由工作进程去处理。
  • 削峰填谷:电商大促时,订单处理可先进入队列以防止瞬时并发过高;消费者慢慢消化队列里积压的订单。
  • 分布式系统间解耦:生产者和消费者可以各自升级或故障重启,而不影响对方的逻辑。

6.7.2 发布-订阅(Pub/Sub, Publisher-Subscriber)

6.7.2.1 模式概念
  • 发布者(Publisher):向某个主题或频道(Topic/Channel)发布消息。
  • 订阅者(Subscriber):对指定主题或频道进行订阅,一旦有新的消息发布,就会被推送接收到。

可以把它理解为“广播”式:所有订阅了某个频道的订阅者,在发布者推送消息后,都会各自收到一份消息。

6.7.2.2 工作流程
  1. 订阅者先声明对某个主题/频道感兴趣,并订阅之;
  2. 发布者向该主题/频道发布消息;
  3. 消息中间件负责将消息同时分发给所有已订阅该主题/频道的消费者;
  4. 每个订阅者都能够各自收到该条消息,并进行自己的处理。
6.7.2.3 主要特点
  1. 一对多:一个发布者发布的一条消息可以同时被多个订阅者接收,这与“生产者-消费者”中单条消息只被一个消费者处理截然不同。
  2. 实时推送:当发布者有新消息时,订阅者能立即收到;
  3. 不保证存储(取决于实现):如果某个订阅者离线或没有及时消费,消息可能就错过了(除非使用带持久化/主题回溯的消息系统,例如 Kafka、Redis Streams 也有一定存储能力,但传统“Redis Pub/Sub”则不存储历史消息)。
  4. 松耦合:发布者不需要知道订阅者是谁,订阅者也不需要知道具体是哪一个发布者发的消息。它们只通过主题来“对接”。
6.7.2.4 典型应用场景
  • 广播通知:系统中某个事件需要同时通知多个服务或模块。
  • 实时推送:聊天房间、直播间弹幕、股票行情等,需要向所有在线订阅者推送最新数据。
  • 异步事件总线:在微服务架构中,一些服务可能只需要监听特定事件来触发自己的逻辑,发布者只管发布事件,多个订阅者各自执行不同流程。

6.7.3 对比与总结

维度生产者-消费者(消息队列)发布-订阅(Pub/Sub)
消息传递关系一对一:队列中的一条消息只会被一个消费者处理一对多:一个消息可同时被多个订阅者接收
消息存储通常会存储在队列里,若消费者未及时消费,消息也不会丢取决于系统实现,传统 Pub/Sub 通常不存储历史消息
消费模式消费者竞争消费同一个队列;可实现分布式工作池、并行处理所有订阅该主题的订阅者都能各自收到一份消息,并行处理
适用场景异步处理、队列缓冲、削峰填谷、解耦分布式任务实时广播通知、事件订阅、多客户端同时接收相同消息
消息确认 & 重试通常具备ACK/重试机制,确保消息被正确处理传统简单 Pub/Sub 一般无重试/确认,消息分发后即丢
核心关注点可靠消费,队列实现排队、缓冲;保证单条消息只被处理一次消息广播,多消费者同时收到,不保证是否被落地存储

6.7.4 延伸与选择

  1. 生产者-消费者主要关注:
    • 消息排队可靠处理、可控的并发消费、处理完消息后就出队
    • 适用于工作队列、任务处理、订单处理、分布式流水线等。
  2. 发布-订阅主要关注:
    • 分发:一次消息,多个订阅者同时收到;
    • 广播多对多模式;
    • 适用于实时性推送、广播通知、事件总线等。
  3. 组合使用
    • 有时一个系统中既需要排队处理,又需要广播通知,则会在不同子模块选择不同模式,或者使用支持多种模式的消息中间件(如 Kafka 既支持“订阅分区”,也能做分布式队列;RabbitMQ 可用交换器+队列配合实现多播等)。
  4. 不同系统实现
    • 如 RabbitMQ、ActiveMQ、RocketMQ 更偏向队列模型(生产者-消费者),但也能通过不同的 exchange 类型(主题交换、fanout 等)来实现类似发布-订阅;
    • Kafka 虽然更多用于订阅(消费者组对 Topic 的订阅),但本质也能做队列(同一个分区内仍是单消费者消费)。
    • Redis 的 Pub/Sub 属于较简单的纯广播,不存储历史消息,而 Redis Streams 则提供了可做队列或订阅的能力,也支持 Consumer Group。

6.7.5 小结

  • 生产者-消费者(消息队列模型):
    • 核心诉求:每条消息由唯一消费者处理,注重可靠消费、解耦、延迟容忍,可以做异步处理、削峰填谷。
  • 发布-订阅(Pub/Sub 模型):
    • 核心诉求:一次消息可同时被多个订阅者接收,注重实时广播、事件分发;不一定有存储与确认机制。

两种模式各有适用场景,在实际项目中往往结合使用,或者在同一个消息系统内使用不同的交换策略实现不同效果。选择哪种模式取决于消息要给多少人接收、是否需要队列缓冲、是否要落盘与确认、是否需要多对多的实时通信等业务需求。

6.7.6 Redis 实现消息队列

在许多场景下,Redis 经常被用作轻量级的消息队列(Message Queue),实现简单的生产者-消费者模型或发布订阅功能。根据 Redis 版本和需求的不同,常见的实现方式主要有:

  1. 使用 List(列表)+ 阻塞弹出:最传统的方式,利用 LPUSH/RPUSHBLPOP/BRPOP 组合,构建简单队列。
  2. 使用 Redis Pub/Sub:更适合实时广播式的消息分发,但消息不能持久化。
  3. 使用 Redis Streams(5.0+):官方推荐的现代化消息流结构,支持持久化、有序存储、消费组、ACK 等功能,更适合复杂的消息队列场景。

下面依次介绍各方案的特点与适用场景,并总结常见注意事项。


6.7.6.1 使用 List 构建简单队列
6.7.6.1.1 基本原理
  • 生产者:在列表尾部或头部插入消息
    • 常用 LPUSH queue "message"RPUSH queue "message"
  • 消费者:使用 阻塞命令 BLPOPBRPOP 来弹出消息
    • 当队列为空时,消费者阻塞等待,直到超时(若指定)或有新消息入队

这样就构成了一个FIFO(先进先出)或类似队列的模型。例如:

# 生产者往右端插入
RPUSH task_queue "task1"
RPUSH task_queue "task2"

# 消费者阻塞等待
BLPOP task_queue 0
# 如果为空,则阻塞等待;有数据就立刻返回
  • 如果需要严格的 FIFO,可约定同一端入队,另一端出队,比如:
    • 生产者使用 RPUSH
    • 消费者使用 BLPOP
    • 数据会从右端进,左端出,实现最常见的队列顺序。
6.7.6.1.2 特点与适用场景
  • 优点
    • 实现简单,命令直观;
    • 无需额外插件或复杂配置,适合小规模队列或临时需求;
    • BLPOP/BRPOP阻塞模式,避免频繁轮询,实时性好。
  • 缺点
    • 无内置消息确认(ACK)重试消费组等机制;如果消费者在处理后崩溃,消息就丢失了(除非自行实现“处理后写回”流程)。
    • 不便于多消费者并行处理同一个队列里的同一条消息,需要自己设计逻辑避免重复消费。
    • 没有持久化机制时,Redis 重启后现有队列内容是否能够完整恢复,需结合 AOF/RDB 机制进行持久化;但如果严格消息可靠性要求很高,Redis 并不是专业的“持久化消息队列”解决方案(可考虑 RabbitMQ、Kafka)。

这种方式非常适合简单任务队列小规模异步处理

  • 例如把用户的某些操作异步处理掉,不要求强一致或复杂消息确认;
  • 如果对消息丢失容忍度高或可以接受自己做补偿机制。

6.7.6.2 使用 Redis Pub/Sub
6.7.6.2.1 基本原理
  • Redis 的 Publish/Subscribe功能实现了发布和订阅模型:
    • 发布者通过 PUBLISH channel message 向指定频道发送消息;
    • 订阅者使用 SUBSCRIBE channel 监听该频道并实时接收消息;
  • 这是一种广播式一对多的消息分发:只要多个订阅者都订阅了同一个 channel,它们都会收到发布者的消息副本。
6.7.6.2.2 特点与应用场景
  • 优点
    • 实时性好,消息一发送,所有订阅者都能立即收到;
    • 适合推送通知类场景,比如聊天室、服务器实时事件广播等。
  • 缺点
    • 无持久化:消息不是存储在 Redis 中,而是一经发布即向所有订阅者推送,如果当时订阅者掉线或来不及处理,就会丢失。
    • 无法回放历史消息,也没有内置的队列特性(排队、确认、重试)等。

因此,Redis Pub/Sub 不大适合“传统队列”需求,更像实时广播,用来做直播聊天室、在线游戏通知、事件总线等。在需要可靠消费的场景中则不合适。


6.7.6.3 使用 Redis Streams(5.0+)
6.7.6.3.1 Streams 简介
  • Redis Streams 是 Redis 5.0 引入的新数据结构,专为消息流(log stream)设计。
  • 它既可以看作有序存储的消息队列,也可像 Kafka 一样保留历史消息,客户端可以从指定位置开始读取、重复读取并标记消费进度。
6.7.6.3.2 关键概念
  1. Stream:按时间或插入顺序将消息(Entry)排成有序队列,每条消息带有一个 ID(类似时间戳+序列号)。
  2. XADD:生产者向 Stream 中添加消息(XADD mystream * field1 value1 field2 value2 ...)。
  3. XREAD / XREADGROUP:消费者读取消息,可指定从哪个 ID 开始读,实现增量消费
  4. Consumer Group(消费者组):允许多个消费者分组消费同一个 Stream 中的数据;Redis 会自动做分配,并支持消息ACK未确认消息管理,适合多消费者并行且保证消息不会重复消费或丢失太多。
  5. XACK / XPENDING:通过ACK机制,只有消费者成功处理后才算消息被确认,从而减少消息丢失。未确认的消息可以查看和重新分配。
6.7.6.3.3 Streams 使用示例(简化)
# 生产者
XADD mystream * sensor-id 1 temperature 30.2

# 消费者(若非消费者组)
XREAD COUNT 10 STREAMS mystream 0
# 读取 mystream 中的全部消息(从 id=0 开始)

# 创建消费者组
XGROUP CREATE mystream mygroup $ MKSTREAM
# 消费者在该组内读取
XREADGROUP GROUP mygroup consumer1 COUNT 10 STREAMS mystream >
# 读取从上次读取位置以后的新消息

# 消费后,手动确认
XACK mystream mygroup <message-id1> <message-id2> ...
# 标记这些消息已成功处理
6.7.6.3.4 优势与特点
  1. 可靠性:通过消费者组和 ACK 机制,避免消费者崩溃导致消息永久丢失。
  2. 多消费者并发:同一个消费者组可以有多个消费者竞争消费消息,Redis 会自动分配未被其它消费者领取的消息给它们。
  3. 消息回溯:默认消息不会自动丢弃,可以根据需要保留一定长度或时间周期,以便后续读重或做故障回放。也可通过 MAXLEN 配置限制流长度。
6.7.6.3.5 适用场景
  • 分布式系统中需要可靠消息队列:既要并发消费,又要防止某台消费者挂了导致消息丢失;
  • 需要历史消息回溯(至少在一定范围内),比如日志、事件存档等;
  • 相对大规模、严谨的异步消息处理,比 List 方案更“专业”,且支持高级特性。

6.7.6.4 选择哪种方式?
  1. 最简单:List + 阻塞弹出
    • 适合轻量级的单一队列,不需要复杂确认或重复消费管理;
    • 代码简单易上手,如果消息丢了问题不大(或能用自定义补偿),就行。
  2. Pub/Sub
    • 适合实时推送一对多通知场景,不做持久化,消息即时投递后即丢弃;
    • 不适合需要存储与可靠消费的排队场景。
  3. Redis Streams
    • 适合对消息的可靠性、持久性、多消费者管理有一定要求的场景,提供近似 MQ 的能力;
    • 结构更复杂,需要掌握 XREADGROUP, XACK, XPENDING, XCLAIM 等命令;
    • 如果需要专业、超大规模的队列并发(数十万~百万 QPS 级别)或高级分布式事务,可考虑专业消息系统(Kafka、RabbitMQ、RocketMQ 等);但 Redis Streams 对于中等规模的可靠队列、实时处理依然非常合适。

6.7.6.5 常见注意事项
  1. Redis 单线程特性
    • 所有命令都在单线程内执行,若消息量巨大、消费者或生产者并发极高,可能造成阻塞。
    • Redis 6.0 引入多 I/O 线程,但命令执行依然是单线程,需根据实际场景评估性能。
  2. 持久化
    • 如果你希望消息在 Redis 重启后保留,需要启用 RDB / AOF 持久化,但也得考虑性能开销数据一致性
    • 如果需要100%不丢消息,需要确保 Redis 持久化策略和硬件环境都可靠。
  3. 内存限制
    • Redis 主要在内存中操作,如果消息积压量非常大,就可能导致内存压力,或大量写入也影响性能。可开启 eviction 策略,但那会导致可能的消息丢失。
  4. 消费端处理失败重试
    • 简单 List 模型下,若处理失败,需要手动将消息再 push 回队列,或记录日志后再做补偿。
    • 使用 Streams + 消费者组可以重新分配未确认的消息(XPENDING, XCLAIM),从而实现一定程度的重试、死信队列等机制。
  5. 吞吐 vs. 功能
    • Redis 消息队列方式更倾向于“轻量、高速、短期缓存/队列”场景;不提供像专业 MQ 那样完善的路由、扩展、跨节点分布功能。
    • 如果需求简单、无需过度扩展,Redis 够用且非常快;若是大规模企业级 MQ 场景或需要严格事务和更灵活的路由策略,可考虑 RabbitMQ、Kafka、RocketMQ 等。

6.7.6.6 小结

Redis 作为内存数据库,本身并不是专业的消息中间件,但它速度快、命令简单,对很多轻量级消息队列实时通知场景来说很实用:

  1. List + BLPOP / BRPOP:最经典的简易队列方案。适合单队列、简单生产-消费、对可靠性无特殊要求的场景。
  2. Pub/Sub:适合实时广播的一对多订阅,不保留历史消息,也无确认机制。
  3. Redis Streams:Redis 5.0+ 官方提供的“日志流”数据结构,支持有序存储、消费者组、ACK 确认、回溯消息等,能满足中等规模、较高可靠性要求的消息队列场景。

根据业务需求,选择合适的模式和实现方式,才能在性能与可靠性之间找到平衡。对简单场景,List 队列最易上手;若需要相对专业的队列功能又想依赖 Redis生态,则 Streams 是更好的选择;若需要高可靠性和大规模分布式集群,可进一步研究专业MQ或结合 Redis 与其他系统。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2279536.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【自动驾驶BEV感知之Transformer】

欢迎大家关注我的B站&#xff1a; 偷吃薯片的Zheng同学的个人空间-偷吃薯片的Zheng同学个人主页-哔哩哔哩视频 (bilibili.com) 本文为深蓝学院《BEV感知理论与实践》 的学习笔记 以图书馆看书举例 query&#xff1a;查询&#xff0c;感兴趣的东西 Key&#xff1a;索引&…

http转化为https生成自签名证书

背景 项目开发阶段前后交互采用http协议&#xff0c;演示环境采用htttps协议 &#xff0c;此处为个人demo案例 组件 后端&#xff1a;springBoot 前端&#xff1a;vue web 服务&#xff1a;tomcat 部署环境&#xff1a;linux 生成自签名证书 创建目录 存储证书位置 # mkdir -p…

AAPM:基于大型语言模型代理的资产定价模型,夏普比率提高9.6%

“AAPM: Large Language Model Agent-based Asset Pricing Models” 论文地址&#xff1a;https://arxiv.org/pdf/2409.17266v1 Github地址&#xff1a;https://github.com/chengjunyan1/AAPM 摘要 这篇文章介绍了一种利用LLM代理的资产定价模型&#xff08;AAPM&#xff09;…

Unity HybridCLR Settings热更设置

需要热更的程序集放到 热更新Assembly Definitions中。 记得补充元数据AOT dlls&#xff08;在热更新程序集的就不用补充元数据了&#xff09; 打包完成后遇到TypeLoadException: could not load type 时 可能需要在Assets/link.xml中增加对应的设置 <assembly fullname&q…

PyTest自学-认识PyTest

1 PyTest自学-认识PyTest 1.1 PyTest可以用来做什么&#xff1f; PyTest是一个自动化测试框架&#xff0c;支持单元测试和功能测试&#xff0c;有丰富的插件&#xff0c;如&#xff0c;pytest-selemium, pytest-html等。 1.2 安装pytest 使用pip install -U pytest。 1.3 py…

Hive SQL必刷练习题:留存率问题

首次登录算作当天新增&#xff0c;第二天也登录了算作一日留存。可以理解为&#xff0c;在10月1号登陆了。在10月2号也登陆了&#xff0c;那这个人就可以算是在1号留存 今日留存率 &#xff08;今日登录且明天也登录的用户数&#xff09; / 今日登录的总用户数 * 100% 解决思…

使用 Parcel 和 NPM 脚本进行打包

使用 Parcel 和 NPM 脚本进行打包 Parcel Parcel 是一个零配置的网页应用程序打包工具&#xff0c;主要用于快速构建现代 JavaScript 应用。 我们可以使用npm直接安装它 npm install --save-dev parcel //这将把 Parcel 添加到 devDependencies 中&#xff0c;表明它是一个…

数据结构——堆(介绍,堆的基本操作、堆排序)

我是一个计算机专业研0的学生卡蒙Camel&#x1f42b;&#x1f42b;&#x1f42b;&#xff08;刚保研&#xff09; 记录每天学习过程&#xff08;主要学习Java、python、人工智能&#xff09;&#xff0c;总结知识点&#xff08;内容来自&#xff1a;自我总结网上借鉴&#xff0…

要获取本地的公网 IP 地址(curl ifconfig.me)

文章目录 通过命令行查询&#xff08;适用于 Linux/Mac/Windows&#xff09;Linux/MacWindows 注意事项 要获取本地的公网 IP 地址&#xff0c;可以通过以下简单的方法&#xff1a; 通过命令行查询&#xff08;适用于 Linux/Mac/Windows&#xff09; Linux/Mac 打开终端。输入…

【博客之星】2024年度个人成长、强化学习算法领域总结

&#x1f4e2;在2025年初&#xff0c;非常荣幸能通过审核进入到《2024年度CSDN博客之星总评选》TOP300的年度评选中&#xff0c;排名40。这还是第一次来到这个阶段&#xff0c;作为一名博士研究生&#xff0c;还是备受鼓舞的。在这里我将以回顾的方式讲述一下这一年在CSDN中走过…

GoLang教程004:流程控制和if语句介绍

文章目录 3、流程控制3.1 流程控制的作用3.2 控制语句的分类3.3 if分支3.3.1 单分支3.3.2 多分支3.3.3 双分支 3、流程控制 3.1 流程控制的作用 流程控制的作用&#xff1a;流程控制语句是用来控制程序中各语句执行顺序的语句&#xff0c;可以把语句组合成能完成一定功能的小…

【Web】2025-SUCTF个人wp

目录 SU_blog SU_photogallery SU_POP SU_blog 先是注册功能覆盖admin账号 以admin身份登录&#xff0c;拿到读文件的权限 ./article?filearticles/..././..././..././..././..././..././etc/passwd ./article?filearticles/..././..././..././..././..././..././proc/1…

uniApp开通uniPush1.0个推,SpringBoot集成uniPush1.0个推

uniApp开通unipush1.0个推&#xff0c;SpringBoot程序集成 一、APP开通unipush1.0个推(商户App源码仅支持1.0个推) 1.app模块配置开通推送 2.应用开通推送 3.开通后点击消息推送菜单会看到如下页面 完成以上步骤后 此时android 仅支持在线推送。 4.配置各厂商离线推送 暂未…

华为昇腾910B1基于 LoRA 的 Qwen2.5-7B-Instruct 模型微调

目录 系统环境虚拟环境微调模型yaml文件training_losstraining_eval_loss 系统环境 Ascend-hdk-910b-npu-driver_24.1.rc3_linux-aarch64.run Ascend-hdk-910b-npu-firmware_7.5.0.1.129.run Ascend-cann-toolkit_8.0.RC3.alpha003_linux-aarch64.run Ascend-cann-kernels-910…

窥探QCC518x/308x系列与手机之间的蓝牙HCI记录与分析 - 手机篇

今天要介绍给大家的是, 当我们在开发高通耳机时如果遇到与手机之间相容性问题, 通常会用Frontline或Ellisys的Bluetooth Analyzer来截取资料分析, 如果手边没有这样的仪器, 要如何窥探Bluetooth的HCI log.这次介绍的是手机篇. 这次跟QCC518x/QCC308x测试的手机是Samsung S23 U…

【GIS操作】使用ArcGIS Pro进行海图的地理配准(附:墨卡托投影对比解析)

文章目录 一、应用场景二、墨卡托投影1、知识点2、Arcgis中的坐标系选择 三、操作步骤1、数据转换2、数据加载3、栅格投影4、地理配准 一、应用场景 地理配准是数字化之前必须进行的一项工作。扫描得到的地图数据通常不包含空间参考信息&#xff0c;需要通过具有较高位置精度的…

【云岚到家】-day02-客户管理-认证授权

第二章 客户管理 1.认证模块 1.1 需求分析 1.基础概念 一般情况有用户交互的项目都有认证授权功能&#xff0c;首先我们要搞清楚两个概念&#xff1a;认证和授权 认证: 就是校验用户的身份是否合法&#xff0c;常见的认证方式有账号密码登录、手机验证码登录等 授权:则是该用…

VUE学习笔记(入门)5__vue指令v-html

v-html是用来解析字符串标签 示例 <!doctype html> <html lang"en"> <head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>Document<…

二、华为交换机 Trunk

一、Trunk功能 Trunk口主要用于连接交换机与交换机&#xff08;或路由器&#xff09;&#xff0c;允许在一条物理链路上传输多个VLAN的数据。这大大增加了网络的灵活性和可扩展性&#xff0c;使得不同VLAN之间的通信变得更加便捷。 二、作用原理 标签处理&#xff1a;Trunk口能…

基于SSM的自助购药小程序设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…