帝国网站数据库配置文件,关于建设门户网站,wordpress自动多语言,不用服务器做视频网站1.浏览器缓存过期机制
1.1 最后修改时间 last-modified 浏览器缓存机制是优化网页加载速度和减少服务器负载的重要手段。以下是关于浏览器缓存过期机制、Last-Modified 和 ETag 的详细讲解#xff1a; 一、Last-Modified 头部 定义#xff1a;Last-Modified 表示服务器上资源…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 头部 定义ETagEntity 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)
[rootRocky9.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 GMT3. 使用场景 适用于静态资源如图片、CSS、JavaScript文件这些资源不经常变化。适合设置较长的缓存时间减少浏览器对服务器的请求频率提升加载速度。 4. 缺点 使用绝对时间可能受客户端和服务器时间不同步的影响。当资源更新时若不改变 Expires可能导致浏览器继续使用过期的缓存出现内容不一致的问题。 三、Cache-Control 头部 1. 定义与作用 Cache-Control 是一个更为灵活和强大的HTTP响应头用于控制缓存策略。它可以替代或补充 Expires 头部提供更精确的缓存控制。 2. 常用指令 max-age秒数指定资源在多少秒内被认为是新鲜的。max-age 的优先级高于 Expires。 示例 Cache-Control: max-age3600no-cache资源必须在使用前重新验证即使资源没有过期。 示例 Cache-Control: no-cacheno-store禁止任何形式的缓存既不存储请求信息也不存储响应信息。 示例 Cache-Control: no-storepublic响应可被任何缓存区缓存包括浏览器和中间缓存如CDN。 示例 Cache-Control: publicprivate响应仅为单个用户缓存不能被共享缓存如CDN缓存。 示例 Cache-Control: privatemust-revalidate一旦资源过期必须向服务器验证其有效性。 示例 Cache-Control: must-revalidateproxy-revalidate与 must-revalidate 类似但仅适用于共享缓存。 示例 Cache-Control: proxy-revalidate3. 使用场景 动态资源可以灵活设置缓存策略如需要频繁更新但又希望利用缓存提升性能的资源。细粒度控制通过组合多个指令实现更复杂的缓存策略。 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-age31536000
Expires: Wed, 21 Oct 2025 07:28:00 GMT解释资源可以被公共缓存如CDN缓存且在1年内31536000秒不需要重新验证。 2. 设置短时间缓存需重新验证适用于可能会频繁更新的资源 Cache-Control: no-cache解释浏览器每次使用缓存前必须向服务器验证资源是否有更新。 3. 禁止缓存适用于敏感数据 Cache-Control: no-store解释禁止任何形式的缓存确保每次请求都从服务器获取最新数据。 六、结合 ETag 和 Last-Modified 使用缓存验证 即使设置了 Cache-Control 或 Expires浏览器在某些情况下仍可能需要验证缓存资源的有效性。此时ETag 和 Last-Modified 提供了有效的验证机制 ETag提供资源的唯一标识符确保缓存的资源与服务器上的一致。Last-Modified记录资源的最后修改时间供浏览器进行条件请求。 示例 Cache-Control: max-age3600, must-revalidate
ETag: 33a64df551425fcc55e4d42a148795d9f25f89d4
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT七、最佳实践 优先使用 Cache-Control 由于其灵活性和优先级现代Web开发中应优先配置 Cache-Control 头部。 合理设置 max-age 根据资源的更新频率合理设置缓存时间。静态资源可以设置较长时间动态资源设置较短时间或不缓存。 结合使用 ETag 和 Last-Modified 提供双重验证机制确保缓存的资源始终是最新的。 版本化静态资源 通过在资源URL中添加版本号如 style.v1.css确保资源更新时浏览器能够获取到最新版本避免缓存问题。 使用CDN 配合缓存头部利用内容分发网络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通常会使用一些缓存策略来决定哪些内容需要缓存以及缓存多久。常见的缓存策略包括 缓存时间TTLTime 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 专门用于视频流、直播视频内容的传输优化了大带宽视频数据的分发和传输。常见的技术包括流媒体协议如 HLSHTTP Live Streaming和 DASHDynamic 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.pngCDN的边缘节点可能已经缓存了这个文件且TTL过期时间没有到期此时CDN直接返回文件。 缓存未命中如果缓存中没有该资源或者缓存的资源已经过期CDN会向源服务器发起请求以获取资源。 6. 从源服务器获取资源 当缓存未命中时CDN边缘节点会向原始服务器origin server请求该资源。此时源服务器会根据请求返回最新的资源并且将该资源缓存到边缘节点以供下次请求使用。 资源返回后CDN会缓存到边缘节点并设置适当的缓存过期时间TTL。这意味着下一次请求时边缘节点可以直接返回缓存的内容而不需要再访问源服务器。 7. 返回资源给用户 无论是缓存命中还是从源服务器获取资源最终CDN的边缘节点会把响应数据返回给用户的浏览器。用户的浏览器从CDN边缘节点接收到资源并进行展示。 8. 浏览器缓存 在资源返回给浏览器后浏览器也会根据响应头如 Cache-Control、Expires 等进行本地缓存以便在下一次访问时直接从本地缓存中获取资源而不再发送请求到CDN或源服务器。 三、缓存策略与内容更新 CDN中的缓存策略非常关键它决定了缓存内容的过期时间、更新方式以及缓存策略的灵活性。 TTLTime to Live生存时间 每个缓存的资源都会设置一个TTLTTL指定了该资源在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分层缓存 CDNContent 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简介 RedisRemote Dictionary Server是一个开源的内存数据结构存储系统可以用作数据库、缓存和消息队列中间件。它以Key-Value形式存储数据提供多种数据结构和丰富的功能特性。Redis的核心价值在于高速访问、简单的数据操作模型以及丰富的数据结构支持使其在需要快速读写、实时计算和高并发的场景中表现突出。 Redis的主要特性 内存存储 Redis将数据存储在内存中从而达到非常高的访问速度读写操作通常在微秒级别。这使其在对实时性要求高的场景如会话存储、实时排行、实时计数器等表现优异。 多种数据结构支持 相较于传统的Key-Value存储仅支持字符串Redis支持多种丰富的数据结构类型这些数据结构以简单命令即可操作 String字符串最基础的数据结构可存储普通字符串、数字、二进制数据。Hash哈希类似于Key-Value映射的集合可方便存储对象属性并对属性进行增删改操作。List列表双端链表实现支持从头尾插入、弹出元素可用来实现消息队列、任务列表等功能。Set集合无序集合结构支持求交集、并集和差集等集合运算常用于去重、标签管理。Sorted Set有序集合每个元素会关联一个分数scoreRedis会根据分数对元素进行排序可用于排行榜、延时队列等场景。Bitmap位图、HyperLogLog、Geo地理位置等特殊数据类型满足统计计数、地理位置查询等特殊需求。 持久化能力 虽然Redis是内存数据库但它并非易失性存储。Redis提供两种持久化机制让数据在断电后仍能恢复 RDBRedis Database Backup定时生成内存快照并持久化到磁盘恢复速度快数据略有延迟。AOFAppend 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 由系统源提供
[rootredis1.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.[rootRocky9.4 ~]## CentOS 7由 epel 源提供
[rootCentOS7 ~]#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[rootredis1.xyy.org ~]#dnf install redis
[rootredis1.xyy.org ~]#systemctl enable --now redis
[rootredis1.xyy.org ~]#pstree -p | grep redis|-redis-server(4237)--{redis-server}(4238)| |-{redis-server}(4239)| |-{redis-server}(4240)| -{redis-server}(4241)
[rootredis1.xyy.org ~]#
[rootredis1.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:63792.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_SYSTEMDyes 选项以生成可与 systemd 交互的可执行文件。)
make -j $(nproc) USE_SYSTEMDyes# 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
#dirRDB/快照文件存放目录一般设为 /apps/redis/data
#logfile日志文件路径如 /apps/redis/log/redis-6379.log
#pidfilepid 文件路径如 /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 1sysctl -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]
DescriptionRedis persistent key-value database
Afternetwork.target[Service]
ExecStart/apps/redis/bin/redis-server /apps/redis/etc/redis.conf --supervised systemd
ExecStop/bin/kill -s QUIT $MAINPID
# 启动notify一定要编译了 USE_SYSTEMDyes,否则启动服务有问题
Typenotify
Userredis
Groupredis
RuntimeDirectoryredis
RuntimeDirectoryMode0755[Install]
WantedBymulti-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 ]
#-----------------------------------------------------
VERSIONredis-7.4.0
PASSWORD123456
INSTALL_DIR/apps/redisos_type() {awk -F[ ] /^NAME/{print $2} /etc/os-release
}color() {RES_COL80MOVE_TO_COLecho -en \e[${RES_COL}GSETCOLOR_SUCCESSecho -en \e[1;32mSETCOLOR_FAILUREecho -en \e[1;31mSETCOLOR_WARNINGecho -en \e[1;33mSETCOLOR_NORMALecho -en \e[0mecho -n $1 $MOVE_TO_COLecho -n [if [ $2 success -o $2 0 ]; then${SETCOLOR_SUCCESS}echo -n $ OK elif [ $2 failure -o $2 1 ]; then${SETCOLOR_FAILURE}echo -n $FAILEDelse${SETCOLOR_WARNING}echo -n $WARNINGfi${SETCOLOR_NORMAL}echo -n $]echo
}install_redis() {wget https://download.redis.io/releases/${VERSION}.tar.gz || {color Redis 源码下载失败 1exit}tar xf ${VERSION}.tar.gzcd ${VERSION}CPUSlscpu | awk /^CPU\(s\)/{print $2}make -j $CPUS USE_SYSTEMDyes PREFIX${INSTALL_DIR} install color Redis 编译安装完成 0 || {color Redis 编译安装失败 1exit}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.confif id redis /dev/null; thencolor Redis 用户已经存在,无需创建 0elseuseradd -r -s /sbin/nologin rediscolor Redis 用户创建成功 0fichown -R redis.redis ${INSTALL_DIR}cat /etc/sysctl.conf EOF
net.core.somaxconn 1024
vm.overcommit_memory 1
EOFsysctl -pif [ os_type Ubuntu ];thencat /lib/systemd/system/rc-local.service EOF
[Install]
WantedBymulti-user.target
EOFecho #!/bin/bash /etc/rc.localecho echo never /sys/kernel/mm/transparent_hugepage/enabled /etc/rc.localchmod 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]
DescriptionRedis persistent key-value database
Afternetwork.target[Service]
ExecStart${INSTALL_DIR}/bin/redis-server ${INSTALL_DIR}/etc/redis.conf --supervised systemd
ExecStop/bin/kill -s QUIT \$MAINPID
Typenotify
Userredis
Groupredis
RuntimeDirectoryredis
RuntimeDirectoryMode0755[Install]
WantedBymulti-user.target
EOFelseecho echo never /sys/kernel/mm/transparent_hugepage/enabled /etc/rc.d/rc.localchmod x /etc/rc.d/rc.local/etc/rc.d/rc.localcat /usr/lib/systemd/system/redis.service EOF
[Unit]
DescriptionRedis persistent key-value database
Afternetwork.target[Service]
ExecStart${INSTALL_DIR}/bin/redis-server ${INSTALL_DIR}/etc/redis.conf --supervised systemd
ExecStop/bin/kill -s QUIT \$MAINPID
Typenotify
Userredis
Groupredis
RuntimeDirectoryredis
RuntimeDirectoryMode0755[Install]
WantedBymulti-user.targetEOFfisystemctl daemon-reloadsystemctl enable --now redis /dev/nullsystemctl is-active redis /dev/null color Redis 服务启动成功,Redis信息如下: 3 || { color Redis 启动失败 1 ;exit; }#sleep 5redis-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 安装软件包失败,请检查网络配置 1exit}rpm -q wget /dev/null || yum -y install wget /dev/nullwget https://download.redis.io/releases/${VERSION}.tar.gz || {action Redis 源码下载失败 falseexit}tar xf ${VERSION}.tar.gzcd ${VERSION}CPUSlscpu | awk /^CPU\(s\)/{print $2}make -j $CPUS USE_SYSTEMDyes PREFIX${INSTALL_DIR} install action Redis 编译安装完成 || {action Redis 编译安装失败 falseexit}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.confif id redis /dev/null; thenaction Redis 用户已经存在 falseelseuseradd -r -s /sbin/nologin redisfichown -R redis.redis ${INSTALL_DIR}cat /etc/sysctl.conf EOF
net.core.somaxconn 1024
vm.overcommit_memory 1
EOFsysctl -pecho echo never /sys/kernel/mm/transparent_hugepage/enabled /etc/rc.d/rc.localchmod x /etc/rc.d/rc.local/etc/rc.d/rc.localcat /usr/lib/systemd/system/redis.service EOF
[Unit]
DescriptionRedis persistent key-value database
Afternetwork.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
Typenotify
Userredis
Groupredis
RuntimeDirectoryredis
RuntimeDirectoryMode0755[Install]
WantedBymulti-user.targetEOFsystemctl daemon-reloadsystemctl enable --now redis /dev/nullsystemctl is-active redis /dev/null ${COLOR}Redis 服务启动成功,Redis信息如下:${END} || { ${COLOR}Redis 启动失败${END};exit; }#sleep 5redis-cli -a $PASSWORD INFO Server 2/dev/null
}install_Ubuntu() {apt -y install make gcc libjemalloc-dev libsystemd-dev || {color 安装软件包失败,请检查网络配置 1exit}install_redis
}install_Rocky() {# jemalloc-devel依赖于epel源yum -y install epel-release yum -y install gcc jemalloc-devel systemd-devel || {color 安装软件包失败,请检查网络配置 1exit}rpm -q wget /dev/null || yum -y install wget /dev/nullinstall_redis
}if [ $(os_type) CentOS ]; theninstall_CentOS7
elif [ $(os_type) Rocky ]; theninstall_Rocky
elif [ $(os_type) Ubuntu ]; theninstall_Ubuntu
elsecolor 未识别的操作系统 1
fi
2.3 连接到 Redis
2.3.1 客户端连接到 Redis
1.本机无密码连接
redis-cli2.跨主机无密码连接
redis-cli -h HOSTNAME/IP -p PORT3.跨主机密码连接
redis-cli -h HOSTNAME/IP -p PORT -a PASSWORD2.3.2 程序连接 Redis
redis 支持多种开发语言访问
https://redis.io/docs/latest/develop/clients/shell 脚本写入数据到 Redis
#!/bin/bashNUM100
PASS123456for i in seq $NUM; doredis-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/redis6379.service、redis6380.service 等文件然后通过 systemctl start redis6379 启动指定实例。 安全与访问控制 确保为每个实例设置合理的访问控制如 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 多实例
# 生成的文件列表
[rootRocky9.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[rootRocky9.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.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.aofvim /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.aofvim /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]
DescriptionRedis persistent key-value database
Afternetwork.target[Service]
ExecStart/apps/redis/bin/redis-server /apps/redis/etc/redis_6379.conf --supervised systemd
ExecStop/bin/kill -s QUIT $MAINPID
Typenotify
Userredis
Groupredis
RuntimeDirectoryredis
RuntimeDirectoryMode0755[Install]
WantedBymulti-user.target[rootRocky9.4 ~]## 2./usr/lib/systemd/system/redis6380.service
[Unit]
DescriptionRedis persistent key-value database
Afternetwork.target[Service]
ExecStart/apps/redis/bin/redis-server /apps/redis/etc/redis_6380.conf --supervised systemd
ExecStop/bin/kill -s QUIT $MAINPID
#Typenotify
Userredis
Groupredis
RuntimeDirectoryredis
RuntimeDirectoryMode0755[Install]
WantedBymulti-user.target[rootRocky9.4 ~]## 3./usr/lib/systemd/system/redis6381.service
[Unit]
DescriptionRedis persistent key-value database
Afternetwork.target[Service]
ExecStart/apps/redis/bin/redis-server /apps/redis/etc/redis_6381.conf --supervised systemd
ExecStop/bin/kill -s QUIT $MAINPID
#Typenotify
Userredis
Groupredis
RuntimeDirectoryredis
RuntimeDirectoryMode0755[Install]
WantedBymulti-user.target
[rootRocky9.4 ~]#systemctl daemon-reload
systemctl enable --now redis6379.service redis6380.service redis6381.service
# 这里有个问题,通过二进制安装好的Redis,start的时候用tab键无法补全4.Redis 持久化
Redis 是一个基于内存的数据结构存储系统但它提供了多种持久化机制可以将内存中的数据保存到磁盘中从而在 Redis 重启或服务器宕机后依然能够恢复数据。Redis 主要提供了两种持久化方式RDBRedis Database 和 AOFAppend Only File。这两种方式可以单独使用也可以配合使用具体选择取决于业务需求对数据一致性、写入性能、磁盘空间等的不同要求。
4.1 RDB(Redis Database)
RDB 方式是 Redis 最早的持久化模式即在某个时间点对内存数据做快照并保存到一个 .rdb 文件中
4.1.1 RDB 的工作机制 方法1: SAVE 命令是“阻塞式”保存Redis 不会创建子进程而是直接由主进程把内存数据写到 RDB 文件里。 [rootRocky9.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 redispoolredis.ConnectionPool(host10.0.0.41,port6379,password123456)
rredis.Redis(connection_poolpool)
for i in range(10000000):r.set(k%d % i,v%d % i)datar.get(k%d % i)print(data)方法2: BGSAVE 才是“后台”保存Redis 会 fork 一个子进程来完成 RDB 持久化主进程继续对外提供服务。 [rootRocky9.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在备份
[rootRocky9.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
# 备份结束以后,将文件重命名
[rootRocky9.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# 也可以查看日志
[rootRocky9.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方法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 AOFAppend Only File 从 Redis 7.0.0开始以及之后的版本中AOFAppend 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相关配置
[rootRocky9.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# 查看备份的目录
[rootRocky9.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.rdb1 directory, 6 files
[rootRocky9.4 data]#pwd
/apps/redis/data
[rootRocky9.4 data]#根据业务需求选择合适的同步策略
always适用于对数据安全性要求极高的场景但性能开销较大。everysec默认策略适用于大多数场景平衡了性能和数据安全性。no适用于对性能要求极高且可以容忍数据丢失的场景。 在 Redis 7.4 中AOFAppend 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.rdb180M 作用 这是一个 基础 RDB 快照 文件。它包含了在某个时间点上 Redis 数据库的完整状态。作为 AOF 持久化的一部分Redis 7.4 结合了 RDB 和 AOF 的优点通过基础 RDB 文件和增量 AOF 文件来实现高效的数据持久化和恢复。 优点 快速恢复通过加载 RDB 快照Redis 可以快速恢复到某个时间点的状态而无需重放所有 AOF 命令。减少文件大小基础 RDB 文件存储了数据的全量快照后续的增量 AOF 文件只记录自快照以来的变化避免了 AOF 文件过大的问题。 2. appendonly-6379.aof.1.incr.aof56 作用 这是一个 增量 AOF 文件记录了自基础 RDB 快照以来的所有写操作命令如 SET, HSET, LPUSH 等。增量 AOF 文件用于补充基础 RDB 快照确保在恢复时可以通过重放这些命令来达到最新的数据状态。 优点 高效写入相比传统 AOF 记录所有写操作增量 AOF 只记录快照之后的变化减少了磁盘写入量。灵活管理可以定期生成新的基础 RDB 快照并清理旧的增量 AOF 文件优化存储空间。 3. appendonly-6379.aof.manifest98 作用 这是一个 清单文件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 参数配置
[rootredis.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 手动触发重写机制
[rootRocky9.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[rootRocky9.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
[rootRocky9.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 命令。可以按照以下方式配置
[rootRocky9.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
[rootRocky9.4 etc]#redis-cli -u redis://alice:123456127.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 exitKey 权限~ / %
~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 和 flushdb
user default on 123456 logs:* ~* all -FLUSHALL -FLUSHDB# 动态管理 ACL
# 1.ACL List(查看当前所有用户配置的详细信息包括用户名称、密码哈希、权限列表、key patterns 等。)
[rootRocky9.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 alice1) flags2) 1) on2) sanitize-payload3) passwords4) 1) 87b9b7660e9fe77503b14eb6da0277151e031aad3a88a1673b798d8443af242b5) commands6) -all7) keys8) 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.conf5.2 INFO
作用查看 Redis 服务器的各种统计信息和状态例如内存使用情况、复制状态、连接数、持久化信息、keyspace 信息等。
使用示例
127.0.0.1:6379 INFO keyspace
# Keyspace
db0:keys10000001,expires0,avg_ttl0,subexpiry0
127.0.0.1:6379127.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:nametcp,bind0.0.0.0,bind-::1,port6379
127.0.0.1:6379Redis 会返回一个多段文本包含大量信息可按模块划分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。如果只想保存数据而不关闭可以执行 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 表示扫描结束。相比 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) k45337792) k2521143) k9332354) k36767895) k25765376) k75736777) k52857708) k22679509) k247360110) k4433328返回结果中的第一个元素 13107200 是新的游标值下次扫描时要用它。第二个数组是本次扫描到的一批 Key例如 5~10 个。
后续调用
127.0.0.1:6379 SCAN 13107200 COUNT 10
1) 4456448
2) 1) k14509122) k91029893) k68297084) k34106775) k25138696) k95642077) k76832968) k29511799) k611372610) 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 直到扫描完所有 KeySCAN 采用增量扫描每次只处理一部分能把阻塞时间分散到多个小的时间片对线上性能影响更小。 用法 KEYS 适合在测试或小规模场景下调试时使用方便一次性获取所有匹配 key生产环境中强烈推荐使用 SCAN能避免大规模阻塞。 一致性 KEYS 在那一刻会返回快照式的所有 KeySCAN 可能会出现漏扫或重复尤其当 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 参数
SETNXSet if Not eXists只在 key 不存在时设置成功。等价于 SET key value NX。SETXXSet 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 1306.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 Redis6.1.2.3.4 STRLEN 作用返回字符串值的长度字节数。 示例 SET name Alice
STRLEN name # 返回 56.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、NFSRedis 中只存元数据或指针。 使用过期策略 若用 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:63796.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 会创建一个新的哈希并设置字段 nameAlice、age20 HGET 作用获取 hash 表中某个字段的值。 语法 HGET key field示例 HGET user:1001 name
# 返回 Alice6.2.2.1.2 HMSET / HMGET HMSET在新版本 Redis 中 HSET key field value ... 已经可以替代HMSET 旧命令仍兼容 作用同时设置多个字段值和 HSET 的多字段形式一致。 示例 HMSET user:1001 city Beijing gender femaleHMGET 作用一次性获取多个字段的值。 示例 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否则返回 06.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
# 如果 price100.0则更新后 price109.996.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获取字段总数 删除 HDEL删除一个或多个字段 扫描 HSCAN分批遍历所有字段 其他 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 # 返回 hellomylist 变为 [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 # 返回 36.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 移除 banana6.4.2.1.3 SISMEMBER 作用判断某个元素是否为集合成员。 语法 SISMEMBER key member返回值1 表示是集合成员0 表示不是或不存在。 示例 SISMEMBER myset apple
# 若 myset 包含 apple 则返回 1否则返回 06.4.2.2 获取、查看和随机操作
6.4.2.2.1 SMEMBERS 作用返回集合中的所有元素。 示例 SMEMBERS myset
# 比如返回 [apple,orange,grape]注意 如果集合元素非常多一次性返回也会占用较多内存和带宽生产环境中若集合规模庞大可以考虑使用 SSCAN 命令分批次扫描类似于 SCAN。
6.4.2.2.2 SCARD 作用返回集合中的元素数量。 示例 SCARD myset # 返回例如 36.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 提供了三个重要的集合运算命令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/LIFOList 或 Stream 更适合。 集合运算开销 SINTER, SUNION, SDIFF 等运算在集合很大时也可能比较耗时尤其当参与运算的集合规模都非常庞大时。若是在线查询需要注意别在关键路径上做大规模集合运算以免阻塞 Redis 主线程。可以在业务层缓存结果或在后端服务分层处理。 过期策略 Redis 不支持对 Set 中的单个元素设置过期但可以对整个 key 设置过期时间EXPIRE key seconds。若需要对其中的部分元素做时间控制可能需要别的结构或自行管理。 6.4.5 小结
Set 在 Redis 中是一个非常基础且强大的数据结构最显著的特点是
元素唯一、无序、快速判重提供交集、并集、差集等集合运算适合去重、好友列表、标签管理、抽奖等常见场景。
其核心命令可以简要归纳为
添加 / 删除 SADD / SREM 查询 SMEMBERS全量SISMEMBER单一判断SCARD数量SSCAN分批扫描 随机 SPOP随机弹出并删除SRANDMEMBER随机获取不删除 集合运算 SINTER, SUNION, SDIFF 以及对应的 STORE 版本 其他 SMOVE已弃用命令Redis 版本里没有 SMOVE 可以用 SPOPSADD 替代LTRIM 不适用于 SetSSCAN 用于分批遍历等。
只要掌握这些命令和组合应用就能应对大部分业务中需要“去重、随机、集合运算”的需求。在实际开发中还要结合 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 会被更新为 1006.5.2.1.2 ZREM 作用从有序集合中删除指定元素支持删除多个。 语法 ZREM key member1 [member2 ...]示例 ZREM leaderboard playerC
# 删除 playerC 这个 member6.5.2.1.3 ZINCRBY 作用对指定 member 的分数进行增量操作加上给定的数值。 语法 ZINCRBY key increment member示例 ZINCRBY leaderboard 50 playerA
# playerA 的分数在原来的基础上 50注意如果 member 不存在会先以 score0 创建该 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 / ZRANGEBYDICTRedis 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 keyZCOUNT统计指定 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 做相应的合并或计算。常见的是 ZINTERSTORE 和 ZUNIONSTORE。
ZINTERSTORE计算给定的一个或多个有序集合的交集将结果存储到新集合中并根据每个元素在各集合中的 score 值执行相应的加权或求和。ZUNIONSTORE并集操作score 同样可以加权或求和。
示例
ZADD zset1 1 a 2 b
ZADD zset2 2 a 3 cZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3
# 这里 2 表示有 2 个集合WEIGHTS 2 3 分别表示 zset1 的 score 乘以2, zset2 的 score 乘以3
# a 在 zset1 score1, zset2 score2
# 计算交集则 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 查询到期任务进行处理后删除可以替代一些简单的定时任务逻辑。 时间序列 以时间戳作为 scoremember 是事件或数据 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查看排名索引位置ZCARDZSet 大小 获取数据 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-SubscriberPub/Sub 6.7.1 生产者-消费者Producer-Consumer
6.7.1.1 模式概念
生产者Producer负责发送消息或任务到队列Queue中。消费者Consumer从队列中取出消息或任务并进行处理。
常见的形态是“消息队列”生产者把消息投递到队列消费者从队列里取出并消费最终队列中存储的是待处理的消息。
6.7.1.2 工作流程
生产者将消息发送到消息队列消息队列负责临时存储这些消息充当缓冲区消费者从队列中轮询或阻塞获取消息通常是 FIFO先进先出 first infirst 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 Streams5.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 Streams5.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 中的全部消息从 id0 开始# 创建消费者组
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 StreamsRedis 5.0 官方提供的“日志流”数据结构支持有序存储、消费者组、ACK 确认、回溯消息等能满足中等规模、较高可靠性要求的消息队列场景。
根据业务需求选择合适的模式和实现方式才能在性能与可靠性之间找到平衡。对简单场景List 队列最易上手若需要相对专业的队列功能又想依赖 Redis生态则 Streams 是更好的选择若需要高可靠性和大规模分布式集群可进一步研究专业MQ或结合 Redis 与其他系统。