1.欣赏器缓存过期机制
1.1 末了修改时间 last-modified
欣赏器缓存机制是优化网页加载速率和淘汰服务器负载的重要本领。以下是关于欣赏器缓存过期机制、Last-Modified 和 ETag 的具体讲解:
一、Last-Modified 头部
- 界说:Last-Modified 表示服务器上资源的末了修改时间。
- 作用:用于资源的条件请求,帮助欣赏器判断缓存的资源是否是最新的。
- 工作流程:
- 欣赏器第一次请求资源时,服务器返回资源内容和 Last-Modified 时间。
- 下次请求同一资源时,欣赏器发送 If-Modified-Since 头部,值为之前的 Last-Modified 时间。
- 服务器比力资源的当前修改时间与 If-Modified-Since的值:
- 假如资源未修改,返回 304 Not Modified,欣赏器继承利用缓存。
- 假如资源已修改,返回新的资源内容和更新后的 Last-Modified 时间。
- 示例:
- Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT
复制代码 二、ETag 头部
- 界说:ETag(Entity Tag)是服务器为资源天生的唯一标识符,通常是资源内容的哈希值或版本号。
- 作用:比 Last-Modified 更加精确,用于验证资源是否变化。
- 工作流程:
- 欣赏器第一次请求资源时,服务器返回资源内容和 ETag 值。
- 下次请求同一资源时,欣赏器发送 If-None-Match 头部,值为之前的 ETag。
- 服务器比力当前资源的 ETag与 If-None-Match的值:
- 假如 ETag 未变化,返回 304 Not Modified,欣赏器继承利用缓存。
- 假如 ETag 变化,返回新的资源内容和新的 ETag 值。
- 示例:
- ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
复制代码 五、Last-Modified vs ETag
- 精确度:
- Last-Modified 仅记录末了修改时间,可能无法检测到在同一秒内的多次修改。
- ETag 通常基于内容的哈希值,能够更精确地检测到任何变化。
- 性能:
- 天生 ETag 可能需要更多的计算资源,尤其是在大规模资源或高频请求的环境下。
- Last-Modified 相对简朴,性能开销较小。
- 利用场景:
- 对于静态资源,ETag 更加适用。
- 对于动态资源,可以结合 Last-Modified 和其他缓存计谋利用。
六、最佳实践
- 合理设置缓存计谋:
- 对于不经常变化的静态资源,设置较长的 max-age 以充实利用缓存。
- 对于经常变化的资源,利用较短的 max-age 或结合验证机制。
- 利用 ETag 和 Last-Modified:
- 同时利用两者可以提供更可靠的缓存验证,但需注意服务器的性能开销。
- 版本化资源:
- 通过在资源URL中包罗版本号(如 style.v1.css),可以在资源更新时逼迫欣赏器下载新版本,避免缓存问题。
七、总结
欣赏器缓存机制通过多种HTTP头部字段控制资源的缓存和过期,Last-Modified 和 ETag 是此中重要的验证本领。合理配置这些头部字段,可以显著提升网页性能,优化用户体验,同时有效管理服务器资源。
第一次访问
第二次访问
1.2 Etag 标志
但是假如访问的时间一样的,怎么办?If-Modefied精确到的是秒,要知道的是,计算机一秒中可以干很多多少事的,好比一秒中修改上千次的图片
- # 使用 touch 模拟访问时间是一样的(移走1.jpeg,在重新上传一张图片,重命名为1.jpeg)
- [root@Rocky9.4 html]#touch -t 202407020931.48 1.jpeg
复制代码 第一次访问
第一次访问是由于我将时间设置成一样了,但是因为服务器返回的Etag是新的,而欣赏器生存的照旧旧的,所以Etag不一致,所以返回状态码是200
第二次访问
第二次访问,Etag也同一了,所以返回了状态码304
1.3 过期时间 expires 和 Cache-Control
一、欣赏器缓存机制概述
欣赏器缓存通过在当地存储网页资源(如HTML、CSS、JavaScript、图片等),避免每次访问网页时都从服务器重新下载这些资源,从而加快页面加载速率,提高用户体验,同时淘汰服务器带宽的利用。
明确了,你希望更具体地了解欣赏器缓存中的 Expires 和 Cache-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:资源必须在利用前重新验证(即使资源没有过期)。
示例:
- no-store:禁止任何形式的缓存,既不存储请求信息,也不存储响应信息。
示例:
- public:响应可被任何缓存区缓存,包括欣赏器和中间缓存(如CDN)。
示例:
- private:响应仅为单个用户缓存,不能被共享缓存(如CDN)缓存。
示例:
- must-revalidate:一旦资源过期,必须向服务器验证其有效性。
示例:
- Cache-Control: must-revalidate
复制代码
- proxy-revalidate:与 must-revalidate 类似,但仅适用于共享缓存。
示例:
- Cache-Control: proxy-revalidate
复制代码 3. 利用场景
- 动态资源:可以机动设置缓存计谋,如需要频仍更新但又希望利用缓存提升性能的资源。
- 细粒度控制:通过组合多个指令,实现更复杂的缓存计谋。
4. 与 Expires 的关系
- 优先级:当同时存在 Cache-Control: max-age 和 Expires 时,Cache-Control 优先级更高。
- 保举利用:当代欣赏器和服务器更保举利用 Cache-Control,因为它更机动且不依赖绝对时间。
四、Expires 与 Cache-Control 的对比
特性ExpiresCache-Control类型绝对时间相对时间及其他缓存指令格式HTTP日期格式指令列表优先级低于 Cache-Control高于 Expires机动性较低,只有一个绝对过期时间高,可以组合多种指令控制缓存行为保举利用场景重要用于向后兼容旧欣赏器当代Web应用的首选缓存控制方式 五、实际应用示例
1. 设置长时间缓存(适用于不经常变化的静态资源)
- Cache-Control: public
- , max-age=31536000Expires: Wed, 21 Oct 2025 07:28:00 GMT
复制代码
- 解释:资源可以被公共缓存(如CDN)缓存,且在1年内(31536000秒)不需要重新验证。
2. 设置短时间缓存,需重新验证(适用于可能会频仍更新的资源)
- 解释:欣赏器每次利用缓存前必须向服务器验证资源是否有更新。
3. 禁止缓存(适用于敏感数据)
- 解释:禁止任何形式的缓存,确保每次请求都从服务器获取最新数据。
六、结合 ETag 和 Last-Modified 利用缓存验证
即使设置了 Cache-Control 或 Expires,欣赏器在某些环境下仍可能需要验证缓存资源的有效性。此时,ETag 和 Last-Modified 提供了有效的验证机制:
- ETag:提供资源的唯一标识符,确保缓存的资源与服务器上的一致。
- Last-Modified:记录资源的末了修改时间,供欣赏器进行条件请求。
示例:
- Cache-Control: max-age=3600
- , must-revalidateETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
- Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT
复制代码 七、最佳实践
- 由于其机动性和优先级,当代Web开发中应优先配置 Cache-Control 头部。
- 根据资源的更新频率,合理设置缓存时间。静态资源可以设置较长时间,动态资源设置较短时间或不缓存。
- 结合利用 ETag 和 Last-Modified:
- 通过在资源URL中添加版本号(如 style.v1.css),确保资源更新时欣赏器能够获取到最新版本,避免缓存问题。
- 共同缓存头部,利用内容分发网络(CDN)提升全球范围内的资源加载速率,并有效管理缓存计谋。
八、总结
- Expires 和 Cache-Control 都用于控制资源的缓存和过期,但 Cache-Control 提供了更高的机动性和优先级。
- ETag 和 Last-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-age、no-cache)。
- 动态内容缓存:CDN一般针对动态内容(如用户特定数据、实时信息)利用不同的缓存计谋,可能会利用“按需缓存”或“低过期时间”的方式进行处理。
4. 智能路由与负载均衡
CDN通常会根据多个因素(如地理位置、网络负载、带宽等)选择最优的边缘服务器来响应用户请求。这一过程称为智能路由或负载均衡。通过此方式,CDN能够确保用户始终通过最快的路径获取到资源。
二、CDN的优势
- 提高加载速率
- 淘汰延迟:通过将内容分发到全球多个节点,用户总是能够从离自己最近的节点获取资源,从而大幅淘汰延迟,提高加载速率。
- 更高的可用性:通过分布式缓存,用户能够在多个服务器之间获取资源,即使某个服务器出现故障,也不会影响服务的可用性。
- 减轻原始服务器负载
- CDN缓存了大量静态内容,淘汰了原始服务器的直接负担,降低了带宽利用和处理请求的压力。
- 提升网站的可扩展性
- CDN帮助网站应对流量激增,能够在不同地区和时段主动调解资源的分配和流量管理,提供更好的扩展性。
- 加强网站的安全性
- DDoS防护:许多CDN提供DDoS攻击防护,能够通过分布式架构分担攻击流量,从而减轻原始服务器的压力。
- SSL加密:CDN服务提供SSL证书支持,帮助加密数据传输,提升安全性。
- 节省带宽成本
- 通过淘汰从原始服务器到客户端的流量,CDN有助于降低带宽费用,尤其是对于全球性网站。
- 高可用性和容错性
- CDN通过将资源缓存到多个节点,提升了资源的冗余度。在某个节点出现故障时,流量可以被主动引导到其他正常工作的节点,包管网站的高可用性。
三、CDN的类型
- 静态内容CDN
- 重要缓存静态内容,如图片、JavaScript文件、CSS文件等。通过将这些内容缓存到多个位置,能够加速资源加载速率。
- 动态内容CDN
- 动态内容指的是根据用户请求天生的内容,好比数据库查询结果或用户个性化信息。动态内容通常不缓存,但当代CDN提供商提供了对动态内容的优化方案,通过智能缓存计谋加速动态内容的加载。
- 直播和视频流CDN
- 专门用于视频流、直播视频内容的传输,优化了大带宽视频数据的分发和传输。常见的技术包括流媒体协议如 HLS(HTTP Live Streaming)和 DASH(Dynamic Adaptive Streaming over HTTP)。
- 边缘计算CDN
- 这种类型的CDN不仅提供缓存功能,还支持在边缘服务器上实行计算使命。它能够在靠近用户的地方处理请求,提高性能和降低延迟。
四、CDN的工作流程
- 资源上传到CDN:
- 将网站的静态资源上传到CDN供应商的服务器。资源可能会分发到多个全球节点进行缓存。
- 用户请求访问资源:
- 用户访问网页时,欣赏器向CDN发起请求。CDN会根据用户的地理位置,智能选择离用户最近的服务器响应请求。
- 缓存命中与未命中:
- 假如边缘服务器已缓存该资源(缓存命中),CDN直接返回缓存的内容。
- 假如缓存过期或没有缓存该资源(缓存未命中),CDN会向原始服务器请求资源,并将返回的资源缓存起来供后续用户利用。
- 返回资源给用户:
- 一旦缓存的资源通过CDN的边缘节点返回给用户,用户的欣赏器会在当地缓存该资源,下次访问时,直接从欣赏器当地获取。
五、CDN的服务提供商
目前,全球有多个重要的CDN服务提供商,最着名的包括:
- Cloudflare
- 提供免费和收费的CDN服务,支持全球分布的边缘节点,提供DDoS防护和Web应用防火墙(WAF)。
- Akamai
- 全球领先的CDN供应商,服务覆盖范围广,适用于大规模企业和高流量网站,提供强盛的内容加速和安全功能。
- Amazon CloudFront
- AWS提供的CDN服务,能够与AWS的其他服务(如S3、EC2等)无缝集成,提供高可扩展性和机动性。
- Fastly
- 以高性能为特点,支持即时缓存清除和高效的动态内容传输,适用于对延迟要求极高的应用。
- KeyCDN
- 提供较为简朴和成本效益高的CDN办理方案,适用于中小型网站。
六、CDN的优化计谋
- 合理设置缓存过期时间:
- 根据内容的更新频率,合理设置缓存过期时间(TTL),避免缓存过期导致频仍访问原始服务器。
- 利用分布式缓存:
- 利用CDN的全球节点分布,将内容缓存到多个节点,从而提供更好的负载均衡和冗余。
- 压缩和优化内容:
- 对资源进行压缩(如图片、CSS、JavaScript等),淘汰传输的数据量,提高加载速率。
- 结合HTTPS加密:
- 利用CDN的SSL证书加密功能,为网站提供HTTPS支持,提升数据传输的安全性。
七、总结
CDN是一种通过将网站内容分发到全球多个节点,淘汰延迟、提高加载速率、减轻服务器负载的技术。它不仅能加速资源的交付,还能提高网站的安全性、可用性和可扩展性。随着互联网应用的增长,CDN已成为优化网站性能和提供全球用户精良体验的重要工具。
1.4.1 用户请求CDN流程
用户请求CDN资源的流程可以分为几个步骤。这个流程涉及到用户如何向CDN发起请求,CDN如何决定从哪个服务器提供资源,以及缓存如何影响响应时间。以下是具体的用户请求CD能资源的流程:
一、请求流程概述
- 用户发起请求:用户的欣赏器或应用步伐向服务器请求某个资源(如图片、CSS、JavaScript文件等)。
- DNS解析:请求首先通过DNS解析,将资源的域名解析为CDN的IP地址。
- 路由到CDN边缘节点:用户的请求被路由到间隔用户最近的CDN边缘节点。
- 边缘节点缓存查抄:CDN的边缘节点查抄缓存中是否已有该资源。
- 缓存命中或未命中:根据缓存的环境,决定是直接返回缓存的内容,照旧从源服务器获取最新的资源。
- 返回资源给用户:资源通过边缘节点传输给用户,用户的欣赏器吸收并展示。
二、具体步骤
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-Control、Expires 等)进行当地缓存,以便在下一次访问时直接从当地缓存中获取资源,而不再发送请求到CDN或源服务器。
三、缓存计谋与内容更新
CDN中的缓存计谋非常关键,它决定了缓存内容的过期时间、更新方式以及缓存计谋的机动性。
- TTL(Time to Live,生存时间)
- 每个缓存的资源都会设置一个TTL,TTL指定了该资源在CDN边缘节点缓存的有效期。TTL过期后,缓存的内容会被认为是过期的,需要重新向源服务器请求内容。
- 缓存清除
- 主动清除:CDN提供商允许通过管理控制台或API来主动清除缓存中的某些资源。这对于资源更新频仍或告急更新的环境非常重要。
- 主动清除:当资源的TTL到期时,CDN会主动清除缓存并向源服务器请求新的内容。
- 缓存验证
- 利用 ETag 和 Last-Modified 等HTTP头部字段,CDN可以验证缓存是否有效。即使TTL未到期,CDN也可以通过向源服务器发送条件请求(If-None-Match 或 If-Modified-Since)来判断缓存是否需要更新。
四、CDN的优势
- 淘汰延迟:用户总是能从离自己最近的边缘服务器获取资源,淘汰了传输延迟。
- 提高可用性:即使源服务器宕机,CDN仍可以从其他节点提供缓存的内容,保持服务可用。
- 减轻源服务器负担:通过缓存大量请求,CDN能够减轻源服务器的负载,淘汰带宽消耗。
- 提高网站性能:加速资源加载,提升用户体验,尤其是对于全球用户。
五、CDN请求流程示意图
- 用户请求 --> DNS解析 --> CDN边缘节点 --> 缓存检查 -->
- | | |
- 缓存命中 缓存未命中 |
- | | |
- 返回缓存的资源 从源服务器请求资源 |
- | | |
- 返回给用户的资源 缓存资源并返回给用户 |
复制代码 六、总结
- CDN工作流程:CDN通过将资源分发到多个边缘节点,利用智能路由、缓存和负载均衡技术,将资源快速交付给用户,淘汰延迟,提高网站性能。
- 缓存命中与未命中:CDN根据缓存计谋决定是否直接返回缓存的内容,大概向源服务器请求更新内容。
- 欣赏器与CDN缓存:欣赏器当地缓存和CDN的缓存共同工作,确保资源加载更快,淘汰重复请求。
CDN在提高网站性能、加强网站可用性、降低带宽消耗等方面发挥了重要作用,是当代Web应用不可或缺的构成部分。
1.4.2 CDN分层缓存
CDN(Content Delivery Network,内容分发网络)的分层缓存(Layered Caching)是指通过多级缓存架构有效提升内容分发服从的一种计谋。在CDN中,请求的内容通常会经过多个层级的缓存节点,以实现更佳的性能和资源利用率。整个流程通常可以分为以下几个条理:
- L1 边缘节点缓存(Edge Cache):
这是离用户最近的一层缓存节点。当用户向CDN请求内容时,边缘节点首先查抄当地缓存是否已存有该内容。若存在并未过期,便直接从该节点返回内容给用户,降低传输延迟,提高用户体验;若缓存中无此内容或内容已过期,则向上层的缓存节点或源站请求。
- L2 区域或中间层缓存(Mid-Tier/Regional Cache):
当边缘节点未能在当地拿到所需内容时,会将请求向上层的区域缓存节点发出。区域缓存通常位于更靠近源站的核心网络,储存那些在肯定时间窗口内被多个边缘节点重复请求的内容。通过在此层进行缓存,CDN淘汰了向源站多次重复请求同一内容的频率。这一层有助于将热门内容在更广的地理范围内进行共享,降低源站负载,并淘汰跨区域的回源请求延迟。
- 源站(Origin Server):
当所有中间层缓存与边缘缓存均无请求内容时,才会到达终极的源站。源站是内容的原始出处,CDN会从这里获取最新版本的内容,然后将其分发给请求用户,并在适当的层级缓存节点中储存副本,以便满意未来类似请求。
分层缓存的工作原理
以下是一个范例的用户请求过程:
- 用户访问网站,请求某个资源(比方一张图片)。
- 用户的DNS解析请求将用户导向离他最近的L1边缘节点。
- L1节点查抄自身是否缓存了该资源。
- 假如有,则直接将资源返回给用户,请求竣事。这称为“缓存命中”。
- 假如没有,则L1节点向其上层的L2区域节点发起请求。
- L2节点实行相同的查抄,查察自身是否缓存了该资源。
- 假如有,则将资源返回给L1节点,L1节点再将其返回给用户。同时,L1节点也会缓存该资源,以便下次相同的请求可以直接命中。
- 假如没有,则L2节点继承向上,向源站发起请求。
- 源站将资源返回给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的重要特性:
- 内存存储:
Redis将数据存储在内存中,从而到达非常高的访问速率(读写操作通常在微秒级别)。这使其在对实时性要求高的场景(如会话存储、实时排行、实时计数器等)体现优秀。
- 多种数据结构支持:
相较于传统的Key-Value存储仅支持字符串,Redis支持多种丰富的数据结构类型,这些数据结构以简朴命令即可操作:
- String(字符串):最基础的数据结构,可存储普通字符串、数字、二进制数据。
- Hash(哈希):类似于Key-Value映射的集合,可方便存储对象属性并对属性进行增删改操作。
- List(列表):双端链表实现,支持从头尾插入、弹出元素,可用来实现消息队列、使命列表等功能。
- Set(集合):无序集合结构,支持求交集、并集和差集等集合运算,常用于去重、标签管理。
- Sorted Set(有序集合):每个元素会关联一个分数(score),Redis会根据分数对元素进行排序,可用于排行榜、延时队列等场景。
- Bitmap(位图)、HyperLogLog、Geo(地理位置)等特殊数据类型:满意统计计数、地理位置查询等特殊需求。
- 持久化本领:
虽然Redis是内存数据库,但它并非易失性存储。Redis提供两种持久化机制,让数据在断电后仍能规复:
- RDB(Redis Database Backup):定时天生内存快照并持久化到磁盘,规复速率快,数据略有延迟。
- AOF(Append Only File):将每次写操作以日志的形式追加到文件中,数据规复更为完备,可根据计谋对AOF文件进行定期重写压缩。
可以根据业务需求选择合适的持久化方案,或同时开启RDB和AOF实现数据安全与高服从的折中。
- 高可用与分布式:
Redis提供主从复制(Master-Slave Replication)实现数据的多份冗余,主节点负责写操作,从节点同步主节点的数据,提供读取分流和故障切换。当主节点出现故障时,可手动或借助Redis Sentinel(哨兵)实现主动故障转移。
对于更大规模的数据集与访问压力,Redis Cluster可以将数据分片至多个节点,提升团体存储本领和吞吐性能。
- 事件支持:
Redis提供简朴的事件机制(MULTI/EXEC命令),可以将一组操作打包,包管这些操作的顺序性和原子性。虽然不支持复杂的回滚功能,但事件可以确保一组命令要么都实行要么都不实行。
- Lua脚本扩展:
Redis内置了Lua解释器,用户可以在Redis内原子实行Lua脚本,对数据进行复杂操作,而无需在客户端与Redis之间多次往返,提高复杂操作的性能和一致性。
- 丰富的利用场景:
凭借高性能和多数据结构支持,Redis可广泛应用于各种场景:
- 缓存热点数据(比方:热门商品信息、用户会话数据、应用步伐配置)
- 消息队列与使命调理(利用List或Stream)
- 实时统计(计数器、排行榜、实时分析)
- 分布式锁(利用SetNx命令实现简朴的分布式锁机制)
- 简朴易用的命令行与客户端支持:
Redis提供简洁直观的命令行客户端和与主流编程语言(如Java、Python、Go、C#等)兼容的客户端库,降低学习成本与集成难度。
总结:
Redis作为一个内存数据存储体系,具有高性能、丰富的数据类型、机动的持久化计谋以及高可用性架构支持。它在高并发、低延迟与实时处理场景中得到广泛应用,已成为构建当代互联网应用的重要基础组件。
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。
 - # 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.本机无密码连接
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/
复制代码
shell 脚本写入数据到 Redis
- #!/bin/bashNUM=100PASS=123456for 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} 写入完成"doneecho "$NUM 个key写入到Redis完成"
复制代码 3.Redis 的多实例
在生产环境中,为了更好地利用资源、实现多租户隔离或分离不同业务的数据与配置,运维职员往往会在一台服务器上运行多个 Redis 实例。Redis 的多实例部署并非Redis内建的特性,而是通过为每个实例指定独立的配置文件、独立的运行端口与数据目次来实现的。以下是关于Redis多实例的具体讲解:
为什么需要多实例
- 资源隔离与多租户支持:
在某些场景下,不同的业务线或不同的用户需要独立的Redis服务,以免数据和性能相互影响。多实例可以为每个业务运行独立的Redis,包管数据和访问流量的隔离。
- 不同的配置要求:
某些业务可能需要不同的持久化计谋(RDB或AOF)、内存管理计谋或安全设置。多实例部署允许针对每个实例利用单独的配置文件,从而机动定制每个实例的行为。
- 更好地利用硬件资源:
一台物理机/虚拟机的CPU、内存、网络资源较为充裕时,可以在同一台呆板上运行多个Redis实例,充实利用硬件资源。尤其在内存较大时,不同实例分别作为缓存、队列、会话存储利用,可以最大化硬件利用率。
配置多实例的关键点
- 独立的配置文件:
每个实例都需要一个独立的配置文件(比方 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 进行管理和监控。
- 独立的启动命令:
启动时为每个实例指定相应的配置文件。常用命令形式:
- redis-server /path/to/redis-6379.conf
- redis-server /path/to/redis-6380.conf
复制代码 确保每个实例正常监听自己的端口并利用自己的配置。
- 服务管理与保卫历程:
为每个实例创建单独的systemd服务文件或init脚本,方便运维管理。如在systemd中创建 /etc/systemd/system/redis@6379.service、redis@6380.service 等文件,然后通过 systemctl start redis@6379 启动指定实例。
- 安全与访问控制:
确保为每个实例设置合理的访问控制,如 bind参数、protected-mode设置、requirepass或ACL计谋。多实例运行时应确保不同实例的数据和访问计谋独立,避免安全隐患。
- 监控与报警:
多实例运行时需要对每个实例分别进行监控,网络其内存利用、连接数、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 0drwxr-xr-x 2 redis redis 134 Dec 17 23:22 bindrwxr-xr-x 2 redis redis 22 Dec 18 20:04 datadrwxr-xr-x 2 redis redis 24 Dec 18 20:04 etcdrwxr-xr-x 2 redis redis 28 Dec 17 23:22 logdrwxr-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.pid5 directories, 19 files# 配置文件需要修改的地方vim /apps/redis/etc/redis_6379.confbind 0.0.0.0 -::1port 6379daemonize nopidfile /apps/redis/run/redis-6379.pidlogfile /apps/redis/log/redis-6379.log# 写入数据的时候,而且满意save才会生产dump-6379.rdb这个文件dbfilename dump-6379.rdbdir /apps/redis/data/# 3600秒,写一次数据 300秒,100次数据,60秒,10000次数据 满意就会备份,为了更快的看到结果可以更改,比方:save 60 1save 3600 1 300 100 60 10000appendfilename "appendonly-6379.aof"vim /apps/redis/etc/redis_6380.confbind 0.0.0.0 -::1port 6380daemonize nopidfile /apps/redis/run/redis-6380.pidlogfile /apps/redis/log/redis-6380.logdbfilename dump-6380.rdbdir /apps/redis/data/save 3600 1 300 100 60 10000appendfilename "appendonly-6380.aof"vim /apps/redis/etc/redis_6381.confbind 0.0.0.0 -::1port 6381daemonize nopidfile /apps/redis/run/redis-6381.pidlogfile /apps/redis/log/redis-6381.logdbfilename dump-6381.rdbdir /apps/redis/data/save 3600 1 300 100 60 10000appendfilename "appendonly-6381.aof"# 创建service文件# 1./usr/lib/systemd/system/redis6379.service[Unit]Description=Redis persistent key-value databaseAfter=network.target[Service]ExecStart=/apps/redis/bin/redis-server /apps/redis/etc/redis_6379.conf --supervised systemdExecStop=/bin/kill -s QUIT $MAINPIDType=notifyUser=redisGroup=redisRuntimeDirectory=redisRuntimeDirectoryMode=0755[Install]WantedBy=multi-user.target[root@Rocky9.4 ~]## 2./usr/lib/systemd/system/redis6380.service[Unit]Description=Redis persistent key-value databaseAfter=network.target[Service]ExecStart=/apps/redis/bin/redis-server /apps/redis/etc/redis_6380.conf --supervised systemdExecStop=/bin/kill -s QUIT $MAINPID#Type=notifyUser=redisGroup=redisRuntimeDirectory=redisRuntimeDirectoryMode=0755[Install]WantedBy=multi-user.target[root@Rocky9.4 ~]## 3./usr/lib/systemd/system/redis6381.service[Unit]Description=Redis persistent key-value databaseAfter=network.target[Service]ExecStart=/apps/redis/bin/redis-server /apps/redis/etc/redis_6381.conf --supervised systemdExecStop=/bin/kill -s QUIT $MAINPID#Type=notifyUser=redisGroup=redisRuntimeDirectory=redisRuntimeDirectoryMode=0755[Install]WantedBy=multi-user.target[root@Rocky9.4 ~]#systemctl daemon-reloadsystemctl 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/ -lhWarning: 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)
复制代码
方法2:
BGSAVE 才是“后台”生存,Redis 会 fork 一个子历程来完成 RDB 持久化,主历程继承对外提供服务。
- [root@Rocky9.4 data]#redis-cli
- -a 123456Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.127.0.0.1:6379> bgsaveBackground saving started127.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 10371037:C 24 Dec 2024 15:15:22.016 * DB saved on disk1037:C 24 Dec 2024 15:15:22.026 * Fork CoW for RDB: current 0 MB, peak 0 MB, average 0 MB638: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
复制代码
方法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 的优点
- 性能开销小:由于天生快照时是通过 fork 子历程来实行,主历程只需做少量工作,对性能影响较小。
- 适合做冷备:假如业务允许肯定程度的数据丢失(因为 RDB 只能反映天生快照时的数据状态),那么 RDB 非常简洁且容易做冷备份与全量备份。
- 启动速率快:从 RDB 文件进行数据规复时,因为只是加载一个快照文件,启动速率通常比力快。
4.1.3 RDB 的缺点
- 可能丢失数据:快照通常并不会很频仍地天生(除非你把 save 指令配置得极短,这会带来极大的性能损耗),所以在两次快照之间发生的数据写操作可能会丢失。
- 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参数控制:
- appendfsync always:每次有写操作时都同步写入磁盘,最安全但最慢。
- appendfsync everysec:每秒将缓存中的写命令同步写到磁盘,默认配置,在体系断电时最多丢失1秒的数据。
- appendfsync no:由操作体系决定何时同步写到磁盘,性能最高,安全性最低。
- AOF 重写(Rewrite):随着大量写操作的发生,AOF 文件会越来越大,因此需要对 AOF 文件进行“重写压缩”。
- Redis 会 fork 出子历程,把内存中的数据以最精简的命令集合重新写到一个新文件中。
- 重写过程中,主历程连续将新的写操作命令追加到一个缓冲区,待子历程重写完成后,再将这些命令同步到新文件末尾,末了原子地替换旧 AOF 文件。
4.2.2 AOF 的优点
- 数据安全:AOF 可以配置成每次写操作都写入磁盘(always),大概至少每秒写一次(everysec),相比 RDB,数据丢失的风险会小得多。
- 日志记录:AOF 文件是按命令记录的文本文件,人为可读,而且在出现告急环境时可以对其进行分析或修复(好比手动删除错误指令)。
4.2.3 AOF 的缺点
- 文件体积大:和 RDB 相比,AOF 文件会更大,尤其是在没有做 AOF 重写的环境下。
- 写性能影响:假如采用最安全的 appendfsync always 模式,那么每次写操作都要同步到磁盘,会带来显着的性能损耗。
- 规复速率:AOF 重放所有写命令来规复数据,可能比载入一个完备的 RDB 文件更慢。
4.3 如何选择 RDB 和 AOF
- 只用 RDB:
- 对数据一致性要求不高,能容忍几分钟的数据丢失,且更倾向于更好的写性能。
- 能够定期手动备份 RDB 文件,大概通过复制等方式冗余数据。
- 只用 AOF:
- 对数据安全性要求更高,不能容忍太多数据丢失,希望可以在秒级甚至实时上落盘。
- 愿意投入更多的磁盘性能和空间成本,接受 AOF 重放带来的规复速率影响。
- 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 结合起来,以充实利用两者的优势:
- 基础 RDB + 增量 AOF:
- 定期天生基础 RDB 快照,作为持久化的基准点。
- 记录基础 RDB 之后的所有写操作到增量 AOF 文件中,确保数据的实时性和持久性。
- 高效规复:
- 在规复数据时,Redis 首先加载基础 RDB 文件,快速规复到某个时间点的状态。
- 然后重放对应的增量 AOF 文件,到达最新的数据状态。
- 优化存储和性能:
- 通过将持久化过程分为全量快照和增量记录,淘汰了 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 文件的工作机制
- 基础 RDB 文件 (appendonly-6379.aof.1.base.rdb)
- 作用:生存某一时间点的数据库完备状态,相当于一个 RDB 快照。
- 更新条件:当进行 AOF 重写(AOF Rewrite)操作时,Redis 会天生一个新的基础 RDB 文件。此操作可以主动触发,也可以手动实行。
- 增量 AOF 文件 (appendonly-6379.aof.1.incr.aof)
- 作用:记录自上一个基础 RDB 快照以来的所有写命令(增量操作)。
- 更新方式:在 Redis 运行过程中,所有写操作都会被追加到当前的增量 AOF 文件中。
- 清单文件 (appendonly-6379.aof.manifest)
- 作用:跟踪和管理基础 RDB 文件与增量 AOF 文件之间的关系,确保在数据规复时能够精确加载基础快照并应用增量命令。
4.5.2 增量备份的工作原理与配置
增量备份重要依赖于基础 RDB 文件和增量 AOF 文件的组合。通过这种方式,你可以在保持高效的同时,实现数据的连续备份。
增量备份的工作流程
- 基础快照天生:
- 当实行 AOF 重写操作时,Redis 会天生一个新的基础 RDB 文件,记录当前数据库的完备状态。
- 记录增量操作:
- 在基础快照天生后,所有新的写操作会被记录到新的增量 AOF 文件中。
- 管理文件关系:
- 清单文件 (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 操作,平衡性能与持久性。
- no-appendfsync-on-rewrite no
- 解释:当设置为 yes 时,Redis 在实行 AOF 重写期间会停止实行 FSYNC 操作,从而提高性能;设置为 no 则不会停止 FSYNC。
- 建议:默认环境下建议保持 no,确保数据的持久性,尤其在对数据一致性要求较高的场景。
- auto-aof-rewrite-percentage 100
- 解释:界说 AOF 文件增长的比例,到达此比例后触发 AOF 重写。100 表示当当前 AOF 文件巨细是上一次重写后的巨细的 2 倍时触发重写(增长了 100%)。
- 建议:根据实际数据写入量和体系性能调解此值。较低的比例会更频仍地进行重写,但可能影响性能;较高的比例则淘汰重写频率,但可能导致 AOF 文件过大。
- auto-aof-rewrite-min-size 64mb
- 解释:设置 AOF 重写的最小触发文件巨细。只有当 AOF 文件巨细凌驾 64MB 且增长比例到达 auto-aof-rewrite-percentage 时,才会触发重写。
- 建议:确保设置一个合理的最小值,以避免频仍的小规模重写,影响性能。
- aof-load-truncated yes
- 解释:当 AOF 文件不完备或被截断时,是否允许 Redis 加载这些文件。yes 表示允许加载,并尽可能规复数据。
- 建议:在生产环境中建议设置为 no,以避免加载损坏的数据。假如设置为 yes,需要确保有其他数据规复机制,以防止数据丢失。
- aof-use-rdb-preamble yes
- 解释:在 AOF 文件开头包罗一个 RDB 快照的前导数据(preamble)。这有助于加快数据加载速率。
- 建议:默认建议保持 yes,提高数据规复的服从。
- aof-timestamp-enabled no
- 解释:是否在 AOF 文件中记录命令的时间戳。no 表示不记录,yes 表示记录。
- 建议:通常设置为 no,除非你有特定需求需要记录时间戳。
- # 通过 bgrewriteaof 手动触发重写机制[root@Rocky9.4 data]#redis-cli
- -a 123456 bgrewriteaof;pstree -p | grep redis ;ll appendonlydir/ -hWarning: 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 的形式添加特定子命令权限
on 或 off:启用或禁用用户。
>password:设置用户的密码,可以有多个密码。
~pattern:指定用户可以访问的键的模式(可选)。
+permissions 和 -permissions:授予或打消用户的权限,可以利用命令类别或具体命令。
- # 假设您希望创建一个名为 alice 的用户,设置密码为 123456,而且授予她所有权限但禁用 FLUSHALL 命令。可以按照以下方式配置:[root@Rocky9.4 etc]#vim redis_6379.confuser alice on >123456 ~* +@all -FLUSHALL -FLUSHDBuser 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:6379Warning: 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' command127.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:打消 FLUSHALL 和 FLUSHDB 命令的权限,防止用户实行这些伤害命令。
- # 默认用户去掉flushall 和 flushdbuser default on >123456 &logs:* ~* +@all -FLUSHALL -FLUSHDB# 动态管理 ACL# 1.ACL List(查察当前所有用户配置的具体信息,包括用户名称、密码哈希、权限列表、key patterns 等。)[root@Rocky9.4 etc]#redis-cli
- -a 123456Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.127.0.0.1:6379> acl list1) "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 aliceACL 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),然后退出历程。
利用示例:
连接会立即断开,Redis 服务停止。
注意点:
- 假如想避免生存数据(即不再进行 RDB/AOF 落盘),可加选项 SHUTDOWN NOSAVE。
- 假如只想生存数据而不关闭,可以实行 SAVE 或 BGSAVE。
5.10 SCAN
在 Redis 中,SCAN 命令是一种基于游标(cursor)**的迭代查询方式,能够**分批且非壅闭地遍历数据库中的 Key(或集合、哈希、ZSet 等),避免像 KEYS * 这样一次性扫描全部 Key 导致大规模壅闭的问题。下面先容一下 SCAN 的核心概念、利用方法以及与 KEYS 的区别。
一、为什么要用 SCAN?
- KEYS 的缺点:
- KEYS pattern 命令会一次性遍历所有 Key,并返回所有匹配的结果。
- 在 Key 数量很大的环境下(几百万上万万),一次扫描会造成主线程壅闭,期间无法处理其他请求,导致服务卡顿甚至超时。
- 因为 Redis 是单线程架构,这种大规模壅闭会严重影响线上业务。
- SCAN 的优点:
- 将大范围扫描拆分成多次小范围扫描,每次只返回一部分数据。
- 采用“游标(Cursor)+ 增量遍历”的模式,每扫描一部分,Redis 就返回一个新的 Cursor,并在响应中包罗本次扫描到的部分数据。
- 用户可以根据返回的 Cursor 继承下一次扫描,直到 Cursor 回到 0 表示扫描竣事。
- 相比 KEYS,SCAN 对服务器的壅闭时间更短,也更可控。
二、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 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 的字符串值。
- 示例:
若 key 存在,则返回对应的值;若 key 不存在,返回 nil
6.2.2.1.3 MSET / MGET
- MSET:同时设置多个 key-value 对。
- MSET k1 "v1" k2 "v2" k3 "v3"
复制代码 一次性写入多对数据,淘汰多次网络往返。
- MGET:批量获取多个 key 的值。
返回一个数组,如 [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 命令时直接附带 EX 或 PX 参数来设置过期时间。
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
- 最常用的操作:
- SET / GET:基础读写;
- MSET / MGET:批量读写;
- INCR / DECR / INCRBYFLOAT:实现计数器或数值操作;
- APPEND / GETRANGE / SETRANGE:在字符串上做增改或截取;
- EXPIRE / SET ... EX:共同过期管理。
- 注意字符串与数值:
- 只有当字符串能被解析为整数或浮点数时,INCR/DECR/INCRBYFLOAT 才华工作,否则会报错;
- 若您频仍进行数值操作,不要在中间手动 GET 并转回数字,以免造成竞态。直接用原子操作(INCR…)更可靠。
- 内存与巨细限制:
- Redis 默承认存储最大 512MB 的值,但太大的值会带来内存和网络传输负担,不建议把超大文件直接放 Redis。
- 建议拆分或利用外部文件存储(OSS、S3、NFS),Redis 中只存元数据或指针。
- 利用过期计谋:
- 若用 Redis 做缓存,通常会为 key 设置过期时间,避免数据无限堆积或过期失效。
- 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 的存储与特点
- 内部结构
- 小规模的哈希对象(字段数量较少时),Redis 会采用一种紧凑的数据结构(ziplist 或 listpack)来节省内存。
- 当字段数量或字段长度变大时,会转换成真正的哈希表(Hashtable)。
- 这对我们是透明的,Redis 会主动处理。但它解释了为什么 Hash 往往在小规模场景下特别高效。
- 数据特征
- Key:在 Redis 层面标识一个哈希对象的名称(如 user:1001)。
- Fields 和 Values:在哈希对象内部,每个 field 都拥有一个独立的 value。不同 field 之间互不干扰,但都属于同一个 key 的管理之下。
- 字段唯一:同一个哈希键下的 field 名称是不能重复的,若重复则会更新原有值。
- 常用场景
- 存储用户信息(字段: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 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
- 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 的利用场景
- 用户配置、信息
- user:1001 存放 name、email、age、login_count 等多个字段;
- 避免把每个字段都做一个单独的 String key。
- 数据对象缓存
- 比方商品信息 product:2001,此中字段 price、title、stock 等;
- 更新时只需改对应字段,不必覆盖整个对象;
- 还能减小内存,因为小哈希在 Redis 内部可用紧凑结构存储。
- 记录统计量
- 可以在 Hash 中存放各种计数器字段(点击量、点赞量、复兴数等),通过 HINCRBY 实现原子自增。
- 一个大对象的多种统计值都集中在同一个 hash key 下,管理更方便。
- 淘汰 key 的数量
- 在某些业务场景中,为了避免 key 过多带来的管理压力,把多个相关字段汇总到同一个 Hash key 下会简化命名空间;
- 但要注意单个哈希不宜过大,否则一次获取全部字段仍可能带来性能开销。
6.2.4 常见注意事项 & Best Practices
- 单个 Hash 不宜过度膨胀
- 虽然 redis.conf 里会控制 ziplist/listpack 的一些巨细阈值,但假如字段规模过大,一次性 HGETALL 会占用大量内存带宽;
- 对于超大哈希(上百万字段),谨慎利用或思量拆分、利用 HSCAN 分批遍历。
- 字段值只能是字符串
- Redis Hash 的 value 终极以字符串形式存储(即使是数字,也生存为字符串);
- 若要存储复杂对象,可以序列化后放进字段值,如 JSON 或 MsgPack,更新局部字段可能就需要再解析 JSON。
- 过期和生命周期
- Redis 只能对整个 Hash key 设置过期时间(如 EXPIRE user:1001 3600),无法对 Hash 里某个字段单独设置过期。
- 若只想让特定字段有失效时间,需要业务层逻辑共同。
- Hash vs. String
- 假如只有一个字段,利用 String 即可;
- 假如对象字段较多或需要一起管理,Hash 更方便,也可能更省内存(对小字段场景)。
- Hash vs. Set、ZSet
- Hash 专注于“字段-值”映射的结构,字段之间无顺序,重要用于描述对象属性;
- Set/ZSet 更侧重“集合”或“排序”语义;
- 业务逻辑决定选择。
- HXxx 命令的性能
- 在绝大多数环境下都为 O(1) 或 O(N)(N 为字段数)的操作,和内部哈希表实现相关。
- 大量字段更新时,也要注意操作分散性,别在高并发下对同一个大哈希做高频更新——可能会增大锁竞争(Redis 单线程)。
6.2.5 小结
Redis Hash 提供了紧凑且机动的 “字段-值” 结构,适合存储类似对象、配置、多字段记录等。它的核心命令可总结为:
- 增改
- HSET / HMSET:设置或更新字段
- HINCRBY / HINCRBYFLOAT:对数值字段原子自增
- 查询
- HGET / HMGET:获取单个或多个字段值
- HGETALL:获取全部字段和值
- HKEYS / HVALS:只获取字段 / 值列表
- HEXISTS:判断字段是否存在
- HLEN:获取字段总数
- 删除
- 扫描
- 其他
- 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 的存储与特点
- 存储结构:在内部,Redis 为 List 利用了一种类似双端链表或QuickList(在较新的版本中)来维护元素。
- 数据特征:
- 有序:按插入顺序生存元素,可在头/尾插入或弹出。
- 元素类型:每个元素都是一个字符串,可以是文本、JSON、二进制等,长度最大可达 512MB(与普通 String 的限制相同)。
- 常用场景:
- 实现消息队列 / 使命队列(如生产者-消费者模型)。
- 记录最新的操作日志或信息(用 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 的利用场景
- 消息/使命队列
- 生产者利用 LPUSH/RPUSH 将使命入队;
- 消费者利用 BLPOP/BRPOP 壅闭式弹出使命进行处理;
- 实现简朴可靠的异步队列机制。
- 最新消息/日志列表
- 利用 LPUSH 不断往头部插入最新的消息;
- 共同 LTRIM mylist 0 99 等截取操作,保存最近 100 条;
- 方便快速获取最新信息,同时限制长度防止无限膨胀。
- 数据分页或流式读取
- 可以用 List 存储一批数据,利用 LRANGE 分段获取;
- 但要注意大规模数据可能导致内存占用多,可分而治之或思量其他结构。
- Rotating Queue
- 利用 RPOPLPUSH 在多个队列之间转移数据,实现轮转或多队列消费等计谋。
6.3.4 常见注意事项 & Best Practices
- 避免存放超大量元素
- 虽然 List 可存数百万个元素,但一次 LRANGE 全量拉取也会占用大量内存和带宽。
- 假如只需要最新部分数据,建议利用 LTRIM 保存肯定区间,或按业务逻辑拆分存储。
- 利用壅闭命令需注意
- BLPOP/BRPOP 在等候时会占用一个连接。假如连接数有限或网络环境复杂,需要注意不要同时有太多壅闭请求。
- List 并非随机访问结构
- 只能通过索引下标进行有限的随机访问(LINDEX),或是依赖 LRANGE 全面遍历。
- 若需要频仍随机读写中间元素或多字段操作,或许 Hash/Set 等其他结构更合适。
- 利用场景决定操作端
- 对于队列场景,一般约定一端入队,另一端出队,避免混用增删端,以形成一致的 FIFO(或 LIFO)顺序。
- 对于日志场景,通常是 LPUSH + LTRIM,永久从头部插入最新消息。
- Watch out for LRANGE large ranges
- 只管 Redis 的 List 操作性能较优,但一次性获取几百万条也会造成壅闭和内存压力。
- 生产环境中可以 LRANGE key start end 分段获取,或设计合理的数据拆分。
6.3.5 小结
List 在 Redis 中是一个强盛且机动的数据结构,可满意多种顺序场景需求,包括消息队列、最新列表、双端队列等。其核心操作可以概括为:
- 头/尾插入:LPUSH、RPUSH
- 头/尾弹出:LPOP、RPOP
- 读取区间:LRANGE、查察长度:LLEN
- 壅闭弹出:BLPOP、BRPOP(常用于队列)
- 部分修改:LSET、LTRIM、LREM
- 多队列转移:RPOPLPUSH
在实际业务中,通过合理地选用这些操作并结合过期计谋或其他结构(比方存储 ID 在 List 中、实际数据放在 Hash 或 String 里),可以构建高效的队列模型、实时日志体系或其他顺序相关的功能。
6.4 集合(Set)
在 Redis 中,Set(集合) 类型用于存储一组不重复的字符串元素,和我们熟悉的数学集合很类似。Redis 提供了丰富的命令来操作集合,包括添加、删除、判断成员以及集合之间的交并差运算等。下面将按照类似前面先容的风格,具体讲解 Set 的根本概念、常用命令以及利用场景和最佳实践。
6.4.1 Redis Set 的存储与特点
- 内部结构
- Redis 利用类似哈希表(Hash Table)来存储 Set 中的元素,每个元素都是一个独立的字符串,而且不允许重复。
- 假如尝试将相同的元素多次添加到同一个 Set,只有第一次会见效,后续重复添加不会改变集合状态,也不会报错。
- 数据特征
- 元素无序:Redis 不包管插入顺序,也不能通过索引下标来获取或遍历。
- 元素唯一:同一个元素在集合中只会出现一次。
- 高效查找:Set 中的元素查找、添加、删除通常是 O(1) 平均复杂度(哈希表机制)。
- 常用场景
- 存储好友列表、标签列表、不重复的元素集合;
- 取交集、并集、差集,做共同好友、共同爱好、共同标签分析等;
- 去重功能(快速判断某元素是否已经存在);
- 随机获取、抽奖(SRANDMEMBER、SPOP)等场景。
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
- 作用:判断某个元素是否为集合成员。
- 语法:
- 返回值: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
6.4.2.2.3 SPOP
- 作用:随机移除并返回集合中的一个或多个元素(默认 1 个)。
- 语法:
- 示例:
- 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 提供了三个重要的集合运算命令:SINTER、SUNION、SDIFF。共同这些命令,能快速做聚合分析,如“共同好友”、“共同爱好标签”等场景。
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 其他常用或补充命令
- MOVE (Redis 6.2 从前版本仅对 key 见效,对 Set 结构可以利用 SPOP + SADD 或 SRANDMEMBER + SADD 的方式来“移动”元素到另一个集合)。
- RENAME (对整个 key 进行重命名,不仅是 Set 类型才有,不过仅限 key 级别操作,不是移动元素)。
- SSCAN (类似 SCAN,可分批扫描大集合,语法:SSCAN key cursor [MATCH pattern] [COUNT count]),在集合规模很大的环境下比一次性 SMEMBERS 友好,避免壅闭。
6.4.3 利用场景
- 去重功能
- 将数据插入 Set 并查抄返回值或 SISMEMBER 判断重复。
- 比方:用户签到(只需生存用户 ID,包管每个用户只签到一次)。
- 交际场景
- 用户关注列表、好友列表都可以用 Set 表示;
- 求共同好友:SINTER userA:friends userB:friends。
- 计算关注量:SCARD user:followers,判断是否互相关注:SISMEMBER user:followers otherUserID 等。
- 标签或权限管理
- 每个用户的标签是一个 Set 或每个标签对应一个 Set。可通过集合运算来机动地求交集或并集。
- 随机抽取、抽奖
- SRANDMEMBER 随机选取;如需“抽完就移除”,可用 SPOP。
- 去除重复消息 / 数据过滤
- 在大量数据处理中,可把已处理过的标志存进 Set,若再次出现,表明是重复,跳过即可。
6.4.4 常见注意事项 & Best Practices
- Set 元素数量过多
- SMEMBERS 会把所有元素一次性返回,可能导致内存和带宽的压力;
- 建议利用 SSCAN 分批遍历;或在设计上拆分成多个小集合,避免单个 Set 过大。
- 巨细限制
- 单个 Set 理论上可存放多达数亿个元素(只要内存充足);但操作如交集并集也会消耗相应资源,需评估性能和内存。
- 不要滥用 Set 做有序需求
- Set 是无序的,若需要按照分数、排名等有序排序,应该思量利用 Sorted Set (ZSet)。
- 若需要按插入顺序遍历或常做 FIFO/LIFO,List 或 Stream 更适合。
- 集合运算开销
- SINTER, SUNION, SDIFF 等运算在集合很大时也可能比力耗时,尤其当加入运算的集合规模都非常巨大时。
- 若是在线查询,需要注意别在关键路径上做大规模集合运算,以免壅闭 Redis 主线程。可以在业务层缓存结果,或在后端服务分层处理。
- 过期计谋
- Redis 不支持对 Set 中的单个元素设置过期,但可以对整个 key 设置过期时间(EXPIRE key seconds)。
- 若需要对此中的部分元素做时间控制,可能需要别的结构或自行管理。
6.4.5 小结
Set 在 Redis 中是一个非常基础且强盛的数据结构,最显著的特点是:
- 元素唯一、无序、快速判重;
- 提供交集、并集、差集等集合运算;
- 适合去重、好友列表、标签管理、抽奖等常见场景。
其核心命令可以简要归纳为:
- 添加 / 删除:
- 查询:
- SMEMBERS(全量),SISMEMBER(单一判断),SCARD(数量)
- SSCAN(分批扫描)
- 随机:
- SPOP(随机弹出并删除),SRANDMEMBER(随机获取不删除)
- 集合运算:
- SINTER, SUNION, SDIFF 以及对应的 STORE 版本
- 其他:
- SMOVE(已弃用命令,Redis 版本里没有 SMOVE 可以用 SPOP+SADD 替换),LTRIM 不适用于 Set,SSCAN 用于分批遍历等。
只要掌握这些命令和组合应用,就能应对大部分业务中需要“去重、随机、集合运算”的需求。在实际开发中,还要结合 Redis 的内存管理、过期计谋及安全控制,来告竣高性能又可靠的 Set 利用方案。
6.5 有序集合(ZSet)
在 Redis 中,有序集合(Sorted Set)是一种在集合基础上,为每个成员额外关联一个分数(score)的数据结构,常被缩写为 ZSet。它的特点是:元素不允许重复,但可以按照分数进行排序。Redis 提供了一系列针对有序集合的操作命令,方便我们在排行榜、排名查询、区间筛选、限时排序等场景中利用。
6.5.1 ZSet 的存储与特点
- 内部结构
- Redis 利用一种结合了 跳表(Skiplist) + 哈希表(Hashtable) 的结构来存储 ZSet。
- 跳表可以让我们在 O(log N) 的复杂度下进行有序操作(如范围查找、排序等),哈希表负责快速定位元素。
- 数据特征
- 元素唯一:和 Set 一样,每个 ZSet 里元素(member)是唯一的。
- 分数(score)可以重复:不同的 member 可以拥有相同的分数,但是 member 自身不能重复。
- 按分数排序:Redis 可以根据 score 值对元素进行从小到大的排序,也可以支持倒序操作。
- 常用场景
- 排行榜:好比按积分、热度、时间戳等排序;
- 时间序列: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 表示末了一个元素;
- 依次类推,start、stop 可以是正负索引。
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:返回有序集合的基数(元素总数)。
- ZCOUNT:统计指定 score 区间内的元素数量。
比方: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 做相应的合并或计算。常见的是 ZINTERSTORE 和 ZUNIONSTORE。
- 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 常见利用场景
- 排行榜
- 比方游戏积分榜、电商热卖榜、点赞数排行等;
- 可以通过 ZADD 不断更新用户的分数,然后利用 ZREVRANGE 或 ZREVRANK 获取某人排行或前 N 名;
- ZINCRBY 用于积分累加,ZRANK / ZREVRANK 用于查询当前名次。
- 实时排序
- 每次用户操作(点赞、查察、消费等)都可以更新其相关分数;
- 通过按分数的正序或倒序可实现某些实时刷新排名的功能,好比直播间送礼排行榜、产物热度排名等。
- 延时队列 / 时间调理
- 把 future task 存成 (score=实行时间, member=使命ID),利用 ZRANGEBYSCORE 查询到期使命,进行处理后删除;
- 可以替换一些简朴的定时使命逻辑。
- 时间序列
- 以时间戳作为 score,member 是事件或数据 ID;
- 利用 ZRANGEBYSCORE 或 ZREVRANGEBYSCORE 在肯定时间范围内获取数据。
6.5.4 注意事项 & Best Practices
- score 的精度
- Redis score 本质是一个双精度浮点数(double),尽量避免过于依赖小数精度(偶然可能出现浮点偏差)。
- 假如分数是整数,保举直接用整数(如 123456)或用一个长整型来表达时间戳/分值以减小偏差。
- ZADD 频仍更新
- 更新分数只需再次实行 ZADD <key> <score> <member>,若 member 存在则覆盖;或 ZINCRBY 增量更新。
- 频仍更新大规模 ZSet 时要注意是否会带来内存或操作耗时,毕竟跳表需要维护顺序。
- 排行榜前 N vs. 全量遍历
- ZRANGE key 0 9、ZREVRANGE key 0 9:获取前 10 名或后 10 名非常高效;
- 假如要获取所有成员(百万级)并排序,返回时就会产生大流量,占用 Redis 线程,对性能影响较大。
- 可以思量分页或分段查询:ZREVRANGE key start end,大概用 ZRANGEBYSCORE + LIMIT 进行分页,不过也要注意性能。
- 删除操作
- ZREM 只针对指定 member;
- 对于区间删除(score 范围或排名范围)可利用 ZREMRANGEBYSCORE 或 ZREMRANGEBYRANK。
- 这些批量删除操作会在肯定程度上壅闭 Redis 主线程,规模很大时也需注意分拆或异步管理。
- 内存占用
- ZSet 同时维持哈希表 + 跳表结构,会比普通 Set 消耗更多内存;
- 大量 ZSet 时需思量内存巨细、持久化和主从复制对性能的影响。
- 过期计谋
- Redis 不支持对 ZSet 的单个 member设置过期,只能对整个 key 设置过期;
- 假如需要对某些分数或元素做定期清理,可以定时实行 ZREMRANGEBYSCORE 或业务逻辑判断后删除。
6.5.5 小结
有序集合(ZSet)是 Redis 中一个非常重要的高级数据结构,结合了“集合去重”与“分数排序”的优势,能在很多排行榜、调理、排序场景下大显身手。
其核心命令可简要概括为:
- 增、删、改、查
- ZADD:添加/更新元素
- ZREM:删除指定元素
- ZINCRBY:分数自增
- ZRANK / ZREVRANK:查察排名(索引位置)
- ZCARD:ZSet 巨细
- 获取数据
- ZRANGE / ZREVRANGE:按索引区间获取排序结果
- ZRANGEBYSCORE / ZREVRANGEBYSCORE:按分数区间获取
- ZCOUNT:分数区间计数
- 范围删除
- ZREMRANGEBYSCORE、ZREMRANGEBYRANK
- 交并集操作
- 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)?
- O(1)(常数时间):
- 表示无论数据规模 N N N 多大,操作所需的时间都近似于一个常数,与 N N N 无关。
- 比方,用哈希表(Hash Table)查询一个元素的平均时间复杂度是 O(1):查找过程可在常数时间内完成。
- 当然这是平均环境。哈希表在最坏环境下(冲突严重)可能退化到 O(N);但在精良散列和低冲突的场景下可视为 O(1)。
- 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 中具体体现
- ZSet 的跳表部分:
- 当我们需要“根据分数范围取某些元素”或“得到某个分数在有序集合中的排名”时,就需要依赖有序结构(跳表)来进行区间遍历或排名查找,复杂度约为 O(log N)(有序查找、插入、删除)或 O(log N + M)(找出 M 个结果)。
- 比方命令 ZRANGEBYSCORE、ZREVRANGEBYSCORE 就是利用跳表快速定位到指定分数区间的起始位置,然后顺序扫描出结果。
- ZSet 的哈希表部分:
- 当我们实行 “给某个元素设置分数” 或 “查一个元素在不在这个 ZSet 里” 时,可先通过哈希表在 O(1) 平均时间内找到该元素;假如更新分数,则跳到跳表对应位置更新节点;
- 这样就兼顾了快速定位(哈希表)和有序操作(跳表)。
- Hash(哈希)类型、Set、HashMap 等其他结构:
- 也都基于哈希表,插入、查询、删除的平均复杂度是 O(1)。
- List 或 Stream:
- 大部分环境下是 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 消息队列
在消息队列大概消息通报领域,一般会提到 两种常见的消息模式:
- 生产者-消费者(Producer-Consumer)
- 发布-订阅(Publisher-Subscriber,Pub/Sub)
6.7.1 生产者-消费者(Producer-Consumer)
6.7.1.1 模式概念
- 生产者(Producer):负责发送消息或使命到队列(Queue)中。
- 消费者(Consumer):从队列中取出消息或使命并进行处理。
常见的形态是“消息队列”:生产者把消息投递到队列,消费者从队列里取出并消费,终极队列中存储的是待处理的消息。
6.7.1.2 工作流程
- 生产者将消息发送到消息队列;
- 消息队列负责临时存储这些消息,充当缓冲区;
- 消费者从队列中轮询或壅闭获取消息(通常是 FIFO,先进先出 first in,first out),然后实行处理;
- 处理完毕后,消息即被标志为消费完成或从队列移除。
6.7.1.3 重要特点
- 解耦:生产者和消费者可以异步运行,生产者发送后就可以继承处理其他使命,不用等候消费者处理完成。
- 缓冲:消息队列在生产者和消费者之间提供了一个缓冲池,能应对突发的高并发或流量不均衡,避免直接把压力通报给消费者。
- 可靠性(可选):大多数消息队列中间件(RabbitMQ、Kafka、RocketMQ 等)会提供持久化、确认(ACK)机制来包管消息不丢失。
- 竞争消费:通常一个队列可被多个消费者并行消费,每条消息只会被此中一个消费者处理一次,适合“使命分发”或“分布式工作池”模式。
6.7.1.4 范例应用场景
- 异步使命处理:如发送短信、邮件、图像处理,把耗时使命扔进队列中,再由工作历程去处理。
- 削峰填谷:电商大促时,订单处理可先进入队列以防止瞬时并发过高;消费者慢慢消化队列里积压的订单。
- 分布式体系间解耦:生产者和消费者可以各自升级或故障重启,而不影响对方的逻辑。
6.7.2 发布-订阅(Pub/Sub, Publisher-Subscriber)
6.7.2.1 模式概念
- 发布者(Publisher):向某个主题或频道(Topic/Channel)发布消息。
- 订阅者(Subscriber):对指定主题或频道进行订阅,一旦有新的消息发布,就会被推送或吸收到。
可以把它理解为“广播”式:所有订阅了某个频道的订阅者,在发布者推送消息后,都会各自收到一份消息。
6.7.2.2 工作流程
- 订阅者先声明对某个主题/频道感爱好,并订阅之;
- 发布者向该主题/频道发布消息;
- 消息中间件负责将消息同时分发给所有已订阅该主题/频道的消费者;
- 每个订阅者都能够各自收到该条消息,并进行自己的处理。
6.7.2.3 重要特点
- 一对多:一个发布者发布的一条消息可以同时被多个订阅者吸收,这与“生产者-消费者”中单条消息只被一个消费者处理截然不同。
- 实时推送:当发布者有新消息时,订阅者能立即收到;
- 不包管存储(取决于实现):假如某个订阅者离线或没有实时消费,消息可能就错过了(除非利用带持久化/主题回溯的消息体系,比方 Kafka、Redis Streams 也有肯定存储本领,但传统“Redis Pub/Sub”则不存储历史消息)。
- 松耦合:发布者不需要知道订阅者是谁,订阅者也不需要知道具体是哪一个发布者发的消息。它们只通过主题来“对接”。
6.7.2.4 范例应用场景
- 广播通知:体系中某个事件需要同时通知多个服务或模块。
- 实时推送:谈天房间、直播间弹幕、股票行情等,需要向所有在线订阅者推送最新数据。
- 异步事件总线:在微服务架构中,一些服务可能只需要监听特定事件来触发自己的逻辑,发布者只管发布事件,多个订阅者各自实行不同流程。
6.7.3 对比与总结
维度生产者-消费者(消息队列)发布-订阅(Pub/Sub)消息通报关系一对一:队列中的一条消息只会被一个消费者处理一对多:一个消息可同时被多个订阅者吸收消息存储通常会存储在队列里,若消费者未实时消费,消息也不会丢取决于体系实现,传统 Pub/Sub 通常不存储历史消息消费模式消费者竞争消费同一个队列;可实现分布式工作池、并行处理所有订阅该主题的订阅者都能各自收到一份消息,并行处理适用场景异步处理、队列缓冲、削峰填谷、解耦分布式使命实时广播通知、事件订阅、多客户端同时吸收相同消息消息确认 & 重试通常具备ACK/重试机制,确保消息被精确处理传统简朴 Pub/Sub 一般无重试/确认,消息分发后即丢核心关注点可靠消费,队列实现排队、缓冲;包管单条消息只被处理一次消息广播,多消费者同时收到,不包管是否被落地存储 6.7.4 延伸与选择
- 生产者-消费者重要关注:
- 消息排队、可靠处理、可控的并发消费、处理完消息后就出队。
- 适用于工作队列、使命处理、订单处理、分布式流水线等。
- 发布-订阅重要关注:
- 分发:一次消息,多个订阅者同时收到;
- 广播或多对多模式;
- 适用于实时性推送、广播通知、事件总线等。
- 组合利用:
- 偶然一个体系中既需要排队处理,又需要广播通知,则会在不同子模块选择不同模式,大概利用支持多种模式的消息中间件(如 Kafka 既支持“订阅分区”,也能做分布式队列;RabbitMQ 可用交换器+队列共同实现多播等)。
- 不同体系实现
- 如 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 版本和需求的不同,常见的实现方式重要有:
- 利用 List(列表)+ 壅闭弹出:最传统的方式,利用 LPUSH/RPUSH 与 BLPOP/BRPOP 组合,构建简朴队列。
- 利用 Redis Pub/Sub:更适合实时广播式的消息分发,但消息不能持久化。
- 利用 Redis Streams(5.0+):官方保举的当代化消息流结构,支持持久化、有序存储、消费组、ACK 等功能,更适合复杂的消息队列场景。
下面依次先容各方案的特点与适用场景,并总结常见注意事项。
6.7.6.1 利用 List 构建简朴队列
6.7.6.1.1 根本原理
- 生产者:在列表尾部或头部插入消息
- 常用 LPUSH queue "message" 或 RPUSH queue "message"
- 消费者:利用 壅闭命令 BLPOP 或 BRPOP 来弹出消息
- 当队列为空时,消费者壅闭等候,直到超时(若指定)或有新消息入队
这样就构成了一个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 关键概念
- Stream:按时间或插入顺序将消息(Entry)排成有序队列,每条消息带有一个 ID(类似时间戳+序列号)。
- XADD:生产者向 Stream 中添加消息(XADD mystream * field1 value1 field2 value2 ...)。
- XREAD / XREADGROUP:消费者读取消息,可指定从哪个 ID 开始读,实现增量消费。
- Consumer Group(消费者组):允许多个消费者分组消费同一个 Stream 中的数据;Redis 会主动做分配,并支持消息ACK和未确认消息管理,适合多消费者并行且包管消息不会重复消费或丢失太多。
- 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 优势与特点
- 可靠性:通过消费者组和 ACK 机制,避免消费者崩溃导致消息永久丢失。
- 多消费者并发:同一个消费者组可以有多个消费者竞争消费消息,Redis 会主动分配未被其它消费者领取的消息给它们。
- 消息回溯:默认消息不会主动抛弃,可以根据需要保存肯定长度或时间周期,以便后续读重或做故障回放。也可通过 MAXLEN 配置限制流长度。
6.7.6.3.5 适用场景
- 分布式体系中需要可靠消息队列:既要并发消费,又要防止某台消费者挂了导致消息丢失;
- 需要历史消息回溯(至少在肯定范围内),好比日志、事件存档等;
- 相对大规模、严谨的异步消息处理,比 List 方案更“专业”,且支持高级特性。
6.7.6.4 选择哪种方式?
- 最简朴:List + 壅闭弹出
- 适合轻量级的单一队列,不需要复杂确认或重复消费管理;
- 代码简朴易上手,假如消息丢了问题不大(或能用自界说补偿),就行。
- Pub/Sub
- 适合实时推送或一对多通知场景,不做持久化,消息即时投递后即抛弃;
- 不适合需要存储与可靠消费的排队场景。
- Redis Streams
- 适合对消息的可靠性、持久性、多消费者管理有肯定要求的场景,提供近似 MQ 的本领;
- 结构更复杂,需要掌握 XREADGROUP, XACK, XPENDING, XCLAIM 等命令;
- 假如需要专业、超大规模的队列并发(数十万~百万 QPS 级别)或高级分布式事件,可思量专业消息体系(Kafka、RabbitMQ、RocketMQ 等);但 Redis Streams 对于中等规模的可靠队列、实时处理依然非常合适。
6.7.6.5 常见注意事项
- Redis 单线程特性
- 所有命令都在单线程内实行,若消息量巨大、消费者或生产者并发极高,可能造成壅闭。
- Redis 6.0 引入多 I/O 线程,但命令实行依然是单线程,需根据实际场景评估性能。
- 持久化
- 假如你希望消息在 Redis 重启后保存,需要启用 RDB / AOF 持久化,但也得思量性能开销与数据一致性。
- 假如需要100%不丢消息,需要确保 Redis 持久化计谋和硬件环境都可靠。
- 内存限制
- Redis 重要在内存中操作,假如消息积压量非常大,就可能导致内存压力,或大量写入也影响性能。可开启 eviction 计谋,但那会导致可能的消息丢失。
- 消费端处理失败重试
- 简朴 List 模型下,若处理失败,需要手动将消息再 push 回队列,或记录日志后再做补偿。
- 利用 Streams + 消费者组可以重新分配未确认的消息(XPENDING, XCLAIM),从而实现肯定程度的重试、死信队列等机制。
- 吞吐 vs. 功能
- Redis 消息队列方式更倾向于“轻量、高速、短期缓存/队列”场景;不提供像专业 MQ 那样完善的路由、扩展、跨节点分布功能。
- 假如需求简朴、无需过度扩展,Redis 够用且非常快;若是大规模企业级 MQ 场景或需要严酷事件和更机动的路由计谋,可思量 RabbitMQ、Kafka、RocketMQ 等。
6.7.6.6 小结
Redis 作为内存数据库,本身并不是专业的消息中间件,但它速率快、命令简朴,对很多轻量级消息队列或实时通知场景来说很实用:
- List + BLPOP / BRPOP:最经典的浅易队列方案。适合单队列、简朴生产-消费、对可靠性无特殊要求的场景。
- Pub/Sub:适合实时广播的一对多订阅,不保存历史消息,也无确认机制。
- Redis Streams:Redis 5.0+ 官方提供的“日志流”数据结构,支持有序存储、消费者组、ACK 确认、回溯消息等,能满意中等规模、较高可靠性要求的消息队列场景。
根据业务需求,选择合适的模式和实现方式,才华在性能与可靠性之间找到平衡。对简朴场景,List 队列最易上手;若需要相对专业的队列功能又想依赖 Redis生态,则 Streams 是更好的选择;若需要高可靠性和大规模分布式集群,可进一步研究专业MQ或结合 Redis 与其他体系。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |