Redis使用

Redis

redis简介

简介

Redis 是完全开源的,遵守 BSD 协议,是一个高性能的 key-value 数据库。

Redis 与其他 key-value 缓存产品有以下三个特点:

  • 1:Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  • 2:Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
  • 3:Redis支持数据的备份,即master-slave模式的数据备份。

Redis 优势

redis性能极高 -- (10万~100万+ QPS),微秒级响应,mysql较慢(通常几千~几万 QPS),毫秒级响应(依赖索引和硬件)

丰富的数据类型 -- Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。

原子 -- Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。

丰富的特性 -- Redis还支持 publish/subscribe, 通知, key 过期等等特性。

Redis与其他key-value存储有什么不同?

Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。

Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。

Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。

在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。

同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。

Redis 数据类型

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

哈希槽的基本概念

槽的数量

Redis集群将整个键空间划分为16384个哈希槽(0-16383),这是经过权衡后确定的固定数量‌

‌数据映射规则

通过HASH_SLT = CRC16(key) mod 16384公式计算键对应的槽号‌,确保相同key会始终映射到同一槽。

数据分布与节点管理

节点分配机制

每个集群节点负责部分槽的集合(如节点A负责0-5000,节点B负责5001-10000)

动态迁移与扩容

槽的分配支持在线调整,但相比传统哈希取余算法,迁移数据量更可控

Redis常见应用场景

Redis 凭借其高性能、丰富的数据结构和灵活的部署方式,已成为现代应用架构中的关键组件。除了大家熟知的缓存功能,Redis 在众多业务场景中都能发挥重要作用。本文将系统梳理 Redis 的常见使用场景,深入分析每种场景的技术实现和最佳实践,帮助开发者充分发挥 Redis 的价值。

热点数据缓存

这是 Redis 最经典的应用场景。在电商、社交等高频访问场景中,将热点数据(如商品详情、用户信息)缓存到 Redis,可大幅降低数据库压力,提升系统响应速度。

实现要点:

  • 缓存策略:采用 Cache-Aside 模式(先查缓存,未命中再查数据库并更新缓存)
  • 过期控制:设置合理的 TTL(生存时间),避免缓存数据长期不更新
  • 缓存键设计:使用业务前缀 + 唯一标识,如product:info:1001
  • 空值处理:对查询不到的数据缓存空值(短 TTL),防止缓存穿透

分布式锁

在分布式系统中,多个服务实例需要协同操作共享资源时,分布式锁是保证数据一致性的关键机制。Redis 的SET NX命令天然适合实现分布式锁。

实现要点:

  • 加锁:使用SET key value NX PX 30000(不存在则设置,过期时间 30 秒)
  • 解锁:通过 Lua 脚本原子性判断并删除锁,避免误删
  • 续期:对长时间运行的任务,需定时延长锁的有效期
  • 重试机制:获取锁失败时,实现合理的重试策略

计数器与限流

Redis 的原子递增命令INCR非常适合实现计数器功能,结合过期时间可实现接口限流。

典型应用:

  • 文章阅读量、视频播放次数统计
  • 接口访问频率限制(如每分钟最多 100 次请求)
  • 秒杀活动中的库存计数

排行榜系统

Redis 的 Sorted Set(有序集合)数据结构完美适配排行榜需求,支持按分数实时排序。

适用场景:

  • 商品销量榜、用户积分榜
  • 游戏排行榜(如分数、通关时间)
  • 实时投票结果展示

分布式会话

在分布式系统中,使用 Redis 存储用户会话信息,可实现多服务实例间的会话共享。

实现优势:

  • 会话信息集中存储,服务水平扩展无压力
  • 支持设置过期时间,自动清理无效会话
  • 可实现会话共享,提升用户体验

消息队列

利用 Redis 的 List 数据结构可实现简单的消息队列,适合对消息可靠性要求不高的场景。

实现方式:

  • 生产者:使用LPUSH将消息推入队列
  • 消费者:使用BRPOP阻塞式获取消息(避免轮询)
  • 支持多个消费者协同工作,实现负载均衡

位图应用

Redis 的 Bitmap(位图)是一种节省空间的二进制数据结构,适合存储大量布尔值信息。

典型场景:

  • 用户签到记录(如一年内的签到情况)
  • 活跃用户统计(如最近 7 天活跃用户)
  • 权限标记(如用户拥有的权限集合)

地理信息存储

Redis 的 GEO 数据结构专门用于存储和操作地理坐标信息,适合 LBS(基于位置的服务)场景。

应用场景:

  • 附近的人 / 店铺查询
  • 地理位置标记与距离计算
  • 区域内的实体搜索

Redis特性

  • 速度快:10WQPS,基于内存,C语言实现
  • 单线程
  • 持久化
  • 支持多种数据结构
  • 支持多种编程语言
  • 功能丰富:支持Lua脚本,发布订阅,事务,pipeline等功能
  • 简单:代码短小精悍(单机核心代码只有23000行左右),单线程开发容易,不依赖外部库,使用简单
  • 主从复制
  • 支持高可用和分布式

单线程

Redis6.0版本前一直是单线程方式处理用户的请求

    1. Redis 的操作访问内存;数据库是访问硬盘
    1. Redis核心功能,比数据库的核心功能更简单

数据库对于数据的插入删除查询... 都有更复杂的功能支持,这样的功能势必要花费更多的开销。

比如,针对插入删除,数据库中的各种约束,都会使数据库做额外的工作(分组,联合查询...)

    1. 单线程模型,避免了一些不必要的线程竞争开销

Redis每个基本操作都是短平快,就是简单操作一下内存数据,不是什么特别消耗 CPU 的操作,就算搞多个线程,提升也不大

    1. 处理网络 IO 的时候,使用了epoll这样的IO多路复用

IO多路复用

本质上是让我们一个线程,管理多个socket

针对TCP来说,服务器这边每次要服务一个客户端,都需要给这个客户端安排一个socket ,通过这个socket来和客户端进行通信

一个服务器要服务多个客户端,同时就有很多个socket 。这些 socket 上都是无时无刻都在传输数据吗?还是大部分时间都是闲置的?

很多情况下,每个客户端和服务器之间的通信也没那么频繁。此时这么多socket大部分时间都是静默的,上面是没有数据需要传输的。

同一时刻,只有少数socket是活跃的,这就是我们使用IO复用的重大前提

最开始介绍 TCP 服务器的时候,有一个版本就是每个客户端都分配一个线程==>客户端多了,线程就多了,系统开销就大了

基于上述的这些原因,就搞了"IO多路复用",一个线程来处理多个socket

操作系统,给程序员提供的机制,提供了一套 API,内部的功能都是操作系统内核实现的

Linux上提供的 IO多路复用,主要是三套 API:select 、poll 、epoll

epoll

比如你今晚,你想吃烧烤,你妈想吃饺子,你爸想吃炒菜

    1. 你自己去买,先买烧烤,等;再买饺子,等;再买炒菜,等;完成。------>单线程,花的时间是最多的
    1. 你们三个一起去买,各买各的,分别等;完成------>多线程,花的时间会缩短很多,等待的时间是三者最大值,系统开销也会更大
    1. 你自己去买,你先去买烧烤,给老板说"好了叫我";再去买饺子,给老板说"好了叫我";再去买炒菜,给老板说"好了叫我",此时一个线程同时等待三份饭------>IO 多路复用的方案,此时等待的时间相比于多线程方案,相差不大,但是只需要一个线程就可以了

最关键的就是老爸能够喊我,哪个客户端来数据了,操作系统就能通知到应用程序

服务器开发中最主流的方案,尤其是 IO多路复用中的epoll

epoll相当于时间通知/回调机制select是你自己再几个摊位之间不停地问,没人提醒你

如果这三件事都是交互特别频繁的,还是老老实实多搞几个线程靠谱,一个线程容易忙不过来

C++可以直接使用 Linux 原生的epoll``API

Java可以使用NIO (标准库提供的一组类,底层就是封装了epoll )

注意事项:

  • 一次只运行一条命令
  • 避免执行长(慢)命令:keys *,flushall, flushdb, slow lua script, mutil/exec,operate bigvalue(collection)
  • 其实不是单线程:早期版本是单进程单线程,3.0版本后实际还有其它的线程,实现特定功能,如:fysnc file descriptor,close file descriptor

缓存实现过程

计算机系统中的三层存储结构

计算机系统中,默认有两种缓存

缓存的第一个特征:在一个层次化的系统中,缓存一定是一个快速子系统,数据存在缓存中时,能避免每次从慢速子系统中存取数据。

缓存的第二个特征:缓存系统的容量大小总是小于后端慢速系统的,我们不可能把所有数据都放在缓存系统中。

Redis缓存处理请求的两种情况

把 Redis 用作缓存时,我们会把 Redis 部署在数据库的前端,业务应用在访问数据时,会先查询 Redis 中是否保存了相应的数据。此时,根据数据是否存在缓存中,会有两种情况。

  • 缓存命中:Redis 中有相应数据,就直接读取 Redis,性能非常快。
  • 缓存缺失:Redis 中没有保存相应数据,就从后端数据库中读取数据,性能就会变慢。而且,一旦发生缓存缺失,为了让后续请求能从缓存中读取到数据,我们需要把缺失的数据写入 Redis,这个过程叫作缓存更新。缓存更新操作会涉及到保证缓存和数据库之间的数据一致性问题。

发生缓存命中或缺失时,应用读取数据的情况

使用 Redis 缓存时,我们基本有三个操作:

  • 应用读取数据时,需要先读取 Redis;
  • 发生缓存缺失时,需要从数据库读取数据;
  • 发生缓存缺失时,还需要更新缓存。

那么,这些操作具体是由谁来做的呢?

Redis 作为旁路缓存的使用操作

Redis 是一个独立的系统软件,和业务应用程序是两个软件,当我们部署了 Redis 实例后,它只会被动地等待客户端发送请求,然后再进行处理。所以,如果应用程序想要使用 Redis 缓存,我们就要在程序中增加相应的缓存操作代码。所以,我们也把 Redis 称为旁路缓存,也就是说,读取缓存、读取数据库和更新缓存的操作都需要在应用程序中来完成。

那么,使用 Redis 缓存时,具体来说,我们需要在应用程序中增加三方面的代码:

  • 当应用程序需要读取数据时,我们需要在代码中显式调用 Redis 的 GET 操作接口,进行查询;
  • 如果缓存缺失了,应用程序需要再和数据库连接,从数据库中读取数据;
  • 当缓存中的数据需要更新时,我们也需要在应用程序中显式地调用 SET 操作接口,把更新的数据写入缓存。

为了使用缓存,Web 应用程序需要有一个表示缓存系统的实例对象 redisCache,还需要主动调用 Redis 的 GET 接口,并且要处理缓存命中和缓存缺失时的逻辑,例如在缓存缺失时,需要更新缓存。

缓存的类型

按照 Redis 缓存是否接受写请求,我们可以把它分成只读缓存和读写缓存。

只读缓存

当 Redis 用作只读缓存时,应用要读取数据的话,会先调用 Redis GET 接口,查询数据是否存在。而所有的数据写请求,会直接发往后端的数据库,在数据库中增删改。对于删改的数据来说,如果 Redis 已经缓存了相应的数据,应用需要把这些缓存的数据删除,Redis 中就没有这些数据了。

当应用再次读取这些数据时,会发生缓存缺失,应用会把这些数据从数据库中读出来,并写到缓存中。这样一来,这些数据后续再被读取时,就可以直接从缓存中获取了,能起到加速访问的效果。

只读缓存直接在数据库中更新数据的好处是,所有最新的数据都在数据库中,而数据库是提供数据可靠性保障的,这些数据不会有丢失的风险。

读写缓存

对于读写缓存来说,除了读请求会发送到缓存进行处理(直接在缓存中查询数据是否存在),所有的写请求也会发送到缓存,在缓存中直接对数据进行增删改操作。

但是,和只读缓存不一样的是,在使用读写缓存时,最新的数据是在 Redis 中,而 Redis 是内存数据库,一旦出现掉电或宕机,内存中的数据就会丢失。

根据业务应用对数据可靠性和缓存性能的不同要求,我们会有同步直写和异步写回两种策略。其中,同步直写策略优先保证数据可靠性,而异步写回策略优先提供快速响应。

同步直写是指,写请求发给缓存的同时,也会发给后端数据库进行处理,等到缓存和数据库都写完数据,才给客户端返回。

而异步写回策略,则是优先考虑了响应延迟。此时,所有写请求都先在缓存中处理。等到这些增改的数据要被从缓存中淘汰出来时,缓存将它们写回后端数据库。

同步直写和异步写回

小结

缓存的两个特征,分别是在分层系统中,数据暂存在快速子系统中有助于加速访问;缓存容量有限,缓存写满时,数据需要被淘汰。而 Redis 天然就具有高性能访问和数据淘汰机制,正好符合缓存的这两个特征的要求,所以非常适合用作缓存。

Redis 作为旁路缓存的特性,旁路缓存就意味着需要在应用程序中新增缓存逻辑处理的代码。

缓存穿透,缓存击穿和缓存雪崩

在程序中,没有缓存的时候,调用流程是这样的:

当我们加入缓存以后,程序就会变成以下样子:

加入缓存之后,程序不直接调用数据库,而是先调用缓存,当缓存中存在数据就直接返回,当缓存中没有数据才去查询数据库大大降低了数据库的压力,加快了程序响应的速度。

缓存的优点

相对于数据库而言,缓存的操作性更高效,原因有以下几点:

  • 缓存一般使用 key-value 查询数据的,不需要想数据库那样还有查询条件。
  • 缓存的数据存储在内存中,而数据库的数据是存储在磁盘中的,内存的操作性能远远大于磁盘。
  • 缓存更容易实现分布式部署(一台服务器变成多台相连的服务器集群),而数据库比较难实现分布式部署,缓存的性能更容易平行扩展。

缓存的分类

缓存可以分为以下两类:

  • 本地缓存:也叫单机缓存,也就是说在一台服务器上缓存,因此只适用于当前系统。
  • 分布式缓存:用来应用在分布式系统中的缓存。分布式系统就是将一套服务器部署到多台服务器,通过负载(通常是 Nginx)分发将用户的请求按照一定的规则分发到不同服务器上,如下图:

在上图加入分布式缓存后变成下图:

雪崩问题

  • 1.定义

指缓存层由于大面积失效(如同一时间批量过期)或缓存服务宕机,导致所有请求瞬间涌入数据库,导致数据库崩溃的现象。

    1. 通俗理解

想象一个游乐场,所有出口的闸机(Redis)同时坏了或者同时过期了,几千人一下子全部冲向检票口,大门(数据库)瞬间被挤爆。

    1. 发生场景

批量 Key 同时过期:例如电商首页促销,1000 个商品商品的缓存都设置了 10 分钟过期,时间一到,1000 个请求同时打库。

Redis 集群宕机:Redis 挂了,请求无路可走,直接穿透到 DB。

    1. 后果

系统级故障,服务不可用,数据库压力达到峰值甚至挂掉。

我们来对比一下正常情况与缓存雪崩情况:

正常情况:

雪崩情况:

如何解决缓存雪崩问题

解决方案

    1. 过期时间加随机值:给 Key 的过期时间加个随机范围(如10分钟 + 1~5分钟),打散过期时间,避免集体爆炸。
    1. 搭建高可用集群:Redis 多主多从,哨兵模式,避免单点故障。
    1. 服务限流熔断:使用 Sentinel 或 Hystrix 限制并发数,保护数据库。
    1. 多级缓存:本地缓存 (Guava) + Redis,即使 Redis 挂了,本地还有数据。

缓存穿透

    1. 定义

指查询一个根本不存在的数据(缓存中没有,数据库中也没有)。因为缓存不缓存空值,所以每次请求都会绕过缓存直接去查数据库。

    1. 通俗理解

你拿着一张假身份证去游乐场检票,检票员(Redis)看了一眼说没这张票,就让你去后台查(数据库)。后台查了半天也没有,就让你回去了。结果来了一万个拿着假身份证的人,都去后台折腾,后台累坏了。

    1. 发生场景

攻击者恶意查询user_id = -1或不存在的商品 ID。

业务代码 Bug 导致反复查无效 Key。

    1. 后果

无效请求反复打击数据库,导致 DB 资源浪费,甚至被 DOS 攻击打挂。

红色表示缓存穿透的执行路径,可以看出缓存穿透会给数据库造成巨大的压力。

如何解决缓存穿透问题

有以下几种解决方案:

  • 接口层增加校验,如用户鉴权校验,ID做基础校验,id<=0的直接拦截
  • 缓存空结果:对查询的空结果也进行缓存,如果是集合,可以缓存一个空的的集合,如果是缓存单个对象,可以字段标识来区分,避免请求穿透到数据库。
  • 布隆过滤器处理:将所有可能对应的数据为空的 key 进行统一的存放,并在请求前做拦截,避免请求穿透到数据库(这样的方式实现起来相对麻烦,比较适合命中不高,但是更行不频繁的数据)。

缓存击穿

    1. 定义

指热点 Key(访问量极大的 Key)在某个时刻过期,此时恰好有大量并发请求同时打过来。因为缓存中没有,这些请求都会去查询数据库。

    1. 通俗理解

游乐场只有一个 VIP 通道(热点 Key),这个通道刚好关闭了(过期了),但此时有一万人正排队等着通过,结果大家一拥而上,把通道旁边的闸机(数据库)挤坏了。

    1. 发生场景

秒杀活动、热点新闻爆发。

某个商品缓存刚好过期,瞬间 10w QPS 涌入。

    1. 区别(重点!)

雪崩是大面积(很多 Key 一起挂)。

击穿是单点(一个热点 Key 挂了,但它很重要)。

    1. 后果

针对特定库表的攻击,导致该表压力剧增,响应变慢甚至拖垮。

如何解决缓存击穿问题

有以下几种解决方案:

  • 加锁排队:和处理缓存雪崩的加锁类似,都是在查询数据库的时候加锁排队,缓存操作请求以此来减少服务器的运行压力。
  • 设置永不过时:对于某些经常使用的缓存,我们可以设置为永不过期,这样就能保证缓存的稳定性,但要注意在数据更改后,要及时更新此热点缓存,否则就会造成查询结果误差。

Pipeline流水线

redis客户端执行一条命令分4个过程:

发送命令-〉命令排队-〉命令执行-〉返回结果

这个过程称为Round trip time(简称RTT, 往返时间),mget mset有效节约了RTT,但大部分命令(如hgetall,并没有mhgetall)不支持批量操作,需要消耗N次RTT ,这个时候需要pipeline来解决这个问题

Redis提供了批量操作命令(例如mget、mset等),有效地节约RTT。但大部分命令是不支持批量操作的,例如要执行n次hgetall命令,并没有mhgetall命令存在,需要消耗n次RTT。Redis的客户端和服务端可能部署在不同的机器上。例如客户端在北京,Redis服务端在上海,两地直线距离约为1300公里,那么1次RTT时间=1300×2/(300000×2/3)=13毫秒(光在真空中传输速度为每秒30万公里,这里假设光纤为光速的2/3),那么客户端在1秒内大约只能执行80次左右的命令,这个和Redis的高并发高吞吐特性背道而驰。

Pipeline(流水线)机制能改善上面这类问题,它能将一组Redis命令进行组装,通过一次RTT传输给Redis,再将这组Redis命令的执行结果按顺序

未使用pipeline执行N条命令

没有使用Pipeline执行了n条命令,整个过程需要n次RTT

使用pipeline执行N条命令

使用Pipeline执行了n次命令,整个过程需要1次RTT。Pipeline并不是什么新的技术或机制,很多技术上都使用过。而且RTT在不同网络环境下会有不同,例如同机房和同机器会比较快,跨机房跨地区会比较慢。Redis命令真正执行的时间通常在微秒级别,所以才会有Redis性能瓶颈是网络这样的说法。

两者性能对比

小结:这是一组统计数据出来的数据,使用Pipeline执行速度比逐条执行要快,特别是客户端与服务端的网络延迟越大,性能体能越明显。

redis的安装

centos7 redis源码安装

bash 复制代码
# 安装依赖包(tcl用于Redis测试,gcc/gcc-c++是C/C++编译器);yum:CentOS/RHEL的RPM包管理器,install:安装子命令,-y:全程自动回答yes不再交互确认
[root@localhost ~]# yum install tcl gcc gcc-c++ -y

# 解压redis安装包到/usr/local/目录(zxvf: gzip解压+解tar+显示过程);tar:归档解压工具,z调用gzip解压,x解包,v显示过程,f指定文件名,-C /usr/local/:解压到指定目录
[root@localhost ~]# tar zxvf redis-6.2.14.tar.gz -C /usr/local/

# 进入解压后的源码目录;cd:切换工作目录命令,后接目标绝对路径
[root@localhost ~]# cd /usr/local/redis-6.2.14/

# 编译源码并安装到系统路径(make编译,make install安装到/usr/local/bin);&&:前一条命令成功才继续执行后一条
[root@localhost redis-6.2.14]# make && make install

# 启动Redis服务并放在后台运行(redis-server启动服务,redis.conf指定配置文件,&后台运行)
[root@localhost redis-6.2.14]# redis-server redis.conf &

# 查看Redis启动端口(netstat查看网络连接,-a所有 -n数字显示 -t TCP -p进程,grep过滤6379);|:管道把前一命令结果交给后一命令过滤
[root@localhost redis-6.2.14]# netstat -antp | grep 6379
tcp        0      0 127.0.0.1:6379          0.0.0:*               LISTEN      7751/redis-server 1     # 6379端口已监听,进程PID=7751
tcp6       0      0 ::1:6379                :::*                  LISTEN      7751/redis-server 1     # IPv6的6379端口也已监听

# 使用redis-cli客户端连接进入Redis交互命令行界面;redis-cli为Redis自带命令行客户端,不带参数默认连接本机127.0.0.1:6379
[root@localhost redis-6.2.14]# redis-cli
127.0.0.1:6379>     # 已进入Redis交互式命令行,等待输入Redis命令

centos 8 yum安装

bash 复制代码
# CentOS Stream 8由appstream仓库提供redis,CentOS7需要额外配置epel仓库;dnf:CentOS8包管理器,info:查询软件包详情子命令,redis:软件包名
[root@localhost ~]# dnf info redis
Last metadata expiration check: 0:00:18 ago on Thu 12 Feb 2026 03:04:51 PM CST.     # 最近一次检查仓库元数据的时间
Available Packages     # 表示该软件包可从仓库获取、尚未安装
Name        : redis     # 软件包名
Version     : 5.0.3     # 版本号
Release     : 5.module_el8.4.0+955+7126e393
Architecture: x86_64     # CPU架构为64位x86
Size        : 927 k
Source      : redis-5.0.3-5.module_el8.4.0+955+7126e393.src.rpm
Repository  : appstream     # 来自appstream仓库
Summary     : A persistent key-value database     # 软件包简介
URL         : http://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.

# 安装redis(dnf是CentOS8的包管理器,-y自动确认);install:安装子命令,redis:软件包名
[root@localhost ~]# dnf install -y redis

# 设置服务开机自启并立即启动(systemctl enable开机自启,--now立即启动服务);redis:服务名
[root@localhost ~]# systemctl enable redis --now

# 查看redis-server监听6379端口(ss查看socket统计,-t TCP -u UDP -n数字 -l监听 -p进程名)
[root@localhost ~]# ss -tunlp
Netid        State         Recv-Q         Send-Q                   Local Address:Port                   Peer Address:Port          Process
tcp          LISTEN        0              128                     0.0.0.0:22                           0.0.0:*                 users:(("sshd",pid=954,fd=3))
tcp          LISTEN        0              511                   127.0.0.1:6379                         0.0.0:*                 users:(("redis-server",pid=10350,fd=6))     # redis-server监听127.0.0.1:6379,PID=10350
tcp          LISTEN        0              128                         [::]:22                              [::]:*                 users:(("sshd",pid=954,fd=4))

# 查看redis进程树(pstree显示进程树,-p显示PID,grep过滤redis)
[root@localhost ~]# pstree -p | grep redis
           |-redis-server(10350)-+-{redis-server}(10351)     # redis-server主进程10350及其3个工作线程
           |                       |-{redis-server}(10352)
           |                       `-{redis-server}(10353)

# 进入redis-cli客户端交互界面;redis-cli为Redis自带命令行客户端,默认连接本机6379
[root@localhost ~]# redis-cli

# 发送ping测试连接,返回PONG表示连接正常
127.0.0.1:6379> ping
PONG     # 返回PONG说明连接正常,Redis服务存活

# 查看Redis服务器完整信息(info命令输出服务端/客户端/内存等统计)
127.0.0.1:6379> info

# Server
redis_version:5.0.3     # 当前Redis版本号
redis_git_sha1:00000000     # Git提交哈希(官方编译版为全0)
......

开启Redis多实例

bash 复制代码
# 开启第二个Redis实例(--port指定端口6380,避免与默认6379冲突);redis-server:启动Redis服务程序,6380:新实例监听端口号
[root@localhost ~]# redis-server --port 6380

# 查看TCP监听端口(ss -ntl: n数字 t TCP l监听)
[root@localhost ~]# ss -ntl
State                 Recv-Q               Send-Q                                   Local Address:Port                                   Peer Address:Port            Process
LISTEN                0                    128                                       0.0.0.0:22                                         0.0.0:*          # SSH远程登录端口
LISTEN                0                    511                                   127.0.0.1:6379                                       0.0.0:*          # 默认Redis实例6379
LISTEN                0                    511                                       0.0.0.0:6380                                       0.0.0:*          # 新开的Redis多实例6380

# 连接到6380端口的Redis实例(-p指定要连接的端口号);redis-cli:命令行客户端
[root@localhost ~]# redis-cli -p 6380

# 发送ping测试连接是否正常
127.0.0.1:6380> ping
PONG     # 6380多实例连接测试成功

安装完,拍摄快照

Redis配置文件说明

why 6379

Redis 默认使用端口号 6379,这一选择主要源于其作者 Salvatore Sanfilippo(昵称 antirez)的一个个人趣闻,而非技术上的特殊含义。

根据作者在个人博客和社交媒体上的说明,6379 这个数字来源于手机键盘上字母 "MERZ" 对应的按键。‌

6379 "MERZ" 是作者及其朋友用来形容"愚蠢"的俚语,源自意大利女演员 Alessia Merz 在电视节目中的一些表现,作者觉得她的话很可笑,于是创造了这个词。‌6379当为 Redis 选择默认端口时,antirez 没有过多思考,直接将 "MERZ" 在手机键盘上对应的数字 6379 用作了 Redis 的默认端口。‌

意大利女演员 Alessia Merz

bash 复制代码
# 使用vim编辑器打开Redis配置文件;vim:文本编辑器,/etc/redis.conf:Redis配置文件路径
[root@localhost ~]# vim /etc/redis.conf
69 bind 127.0.0.1            #Redis 仅监听本地回环地址 127.0.0.1,这意味着只有本机可以连接Redis。若需远程访问,应改为 bind 0.0.0.0(但务必配合密码使用)。
88 protected-mode yes        #启用保护模式。当 Redis 未设置密码且未明确绑定到特定 IP(或绑定了外网 IP)时,只允许来自 127.0.0.1 和 ::1 的连接。这是防止未授权访问的安全机制。
92 port 6379                 #Redis 监听 TCP 端口 6379(默认端口)。
101 tcp-backlog 511          #设置 TCP 连接全队列(completed connection queue)的最大长度为 511。高并发场景下可能需要调大,并同步调整系统参数 /proc/sys/net/core/somaxconn
113 timeout 0                #客户端空闲连接超时时间为 0,表示永不因空闲而断开连接。
130 tcp-keepalive 300        #每 300 秒(5 分钟)向客户端发送一次 TCP keepalive 探测包,用于检测死连接。
136 daemonize no             #Redis 不作为守护进程运行,而是以前台方式启动(适合容器化部署或手动调试)。
147 supervised no            #不与操作系统进程管理器(如 systemd 或 upstart)集成。
158 pidfile /var/run/redis_6379.pid   #指定 Redis 主进程 PID 写入的文件路径。注意:该目录需存在且 Redis 进程有写权限。
166 loglevel notice          #日志级别设为 notice,记录重要信息(如启动、关闭、持久化事件),适合生产环境。
171 logfile /var/log/redis/redis.log  #日志输出到指定文件 /var/log/redis/redis.log。确保该路径可写。
186 databases 16             #Redis 启动时创建 16 个逻辑数据库(编号 0 到 15),可通过 SELECT <dbid> 切换。
194 always-show-logo yes     #在启动redis时是否显示或在日志中记录记录redis的logo

# RDB 快照持久化策略
218 save 900 1               #900 秒内至少有 1 个 key 变更 → 触发 bgsave
219 save 300 10              #300 秒内至少有 10 个 key 变更 → 触发 bgsave
220 save 60 10000            #60 秒内至少有 10000 个 key 变更 → 触发 bgsave
stop-writes-on-bgsave-error yes  #如果后台 RDB 保存失败(如磁盘满),Redis 将拒绝写入操作,防止数据丢失未被察觉。
rdbcompression yes           #对 RDB 文件中的字符串值启用 LZF 压缩,节省磁盘空间(推荐开启)。
rdbchecksum yes              #在 RDB 文件末尾添加 CRC64 校验和,提高文件损坏检测能力(性能损耗约 10%)。
dir /var/lib/redis           #指定 Redis 工作目录。RDB 文件、AOF 文件等都将存储在此目录下。必须是目录路径(不能是文件)。
dbfilename dump.rdb          #RDB默认文件名
replica-serve-stale-data yes #当从节点(replica)与主节点失联时,仍可继续响应客户端请求(可能返回过期数据)。若设为 no,则除部分管理命令外均返回错误。
replica-read-only yes        #从节点默认为只读模式,禁止客户端在其上执行写命令(防止数据不一致)。
repl-diskless-sync no        #主节点在全量同步时,先将 RDB 文件写入磁盘,再发送给从节点(而非直接通过 socket 流式传输)。
repl-diskless-sync-delay 5   #当启用 repl-diskless-sync yes 时,主节点等待 5 秒再开始传输,以便更多从节点能加入批量同步。
replica-priority 100         #从节点优先级(供 Redis Sentinel 选主使用)。数值越小优先级越高;设为 0 表示永不参与选主。
requirepass foobared         #设置 Redis 访问密码为 foobared,客户端需先执行 AUTH foobared 才能操作。
maxclients 10000             # 最大客户端连接数
appendonly no                # 禁用 AOF 持久化(仅使用 RDB)
appendfilename "appendonly.aof"  # AOF 文件名(当前未生效)
appendfsync everysec         # 若启用 AOF,每秒 fsync 一次(平衡性能与安全)
no-appendfsync-on-rewrite no  # RDB/AOF 重写期间仍执行 appendfsync
auto-aof-rewrite-percentage 100  # AOF 文件比上次重写后增长 100% 时触发重写
auto-aof-rewrite-min-size 64mb    # AOF 文件至少 64MB 才触发自动重写
aof-load-truncated yes       # 启动时若 AOF 被截断,尝试加载并继续启动
aof-use-rdb-preamble yes     # AOF 重写使用 RDB 前导(混合持久化,加快恢复)
lua-time-limit 5000          # Lua 脚本最大执行时间(毫秒)
slowlog-log-slower-than 10000  # 慢查询阈值:10000 微秒(10 毫秒)
slowlog-max-len 128          # 慢查询日志最多保留 128 条
latency-monitor-threshold 0  # 延迟监控阈值(0 = 关闭)
notify-keyspace-events ""    # 键空间通知事件(空 = 禁用)
hash-max-ziplist-entries 512 # Hash 使用 ziplist 编码的最大元素数
hash-max-ziplist-value 64    # Hash 使用 ziplist 编码的单个 field/value 最大字节数
list-max-ziplist-size -2     # List 的 quicklist 每个 ziplist 节点最大为 8KB(-2)
list-compress-depth 0        # List 的 quicklist 压缩深度(0 = 不压缩)
set-max-intset-entries 512   # Set 使用 intset 编码的最大整数元素数
zset-max-ziplist-entries 128 # Sorted Set 使用 ziplist 编码的最大元素数
zset-max-ziplist-value 64    # Sorted Set 使用 ziplist 编码的单个 member/score 最大字节数
hll-sparse-max-bytes 3000    # HyperLogLog 稀疏表示最大字节数(超过转稠密)
stream-node-max-bytes 4096    # Stream 单个节点最大字节数
stream-node-max-entries 100   # Stream 单个节点最大消息条数
activerehashing yes          # 启用渐进式 rehash,避免卡顿
client-output-buffer-limit normal 0 0 0        # 普通客户端输出缓冲区无限制
client-output-buffer-limit replica 256mb 64mb 60  # 从节点:硬限 256MB,软限 64MB 持续 60 秒则断开
client-output-buffer-limit pubsub 32mb 8mb 60   # Pub/Sub 客户端:硬限 32MB,软限 8MB 持续 60 秒则断开
hz 10                        # 后台任务每秒执行 10 次(如过期 key 清理)
dynamic-hz yes               # 动态调整 hz(连接多时自动提高)
aof-rewrite-incremental-fsync yes  # AOF 重写时增量 fsync(每 32MB 刷一次)
rdb-save-incremental-fsync yes    # RDB 保存时增量 fsync(每 32MB 刷一次)

config命令实现动态修改配置

config命令用于查看当前redis配置,以及不重启redis服务实现动态更改redis配置等

Caution

不是所有配置都可以动态修改,且此方法无法持久化保存

CONFIG SET parameter value

时间复杂度:O(1)

CONFIG SET命令可以动态地调整Redis服务器的配置(configuration)而无须重启。

可以使用它修改配置参数,或者改变Redis的持久化(Persistence)方式。

CONFIG SET可以修改的配置参数可以使用命令CONFIG GET *来列出,所有被CONFIG SET修改的配置参数都会立即生效。

CONFIG GET parameter

时间复杂度:O(N),其中N为命令返回的配置选项数量。

cONFIG GET命令用于取得运行中的Redis服务器的配置参数(configuration parameters),在Redis2.4版本中,有部分参数没有办法用CONFIG GET访问,但是在最新的Redis2.6版本中,所有配置参数都已经可以用CONFIG GET访问了。

CONFIG GET接受单个参数parameter作为搜索关键字,查找所有匹配的配置参数,其中参数和值以"键-值对"(key-value pairs)的方式排列。

比如执行CONFIG GET s*命令,服务器就会返回所有以s开头的配置参数及参数的值:

案例:版本差异

bash 复制代码
# redis 7支持动态修改端口(config set动态设置,port端口,8888新端口号)
127.0.0.1:6379> config set port 8888
OK     # Redis7动态修改端口成功,立即生效

# redis 5不支持动态修改端口,返回错误提示
127.0.0.1:6379> config set port 8888
(error) ERR Unsupported CONFIG parameter: port     # Redis5不支持动态修改port参数

设置客户端连接密码

bash 复制代码
# 设置连接密码(CONFIG SET动态配置,requirepass访问密码,123456新密码)
127.0.0.1:6379> CONFIG SET requirepass 123456
OK     # 密码设置成功,后续连接需认证

# 带密码连接redis-cli(-a后跟密码,会有安全警告)
[root@localhost ~]# redis-cli -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.     # 安全警告:命令行明文传密码可能被历史记录泄露

# 查看当前设置的密码(CONFIG GET获取配置,requirepass查看密码)
127.0.0.1:6379> CONFIG GET requirepass
1) "requirepass"     # 返回的配置项键名
2) "123456"     # 返回的密码值

获取当前配置

bash 复制代码
# 查看所有配置(CONFIG GET *通配符获取全部配置,奇数行是键,偶数行是值)
127.0.0.1:6379> CONFIG GET *
1) "dbfilename"     # 奇数行:配置键名
2) "dump.rdb"     # 偶数行:对应的配置值
3) "requirepass"     # 配置键名
4) "123456"     # 对应的密码值
......

# 查看bind配置项;CONFIG GET:查询运行配置,bind:监听地址配置项
127.0.0.1:6379> CONFIG GET bind
1) "bind"     # 键名
2) "127.0.0.1"     # 当前bind的值为本地回环地址

# Redis5.0有些设置无法修改,Redis6.2.14版本支持修改bind;CONFIG SET:动态设置配置,BIND(大写):参数名,0.0.0.0:监听所有网卡
127.0.0.1:6379> CONFIG SET BIND 0.0.0.0
(error) ERR Unsupported CONFIG parameter: BIND     # Redis5不支持大写BIND,报错

# config set bind:用小写参数名动态设置,bind为监听地址,0.0.0.0表示监听所有网卡(Redis6.2.14支持)
127.0.0.1:6379> config set bind 0.0.0.0
OK     # Redis6.2.14小写bind动态设置成功,监听所有网卡

设置Redis使用最大内存容量

bash 复制代码
# 查看默认最大内存配置(CONFIG GET maxmemory查看内存限制,0表示无限制)
127.0.0.1:6379> CONFIG GET maxmemory
1) "maxmemory"     # 键名
2) "0"     # 默认值0表示不限制最大内存

# 设置最大内存为10086字节(默认以字节为单位);CONFIG SET:动态设置,maxmemory:最大内存项,10086:以字节为单位
127.0.0.1:6379> CONFIG SET maxmemory 10086
OK     # 设置成功,单位为字节

# 再次查看确认设置生效;CONFIG GET:查询maxmemory当前值
127.0.0.1:6379> CONFIG GET maxmemory
1) "maxmemory"     # 键名
2) "10086"     # 已生效,限制为10086字节

#  也可以直接写单位G(1G=10亿字节);maxmemory:最大内存,1G:带单位写法
127.0.0.1:6379> CONFIG SET maxmemory 1G
OK     # 用G单位设置成功

# 查看结果,内部已转换为字节数;CONFIG GET:查询,结果统一以字节返回
127.0.0.1:6379> CONFIG GET maxmemory
1) "maxmemory"     # 键名
2) "1000000000"     # 1G=1000000000字节,自动换算

慢查询

许多存储系统(例如MySQL)提供慢查询日志帮助开发和运维人员定位系统存在的慢操作。所谓慢查询日志就是系统在命令执行前后计算每条命令的执行时间,当超过预设阀值,就将这条命令的相关信息(例如:发生时间,耗时,命令的详细信息)记录下来,Redis也提供了类似的功能。

一条客户端命令声明周期如下:

  • 1)发送命令
  • 2)命令排队
  • 3)命令执行
  • 4)返回结果

两点说明:

    1. 慢查询发生在第三阶段
    1. 客户端超时不一定慢查询,但慢查询是客户端超时的一个可能因数

多慢算慢?---》超出阈值就算慢

bash 复制代码
# 1000us(微秒)=1ms(毫秒);vim:编辑Redis配置文件,/etc/redis.conf:配置文件路径
[root@localhost ~]# vim /etc/redis.conf
995 slowlog-log-slower-than 1    #单位为us,指定超过1us即为慢的指令,默认值为10000us
999 slowlog-max-len 1024         #指定只保存最近的1024条慢记录,默认值为128

# systemctl:systemd服务管理工具,restart:重启服务,redis:服务名(重启使慢查询配置生效)
[root@localhost ~]# systemctl restart redis

# 查看慢日志的记录条数;redis-cli:命令行客户端,-a:后接认证密码123456
[root@localhost ~]# redis-cli -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface     # 命令行明文传密码的安全警告(换行显示为两行)
may not be safe.

# SLOWLOG:慢查询日志子命令,LEN:查看慢日志当前记录条数
127.0.0.1:6379> SLOWLOG LEN
(integer) 5     # 当前慢日志共记录5条

# SLOWLOG GET:查看慢日志详细列表(每条含ID/时间戳/耗时/命令/客户端信息)
127.0.0.1:6379> SLOWLOG GET
1) 1) (integer) 5              #项目唯一ID
   2) (integer) 1770892970     #unix时间戳
   3) (integer) 3              #执行耗时(微秒)
   4) 1) "SLOWLOG"             #执行的命令
      2) "LEN"
   5) "127.0.0.1:56406"        #客户端IP和端口
   6) ""                       #客户端名称(未设置)
2) 1) (integer) 4
   2) (integer) 1770892963
   3) (integer) 379
   4) 1) "COMMAND"
   5) "127.0.0.1:56406"
   6) ""
3) 1) (integer) 3
   2) (integer) 1770892963
   3) (integer) 1
   4) 1) "AUTH"
      2) "123456"
   5) "127.0.0.1:56406"
   6) ""
4) 1) (integer) 2
   2) (integer) 1770892863
   3) (integer) 18
   4) 1) "CONFIG"
      2) "GET"
      3) "bind"
   5) "127.0.0.1:38008"
   6) ""
5) 1) (integer) 1
   2) (integer) 1770892859
   3) (integer) 659
   4) 1) "COMMAND"
   5) "127.0.0.1:38008"
   6) ""
6) 1) (integer) 0
   2) (integer) 1770892859
   3) (integer) 1
   4) 1) "AUTH"
      2) "123456"
   5) "127.0.0.1:38008"
   6) ""
127.0.0.1:6379>     # 慢日志列表输出完毕,回到命令提示符

# 清空慢日志;SLOWLOG RESET:清空所有慢查询日志记录
127.0.0.1:6379> SLOWLOG RESET
OK     # 慢日志已全部清空

RAIDS持久化

什么是 Redis 持久化?

Redis 作为一个键值对内存数据库(NoSQL),数据都存储在内存当中,在处理客户端请求时,所有操作都在内存当中进行,如下所示

这样做有什么问题呢?其实,只要稍微有点计算机基础知识的人都知道,存储在内存当中的数据,只要服务器关机(各种原因引起的),内存中的数据就会消失了。

不仅服务器关机会造成数据消失,Redis 服务器守护进程退出,内存中的数据也一样会消失。

对于只把 Redis 当缓存来用的项目来说,数据消失或许问题不大,重新从数据源把数据加载进来就可以了。

但如果直接把用户提交的业务数据存储在 Redis 当中,把 Redis 作为数据库来使用,在其放存储重要业务数据,那么 Redis 的内存数据丢失所造成的影响也许是毁灭性。

为了避免内存中数据丢失,Redis 提供了对持久化的支持,我们可以选择不同的方式将数据从内存中保存到硬盘当中,使数据可以持久化保存。

Redis 提供了 RDB 和 AOF 两种不同的数据持久化方式:

RDB:Redis DataBase

AOF:Appen-only File

RDB

RDB 是一种快照存储持久化方式,具体就是将 Redis 某一时刻的内存数据保存到硬盘的文件当中,默认保存的文件名为 dump.rdb ,而在 Redis 服务器启动时,会重新加载 dump.rdb 文件的数据到内存当中恢复数据。

开启 RDB 持久化方式

开启 RDB 持久化方式很简单,客户端可以通过向 Redis 服务器发送 save 或 bgsave 命令让服务器生成 RDB 文件,或者通过服务器配置文件指定触发 RDB 条件。

save 命令:是一个同步操作
bash 复制代码
# 同步数据到磁盘上(SAVE 同步阻塞保存,保存期间服务器无法响应其他请求)
127.0.0.1:6379> save
OK     # RDB快照已同步保存完成

当客户端向服务器发送 Save 命令请求进行持久化时,服务器会阻塞 Save 命令之后的其他客户端的请求,直到数据同步完成。

如果数据量太大,同步数据会执行很久,而这期间 Redis 服务器也无法接收其他请求,所以,最好不要在生产环境使用 Save 命令。

范例:save执行过程会使用主进程进行快照

bash 复制代码
# 查看默认进程;pstree:查看进程树,-p:显示PID,| grep:过滤redis-server,;:分隔执行后续命令,ll -h:人类可读长列表,/var/lib/redis:Redis数据目录
[root@localhost ~]# pstree -p | grep redis-server ; ll -h /var/lib/redis
           |-redis-server(2104)-+-{redis-server}(2105)     # redis-server主进程2104及其3个工作线程
                                   |-{redis-server}(2106)
                                   `-{redis-server}(2107)
total 4.0K     # 该目录总用量
-rw-r--r-- 1 redis redis  92 Feb 21 17:58 dump.rdb     # RDB文件仅92字节(几乎无数据)

# 执行save(SAVE 同步阻塞保存,直接使用主进程做快照);redis-cli:进入命令行客户端,随后在客户端内执行save
[root@localhost ~]# redis-cli

# debug populate:调试用批量造数命令,5000000:生成500万个测试key(调试命令不写入持久化)
127.0.0.1:6379> debug populate 5000000    #这是 Redis 的一个调试命令(debug command),用于快速生成大量测试数据,这个命令不会触发持久化(AOF / RDB 不会记录它,因为它属于调试命令)。
OK     # 500万测试key生成成功
(5.99s)     # 生成耗时5.99秒

# save:同步阻塞执行RDB快照,期间主进程无法响应其他请求
127.0.0.1:6379> save
OK     # 同步RDB保存完成
(5.07s)     # 保存耗时5.07秒(阻塞期间)

# 再开个窗口;在新窗口查看进程与数据目录,确认save仍由主进程执行、未fork子进程
[root@localhost ~]# pstree -p | grep redis-server ; ll -h /var/lib/redis
           |-redis-server(2104)-+-{redis-server}(2105)     # 主进程2104仍只有原来的3个线程,未fork子进程(对比bgsave)
                                   |-{redis-server}(2106)
                                   `-{redis-server}(2107)
total 64M     # 目录总用量
-rw-r--r-- 1 redis redis  92 Feb 21 17:59 dump.rdb     # dump.rdb仍是92字节
-rw-r--r-- 1 redis redis 51M Feb 21 17:59 temp-2104.rdb        #主进程号2104
Bgsave:与 Save 命令不同,Bgsave 命令是一个异步操作。

当客户端发服务发出 bgsave 命令时,Redis 服务器主进程会 Forks 一个子进程来数据同步问题,在将数据保存到 RDB 文件之后,子进程会退出。

所以,与 save 命令相比,Redis 服务器在处理 Bgsave 采用子线程进行 IO 写入。

而主进程仍然可以接收其他请求,但 Forks 子进程是同步的,所以 Forks 子进程时,一样不能接收其他请求。

这意味着,如果 Forks 一个子进程花费的时间太久(一般是很快的),Bgsave 命令仍然有阻塞其他客户的请求的情况发生。

**服务器配置自动触发:**除了通过客户端发送命令外,还有一种方式,就是在 Redis 配置文件中的 Save 指定到达触发 RDB 持久化的条件,比如【多少秒内至少达到多少写操作】就开启 RDB 数据同步。

例如我们可以在配置文件 redis.conf 指定如下的选项:

这种通过服务器配置文件触发 RDB 的方式,与 Bgsave 命令类似,达到触发条件时,会 Forks 一个子进程进行数据同步。

范例:手动执行备份RDB

bash 复制代码
# 异步保存数据到磁盘上(BGSAVE 后台fork子进程保存)
127.0.0.1:6379> bgsave
Background saving started     # 后台RDB保存已开始,主进程可继续响应请求
bash 复制代码
# 以下是默认值;vim:查看/etc/redis.conf中RDB自动触发策略配置
 [root@localhost ~]# vim /etc/redis.conf
218 save 900 1      # 900秒内修改了1个KEY即触发保存RDB
219 save 300 10     # 300秒内修改了10个KEY即触发保存RDB
220 save 60 10000   # 60秒内修改了10000个KEY即触发保存RDB
bash 复制代码
# 连接本地Redis命令行客户端
[root@localhost ~]# redis-cli

# flushall:清空所有数据库中的全部key
127.0.0.1:6379> flushall
OK     # 已清空全部数据库

# debug populate:调试命令批量生成500万测试key
127.0.0.1:6379> debug populate 5000000
OK     # 500万key生成成功
(8.99s)     # 耗时8.99秒

# get key:查询指定key的值(下面依次验证造数结果)
127.0.0.1:6379> get key:0
"value:0"     # key:0的值为value:0
127.0.0.1:6379> get key:1
"value:1"     # key:1的值
127.0.0.1:6379> get key:2
"value:2"     # key:2的值
127.0.0.1:6379> get key:499999
"value:499999"     # 最大有效key为499999(共500万个,编号0~499999)
127.0.0.1:6379> get key:5000000
(nil)     # key:5000000不存在,返回nil

# bgsave:再次异步后台保存,观察是否fork子进程
127.0.0.1:6379> bgsave
Background saving started     # 后台RDB保存已开始

#再开个窗口;在新窗口查看进程树与数据目录,确认bgsave fork出了子进程
[root@localhost ~]# pstree -p | grep redis-server ; ll -h /var/lib/redis
           |-redis-server(2104)-+-redis-server(2266)     # 主进程2104 fork出子进程2266专门做快照
                                   |-{redis-server}(2105)
                                   |-{redis-server}(2106)
                                   `-{redis-server}(2107)
total 191M     # 目录总用量
-rw-r--r-- 1 redis redis 127M Feb 21 17:54 dump.rdb     # 上一次保存的正式RDB文件127M
-rw-r--r-- 1 redis redis  62M Feb 21 17:55 temp-2266.rdb         #新开了个子进程
2266     # fork出的子进程PID号

RDB 文件

前面介绍了三种让服务器生成 RDB 文件的方式,无论是由主进程生成还是子进程来生成,其过程如下:

  • 生成临时 RDB 文件,并写入数据。
  • 完成数据写入,用临时文代替代正式 RDB 文件。
  • 删除原来的 DB 文件。

RDB 默认生成的文件名为 dump.rdb,当然,我可以通过配置文件进行更加详细配置。

bash 复制代码
241 rdbcompression yes     # 是否压缩rbd文件
253 dbfilename dump.rdb    # rdb文件的名称(RDB文件名)
263 dir /var/lib/redis     # rdb文件保存目录(保存目录)

RDB的几个优点:

  • 与 AOF 方式相比,通过 RDB 文件恢复数据比较快。
  • RDB 文件非常紧凑,适合于数据备份。
  • 通过 RDB 进行数据备份,由于使用子进程生成,所以对 Redis 服务器性能影响较小。

RDB 的几个缺点:

如果服务器宕机的话,采用 RDB 的方式会造成某个时段内数据的丢失,比如我们设置 10 分钟同步一次或 5 分钟达到 1000 次写入就同步一次,那么如果还没达到触发条件服务器就死机了,那么这个时间段的数据会丢失。

使用 Save 命令会造成服务器阻塞,直接数据同步完成才能接收后续请求。

使用 Bgsave 命令在 Forks 子进程时,如果数据量太大,Forks 的过程也会发生阻塞,另外,Forks 子进程会耗费内存。

AOF

聊完了 RDB,来聊聊 Redis 的另外一个持久化方式:AOF(Append-only file)。

与 RDB 存储某个时刻的快照不同,AOF 持久化方式会记录客户端对服务器的每一次写操作命令,并将这些写操作以 Redis 协议追加保存到以后缀为 AOF 文件末尾。

在 Redis 服务器重启时,会加载并运行 AOF 文件的命令,以达到恢复数据的目的。

①开启 AOF 持久化方式

Redis 默认不开启 AOF 持久化方式,我们可以在配置文件中开启并进行更加详细的配置,如下面的 redis.conf 文件:

bash 复制代码
# aof机制默认关闭
699 appendonly no     # appendonly:是否开启AOF持久化,no=默认关闭

# aof文件名(AOF文件名)
703 appendfilename "appendonly.aof"     # AOF持久化文件名

# 写入策略,always表示每个写操作都保存到aof文件中,也可以是everysec或no(appendfsync 刷盘策略)
729 appendfsync everysec     # AOF刷盘策略:everysec=每秒fsync一次

# 默认不重写aof文件
751 no-appendfsync-on-rewrite no     # 重写期间是否暂停fsync,no=照常刷盘

# 保存目录
dir /var/lib/redis     # AOF/RDB文件保存目录

错误开启AOF功能,会导致数据丢失

注意:AOF模式默认是关闭的,第一次开启AOF后,并重启服务生效后,会因为AOF的优先级高于RDB,而AOF默认没有数据文件存在,从而导致所有数据丢失

bash 复制代码
# redis-cli:连接本地Redis命令行客户端
[root@localhost ~]# redis-cli

# dbsize:统计当前数据库的key总数
127.0.0.1:6379> dbsize
(integer) 5000000     # 当前库共有500万个key

# vim:编辑Redis配置文件
[root@localhost ~]# vim /etc/redis.conf
699 appendonly yes               #修改此行(开启AOF)

# systemctl restart redis:重启Redis服务使配置生效
[root@localhost ~]# systemctl restart redis

# 重启后再次连接redis-cli
[root@localhost ~]# redis-cli

# dbsize:再次统计key数量
127.0.0.1:6379> dbsize
(integer) 0     # 重启后key数为0(未开启AOF/RDB未恢复)

# 还原步骤;rm -f:强制删除不提示,删除AOF文件以便还原
[root@localhost ~]# rm -f /var/lib/redis/appendonly.aof

# 再次编辑配置文件
[root@localhost ~]# vim /etc/redis.conf
699 appendonly no               #修改此行

# systemctl restart redis:重启Redis服务
[root@localhost ~]# systemctl restart redis

# 连接redis-cli
[root@localhost ~]# redis-cli

# FLUSHALL:清空所有数据库的全部key
127.0.0.1:6379> FLUSHALL
OK     # 已清空全部数据

# DEBUG POPULATE:调试命令重新生成500万测试key
127.0.0.1:6379> DEBUG POPULATE 5000000
OK     # 生成成功
(4.86s)     # 耗时4.86秒

# DBSIZE:确认恢复后的key总数
127.0.0.1:6379> DBSIZE
(integer) 5000000     # 恢复后仍有500万key

正确启用AOF功能,防止数据丢失

bash 复制代码
# ll /var/lib/redis/:长列表查看Redis数据目录内容
[root@localhost ~]# ll /var/lib/redis/
total 4     # 目录总用量
-rw-r--r-- 1 redis redis 127M Feb 21 17:27 dump.rdb     # 仅有RDB文件,尚无AOF文件

# 连接redis-cli
[root@localhost ~]# redis-cli

# config get appendonly:查询AOF持久化是否开启
127.0.0.1:6379> config get appendonly
1) "appendonly"     # 配置键名
2) "no"     # 当前AOF为关闭状态

# config set appendonly yes:动态开启AOF,会自动重写并备份全部数据到AOF文件
127.0.0.1:6379> config set appendonly yes    #自动触发AOF重写,会自动备份所有数据到AOF文件
OK     # 已动态开启AOF

# 再次查询appendonly确认
127.0.0.1:6379> config get appendonly
1) "appendonly"     # 配置键名
2) "yes"     # 现已开启AOF

# 再次查看数据目录
[root@localhost ~]# ll /var/lib/redis/
total 8     # 目录总用量
-rw-r--r-- 1 redis redis 127M Feb 21 17:29 appendonly.aof     # AOF文件已自动生成
-rw-r--r-- 1 redis redis 127M Feb 21 17:29 dump.rdb     # RDB文件仍保留

# 修改配置文件使其永久生效
[root@localhost ~]# vim /etc/redis.conf
699 appendonly yes               #修改此行

# 验证;systemctl restart redis:重启服务验证持久化效果
[root@localhost ~]# systemctl restart redis

# 重启后连接redis-cli
[root@localhost ~]# redis-cli

# DBSIZE:统计key总数验证数据是否恢复
127.0.0.1:6379> DBSIZE
(integer) 5000000     # 重启后数据仍在,AOF持久化生效

②三种写入策略

在上面的配置文件中,我们可以通过 appendfsync 选项指定写入策略,有三个选项:

bash 复制代码
# appendfsync:AOF刷盘策略配置项,三选一(always最安全最慢,everysec默认折中,no最快但不安全)
appendfsync always     # 每次写操作立刻fsync刷盘,最安全但性能最低(当前启用此项)

# appendfsync everysec     # 每秒fsync一次(默认折中方案,本行被注释未启用)
# appendfsync no     # 由操作系统决定刷盘时机,最快但宕机可能丢数据(本行被注释未启用)

always:客户端的每一个写操作都保存到 AOF 文件当中,这种策略很安全,但是每个写操作都有 IO 操作,所以也很慢。

everysec:appendfsync 的默认写入策略,每秒写入一次 AOF 文件,因此,最多可能会丢失 1s 的数据。

no:Redis 服务器不负责写入 AOF,而是交由操作系统来处理什么时候写入 AOF 文件。更快,但也是最不安全的选择,不推荐使用。

③AOF 文件重写

AOF重写是通过读取服务器当前的数据库状态来实现的。

我举个例子大家就明白了,假设我对 Redis 执行了下面六条命令:

bash 复制代码
# RPUSH:列表写入命令,从右侧(尾部)依次插入元素;list:列表键名;"A"~"F":依次插入的元素值(这些写命令会被AOF记录)
rpush list "A"
rpush list "B"
rpush list "C"
rpush list "D"
rpush list "E"
rpush list "F"

那么服务器为了保存当前 list键 的状态,会在AOF文件中写入上述六条命令。

而我现在要对 AOF 进行重写的话,其实最高效最简单的方式不是挨个读取和分析现有AOF文件中的这六条命令。

而是直接从数据库中读取键 list 的值,然后用一条命令:rpush list "A" "B" "C" "D" "E" "F" ,可以直接代替原 AOF 文件中的六条命令。

命令由六条减少为一条,重写的目的就达到了。

两种重写方式:通过在 redis.conf 配置文件中的选项 no-appendfsync-on-rewrite 可以设置是否开启重写。

这种方式会在每次 Fsync 时都重写,影响服务器性能,因此默认值为 no,不推荐使用。

bash 复制代码
# 默认不重写aof文件;no-appendfsync-on-rewrite:重写期间是否暂停fsync,no=照常刷盘
no-appendfsync-on-rewrite no
bash 复制代码
# 让服务器异步重写追加aof文件命令(BGREWRITEAOF AOF重写)
127.0.0.1:6379> bgrewriteaof
Background append only file rewriting started     # AOF后台重写已开始,fork子进程压缩AOF体积

# pstree查看进程树,; 分隔执行ll -h查看数据目录,确认重写fork了子进程
[root@localhost ~]# pstree -p | grep redis-server ; ll -h /var/lib/redis/
           |-redis-server(1924)-+-redis-server(1940)     # 主进程1924 fork出重写子进程1940
                                   |-{redis-server}(1925)
                                   |-{redis-server}(1926)
                                   `-{redis-server}(1927)
total 318M     # 目录总用量
-rw-r--r-- 1 redis redis 127M Feb 24 14:29 appendonly.aof     # 正在使用的AOF文件
-rw-r--r-- 1 redis redis 127M Feb 24 14:27 dump.rdb     # RDB文件
-rw-r--r-- 1 redis redis  43M Feb 24 14:32 temp-rewriteaof-1940.aof     # 重写期间的临时AOF文件,由子进程1940生成

客户端向服务器发送 bgrewriteaof 命令,也可以让服务器进行 AOF 重写。

AOF 重写方式也是异步操作,即如果要写入 AOF 文件,则 Redis 主进程会 Forks 一个子进程来处理,如下所示:

重写 AOF 文件的好处:
  • 压缩 AOF 文件,减少磁盘占用量。
  • 将 AOF 的命令压缩为最小命令集,加快了数据恢复的速度。

③AOF 文件损坏

在写入 AOF 日志文件时,如果 Redis 服务器宕机,则 AOF 日志文件文件会出格式错误。

在重启 Redis 服务器时,Redis 服务器会拒绝载入这个 AOF 文件,可以通过以下步骤修复 AOF 并恢复数据:

  • 备份现在 AOF 文件,以防万一。
  • 使用 redis-check-aof 命令修复 AOF 文件,该命令格式如下:
bash 复制代码
# 修复aof日志文件;redis-check-aof:AOF检查修复工具,--fix:自动修复截断尾部,后接AOF文件路径
[root@localhost ~]# redis-check-aof --fix /var/lib/redis/appendonly.aof
The AOF appears to start with an RDB preamble.     # AOF以RDB前导开头(混合持久化)
Checking the RDB preamble to start:
[offset 0] Checking RDB file --fix
[offset 26] AUX FIELD redis-ver = '5.0.3'
[offset 40] AUX FIELD redis-bits = '64'
[offset 52] AUX FIELD ctime = '1771664520'
[offset 67] AUX FIELD used-mem = '858552'
[offset 83] AUX FIELD aof-preamble = '1'
[offset 85] Selecting DB ID 0
[offset 1202] Selecting DB ID 1
[offset 1245] Checksum OK     # 校验和正确
[offset 1245] \o/ RDB looks OK! \o/     # RDB前导部分完整无损
[info] 110 keys read     # 共读取110个key
[info] 0 expires
[info] 0 already expired
RDB preamble is OK, proceeding with AOF tail...     # RDB部分正常,继续检查AOF尾部
AOF analyzed: size=1245, ok_up_to=1245, diff=0     # 分析:文件1245字节,完好到1245,差异0(无需修复)
AOF is valid     # AOF文件有效,无需修复

重启 Redis 服务器,加载已经修复的 AOF 文件,恢复数据。

AOF 的优点:

AOF 只是追加日志文件,因此对服务器性能影响较小,速度比 RDB 要快,消耗的内存较少。

AOF 的缺点:
  • AOF 方式生成的日志文件太大,即使通过 AFO 重写,文件体积仍然很大。
  • 恢复数据的速度比 RDB 慢。

选择 RDB 还是 AOF 呢?

通过上面的介绍,我们了解了 RDB 与 AOF 各自的优点与缺点,到底要如何选择呢?

通过下面的表示,我们可以从几个方面对比一下 RDB 与 AOF,在应用时,要根据自己的实际需求,选择 RDB 或者 AOF。

其实,如果想要数据足够安全,可以两种方式都开启。

当 RDB 与 AOF 两种方式都开启时,Redis 会优先使用 AOF 日志来恢复数据,因为 AOF 保存的文件比 RDB 文件更完整。

**小结:**上面讲了一大堆 Redis 的持久化机制的知识,其实,如果你只是单纯把 Redis 作为缓存服务器,那么可以完全不用考虑持久化。

但是,在如今的大多数服务器架构中,Redis 不单单只是扮演一个缓存服务器的角色,还可以作为数据库,保存我们的业务数据,此时,我们则需要好好了解有关 Redis 持久化策略的区别与选择。

客户端连接Redis

bash 复制代码
# 语法
# redis-cli -h IP/HOSTNAME -p PORT -a PASSWORD(redis-cli -h host -p port -a password);redis-cli:命令行客户端,不带参数时连接本机127.0.0.1:6379
bash 复制代码
[root@localhost ~]# redis-cli

# ping:向服务器发送PING测试连通性
127.0.0.1:6379> ping
PONG     # 返回PONG表示连接正常、服务存活
127.0.0.1:6379> info            #显示当前节点redis运行状态信息

# Server     # 服务端信息段(版本/操作系统/进程/端口等)
redis_version:5.0.3     # Redis版本号
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:9529b692c0384fb7
redis_mode:standalone     # 运行模式:单机
os:Linux 4.18.0-553.6.1.el8.x86_64 x86_64
arch_bits:64
multiplexing_api:epoll
atomicvar_api:atomic-builtin
gcc_version:8.4.1
process_id:10350     # 服务进程PID
run_id:fd68b669756ef777ee5d2d4f2a74e851cc243d6a
tcp_port:6379     # 监听端口
uptime_in_seconds:676     # 已运行秒数
uptime_in_days:0
hz:10
configured_hz:10
lru_clock:9272993
executable:/usr/bin/redis-server
config_file:/etc/redis.conf     # 加载的配置文件路径

# Clients     # 客户端连接信息段
connected_clients:1     # 当前已连接客户端数
client_recent_max_input_buffer:2
client_recent_max_output_buffer:0
blocked_clients:0

# Memory     # 内存信息段
used_memory:853896
used_memory_human:833.88K     # Redis已用内存
used_memory_rss:13340672
used_memory_rss_human:12.72M     # 系统分配给Redis的物理内存
used_memory_peak:853896
used_memory_peak_human:833.88K
used_memory_peak_perc:100.00%
used_memory_overhead:840694
used_memory_startup:791000
used_memory_dataset:13202
used_memory_dataset_perc:20.99%
allocator_allocated:1457768
allocator_active:1818624
allocator_resident:11468800
total_system_memory:3810115584
total_system_memory_human:3.55G
used_memory_lua:37888
used_memory_lua_human:37.00K
used_memory_scripts:0
used_memory_scripts_human:0B
number_of_cached_scripts:0
maxmemory:0     # 最大内存限制,0=不限制
maxmemory_human:0B
maxmemory_policy:noeviction     # 内存满时淘汰策略:不淘汰
allocator_frag_ratio:1.25
allocator_frag_bytes:360856
allocator_rss_ratio:6.31
allocator_rss_bytes:9650176
rss_overhead_ratio:1.16
rss_overhead_bytes:1871872
mem_fragmentation_ratio:16.43     # 内存碎片率
mem_fragmentation_bytes:12528776
mem_not_counted_for_evict:0
mem_replication_backlog:0
mem_clients_slaves:0
mem_clients_normal:49694
mem_aof_buffer:0
mem_allocator:jemalloc-5.1.0
active_defrag_running:0
lazyfree_pending_objects:0

# Persistence     # 持久化信息段(RDB/AOF状态)
loading:0     # 是否正在加载持久化文件,0=否
rdb_changes_since_last_save:0
rdb_bgsave_in_progress:0     # 是否正在后台bgsave,0=否
rdb_last_save_time:1770879997
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:-1
rdb_current_bgsave_time_sec:-1
rdb_last_cow_size:0
aof_enabled:0     # AOF是否开启,0=未开启
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:-1
aof_current_rewrite_time_sec:-1
aof_last_bgrewrite_status:ok
aof_last_write_status:ok
aof_last_cow_size:0

# Stats     # 统计信息段
total_connections_received:2     # 累计接收连接数
total_commands_processed:5     # 累计处理命令数
instantaneous_ops_per_sec:0
total_net_input_bytes:90
total_net_output_bytes:26219
instantaneous_input_kbps:0.00
instantaneous_output_kbps:0.00
rejected_connections:0
sync_full:0
sync_partial_ok:0
sync_partial_err:0
expired_keys:0
expired_stale_perc:0.00
expired_time_cap_reached_count:0
evicted_keys:0
keyspace_hits:0
keyspace_misses:0
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:0
migrate_cached_sockets:0
slave_expires_tracked_keys:0
active_defrag_hits:0
active_defrag_misses:0
active_defrag_key_hits:0
active_defrag_key_misses:0

# Replication     # 主从复制信息段
role:master     # 节点角色:主节点
connected_slaves:0     # 已连接从节点数
master_replid:24b2280440a498439ac89b31c65249a5af62b08f
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

# CPU     # CPU占用信息段
used_cpu_sys:0.628455
used_cpu_user:0.440542
used_cpu_sys_children:0.000000
used_cpu_user_children:0.000000

# Cluster     # 集群信息段
cluster_enabled:0     # 集群未启用

# Keyspace     # 数据库键空间信息段(此处为空,无db统计)
127.0.0.1:6379>     # info输出完毕,回到命令提示符

安装的相关程序介绍

bash 复制代码
# ll /usr/bin/redis-* 列出本机所有 Redis 相关可执行程序(ll 是 ls -l 的别名)
[root@localhost ~]# ll /usr/bin/redis-*
-rwxr-xr-x 1 root root  656280 Oct 20  2021 redis-benchmark            #性能测试程序
lrwxrwxrwx 1 root root       12 Oct 20  2021 redis-check-aof -> redis-server    #AOF文件检查程序
lrwxrwxrwx 1 root root       12 Oct 20  2021 redis-check-rdb -> redis-server    #RDB文件检查程序
-rwxr-xr-x 1 root root  827608 Oct 20  2021 redis-cli    #客户端程序
lrwxrwxrwx 1 root root       12 Oct 20  2021 redis-sentinel -> redis-server    #哨兵程序,软连接到服务器端主程序
-rwxr-xr-x 1 root root 1800864 Oct 20  2021 redis-server    #服务端主程序
客户端程序redis-cli
bash 复制代码
# 默认本机无密码连接;不带任何参数时默认连接本机 127.0.0.1 的 6379 端口,无密码
redis-cli

# 远程客户端连接,注意:Redis没有用户的概念;-h 指定 Redis 服务器 IP;-p 指定端口(默认 6379);-a 指定连接密码(requirepass);--no-auth-warning 取消 -a 明文密码带来的安全警告提示
redis-cli -h <Redis服务器IP> -p <PORT> -a <PASSWORD> --no-auth-warning

Redis常用命令

info

显示当前节点redis运行状态信息

bash 复制代码
# INFO 查看 Redis 服务器各项运行信息,不带参数时一次性输出全部分段(Server/Clients/Memory/Persistence/Stats/Replication/Cluster 等)
127.0.0.1:6379> INFO     # Server     # 服务器分段(section)标题
redis_version:5.0.3
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:9529b692c0384fb7
redis_mode:standalone
os:Linux 4.18.0-553.6.1.el8.x86_64 x86_64
......

# 只显示指定部分内容,INFO命是分段的
# 只显示INFO内容# Server这一段;在 INFO 后追加分段名只输出该段,分段名不区分大小写
127.0.0.1:6379> INFO Server     # Server     # 只输出服务器(Server)分段标题及内容
redis_version:5.0.3
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:9529b692c0384fb7
redis_mode:standalone
os:Linux 4.18.0-553.6.1.el8.x86_64 x86_64
......

# 非交互式执行:在系统 shell 中用 redis-cli 直接带一条命令,结果输出后即退出
[root@localhost ~]# redis-cli info Cluster     # Cluster     # 集群(Cluster)分段标题
cluster_enabled:0     # 是否启用集群模式:0 表示未启用

# 不带命令参数,进入交互式 redis-cli 会话
[root@localhost ~]# redis-cli

# 在交互式会话中执行 info cluster,分段名同样不区分大小写
127.0.0.1:6379> info cluster     # Cluster     # 集群(Cluster)分段标题
cluster_enabled:0     # 是否启用集群模式:0 表示未启用
select

切换数据库,相当于MySQL的USE DBNAME指令

bash 复制代码
# SELECT n 切换数据库;n 为数据库编号 dbindex,Redis 默认有 0~15 共 16 个库,编号范围 0~15;切换成功后提示符会显示当前库号
127.0.0.1:6379> select 0
OK     # 成功切换到 0 号库

# 切换到 1 号库,提示符变为 6379[1]
127.0.0.1:6379> select 1
OK     # 成功切换到 1 号库

# 切换到 15 号库,提示符变为 6379[15]
127.0.0.1:6379[1]> select 15
OK     # 成功切换到 15 号库

# 尝试切换到 16 号库,超出合法范围
127.0.0.1:6379[15]> select 16
(error) ERR DB index is out of range     # 16 超出 0~15 范围而报错,且仍停留在 15 号库
127.0.0.1:6379[15]>

注意:在Redis cluster模式下不支持多个数据库

KEYS

查看当前库下所有key,此命令慎用

一、 顺丰高级开发工程师在线执行了 Redis 危险命令导致某公司损失 400 万

最近安全事故濒发啊,前几天发生了《顺丰高级运维工程师的删库事件》,今天又看到了 PHP 工程师在线执行了 Redis 危险命令导致某公司损失 400 万。。

什么样的 Redis 命令会有如此威力,造成如此大的损失?

具体消息如下:

据云头条报道,某公司技术部发生 2 起本年度 PO 级特大事故,造成公司资金损失 400 万,原因如下:

由于 PHP 工程师直接操作上线 redis,执行 keys * wxdb(此处省略)cf8* 这样的命令,导致redis锁住,导致 CPU 飙升,引起所有支付链路卡住,等十几秒结束后,所有的请求流量全部挤压到了 rds 数据库中,使数据库产生了雪崩效应,发生了数据库宕机事件。

该公司表示,如再犯类似事故,将直接开除,并表示之后会逐步收回运维部各项权限。

一个命令损失数百万,这,需要赔偿吗?

代码不规范,同事两行泪,撸码需谨慎!

bash 复制代码
# KEYS pattern 查找key;pattern 为通配符模式:* 匹配任意多个(含 0 个)字符;? 匹配单个字符;[字符] 匹配括号内任意一个字符
[root@localhost ~]# redis-cli

# 先切回 0 号库,便于查看其中已有的 key
127.0.0.1:6379> SELECT 0
OK     # 已切换到 0 号库

# * 通配符匹配当前库下所有 key(数据量大时会遍历阻塞,生产环境慎用)
127.0.0.1:6379> KEYS *
1) "key:91"     # 0 号库下匹配到的 key(节选)
2) "key:87"
......

# 切换到 1 号库
127.0.0.1:6379> SELECT 1
OK     # 已切换到 1 号库

# 查看 1 号库的所有 key
127.0.0.1:6379[1]> KEYS *
(empty list or set)     # 1 号库为空,没有任何 key

# MSET 批量同时设置多组 key value,原子操作
127.0.0.1:6379[1]> MSET one 1 two 2 three 3 four 4    # 一次设置4个key
OK     # 批量设置成功

# *o* 匹配键名中包含字母 o 的所有 key
127.0.0.1:6379[1]> KEYS *o*
1) "four"     # 匹配到含 o 的 key
2) "two"
3) "one"

# t?? 匹配以 t 开头、后面再跟 2 个任意字符的 key(共 3 个字符)
127.0.0.1:6379[1]> KEYS t??
1) "two"     # 唯一匹配到 two

# t[w]* 匹配以 t 开头、第二个字符为 w、后面任意的 key
127.0.0.1:6379[1]> KEYS t[w]*
1) "two"     # 唯一匹配到 two

# 查看 1 号库全部 key,确认最终共有 4 个键
127.0.0.1:6379[1]> KEYS *
1) "three"     # 1 号库全部 key(共 4 个)
2) "four"
3) "two"
4) "one"
命令 时间复杂度
keys O(n)
dbsize O(1)
del O(1)
exists O(1)
expire O(1)
type O(1)
bgsave

手动在后台执行RDB持久化操作

bash 复制代码
# 交互式执行;BGSAVE 异步后台执行 RDB 持久化保存,派生子进程把数据写盘,不阻塞当前会话
127.0.0.1:6379> BGSAVE
Background saving started     # 后台保存已启动

# 非交互式执行;在 shell 中通过 redis-cli 直接调用同一条 BGSAVE 命令
[root@localhost ~]# redis-cli BGSAVE
Background saving started     # 后台保存已启动
DBSIZE

返回当前库下所有key数量(DBSIZE key数量)

bash 复制代码
# DBSIZE 返回当前数据库(当前库)中 key 的总数
127.0.0.1:6379> DBSIZE
(integer) 5000000     # 0 号库共有 500 万个 key

# 切换到 1 号库
127.0.0.1:6379> SELECT 1
OK     # 已切换到 1 号库

# 查看 1 号库的 key 数量
127.0.0.1:6379[1]> DBSIZE
(integer) 0     # 1 号库为空,key 数为 0
FLUSHDB

强制清空当前库中所有key,此命令慎用(FLUSHDB 清空当前库)

bash 复制代码
# 先切回 0 号库
127.0.0.1:6379[1]> select 0
OK     # 已切回 0 号库

# 确认 0 号库现有 key 数量
127.0.0.1:6379> DBSIZE
(integer) 5000000     # 0 号库现有 500 万个 key

# FLUSHDB 清空当前数据库(仅当前库)的所有数据,不影响其他库
127.0.0.1:6379> FLUSHDB                #清空db0所有数据;FLUSHDB 只清当前库、不影响其他库
OK     # 清空成功
(2.59s)     # 该命令实际耗时 2.59 秒

# 再次查看 0 号库 key 数量
127.0.0.1:6379> DBSIZE
(integer) 0     # 已被清空,key 数为 0

# 切到 1 号库,验证其他库不受影响
127.0.0.1:6379> SELECT 1               #db1数据不影响;切到 1 号库验证其他库未被清空
OK     # 已切换到 1 号库

# 查看 1 号库 key 数量
127.0.0.1:6379[1]> DBSIZE
(integer) 4     # 1 号库仍有 4 个 key,证明 FLUSHDB 只清当前库
FLUSHALL

强制清空当前Redis服务器所有数据库中的所有key,即删除所有数据,此命令慎用(FLUSHALL 清空所有库)

bash 复制代码
# FLUSHALL 清空所有数据库(全部 16 个库)的所有数据,比 FLUSHDB 更危险
127.0.0.1:6379> FLUSHALL
OK     # 已清空所有库的数据

# 生产建议修改配置使用rename-command禁用此命令;编辑 Redis 配置文件,用 rename-command 把 FLUSHALL 重命名为空字符串以禁用该命令
[root@localhost ~]# vim /etc/redis.conf
523 rename-command FLUSHALL ""        #flushdb和flushall 配置和AOF功能冲突,需要设置appendonly no,不区分命令大小写
699 appendonly no     # 关闭 AOF 持久化,与禁用 flush 命令相配合

# 测试;重启 redis 服务使配置生效
[root@localhost ~]# systemctl restart redis

# 重新进入交互式会话
[root@localhost ~]# redis-cli

# 再次执行已被禁用的 FLUSHALL
127.0.0.1:6379> FLUSHALL
(error) ERR unknown command `FLUSHALL`, with args beginning with:     # 命令已被重命名禁用,服务器不再认识它

# 此时 FLUSHDB 未被禁用,仍可正常执行
127.0.0.1:6379> FLUSHDB
OK     # FLUSHDB 执行成功,说明只有 FLUSHALL 被禁用
SHUTDOWN

可用版本:>=1.0.0

时间复杂度:O(N),其中 N 为关机时需要保存的数据库键数量。

SHUTDOWN命令执行以下操作:

  • 关闭Redis服务,停止所有客户端连接
  • 如果有至少一个保存点在等待,执行SAVE命令
  • 如果AOF选项被打开,更新AOF文件
  • 关闭redis服务器(server)

如果持久化被打开的话,SHUTDOWN命令会保证服务器正常关闭而不丢失任何数据。

另一方面,假如只是单纯地执行SAVE命令,然后再执行QUIT命令,则没有这一保证一一因为在执行 SAVE之后、执行QUIT之前的这段时间中间,其他客户端可能正在和服务器进行通讯,这时如果执行QUIT就会造成数据丢失。

bash 复制代码
#建议禁用此指令;编辑配置文件,用 rename-command 将 SHUTDOWN 命令重命名为空字符串来禁用,防止误操作关闭服务
[root@localhost ~]# vim /etc/redis.conf
rename-command shutdown ""     # 把 shutdown 命令重命名为空即禁用;命令名不区分大小写

Redis数据类型

Redis是一个非关系型数据库(NoSQL) ,它是一种 Key-Value 型的 NoSQL。Redis所有的 key(键)都是字符串。对应的 value(值)的数据类型,主要包括常见的 5 种数据类型,分别是:String 、List 、Set 、Zset (Sorted set )、Hash。

  • string(字符串):基本的数据存储单元,可以存储字符串、整数或者浮点数。
  • hash(哈希):一个键值对集合,可以存储多个字段。
  • list(列表):一个简单的列表,可以存储一系列的字符串元素。
  • set(集合):一个无序集合,可以存储不重复的字符串元素。
  • zset(sorted set:有序集合):类似于集合,但是每个元素都有一个分数(score)与之关联。

字符串 string

字符串是一种最基本的 Redis 值类型。Redis 字符串是二进制安全的,这意味着一个 Redis 字符串能包含任意类型的数据,例如:一张 JPEG 格式的图片或者一个序列化的 Ruby 对象。一个字符串类型的值最多能存储 512M 字节的内容。Redis 中所有 key 都是字符串类型的。此数据类型最为常用。

创建一个 key

set 指令可以创建一个 key 并赋值,使用格式:

命令 说明
`SET key value [EX seconds] [PX milliseconds] [NX XX]`
GET key 获取 key 的值
SETEX key seconds value 设置并指定过期时间(秒)
SETNX key value 仅当 key 不存在时设置(用于分布式锁)
INCR key 将 key 中的数字加 1
INCRBY key increment 将 key 中的数字增加指定值
DECR / DECRBY 减操作
MSET key1 value1 [key2 value2 ...] 批量设置
MGET key1 [key2 ...] 批量获取
复制代码
SET key value [EX seconds] [PX milliseconds] [NX|XX]
时间复杂度:O(1)
将字符串值 value 关联到 key。
如果 key 已经持有其他值,SET 就覆写旧值,无视类型。
当 SET 命令对一个带有生存时间(TTL)的键进行设置之后,该键原有的 TTL 将被清除。
从 Redis 2.6.12 版本开始,SET 命令的行为可以通过一系列参数来修改:
  EX seconds:将键的过期时间设置为 seconds 秒。
              执行 SET key value EX seconds 的效果等同于执行 SETEX key seconds value
  PX milliseconds:将键的过期时间设置为 milliseconds 毫秒。
              执行 SET key value PX milliseconds 的效果等同于执行 PSETEX key milliseconds value
  NX:只在键不存在时,才对键进行设置操作。
              执行 SET key value NX 的效果等同于执行 SETNX key value
  XX:只在键已经存在时,才对键进行设置操作

范例:

bash 复制代码
# 不论 key 是否存在,都设置
127.0.0.1:6379> set key1 value1     # 设置键 key1,值 value1
OK     # 设置成功

127.0.0.1:6379> get key1     # 查看键 key1 的值
"value1"     # key1 的值

127.0.0.1:6379> type key1     # 判断类型
string     # key1 是字符串类型

127.0.0.1:6379> set title ceo ex 3     # 设置自动过期时间 3s
OK     # 设置成功,3 秒后自动过期

127.0.0.1:6379> set name wang     # 设置键 name,值 wang
OK     # 设置成功

127.0.0.1:6379> get name     # 查看键 name 的值
"wang"     # name 的值

127.0.0.1:6379> get title     # 3s 后查看键 title
(nil)     # title 已过期不存在,返回空

127.0.0.1:6379> get NAME     # 前面赋值的键是小写 name,区分大小写
(nil)     # 大小写敏感,大写 NAME 不存在

127.0.0.1:6379> set NAME li     # 赋值大写 NAME,li
OK     # 设置成功

127.0.0.1:6379> get NAME     # 大写 NAME 对应 li
"li"     # 大写 NAME 对应的值

127.0.0.1:6379> get name     # 小写 name 对应 wang
"wang"     # 小写 name 对应的值

127.0.0.1:6379> setnx title coo     # 只有键 title 不存在值时,赋值 coo
(integer) 1     # 设置成功,1 表示键原本不存在并被设置

127.0.0.1:6379> get title
"coo"     # title 现在的值为 coo

127.0.0.1:6379> setnx title ceo     # 只有键 title 不存在值时,赋值 ceo
(integer) 0     # 设置失败,title 已存在

127.0.0.1:6379> get title     # 由于键 title 存在值,未更新
"coo"     # 仍为 coo,未被更新

127.0.0.1:6379> set title ceo xx     # 只有键已经存在,才操作
OK     # XX 条件满足,设置成功
127.0.0.1:6379> get title
"ceo"     # title 已被更新为 ceo

127.0.0.1:6379> get age
(nil)     # age 不存在

127.0.0.1:6379> set age 20 xx     # 没有 age 这个键,不操作
(nil)     # XX 要求键已存在,age 不存在故不执行

127.0.0.1:6379> get age
(nil)     # age 仍不存在

查看一个key的值

bash 复制代码
127.0.0.1:6379> get key1
"value1"     # key1 的值

# get 只能查看一个 Key 的值

127.0.0.1:6379> get name age
(error) ERR wrong number of arguments for 'get' command     # get 只能跟一个键,参数过多报错

删除key

bash 复制代码
127.0.0.1:6379> DEL key1
(integer) 1     # 成功删除 1 个 key

127.0.0.1:6379> DEL key1 key2     # 删除多个 key,key2 不存在返回 0
(integer) 0     # key1 已删、key2 不存在,实际删除 0 个

批量设置多个key

bash 复制代码
127.0.0.1:6379> MSET key1 value1 key2 vaule2
OK     # 批量设置成功

批量获取多个key

bash 复制代码
127.0.0.1:6379> MGET key1 key2
1) "value1"     # key1 的值
2) "vaule2"     # key2 的值

127.0.0.1:6379> KEYS n*     # 查看以 n 开头的所有 key
1) "name"     # 以 n 开头的 key

127.0.0.1:6379> KEYS *     # 查看所有 key
1) "name"     # 当前库全部 key(共 5 个)
2) "key1"
3) "NAME"
4) "title"
5) "key2"

追加key的数据

bash 复制代码
127.0.0.1:6379> APPEND key1 " append new value"
(integer) 22     # 追加后值的总字节数

127.0.0.1:6379> get key1
"value1 append new value"     # 追加后的新值

设置新值并返回旧值

bash 复制代码
127.0.0.1:6379> set name wang
OK     # 设置成功

127.0.0.1:6379> getset name xiyangyang
"wang"     # 返回 name 原来的旧值

127.0.0.1:6379> get name
"xiyangyang"     # name 已被设置为新值

返回字符串key对应值的字节数

bash 复制代码
127.0.0.1:6379> set name wang
OK     # 设置成功

127.0.0.1:6379> STRLEN name     # 返回字节数
(integer) 4     # "wang" 占 4 个字节

127.0.0.1:6379> APPEND name " teng"     # 追加 key 的数据
(integer) 9     # 追加后总字节数为 9

127.0.0.1:6379> get name
"wang teng"     # 追加后的新值

127.0.0.1:6379> STRLEN name
(integer) 9     # 当前值占 9 个字节

判断key是否存在

bash 复制代码
# 设置 name 并指定 10 秒后过期
127.0.0.1:6379> set name wang ex 10
OK     # 设置成功,10 秒后过期

127.0.0.1:6379> EXISTS name     # 10 秒内判断 key 是否存在
(integer) 1     # name 存在

127.0.0.1:6379> EXISTS name age     # 10 秒内判断多个 key 是否存在
(integer) 2     # 存在 2 个 key

# 返回值为 2,表示存在 2 个 key,0 表示不存在

127.0.0.1:6379> EXISTS name     # 10 秒后判断 key 是否存在
(integer) 0     # 10 秒已过,name 过期不存在

获取key的过期时长

ttl key 查看key的剩余生存时间,如果key过期后,会自动删除

  • -1 返回值表示永不过期,默认创建的key是永不过期,重新对key赋值,也会从有剩余生命周期变成永不过期
  • -2 返回值表示没有此key
  • num key的剩余有效期
bash 复制代码
127.0.0.1:6379> ttl key     # 查看 key 的剩余生存时间,-2 表示没有此 key
(integer) -2     # -2 表示该 key 不存在

127.0.0.1:6379> TTL key1     # -1 返回值表示永不过期,默认创建的 key 是永不过期
(integer) -1     # -1 表示存在但永不过期

# 设置 name 并指定 100 秒后过期
127.0.0.1:6379> set name wang EX 100
OK     # 设置成功,100 秒后过期

127.0.0.1:6379> TTL name     # 查看 key 的剩余生存时间(秒)
(integer) 97     # 剩余约 97 秒

127.0.0.1:6379> TTL name
(integer) 92     # 又过了几秒,剩余 92 秒

127.0.0.1:6379> SET name li     # 重新设置,默认永不过期
OK     # 重新设置后清除过期时间
127.0.0.1:6379> TTL name
(integer) -1     # 已变为永不过期

# 设置 name 并指定 200 秒后过期
127.0.0.1:6379> SET name wang EX 200
OK     # 设置成功,200 秒后过期

127.0.0.1:6379> TTL name
(integer) 198     # 剩余约 198 秒

127.0.0.1:6379> get name
"wang"     # name 的值

重置key的过期时长

bash 复制代码
127.0.0.1:6379> TTL name     # 重新对 key 赋值,会从有剩余生命周期变成永不过期
(integer) 97     # 剩余约 97 秒

127.0.0.1:6379> EXPIRE name 1000
(integer) 1     # 成功为 name 设置 1000 秒过期

127.0.0.1:6379> TTL name
(integer) 996     # 剩余约 996 秒

127.0.0.1:6379> TTL name
(integer) 957     # 剩余约 957 秒

取消key的过期时长

bash 复制代码
127.0.0.1:6379> PERSIST name
(integer) 1     # 成功移除过期时间,变为永久键

127.0.0.1:6379> TTL name
(integer) -1     # 已永久,返回 -1

数字递增

利用INCR命令簇(INCR, DECR, INCRBY, DECRBY)来把字符串当作原子计数器使用。

INCR key 将key中的数字加1

INCRBY key increment 将key中的数字增加指定值

bash 复制代码
127.0.0.1:6379> set num 10
OK     # 设置 num=10

127.0.0.1:6379> INCR num
(integer) 11     # 自增 1 后为 11

127.0.0.1:6379> get num
"11"     # num 当前值

127.0.0.1:6379> INCR num     # 数字递增
(integer) 12     # 再自增 1 为 12

127.0.0.1:6379> get num
"12"     # num 当前值

数字递减

bash 复制代码
127.0.0.1:6379> set num 10
OK     # 重置 num=10

127.0.0.1:6379> DECR num
(integer) 9     # 自减 1 后为 9

127.0.0.1:6379> get num
"9"     # num 当前值

数字增加

将key对应的数字加decrement(可以是负数)。如果key不存在,操作之前,key就会被置为0。如果key的value类型错误或者是个不能表示成数字的字符串,就返回错误。这个操作最多支持64位有符号的正型数字。

bash 复制代码
127.0.0.1:6379> SET mykey 10
OK     # 设置 mykey=10

127.0.0.1:6379> INCRBY mykey 5
(integer) 15     # 增加 5 后为 15

127.0.0.1:6379> get mykey
"15"     # mykey 当前值

127.0.0.1:6379> INCRBY mykey -10     # 数字增加负数相当于减少
(integer) 5     # 增加 -10(即减 10)后为 5

127.0.0.1:6379> get mykey
"5"     # mykey 当前值

127.0.0.1:6379> INCRBY nokey1 5     # key 不存在时,操作前会被置为 0,再加 5
(integer) 5     # nokey1 不存在先置 0 再加 5 得 5

127.0.0.1:6379> get nokey1
"5"     # nokey1 当前值

数字减少

decrby可以减小数值(也可以增加)

bash 复制代码
127.0.0.1:6379> SET mykey 10
OK     # 重置 mykey=10

127.0.0.1:6379> DECRBY mykey 8
(integer) 2     # 减去 8 后为 2

127.0.0.1:6379> get mykey
"2"     # mykey 当前值

127.0.0.1:6379> DECRBY mykey -20     # 数字减少负数相当于增加
(integer) 22     # 减去 -20(即加 20)后为 22

127.0.0.1:6379> get mykey
"22"     # mykey 当前值

127.0.0.1:6379> DECRBY nokey2 3     # key 不存在时,操作前会被置为 0,再减 3
(integer) -3     # nokey2 不存在先置 0 再减 3 得 -3

127.0.0.1:6379> get nokey2
"-3"     # nokey2 当前值
bash 复制代码
127.0.0.1:6379> get name
"wang"     # name 当前值

127.0.0.1:6379> del name     # 删除 key
(integer) 1     # 成功删除 name

127.0.0.1:6379> get name
(nil)     # name 已被删除,返回空

列表 list

Redis 列表实际就是简单的字符串数组,按照插入顺序进行排序。支持双向读写,可以添加一个元素到列表的头部(左边)或者尾部(右边),一个列表最多可以包含 2^32-1=4294967295 个元素。

每个列表元素用下标来标识,下标 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。也可以使用负数下标,以 -1 表示列表的最后一个元素,-2 表示列表的倒数第二个元素,元素值可以重复,常用于存入日志等场景,此数据类型比较常用。

列表特点:

  • 有序
  • 可重复
  • 左右都可以操作
命令 说明
LPUSH key value [value ...] 从左边插入元素
RPUSH key value [value ...] 从右边插入元素
LPOP key 弹出左边第一个元素
RPOP key 弹出右边第一个元素
LRANGE key start stop 获取列表指定范围的元素(支持负数索引)
LLEN key 获取列表长度
LINDEX key index 获取指定索引的元素
LREM key count value 删除列表中 count 个等于 value 的元素

创建列表和数据

LPUSH 和 RPUSH 都可以插入列表。

复制代码
LPUSH key value [value ...]
时间复杂度:O(1)
将一个或多个值 value 插入到列表 key 的表头
如果有多个 value 值,那么各个 value 值按从左到右的顺序依次插入到表头:
  对空列表 mylist 执行 LPUSH mylist a b c,列表的值将是 c b a
  这等同于原子性地执行 LPUSH mylist a、LPUSH mylist b 和 LPUSH mylist c 三个命令
如果 key 不存在,一个空列表会被创建并执行 LPUSH 操作。
当 key 存在但不是列表类型时,返回一个错误。

RPUSH key value [value ...]
时间复杂度:O(1)
将一个或多个值 value 插入到列表 key 的表尾(最右边)。
如果有多个 value 值,那么各个 value 值按从左到右的顺序依次插入到表尾:
  对空列表 mylist 执行 RPUSH mylist a b c,得出的结果列表为 a b c
  等同于执行命令 RPUSH mylist a、RPUSH mylist b、RPUSH mylist c。
如果 key 不存在,一个空列表会被创建并执行 RPUSH 操作。
当 key 存在但不是列表类型时,返回一个错误。

右侧插入叫做 rpush,右侧删除叫做 rpop
左侧插入叫做 lpush,左侧删除叫做 lpop

范例:

bash 复制代码
# 从左边添加数据,已添加的需向右移
127.0.0.1:6379> LPUSH phone xiaomi apple huawei     # LPUSH 左推入:从左边插入元素
(integer) 3     # 插入后列表长度为 3

127.0.0.1:6379> TYPE phone     # 判断数据类型
list     # phone 是列表类型

127.0.0.1:6379> RPUSH id 18 19 20     # RPUSH 右推入:从右边插入元素
(integer) 3     # 插入后列表长度为 3

127.0.0.1:6379> type id
list     # id 是列表类型

列表追加新数据

bash 复制代码
# 左推入 tom 到 list1 表头
127.0.0.1:6379> LPUSH list1 tom
(integer) 1     # 插入后列表长度为 1

# 从右边添加数据,已添加的向左移
127.0.0.1:6379> RPUSH list1 jack     # RPUSH 右推入
(integer) 2     # 追加后列表长度为 2

获取列表长度

bash 复制代码
127.0.0.1:6379> LLEN list1     # LLEN 获取列表长度
(integer) 2     # list1 当前长度为 2

获取列表指定位置元素数据

bash 复制代码
127.0.0.1:6379> LPUSH list2 a b c d     # LPUSH 批量左推入
(integer) 4     # 批量左插后列表长度为 4

127.0.0.1:6379> LINDEX list2 0     # LINDEX 获取指定索引的元素(0 表示第一个)
"d"     # 索引 0(表头)元素为 d

127.0.0.1:6379> LINDEX list2 3     # LINDEX 获取索引 3 的元素(最后一个)
"a"     # 索引 3(表尾)元素为 a
# LRANGE list2 1 2:list2 为列表键名;1 为起始索引;2 为结束索引(包含该位置),返回闭区间 [1,2] 内的元素
127.0.0.1:6379> LRANGE list2 1 2     # LRANGE 获取列表指定范围的元素
1) "c"     # 闭区间 [1,2] 返回的元素:索引1=c、索引2=b
2) "b"

# LRANGE list2 0 3:list2 为键名;0 为起始索引(表头);3 为结束索引,返回索引 0~3 的元素
127.0.0.1:6379> LRANGE list2 0 3     # LRANGE 查看索引 0 到 3 的所有元素
1) "d"     # 返回索引 0~3 的全部元素(左→右)
2) "c"
3) "b"
4) "a"

# LRANGE list2 0 -1:list2 为键名;0 为起始索引;-1 表示最后一个元素,故返回整个列表
127.0.0.1:6379> LRANGE list2 0 -1     # LRANGE key 0 -1 查看列表所有元素(-1 表示最后一个)
1) "d"     # 列表全部元素如下(表头 d → 表尾 a)
2) "c"
3) "b"
4) "a"

# RPUSH list3 a b c d e:list3 为列表键名;RPUSH 从列表右端(尾部)依次插入;a b c d e 为依次推入的元素值
127.0.0.1:6379> RPUSH list3 a b c d e     # RPUSH 右推入批量插入
(integer) 5     # 右推成功,列表当前长度为 5

# LINDEX list3 0:list3 为键名;0 为索引,返回该索引处的元素(0 表示表头第一个)
127.0.0.1:6379> LINDEX list3 0     # LINDEX 获取第一个元素
"a"     # 索引 0 处的元素为 a

# LRANGE list3 0 -1:list3 为键名;0 到 -1 表示从头取到尾,返回整个列表
127.0.0.1:6379> LRANGE list3 0 -1     # 查看列表所有元素
1) "a"     # list3 全部元素(表头 a → 表尾 e)
2) "b"
3) "c"
4) "d"
5) "e"

修改列表指定索引值

bash 复制代码
# RPUSH list4 a b c d e f:list4 为新建列表键名;RPUSH 从右端依次推入 a b c d e f 六个元素
127.0.0.1:6379> RPUSH list4 a b c d e f
(integer) 6     # 右推成功,列表当前长度为 6

# LRANGE list4 0 -1:list4 为键名;0 到 -1 返回整个列表的全部元素
127.0.0.1:6379> LRANGE list4 0 -1
1) "a"     # list4 全部元素(表头 a → 表尾 f)
2) "b"
3) "c"
4) "d"
5) "e"
6) "f"

# LSET list4 2 cloud:list4 为键名;2 为要修改的索引;cloud 为该索引的新值,将索引处元素覆盖为 cloud
127.0.0.1:6379> lset list4 2 cloud     # LSET 修改列表指定索引值(索引 2 的元素改为 cloud)
OK     # 修改成功

# LRANGE list4 0 -1:list4 为键名;0 到 -1 返回整个列表,验证索引 2 已被改为 cloud
127.0.0.1:6379> LRANGE list4 0 -1
1) "a"     # 修改后的 list4 全部元素(索引 2 已变为 cloud)
2) "b"
3) "cloud"
4) "d"
5) "e"
6) "f"

删除列表数据

bash 复制代码
# LPUSH list5 a b c d:list5 为列表键名;LPUSH 从列表左端(表头)依次插入;a b c d 为推入的元素(先插 a,最终表头为 d)
127.0.0.1:6379> LPUSH list5 a b c d     # LPUSH 左推入
(integer) 4     # 左推成功,列表当前长度为 4

# lrange list5 0 3:list5 为键名;0 到 3 返回索引 0~3 的元素;左推后表头为 d
127.0.0.1:6379> lrange list5 0 3
1) "d"     # list5 元素(左推后 d 在表头)
2) "c"
3) "b"
4) "a"

# LPOP list5:list5 为键名;LPOP 从左端弹出并移除表头第一个元素,返回被弹出的元素
127.0.0.1:6379> lpop list5     # LPOP 弹出左边第一个元素,即删除第一个
"d"     # 被弹出(删除)的表头元素 d

# lrange list5 0 3:list5 为键名;0 到 3 查看弹出表头 d 后剩余的元素
127.0.0.1:6379> lrange list5 0 3
1) "c"     # 弹出 d 后剩余的元素
2) "b"
3) "a"

# RPOP list5:list5 为键名;RPOP 从右端弹出并移除表尾最后一个元素,返回被弹出的元素
127.0.0.1:6379> RPOP list5     # RPOP 弹出右边第一个元素,即删除最后一个
"a"     # 被弹出(删除)的表尾元素 a

# lrange list5 0 3:list5 为键名;0 到 3 查看弹出表尾 a 后剩余的元素
127.0.0.1:6379> lrange list5 0 3
1) "c"     # 弹出 a 后 list5 仅剩表头两个元素
2) "b"

# LTRIM 对一个列表进行修剪(trim),让列表只保留指定区间内的元素,
# 不在指定区间之内的元素都将被删除;RPUSH list6 a b c d e 中 list6 为新建列表键名,RPUSH 从右端依次推入 a b c d e,准备待修剪的数据
127.0.0.1:6379> RPUSH list6 a b c d e
(integer) 5     # 右推成功,列表当前长度为 5

# lrange list6 0 -1:list6 为键名;0 到 -1 查看修剪前列表的全部元素
127.0.0.1:6379> lrange list6 0 -1
1) "a"     # 修剪前 list6 的全部元素
2) "b"
3) "c"
4) "d"
5) "e"

# LTRIM list6 2 3:list6 为键名;2 为保留的起始索引;3 为保留的结束索引,删除区间之外的元素,只保留 [2,3]
127.0.0.1:6379> LTRIM list6 2 3     # LTRIM 修剪列表,只保留索引 2 到 3 的元素
OK     # 修剪成功,列表只保留索引 2、3 两个元素

# lrange list6 0 -1:list6 为键名;0 到 -1 查看修剪后保留下来的元素
127.0.0.1:6379> lrange list6 0 -1
1) "c"     # 修剪后 list6 仅剩索引 2、3 两个元素 c、d
2) "d"

删除list

bash 复制代码
# DEL list1:list1 为要删除的键名;DEL 删除该键(及其对应的列表),返回被删除的键个数
127.0.0.1:6379> DEL list1     # 删除 list
(integer) 1     # 成功删除 1 个键

# EXISTS list1:list1 为键名;EXISTS 判断该键是否存在,存在返回 1,不存在返回 0
127.0.0.1:6379> EXISTS list1
(integer) 0     # list1 已被删除,返回 0 表示键不存在

集合 set

命令 说明
SADD key member [member ...] 添加一个或多个成员
SMEMBERS key 获取集合所有成员
SISMEMBER key member 判断成员是否在集合中
SREM key member [member ...] 删除一个或多个成员
SCARD key 获取集合成员数量
SINTER key1 [key2 ...] 求多个集合的交集
SUNION key1 [key2 ...] 求多个集合的并集
SDIFF key1 [key2 ...] 求差集

Set 是一个无序的字符串合集。同一个集合中的每个元素是唯一无重复的。支持在两个不同的集合中对数据进行逻辑处理,常用于取交集、并集、统计等场景,例如:实现共同的朋友。

集合特点:

  • 无序
  • 无重复
  • 集合间操作

创建集合

集合中追加数据:

bash 复制代码
# SADD set1 v1:set1 为集合键名;SADD 向集合添加成员;v1 为要添加的成员值,返回实际新增的成员个数
127.0.0.1:6379> SADD set1 v1     # SADD 添加一个成员到集合
(integer) 1     # 成功新增 1 个成员 v1

# SADD set2 v2 v4:set2 为集合键名;v2 v4 为一次批量添加的两个成员,返回新增成功的个数
127.0.0.1:6379> SADD set2 v2 v4     # SADD 批量添加多个成员
(integer) 2     # 成功新增 2 个成员 v2、v4

# TYPE set1:set1 为键名;TYPE 返回该键存储的数据类型
127.0.0.1:6379> TYPE set1
set     # set1 的数据类型为集合 set

# TYPE set2:set2 为键名;TYPE 返回该键存储的数据类型
127.0.0.1:6379> TYPE set2
set     # set2 的数据类型为集合 set
bash 复制代码
# 追加时,只能追加不存在的数据,不能追加已经存在的数值;SADD set1 v2 v3 v4 中 set1 为键名,v2 v3 v4 为待添加成员,集合自动去重,只有不存在的成员才会被真正加入
127.0.0.1:6379> SADD set1 v2 v3 v4     # SADD 添加多个新成员
(integer) 3     # 新增成功 3 个成员 v2、v3、v4

# SADD set1 v2:set1 为键名;v2 为待添加成员;v2 已存在,集合不允许重复,故返回 0
127.0.0.1:6379> SADD set1 v2     # SADD 已存在的 value,无法再添加
(integer) 0     # v2 已存在,未新增任何成员,返回 0

获取集合的所有数据

bash 复制代码
# SMEMBERS set1:set1 为集合键名;SMEMBERS 返回集合中的全部成员(无序)
127.0.0.1:6379> SMEMBERS set1     # SMEMBERS 获取集合所有成员
1) "v4"     # set1 全部成员如下(集合无序,不保证插入顺序)
2) "v3"
3) "v1"
4) "v2"

# SMEMBERS set2:set2 为集合键名;SMEMBERS 返回该集合的全部成员
127.0.0.1:6379> SMEMBERS set2
1) "v4"     # set2 全部成员(共 2 个)
2) "v2"

删除集合中的元素

bash 复制代码
# SADD name1 xiyangyang meiyangyang feiyangyang:name1 为集合键名;后面依次为批量添加的三个成员
127.0.0.1:6379> SADD name1 xiyangyang meiyangyang feiyangyang     # SADD 批量添加成员
(integer) 3     # 成功新增 3 个成员

# SMEMBERS name1:name1 为集合键名;返回该集合的全部成员
127.0.0.1:6379> SMEMBERS name1
1) "feiyangyang"     # name1 全部成员(共 3 个)
2) "meiyangyang"
3) "xiyangyang"

# SREM name1 meiyangyang:name1 为集合键名;meiyangyang 为要删除的成员,返回被成功移除的成员个数
127.0.0.1:6379> srem name1 meiyangyang     # SREM 删除集合中的指定成员
(integer) 1     # 成功删除 1 个成员 meiyangyang

# SMEMBERS name1:name1 为集合键名;查看删除 meiyangyang 后剩余的全部成员
127.0.0.1:6379> SMEMBERS name1
1) "feiyangyang"     # 删除后 name1 剩余成员(共 2 个)
2) "xiyangyang"

获取集合的交集

交集:同时属于集合A和集合B的元素

比如QQ共同好友

bash 复制代码
127.0.0.1:6379> SINTER set1 set2     # SINTER 求多个集合的交集
1) "v4"     # set1 与 set2 的交集元素(共 2 个)
2) "v2"

取集合的并集

并集:属于集合A或者属于集合B的元素

bash 复制代码
127.0.0.1:6379> SUNION set1 set2     # SUNION 求多个集合的并集
1) "v3"     # set1 与 set2 的并集元素(已去重)
2) "v1"
3) "v2"
4) "v4"

取集合的差集

差集:属于集合A但不属于集合B的元素

比如我朋友的朋友

bash 复制代码
127.0.0.1:6379> SDIFF set1 set2     # SDIFF 求差集(set1 有但 set2 没有的)
1) "v3"     # set1 相对 set2 的差集元素(共 2 个)
2) "v1"

有序集合 sorted set(ZSET)

命令 说明
ZADD key score member [score member ...] 添加带分数的成员
ZRANGE key start stop [WITHSCORES] 按分数升序返回成员
ZREVRANGE key start stop [WITHSCORES] 按分数降序返回成员
ZREM key member [member ...] 删除成员
ZCARD key 获取成员数量
ZSCORE key member 获取成员的分数
ZRANK key member 获取成员的排名(升序)

Redis 有序集合和 Redis 集合类似,是不包含相同字符串的合集。它们的差别是,每个有序集合的成员都关联着一个双精度浮点型的评分,这个评分用于把有序集合中的成员按最低分到最高分排序。有序集合的成员不能重复,但评分可以重复,一个有序集合中最多的成员数为 2^32-1=4294967295 个,经常用于排行榜的场景。

有序集合特点:

  • 有序
  • 无重复元素
  • 每个元素是由 score 和 value 组成
  • score 可以重复,value 不可以重复

创建有序集合

实现排名:

bash 复制代码
# ZADD zset1 1 v1:zset1 为有序集合键名;1 为分数 score;v1 为成员 member,按分数排序存储
127.0.0.1:6379> ZADD zset1 1 v1     # ZADD key score member:添加带分数 1 的成员 v1
(integer) 1     # 成功新增 1 个成员 v1

# ZADD zset1 2 v2:zset1 为键名;2 为分数 score;v2 为新成员 member
127.0.0.1:6379> ZADD zset1 2 v2     # ZADD 添加带分数 2 的成员 v2
(integer) 1     # 成功新增 1 个成员 v2

# ZADD zset1 2 v3:zset1 为键名;2 为分数(分数可与 v2 相同);v3 为新成员(成员值不可重复)
127.0.0.1:6379> ZADD zset1 2 v3     # ZADD 分数可重复,元素值不可以重复
(integer) 1     # 成功新增 1 个成员 v3

# ZADD zset1 3 v4:zset1 为键名;3 为分数 score;v4 为新成员 member
127.0.0.1:6379> ZADD zset1 3 v4     # ZADD 添加带分数 3 的成员 v4
(integer) 1     # 成功新增 1 个成员 v4

# TYPE zset1:zset1 为键名;TYPE 返回该键的数据类型
127.0.0.1:6379> TYPE zset1
zset     # zset1 的数据类型为有序集合 sorted set

# 一次生成多个数据;ZADD zset2 1 v1 2 v2 3 v3 4 v4 5 v5 中 zset2 为键名,其后按「分数 成员」成对给出 5 组数据,批量添加
127.0.0.1:6379> ZADD zset2 1 v1 2 v2 3 v3 4 v4 5 v5     # ZADD 批量添加:分数+成员 成对出现
(integer) 5     # 成功新增 5 个成员
bash 复制代码
# ZADD course 90 linux 99 go 60 python 50 cloud:course 为有序集合键名;按「分数 课程名」成对批量添加,如 90 分 linux、99 分 go
127.0.0.1:6379> ZADD course 90 linux 99 go 60 python 50 cloud     # ZADD 添加课程分数
(integer) 4     # 成功新增 4 个课程成员

# ZRANGE course 0 -1:course 为键名;0 到 -1 表示取全部成员;ZRANGE 按分数 score 从小到大(正序)返回成员
127.0.0.1:6379> ZRANGE course 0 -1     # ZRANGE 正序排序后显示集合内所有的 key,按 score 从小到大显示
1) "cloud"     # 正序(分数低→高)排列的成员列表
2) "python"
3) "linux"
4) "go"

# ZREVRANGE course 0 -1:course 为键名;0 到 -1 取全部成员;ZREVRANGE 按分数 score 从大到小(倒序)返回
127.0.0.1:6379> ZREVRANGE course 0 -1     # ZREVRANGE 倒序排序后显示集合内所有的 key,score 从大到小显示
1) "go"     # 倒序(分数高→低)排列的成员列表
2) "linux"
3) "python"
4) "cloud"

# ZRANGE course 0 -1 WITHSCORES:course 为键名;0 到 -1 取全部;WITHSCORES 表示同时返回每个成员对应的分数
127.0.0.1:6379> ZRANGE course 0 -1 WITHSCORES     # ZRANGE WITHSCORES 正序显示指定集合内所有 key 和得分情况
1) "cloud"     # 正序成员与其分数(成员、分数交替列出)
2) "50"
3) "python"
4) "60"
5) "linux"
6) "90"
7) "go"
8) "99"

# ZREVRANGE course 0 -1 WITHSCORES:course 为键名;0 到 -1 取全部;WITHSCORES 同时返回分数,按分数从高到低排列
127.0.0.1:6379> ZREVRANGE course 0 -1 WITHSCORES     # ZREVRANGE WITHSCORES 倒序显示指定集合内所有 key 和得分情况
1) "go"     # 倒序成员与其分数(成员、分数交替列出)
2) "99"
3) "linux"
4) "90"
5) "python"
6) "60"
7) "cloud"
8) "50"

查看集合的成员个数

bash 复制代码
# ZCARD course:course 为有序集合键名;ZCARD 返回集合中的成员总数(基数)
127.0.0.1:6379> ZCARD course     # ZCARD 查看集合的成员个数
(integer) 4     # course 当前共有 4 个成员

# ZCARD zset1:zset1 为键名;ZCARD 返回该有序集合的成员总数
127.0.0.1:6379> ZCARD zset1     # ZCARD 获取成员数量
(integer) 4     # zset1 当前共有 4 个成员

# ZCARD zset2:zset2 为键名;ZCARD 返回该有序集合的成员总数
127.0.0.1:6379> ZCARD zset2
(integer) 5     # zset2 当前共有 5 个成员

基于索引查找数据

bash 复制代码
# ZRANGE course 0 2:course 为键名;0 为起始索引、2 为结束索引,返回正序排名第 0~2(即前 3 名)的成员
127.0.0.1:6379> ZRANGE course 0 2     # ZRANGE 基于索引查找数据(前 3 名)
1) "cloud"     # 正序前 3 名(分数最低的 3 个)
2) "python"
3) "linux"

# ZRANGE course 0 10:course 为键名;结束索引 10 超过了实际成员数,Redis 不会报错而是返回到末尾为止的全部成员
127.0.0.1:6379> ZRANGE course 0 10     # 超出范围不报错
1) "cloud"     # 索引超出范围,仍返回现有全部成员
2) "python"
3) "linux"
4) "go"

# ZRANGE zset1 1 3:zset1 为键名;1 到 3 为索引区间,返回正序排名第 1~3 的成员
127.0.0.1:6379> ZRANGE zset1 1 3
1) "v2"     # zset1 正序索引 1~3 的成员
2) "v3"
3) "v4"

# ZRANGE zset1 0 2:zset1 为键名;0 到 2 为索引区间,返回正序排名第 0~2 的成员
127.0.0.1:6379> ZRANGE zset1 0 2
1) "v1"     # zset1 正序索引 0~2 的成员
2) "v2"
3) "v3"

# ZRANGE zset1 2 2:zset1 为键名;起始与结束索引都为 2,只返回正序排名第 2 的那一个成员
127.0.0.1:6379> ZRANGE zset1 2 2
1) "v3"     # 索引 2 位置的唯一成员 v3

查询指定数据的排名

bash 复制代码
# ZRANK course go:course 为键名;go 为要查询的成员;ZRANK 按分数升序返回其排名,排名从 0 开始计数
127.0.0.1:6379> ZRANK course go     # ZRANK 查询指定数据的排名(升序,从 0 开始)
(integer) 3     # go 分数最高,升序排第 4 位,排名为 3(从 0 开始)

# ZRANK course python:course 为键名;python 为要查询的成员;按分数升序返回其排名(从 0 开始)
127.0.0.1:6379> ZRANK course python     # ZRANK 获取成员的排名(升序)
(integer) 1     # python 升序排名为 1(第 2 名)

获取分数

bash 复制代码
# ZSCORE course cloud:course 为键名;cloud 为要查询的成员;ZSCORE 返回该成员对应的分数 score
127.0.0.1:6379> ZSCORE course cloud     # ZSCORE 获取成员的分数
"50"     # 成员 cloud 的分数为 50

删除元素

bash 复制代码
# ZRANGE course 0 -1:course 为键名;0 到 -1 返回全部成员,删除前先查看当前有序集合
127.0.0.1:6379> ZRANGE course 0 -1
1) "cloud"     # course 删除前的全部成员
2) "python"
3) "linux"
4) "go"

# ZREM course python go:course 为键名;python、go 为要删除的成员,可一次删除多个,返回成功删除的个数
127.0.0.1:6379> ZREM course python go     # ZREM 删除成员(可批量删除多个)
(integer) 2     # 成功删除 2 个成员 python、go

# ZRANGE course 0 -1:course 为键名;0 到 -1 查看删除 python、go 后剩余的成员
127.0.0.1:6379> ZRANGE course 0 -1
1) "cloud"     # 删除后 course 剩余的成员
2) "linux"

哈希 hash

命令 说明
HSET key field value 设置 hash 字段的值
HGET key field 获取 hash 字段的值
HMSET key field1 value1 [field2 value2 ...] 批量设置字段(Redis 4.0+ 推荐用 HSET)
HMGET key field1 [field2 ...] 批量获取字段
HGETALL key 获取 hash 中所有字段和值
HDEL key field [field ...] 删除一个或多个字段
HEXISTS key field 判断字段是否存在

hash 即字典,用于保存字符串字段 field 和字符串值 value 之间的映射,即 key/value 做为数据部分。hash 特别适合用于存储对象场景。一个 hash 最多可以包含 2^32-1 个 key/value 键值对。

哈希特点:

  • 无序
  • K/V 对
  • 适用于存放相关的数据

创建 hash

格式:

复制代码
HSET hash field value
时间复杂度:O(1)
将哈希表 hash 中域 field 的值设置为 value
如果给定的哈希表并不存在,那么一个新的哈希表将被创建并执行 HSET 操作。
如果域 field 已经存在于哈希表中,那么它的旧值将被新值 value 覆盖。

范例:

bash 复制代码
# HSET 9527 name zhouxingxing age 20:9527 为哈希键名;其后成对给出「字段 字段值」,即 name=zhouxingxing、age=20,可一次设置多个字段
127.0.0.1:6379> HSET 9527 name zhouxingxing age 20     # HSET key field value:设置 hash 字段的值(可批量设置多个 field-value)
(integer) 2     # 成功新增 2 个字段

# TYPE 9527:9527 为键名;TYPE 返回该键存储的数据类型
127.0.0.1:6379> TYPE 9527
hash     # 9527 的数据类型为哈希 hash

# 查看所有字段的值;hgetall 9527 中 9527 为哈希键名,HGETALL 以「字段、值」交替的形式返回该哈希的全部字段与值
127.0.0.1:6379> hgetall 9527     # HGETALL 获取 hash 中所有字段和值
1) "name"     # 全部字段与值(字段、值交替列出)
2) "zhouxingxing"
3) "age"
4) "20"

# 增加字段;HSET 9527 gender male 中 9527 为哈希键名,gender 为新字段名,male 为该字段的值,字段不存在则新建
127.0.0.1:6379> HSET 9527 gender male     # HSET 增加新字段
(integer) 1     # 成功新增 1 个字段 gender

# HGETALL 9527:9527 为哈希键名;查看新增 gender 字段后哈希的全部字段与值
127.0.0.1:6379> HGETALL 9527
1) "name"     # 全部字段与值(字段、值交替,共 3 个字段)
2) "zhouxingxing"
3) "age"
4) "20"
5) "gender"
6) "male"

查看hash的指定filed的value

bash 复制代码
# HGET 9527 name:9527 为哈希键名;name 为要查询的字段;HGET 返回该字段对应的单个值
127.0.0.1:6379> HGET 9527 name     # HGET 获取 hash 指定字段的值
"zhouxingxing"     # 字段 name 的值

# HGET 9527 age:9527 为哈希键名;age 为要查询的字段;返回该字段的值
127.0.0.1:6379> HGET 9527 age     # HGET 获取 hash 指定字段的值
"20"     # 字段 age 的值

# 获取多个值;HMGET 9527 name age 中 9527 为哈希键名,name、age 为一次批量查询的多个字段,按给定顺序返回各自的值
127.0.0.1:6379> HMGET 9527 name age     # HMGET 批量获取多个字段的值
1) "zhouxingxing"     # 依次返回 name、age 两个字段的值
2) "20"

删除hash的指定的field/value

bash 复制代码
# HDEL 9527 age:9527 为哈希键名;age 为要删除的字段,返回被成功删除的字段个数
127.0.0.1:6379> HDEL 9527 age     # HDEL 删除 hash 的指定字段
(integer) 1     # 成功删除 1 个字段 age

# HGET 9527 age:9527 为哈希键名;age 字段已被删除,查询不存在的字段返回空
127.0.0.1:6379> HGET 9527 age     # 删除后字段不存在
(nil)     # 字段 age 已删除,返回 nil 表示不存在

# HGETALL 9527:9527 为哈希键名;查看删除 age 后剩余的全部字段与值
127.0.0.1:6379> HGETALL 9527
1) "name"     # 删除 age 后剩余的字段与值
2) "zhouxingxing"
3) "gender"
4) "male"

批量设置hash key的多个field和value

bash 复制代码
# HMSET 9527 name zhouxingxing age 50 city hongkong:9527 为哈希键名;按「字段 值」成对批量设置,已存在的字段会被覆盖、不存在的会新建
127.0.0.1:6379> HMSET 9527 name zhouxingxing age 50 city hongkong     # HMSET 批量设置 hash 的多个 field 和 value
OK     # 批量设置成功

# HGETALL 9527:9527 为哈希键名;查看 HMSET 批量设置后哈希的全部字段与值
127.0.0.1:6379> HGETALL 9527
1) "name"     # HMSET 后的全部字段与值(字段、值交替)
2) "zhouxingxing"
3) "gender"
4) "male"
5) "age"
6) "50"
7) "city"
8) "hongkong"

查看hash指定field的value

bash 复制代码
# HMGET 9527 name age:9527 为哈希键名;批量查询 name、age 两个字段的值(age 已被 HMSET 改为 50)
127.0.0.1:6379> HMGET 9527 name age     # HMGET 批量获取字段
1) "zhouxingxing"     # 依次返回 name、age 两个字段的值
2) "50"

查看hash的所有field

bash 复制代码
# HKEYS 9527:9527 为哈希键名;HKEYS 只返回该哈希的全部字段名(key),不含值
127.0.0.1:6379> HKEYS 9527     # HKEYS 查看 hash 的所有 field(字段名)
1) "name"     # 全部字段名(不含值)
2) "gender"
3) "age"
4) "city"

查看hash所有value

bash 复制代码
# HVALS 9527:9527 为哈希键名;HVALS 只返回该哈希的全部字段值(value),不含字段名
127.0.0.1:6379> HVALS 9527     # HVALS 查看 hash 的所有 value(字段值)
1) "zhouxingxing"     # 全部字段值(不含字段名)
2) "male"
3) "50"
4) "hongkong"

查看指定hash的所有field及value

bash 复制代码
# HGETALL 9527:9527 为哈希键名;同时返回全部字段名与对应值(字段、值交替)
127.0.0.1:6379> HGETALL 9527     # HGETALL 查看指定 hash 的所有 field 及 value
1) "name"     # 全部字段与值(字段、值交替,共 4 个字段)
2) "zhouxingxing"
3) "gender"
4) "male"
5) "age"
6) "50"
7) "city"
8) "hongkong"

删除hash

bash 复制代码
# DEL 9527:9527 为要删除的哈希键名;DEL 删除该键及其全部字段,返回被删除的键个数
127.0.0.1:6379> DEL 9527     # 删除 hash
(integer) 1     # 成功删除 1 个键

# EXISTS 9527:9527 为键名;EXISTS 判断该键是否存在,存在返回 1,不存在返回 0
127.0.0.1:6379> EXISTS 9527
(integer) 0     # 9527 已被删除,返回 0 表示键不存在

消息队列

消息队列:把要传输的数据放在队列中,从而实现应用之间的数据交换。

常用功能:可以实现多个应用系统之间的解耦,异步,削峰/限流等。

常用的消息队列应用:Kafka, RabbitMQ, Redis

消息队列分为两种:

  • 生产者/消费者模式:Producer/Consumer
  • 发布者/订阅者模式:Publisher/Subscriber

生产者/消费者模式

生产者消费者模式下,多个消费者同时监听一个频道(redis 用队列实现),但是生产者产生的一个消息只能被最先抢到消息的一个消费者消费一次,队列中的消息可以由多个生产者写入,也可以有不同的消费者取出进行消费处理,此模式应用广泛。

思来想去,只有 List 和 Streams 两种数据类型,可以实现消息队列的这些需求,当然,Redis 还提供了发布、订阅(pub/sub)模式。

List 实现消息队列

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。所以常用来做异步队列使用。将需要延后处理的任务结构体序列化成字符串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行处理。

挑几个弹入、弹出的命令就可以组合出很多姿势:

  • LPUSH、RPOP:左进右出
  • RPUSH、LPOP:右进左出

案例:

bash 复制代码
# 生产者生成消息;redis-cli 为在 shell 中启动的 Redis 命令行客户端,进入交互式连接本机 6379 端口的 Redis 服务
[root@localhost ~]# redis-cli

# LPUSH haizeiwang jishu1:haizeiwang 为消息队列(列表)键名;LPUSH 从左端写入消息,作为生产端生产一条消息
127.0.0.1:6379> LPUSH haizeiwang jishu1     # LPUSH 生产者从左边写入消息
(integer) 1     # 写入成功,队列当前积压 1 条消息

# LPUSH haizeiwang jishu2:haizeiwang 为队列键名;从左端生产第 2 条消息 jishu2
127.0.0.1:6379> LPUSH haizeiwang jishu2
(integer) 2     # 写入成功,队列当前积压 2 条消息

# LPUSH haizeiwang jishu3:haizeiwang 为队列键名;从左端生产第 3 条消息 jishu3
127.0.0.1:6379> LPUSH haizeiwang jishu3
(integer) 3     # 写入成功,队列当前积压 3 条消息

# LPUSH haizeiwang jishu4:haizeiwang 为队列键名;从左端生产第 4 条消息 jishu4
127.0.0.1:6379> LPUSH haizeiwang jishu4
(integer) 4     # 写入成功,队列当前积压 4 条消息

# LPUSH haizeiwang jishu5:haizeiwang 为队列键名;从左端生产第 5 条消息 jishu5
127.0.0.1:6379> LPUSH haizeiwang jishu5
(integer) 5     # 写入成功,队列当前积压 5 条消息

# 获取所有消息;LRANGE haizeiwang 0 -1 中 haizeiwang 为队列键名,0 到 -1 查看队列全部消息(LPUSH 从左写,故 jishu5 在表头)
127.0.0.1:6379> LRANGE haizeiwang 0 -1
1) "jishu5"     # 队列中全部消息(表头为最后写入的 jishu5)
2) "jishu4"
3) "jishu3"
4) "jishu2"
5) "jishu1"

# 消费者消费消息;RPOP haizeiwang 中 haizeiwang 为队列键名,RPOP 从右端弹出并移除消息,右端是最早写入的 jishu1,实现先进先出
127.0.0.1:6379> RPOP haizeiwang     # RPOP 消费者从右边弹出消息,基于先进先出原则
"jishu1"     # 被消费(弹出)的最早一条消息

# RPOP haizeiwang:haizeiwang 为队列键名;从右端继续弹出下一条消息消费
127.0.0.1:6379> RPOP haizeiwang
"jishu2"     # 被消费的第 2 条消息

# RPOP haizeiwang:haizeiwang 为队列键名;从右端继续弹出下一条消息消费
127.0.0.1:6379> RPOP haizeiwang
"jishu3"     # 被消费的第 3 条消息

# RPOP haizeiwang:haizeiwang 为队列键名;从右端继续弹出下一条消息消费
127.0.0.1:6379> RPOP haizeiwang
"jishu4"     # 被消费的第 4 条消息

# RPOP haizeiwang:haizeiwang 为队列键名;从右端弹出最后一条消息消费
127.0.0.1:6379> RPOP haizeiwang
"jishu5"     # 被消费的第 5 条(最后一条)消息

# RPOP haizeiwang:haizeiwang 为队列键名;队列已空,再弹出时没有消息可返回
127.0.0.1:6379> RPOP haizeiwang     # 验证队列消息消费全部消费完成
(nil)     # 队列已空,返回 nil 表示无消息可消费

# LRANGE haizeiwang 0 -1:haizeiwang 为队列键名;查看消费完毕后队列中的剩余消息
127.0.0.1:6379> LRANGE haizeiwang 0 -1
(empty list or set)     # 队列已清空,返回空列表

# 生产者生成消息;LPUSH haizeiwang jishu1 jishu2 jishu3 jishu4 jishu5 中 haizeiwang 为队列键名,一次从左端批量写入 5 条消息
127.0.0.1:6379> LPUSH haizeiwang jishu1 jishu2 jishu3 jishu4 jishu5     # LPUSH 批量写入消息
(integer) 5     # 批量写入成功,队列当前积压 5 条消息

# 获取所有消息;LRANGE haizeiwang 0 -1 中 haizeiwang 为队列键名,0 到 -1 查看批量写入后队列的全部消息
127.0.0.1:6379> LRANGE haizeiwang 0 -1
1) "jishu5"     # 批量写入后队列中的全部消息
2) "jishu4"
3) "jishu3"
4) "jishu2"
5) "jishu1"

即时消费问题

通过 LPUSH、RPOP 这样的方式,会存在一个性能风险点,就是消费者如果想要及时的处理数据,就要在程序中写个类似 while(true) 这样的逻辑,不停的去调用 RPOP 或 LPOP 命令,这就会给消费者程序带来些不必要的性能损失。

所以,Redis 还提供了 BLPOP、BRPOP 这种阻塞式读取的命令(带 B-Blocking 的都是阻塞式),客户端在没有读到队列数据时,自动阻塞,直到有新的数据写入队列,再开始读取新数据。这种方式就节省了不必要的 CPU 开销。

  • LPUSH、BRPOP:左进右阻塞出
  • RPUSH、BLPOP:右进左阻塞出
bash 复制代码
# 消费者消费消息;BRPOP haizeiwang 10 中 haizeiwang 为队列键名,10 为阻塞超时秒数,BRPOP 阻塞式从右端弹出消息,队列空时最多等 10 秒,有消息则立即返回
127.0.0.1:6379> BRPOP haizeiwang 10     # BRPOP 阻塞式右弹出:等待 10 秒,有消息则立即返回
1) "haizeiwang"     # 返回两个元素:第 1 个是弹出消息所在的队列名,第 2 个是消息内容
2) "jishu1"

# BRPOP haizeiwang 10:haizeiwang 为队列键名;10 为超时秒数;继续阻塞式从右端弹出下一条消息
127.0.0.1:6379> BRPOP haizeiwang 10
1) "haizeiwang"     # 弹出结果:队列名 + 消息内容
2) "jishu2"

# BRPOP haizeiwang 10:haizeiwang 为队列键名;10 为超时秒数;继续阻塞式从右端弹出下一条消息
127.0.0.1:6379> BRPOP haizeiwang 10
1) "haizeiwang"     # 弹出结果:队列名 + 消息内容
2) "jishu3"

# BRPOP haizeiwang 10:haizeiwang 为队列键名;10 为超时秒数;继续阻塞式从右端弹出下一条消息
127.0.0.1:6379> BRPOP haizeiwang 10
1) "haizeiwang"     # 弹出结果:队列名 + 消息内容
2) "jishu4"

# BRPOP haizeiwang 10:haizeiwang 为队列键名;10 为超时秒数;继续阻塞式从右端弹出下一条消息
127.0.0.1:6379> BRPOP haizeiwang 10
1) "haizeiwang"     # 弹出结果:队列名 + 消息内容
2) "jishu5"

# 消费的时候已经没有消息了,10 秒内再开一个窗口使用命令 LPUSH haizeiwang jishu6 产生新消息;BRPOP haizeiwang 10 中 haizeiwang 为队列键名,10 为阻塞超时秒数,队列暂时为空时阻塞等待,直到新消息 jishu6 写入后立即弹出
127.0.0.1:6379> BRPOP haizeiwang 10     # 如果将超时时间设置为 0 时,即可无限等待,直到弹出消息
1) "haizeiwang"     # 阻塞等待后弹出:队列名 + 消息内容
2) "jishu6"
(2.73s)     # 本次阻塞等待耗时约 2.73 秒后才收到消息

发布者/订阅者模式

在发布者订阅者 Publisher/Subscriber 模式下,发布者 Publisher 将消息发布到指定的频道 channel,事先监听此 channel 的一个或多个订阅者 Subscriber 都会收到相同的消息。即一个消息可以由多个订阅者获取到,对于社交应用中的群聊、群发、群公告等场景适用于此模式。

案例:

bash 复制代码
# 订阅者订阅频道
# 窗口 A:;redis-cli 启动Redis命令行客户端,默认连接本机127.0.0.1:6379
[root@localhost ~]# redis-cli

127.0.0.1:6379> SUBSCRIBE douluodalu     # SUBSCRIBE channel:订阅者事先订阅指定的频道,之后发布的消息才能收到;douluodalu为要订阅的频道名
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "douluodalu"
3) (integer) 1

# 窗口 B:;redis-cli 另开一个终端窗口,同样连接本机Redis
[root@localhost ~]# redis-cli

127.0.0.1:6379> SUBSCRIBE douluodalu haizeiwang     # 订阅多个频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "douluodalu"
3) (integer) 1
1) "subscribe"
2) "haizeiwang"
3) (integer) 2

# 发布者发布消息
# 窗口 C:
127.0.0.1:6379> PUBLISH douluodalu fuhuoba,wodeairen     # PUBLISH channel message:发布者向频道发布消息;douluodalu为目标频道,fuhuoba,wodeairen为消息内容
(integer) 2     # 订阅者个数 2 个

127.0.0.1:6379> PUBLISH haizeiwang 5danglufei     # PUBLISH 发布消息到 haizeiwang 频道;haizeiwang为频道名,5danglufei为消息内容
(integer) 1

# 各个订阅者都能收到消息
# 窗口 A:;redis-cli 切回窗口A,观察订阅者是否收到推送
[root@localhost ~]# redis-cli

127.0.0.1:6379> SUBSCRIBE douluodalu
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "douluodalu"
3) (integer) 1
1) "message"
2) "douluodalu"
3) "fuhuoba,wodeairen"

# 窗口 B:;redis-cli 切回窗口B,观察两个订阅频道是否都收到消息
[root@localhost ~]# redis-cli

127.0.0.1:6379> SUBSCRIBE douluodalu haizeiwang
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "douluodalu"
3) (integer) 1
1) "subscribe"
2) "haizeiwang"
3) (integer) 2
1) "message"
2) "douluodalu"
3) "fuhuoba,wodeairen"
1) "message"
2) "haizeiwang"
3) "5danglufei"

# 订阅所有频道;PSUBSCRIBE 模式订阅:*为通配符模式,可匹配任意名称的频道
127.0.0.1:6379> PSUBSCRIBE *

# 订阅匹配的频道;PSUBSCRIBE pattern:(如 douluo* 匹配 douluodalu);douluo*为带通配符的模式,只订阅前缀为douluo的频道
127.0.0.1:6379> PSUBSCRIBE douluo*

shell 脚本访问 Redis

脚本内容:write_redis.sh

bash 复制代码
#!/bin/bash
# Redis 连接参数(可根据需要修改)
REDIS_HOST="127.0.0.1"   # Redis 服务器地址
REDIS_PORT=6379           # Redis 端口
REDIS_DB=0                # 使用的数据库编号

# 写入 100 个键值对;for i in {0..99} 为bash循环,变量i依次取0到99共100个值
for i in {0..99}; do

    # 拼接键名,形如 key:0、key:1 ...... key:99
    KEY="key:$i"
    # 拼接值,VALUE 取循环变量 i 的值,形如 0、1 ...... 99
    VALUE="$i"
    # 调用 redis-cli 写入键值对;-h 指定Redis服务器地址,-p 指定端口,-n 指定使用的数据库编号,SET 后依次跟键名KEY与值VALUE(PDF中 SET "$KEY" 和 "$VALUE" 跨两行,此处合并为一行)
    redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" -n "$REDIS_DB" SET "$KEY" "$VALUE"
done
# 循环结束后打印提示;echo 输出一行文字,表明100条数据已全部写入
echo "已成功写入100条数据到 Redis。"

给脚本添加执行权限:

bash 复制代码
# 给脚本添加执行权限;chmod +x 为文件添加可执行权限,write_redis.sh为脚本文件名
[root@localhost ~]# chmod +x write_redis.sh

运行脚本

bash 复制代码
# 运行脚本;./ 表示执行当前目录下的脚本,write_redis.sh为要运行的脚本名
[root@localhost ~]# ./write_redis.sh
OK     # 一条写入命令执行成功,Redis返回OK
......
OK
已成功写入100条数据到Redis。     # 脚本结束时打印的提示,表明100条数据已全部写入
[root@localhost ~]#

💡 可选增强功能:

带密码认证 :如果 Redis 设置了密码,加上 -a your_password(注意安全性)或使用 --askpass。

批量管道操作(更快) :如需更高性能,可使用 redis-cli --pipe 批量导入(适合大量数据)。

图形工具连接 Redis

Redis Desktop Manager 又名 RDM,它是一款用于 Windows,Linux 和 MacOS 的快速开源 Redis 数据库管理应用程序,能够为用户提供一系列功能选择以及操作设定,以便于用户直接对需要管理的 Redis 数据库进行建设性操作,以树形的方式来罗列出密匙以及 CRUD 密钥,可节约用户的很多宝贵时间,此工具支持识别 shell 命令,管理员能够根据自己的需要添加 shel,以此让程序系统执行对应的命令操作;它为用户提供中文显示界面,同时还提供了通俗易懂的用户界面,无论是新手用户还可以经验十足的用户都可以非常轻松的上手。

Github 地址:

bash 复制代码
https://github.com/qishibo/AnotherRedisDesktopManager

验证结果:

bash 复制代码
# 验证数据写入结果;info keyspace 查看各数据库的键空间统计信息
127.0.0.1:6379> info keyspace

# Keyspace
db0:keys=100,expires=0,avg_ttl=0     # 0号库共有100个键,均未设置过期时间,平均TTL为0

# keys pattern:按模式批量查找匹配的键;key:* 匹配所有以key:开头的键
127.0.0.1:6379> keys key:*

需要提前在 redis 服务器上配置 redis 密码,和关闭防火墙,更改 bind 地址

程序连接 Redis

Redis 支持多种开发语言访问

bash 复制代码
# 关闭防火墙;systemctl stop firewalld 停止firewalld防火墙服务,避免其拦截Redis端口
[root@localhost ~]# systemctl stop firewalld

# 编辑Redis配置文件;vim /etc/redis.conf 用vim编辑器打开Redis主配置文件
[root@localhost ~]# vim /etc/redis.conf     # 第69行,绑定地址改为允许所有;bind 0.0.0.0 表示监听本机所有网卡地址,允许任意IP远程连接
bind 0.0.0.0

# 重启Redis服务;systemctl restart redis 重启redis服务使配置生效
[root@localhost ~]# systemctl restart redis

# 设置Redis密码为123456;CONFIG SET requirepass 123456 动态设置访问密码,requirepass为密码配置项
127.0.0.1:6379> CONFIG SET requirepass 123456
OK     # 密码设置成功

Redis 官方客户端列表地址:

bash 复制代码
https://redis.io/docs/latest/develop/clients/

pycharm 连接 redis

基本语句(python)

python 提供了多种开发库,都可以支持连接访问 Redis,下面选择使用 redis-py 库连接 redis

redis-py 使用手册:

bash 复制代码
# redis-py 使用手册
https://redis.io/docs/latest/develop/clients/redis-py/

# 安装redis-py库;pip install redis 用pip包管理器安装Python的Redis客户端库redis-py
pip install redis

解释:

bash 复制代码
# 导入redis模块,这是Python操作Redis的官方客户端库(需提前安装:pip install redis)
import redis

# 创建一个Redis客户端对象r,用于与远程Redis服务器通信,host: Redis服务器IP地址(这里是局域网中的192.168.108.172),port: Redis默认端口6379,password: 认证密码'123456'(说明Redis配置了requirepass 123456),db: 使用第0号数据库(Redis默认有16个逻辑数据库,编号0~15)
r = redis.Redis(host='192.168.108.172', port=6379, password='123456', db=0)

# 打印Redis客户端对象的描述信息,用于调试,确认连接配置是否正确
print(r)

接收 set 操作的返回值

redis-py 版本与 r.set(...) 成功时的返回值对照:

redis-py 版本 r.set() 成功返回值
4.0.0 及以上(当前主流版本) True(布尔值)✅
3.x 及更早 b'OK'(字节串)

注意文件不要为 redis.py,会和 redis 模块名称冲突。

解释:

bash 复制代码
# 调用set()方法,向Redis中设置一个字符串类型的键值对
# 键(key):'color1',值(value):'red'
# 相当于在Redis CLI中执行:SET color1 red
result = r.set('color1', 'red')

# r.set()的返回值说明:
# 自redis-py 4.0起,为了更符合Python习惯,成功操作统一返回True,失败则抛出异常(如连接错误、认证失败等)
print(result)

完整代码:

bash 复制代码
# 导入redis模块,即Python的Redis客户端库
import redis

# 创建Redis客户端对象r:host为服务器IP,port为端口6379,password为认证密码123456,db指定使用0号数据库
r = redis.Redis(host='192.168.108.172', port=6379, password='123456', db=0)

# 打印客户端对象,用于调试确认连接配置是否正确
print(r)

# 接收set操作的返回值;r.set('color1','red') 写入字符串键值对 color1=red
result = r.set('color1', 'red')

# 打印set操作的返回值
print(result)     # 输出 True(或 'OK',取决于 redis-py 版本)

设置和获取值

可以使用 set 和 get 方法来存储和检索数据

使用字符串操作

redis-py 提供了多种字符串操作方法,如 incr(增加),decr(减少),append(追加)等。

bash 复制代码
# 存储变量;r.set('color1','red') 写入字符串键color1,值为red
r.set('color1', 'red')

# 获取变量值;r.get('color1') 读取键color1的值,返回bytes类型
value = r.get('color1')

# 打印读取到的值
print(value)

# 写入键int1,初始值为10(Redis中以字符串数字存储)
r.set('int1',10)

# 自增;r.incr('int1') 将键int1的值加1,并返回自增后的结果
restult = r.incr('int1')

# 自减;r.decr('int1') 将键int1的值减1,并返回自减后的结果
restult = r.decr('int1')

# 打印自增自减后的最终结果
print(restult)

使用列表操作

Redis 的列表是一个简单的字符串列表,可以进行 lpush(从左边插入)、rpush(从右边插入)、lpop(从左边弹出)、rpop(从右边弹出)等操作。

注释:

bash 复制代码
# 使用LPUSH命令将字符串'nihao'从左侧(头部)推入名为mylist的列表
r.lpush('mylist','nihao')

列表操作代码:

bash 复制代码
# 列表;r.lpush 从左侧(头部)向列表mylist推入元素'nihao'
r.lpush('mylist','nihao')

# 再次从左侧推入'laogao',头插使其排在列表最前面
r.lpush('mylist','laogao')

# r.lrange(mylist,0,-1) 取列表全部元素,0到-1表示从头到尾,打印结果
print(r.lrange('mylist',0,-1))

如果 mylist 不存在,Redis 会自动创建它。执行后,mylist 内容为:['nihao']

bash 复制代码
# 再次使用LPUSH,将'laogao'插入到列表最左边
# 列表是"头进",所以新元素在前
# 此时mylist内容变为:['laogao', 'nihao']
r.lpush('mylist','laogao')
bash 复制代码
# 使用LRANGE命令获取列表mylist中从索引0到-1(即全部元素)的内容
# 在Redis中,-1表示最后一个元素,0是第一个
# r.lrange(...)返回的是字节串(bytes)组成的列表(因为Redis存储的是二进制安全的字符串)
print(r.lrange('mylist',0,-1))

💡 小知识:Redis 列表是"左进右出"还是"右进左出"?

  • LPUSH:从左边 插入 → 新元素在头部
  • RPUSH:从右边 插入 → 新元素在尾部
  • 列表索引:0 是最左边(最新插入的 LPUSH 元素),-1 是最右边(最早插入的)

输出:[b'laogao', b'nihao']

注意:每个字符串前面有 b'...',表示这是 bytes 类型,不是普通字符串(str)。

使用哈希表操作

Redis 的哈希表是一个字符串字段和字符串值之间的映射表,可以进行 hset(设置字段)、hget(获取字段)、hgetall(获取所有字段和值)等操作。

bash 复制代码
# 在名为'student'的哈希表(Hash)中,设置字段(field)'name'的值为'lisi'
# 相当于执行Redis命令:HSET student name lisi
r.hset('student','name','lisi')

# 在同一个哈希表'student'中,再设置字段'age'的值为'18'
# 注意:Redis中所有值都是字符串,即使看起来像数字(如'18'),存储时也是字符串类型
r.hset('student','age','18')

# 获取哈希表'student'中字段'name'的值
# 返回值是bytes类型(因为redis-py默认返回字节)
# 输出:b'lisi'
print(r.hget('student','name'))

# 获取'age'字段的值
# 输出:b'18'
print(r.hget('student','age'))

# 获取'student'哈希表中所有字段和值
# 返回一个字典(dict),但键和值都是bytes类型
print(r.hgetall('student'))

完整代码:

bash 复制代码
# 哈希;r.hset 向哈希表student中设置字段,等价于HSET命令
r.hset('student','name','lisi')

# 再设置age字段,值为18
r.hset('student','age','18')

# 读取name字段的值,返回b'lisi'
print(r.hget('student','name'))

# 读取age字段的值,返回b'18'
print(r.hget('student','age'))

# 读取student的全部字段与值,返回由bytes组成的字典
print(r.hgetall('student'))
主机名 IP 地址 系统
master 192.168.108.10 Centos7.9
slave01 192.168.108.11 Centos7.9
slave02 192.168.108.12 Centos7.9

主从复制 -> 加上自动故障转移 -> 哨兵模式 -> 再加上数据分片和写扩展 -> Redis Cluster。

主从复制

Redis 主从复制(Master-Slave Replication)是 Redis 实现高可用、读写分离和数据冗余的核心机制之一。

目的 :数据冗余 和读写分离。

原理:主从复制是 Redis 高可用性的基础。它允许一个 Redis 服务器(主节点,Master)将其数据复制到一个或多个 Redis 服务器(从节点,Slave/Replica)。

主从复制特点:

  • 一个 master 可以有多个 slave
  • 一个 slave 只能有一个 master
  • 数据流向是从 master 到 slave 单向的
  • master 可读可写,slave 只读

主从复制实现:当 master 出现故障后,可以自动提升一个 slave 节点变成新的 master,因此 redis slave 需要设置和 master 相同的连接密码。此外当一个 slave 提升为新的 master 时需要通过持久化实现数据的恢复。

实验环境

1 主 2 从,准备三台配置一样的虚拟机,将三台虚拟机的防火墙都关闭。

redis 的主从同步工作原理简单概括为:

  1. salve 向 master 发送 sync 命令
  2. master 启动后台存盘进程,并收集所有修改数据命令
  3. master 完成存盘后,传送整个数据文件到 slave
  4. slave 接受数据文件,加载到内存中完成首次完全同步
  5. 后续有新数据产生时,master 继续将新的数据命令传递给 slave 完成同步

配置所有数据库

bash 复制代码
# 所有节点操作;cd 进入Redis安装目录/usr/local/redis-6.2.14
[root@localhost ~]# cd /usr/local/redis-6.2.14/

# 编辑Redis配置文件;vim redis.conf 用vim打开配置文件redis.conf
[root@localhost redis-6.2.14]# vim redis.conf     # 第75行,监听地址改为允许所有;bind 0.0.0.0 -::1 监听所有IPv4和IPv6地址,允许外部连接
bind 0.0.0.0 -::1

配置从 redis

bash 复制代码
# slave01,slave02 指向master的ip和端口;vim redis.conf 编辑从节点的配置文件
[root@localhost redis-6.2.14]# vim redis.conf     # 第480行,指定主库IP和端口(新版写法);replicaof 主库IP 端口,表示本机作为从节点复制指定主库,192.168.108.10为主库IP,6379为主库端口
replicaof 192.168.108.10 6379

# 不修改配置文件,通过配置命令也可以
# REPLICAOF MASTER_IP PORT 在线动态指定主库,MASTER_IP为主库IP、PORT为主库端口,无需改配置文件
127.0.0.1:6379> REPLICAOF MASTER_IP PORT     # 新版推荐使用
127.0.0.1:6379> SLAVEOF MASTER_IP PORT     # 旧版使用,将被淘汰

重启所有 redis

bash 复制代码
# 关闭Redis;redis-cli shutdown 通过客户端命令关闭本机Redis服务
[root@localhost redis-6.2.14]# redis-cli shutdown

# 后台启动Redis;redis-server redis.conf 按配置文件启动Redis,& 表示放到后台运行
[root@localhost redis-6.2.14]# redis-server redis.conf &

验证

在 master 节点查看复制信息:

bash 复制代码
# redis-cli 连接本机主库客户端
[root@master ~]# redis-cli

# 查看主从复制信息;info replication 查看复制相关的运行参数与状态
127.0.0.1:6379> info replication

# Replication
role:master     # 当前节点角色为主库(master)
connected_slaves:2     # 已成功连接的从库数量为2
slave0:ip=192.168.108.12,port=6379,state=online,offset=280,lag=0     # 第1个从库.12在线,复制偏移280,同步延迟0秒
slave1:ip=192.168.108.11,port=6379,state=online,offset=280,lag=0     # 第2个从库.11在线,复制偏移280,同步延迟0秒
master_failover_state:no-failover
master_replid:9abdc709c2e13658f9fe30908d53b7fd2ddd8e29
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:280     # 主库当前的复制偏移量
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:280

在 slave01 节点查看:

bash 复制代码
# redis-cli 连接从库slave01客户端
[root@slave01 ~]# redis-cli

# 查看从节点复制信息;info replication 查看本从库的复制状态
127.0.0.1:6379> info replication

# Replication
role:slave     # 当前节点角色为从库(slave)
master_host:192.168.108.10     # 所连接主库的IP地址
master_port:6379     # 主库端口号
master_link_status:up     # 与主库的连接状态正常(up)
master_last_io_seconds_ago:7
master_sync_in_progress:0
slave_read_repl_offset:280
slave_repl_offset:280     # 从库当前的复制偏移量
slave_priority:100
slave_read_only:1     # 从库为只读模式(1表示开启)
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:9abdc709c2e13658f9fe30908d53b7fd2ddd8e29
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:280
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:280

在 slave02 节点查看:

bash 复制代码
# redis-cli 连接从库slave02客户端
[root@slave02 ~]# redis-cli

# 查看从节点复制信息;info replication 查看本从库的复制状态
127.0.0.1:6379> info replication

# Replication
role:slave     # 当前节点角色为从库(slave)
master_host:192.168.108.10     # 所连接主库的IP地址
master_port:6379     # 主库端口号
master_link_status:up     # 与主库的连接状态正常(up)
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_read_repl_offset:350
slave_repl_offset:350     # 从库当前的复制偏移量
slave_priority:100
slave_read_only:1     # 从库为只读模式(1表示开启)
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:9abdc709c2e13658f9fe30908d53b7fd2ddd8e29
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:350
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:350

从主节点写入数据,数据会被同步到从节点:

bash 复制代码
# master节点写入数据;set name laogao 在主库写入键name,值为laogao
127.0.0.1:6379> set name laogao
OK     # 主库写入成功

# salve01,slave02 查看同步过来了;get name 在从库读取键name,验证数据是否已同步
127.0.0.1:6379> get name
"laogao"     # 从库读到的值与主库一致,说明主从数据同步正常

删除主从同步 :在从节点执行 REPLICAOF NO ONE 或 SLAVEOF NO ONE 指令可以取消主从复制。取消复制会断开和 master 的连接而不再有主从复制关联,但不会清除 slave 上已有的数据。

bash 复制代码
# 新版;REPLICAOF NO ONE 断开与主库的复制关系,并将本机提升为主库
127.0.0.1:6379> REPLICAOF NO ONE

# 旧版;SLAVEOF NO ONE 为旧版写法,作用相同,将从库升级为主库
127.0.0.1:6379> SLAVEOF NO ONE

redis 集群的哨兵服务

Redis 哨兵模式(Sentinel)是 Redis 官方提供的高可用性(High Availability, HA)解决方案 ,用于在主从架构中实现自动故障检测与故障转移。

目的 :在主从复制的基础上,实现自动故障转移(High Availability)。

原理 :哨兵是一个独立的分布式系统,由多个 Sentinel 实例(进程)组成,用于监控 Redis 主从节点的健康状态。

概念

Redis Sentinel(哨兵)是一个分布式监控系统,由一个或多个独立的 sentinel 进程组成。

主要功能:

  • 监控(Monitoring):持续检查主节点(Master)和从节点(Replica)是否正常运行。
  • 通知(Notification):当被监控的 Redis 实例异常时,可通过 API 通知管理员或其他系统。
  • 自动故障转移(Automatic Failover):当主节点宕机,Sentinel 会自动将一个从节点提升为新主节点,并更新其他从节点的复制源。
  • 配置提供者(Configuration Provider):客户端可通过 Sentinel 获取当前主节点地址,实现动态服务发现。

✅ 哨兵本身不存储数据,只负责监控和协调。

核心原理

架构组成
  • Master:主节点,处理写请求。
  • Replica(Slave):从节点,复制主节点数据,可处理读请求。
  • Sentinel 节点:独立进程,通常部署**奇数个(如 3 个)**以避免脑裂。
故障转移流程
  1. 主观下线(SDOWN) :某个 Sentinel 发现主节点无响应(超过 down-after-milliseconds),标记为主观下线。
  2. 客观下线(ODOWN) :多个 Sentinel(≥ quorum 配置值)达成共识,确认主节点已宕机。
  3. 选举 Leader:Sentinel 之间通过 Raft 算法选举出一个 Leader 负责执行故障转移。
  4. 提升新主 :Leader 选择一个"最优"从节点(优先级高、复制偏移量大、运行稳定)执行 REPLICAOF NO ONE,将其转为主节点。
  5. 重配从节点:其他从节点重新指向新主节点。
  6. 通知客户端:通过发布/订阅机制通知客户端主节点变更。
场景 说明
高可用缓存服务 主节点宕机时自动切换,保障业务连续性
避免人工干预 无需运维手动执行 REPLICAOF NO ONE
读写分离 + 自动容灾 结合主从复制,实现读扩展与写高可用
中小规模集群 适用于不需要分片(Sharding)的场景(大规模用 Redis Cluster)

使用场景

⚠️ 注意:Sentinel 不解决数据分片问题,仅解决单主节点的高可用。

操作步骤

延续上面的实验,此时三台 redis 处于一主二从的状态。三台 Redis 都要做。

启动哨兵

三台 Redis 都要做:

bash 复制代码
# 备份哨兵配置文件;cp sentinel.conf sentinel.conf.bak 备份哨兵配置文件,.bak为备份副本
[root@master redis-6.2.14]# cp sentinel.conf sentinel.conf.bak

# 编辑哨兵配置文件;vim sentinel.conf 用vim编辑哨兵配置文件
[root@master redis-6.2.14]# vim sentinel.conf     # 第84行,监控主节点     # 主节点ip,端口以及哨兵投票数量2,当有2个及以上的哨兵认为主节点不可用时那么就是客观下线,就需要进行主从故障转移;mymaster为给主库起的名字,192.168.108.10 6379为主库IP与端口,末尾的2为客观下线所需的投票哨兵数
sentinel monitor mymaster 192.168.108.10 6379 2

# 第125行,主观下线时间
# 超过该时间主节点没有响应哨兵,则哨兵会对主节点主观下线,单位是毫秒;mymaster为主库名,30000即30秒
sentinel down-after-milliseconds mymaster 30000

# 启动哨兵并放在后台运行;redis-sentinel sentinel.conf 启动哨兵进程并加载配置文件,& 放到后台运行
[root@localhost redis-6.2.14]# redis-sentinel sentinel.conf &
[2] 9203     # 后台作业号为2,哨兵进程PID为9203
查看哨兵状态
bash 复制代码
# 连接哨兵端口(默认26379);redis-cli -p 26379 连接哨兵服务,-p 指定哨兵端口26379
[root@master redis-6.2.14]# redis-cli -p 26379

# info sentinel 查看哨兵的监控状态
127.0.0.1:26379> info sentinel

# Sentinel
sentinel_masters:1     # 当前哨兵监控的主库数量为1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.108.10:6379,slaves=2,sentinels=3     # 主库mymaster状态ok,地址.10:6379,下辖2个从库、共3个哨兵
验证

模拟故障将 master 关机,发现剩下两台 slave 主机开始选举新 master 主机。

将 master 恢复试试,master 成为 slave:

bash 复制代码
# 启动并放在后台运行;cd 进入Redis安装目录/usr/local/redis-6.2.14
[root@master ~]# cd /usr/local/redis-6.2.14/

# redis-server redis.conf & 以后台方式按配置文件启动Redis主服务,& 放到后台运行
[root@master redis-6.2.14]# redis-server redis.conf &

# redis-sentinel sentinel.conf & 以后台方式启动哨兵进程,& 放到后台运行
[root@master redis-6.2.14]# redis-sentinel sentinel.conf &

✅ 总结 :哨兵模式是 Redis 实现自动高可用的标准方案,适用于对可用性要求高、但无需数据分片的业务场景。对于超大规模集群,应考虑 Redis Cluster。

redis 集群(3 主 3 从)

Redis 集群(Cluster)是 Redis 3.0+ 官方推出的分布式解决方案 ,核心是数据分片(Sharding)+ 主从复制 + 去中心化故障转移 ,解决单机 Redis 的容量、性能、单点故障瓶颈,实现水平扩展、高可用、高并发。

核心概念

基本定义

Redis 集群是无中心架构的分布式系统,数据自动分散到多个节点,每个节点只存部分数据;节点间通过 Gossip 协议通信,自带故障转移,无需 Sentinel。

关键角色
  • 主节点(Master):处理读写、管理哈希槽、参与故障投票,是核心节点。
  • 从节点(Replica/Slave):异步复制主节点数据,默认只读;主节点宕机时自动选举晋升为主。
  • 哈希槽(Hash Slot) :集群固定 16384 个槽(0~16383),是数据分片的最小单位。
  • 节点 ID:每个节点唯一标识,用于集群内识别与通信。
核心特性
  • 数据分片:突破单机内存限制,总容量 = 所有主节点内存之和。
  • 高可用:部分节点故障不影响整体,自动故障转移。
  • 线性扩展:加节点即可提升容量与并发。
  • 去中心化:无中心节点,避免单点瓶颈。
  • 自动路由:客户端连接任意节点,自动重定向到目标节点。

工作原理

数据分片(哈希槽机制)
  • 槽位总数:16384(2¹⁴)。
  • 键 → 槽映射:
bash 复制代码
# 集群分片公式:HASH_SLOT = CRC16(key) mod 16384,即对key做CRC16哈希后再对16384取模,得到该键归属的哈希槽(0~16383)
HASH_SLOT = CRC16(key) mod 16384
  • 槽位分配:每个主节点负责一段连续槽位(如 3 主集群:0-5460、5461-10922、10923-16383)。
节点通信(Gossip 协议)

节点间通过集群总线(独立端口,默认 + 10000)通信。

消息类型:

  • PING/PONG:心跳检测、状态同步。
  • FAIL:广播节点失效。
  • UPDATE:槽位映射、集群拓扑更新。
系统 IP 地址 主机名
Centos7.9 192.168.108.21 redis1
Centos7.9 192.168.108.22 redis2
Centos7.9 192.168.108.23 redis3
请求路由流程
  1. 客户端连接任意节点,发送命令。
  2. 节点计算 key 所属槽位。
  3. 槽位归属本节点 → 直接处理;否则 → 返回 MOVED 重定向。
  4. 智能客户端缓存槽位映射,减少重定向。
故障转移(高可用)
  1. 故障检测 :某主节点超时无响应,多数主节点标记为 FAIL。
  2. 从节点选举 :该主节点的从节点发起 FAILOVER 请求。
  3. 投票晋升:主节点投票通过,从节点升级为主,接管槽位。
  4. 拓扑更新:Gossip 同步新状态,集群恢复。
数据一致性
  • 异步复制:主从复制异步,存在短暂数据不一致。
  • 最终一致性:故障转移后数据最终一致,不保证强一致。
  • 写安全:多数主节点可达时,写操作尽量保留。

使用场景

海量数据存储(>100GB)

电商商品库、用户画像、社交内容、IoT 设备数据。突破单机内存上限,分布式存储海量热数据。

高并发读写(QPS ≥ 5 万)

秒杀、抢购、热点评论、实时排行榜。多主节点并行处理,并发能力线性提升。

强事务、多键跨槽操作(如 MSET 多键不同槽)。- 数据量小、低并发(哨兵/主从更简单)。
  • 需强一致性(可搭配分布式事务)。

操作步骤(3 主 3 从实战)

系统 IP 地址 主机名
Centos7.9 192.168.108.24 redis4
Centos7.9 192.168.108.25 redis5
Centos7.9 192.168.108.26 redis6
架构cluster 集群是 Redis 从 3.0 版本开始支持,自带的一种集群方式。它的原理使用了分布的思想,其数据会均分到所有的主节点上。此部署方式,当数据量过大时,会让服务器均摊压力。在各个主节点上分配的数据都不是全量的。是分片存储的。目前此种部署方式在生产环境的较多。至少六台,三台主,三台从。

目的 :同时解决高可用 、海量数据存储 和高并发读写 的问题,即分布式扩展。

原理 :Redis Cluster 是 Redis 官方提供的分布式数据库解决方案。它通过数据分片(Sharding) 进行数据存储,并通过多主多从架构来实现高可用。

准备 6 台安装了 redis 的虚拟机,同时操作:关闭防火墙,修改配置文件。

配置

所有主机操作:

bash 复制代码
# 修改配置文件;systemctl stop firewalld 关闭防火墙,避免其拦截集群节点端口
[root@localhost ~]# systemctl stop firewalld

# cd 进入Redis安装目录
[root@localhost ~]# cd /usr/local/redis-6.2.14/

# vim redis.conf 编辑该集群节点的配置文件
[root@localhost redis-6.2.14]# vim redis.conf     # 第75行,监听地址;bind 0.0.0.0 -::1 监听所有IPv4与IPv6地址,允许集群节点间互通
bind 0.0.0.0 -::1

# 第1387行,启动cluster集群;cluster-enabled yes 开启集群模式,使本节点成为集群节点
cluster-enabled yes

# 启动Redis;redis-server redis.conf & 以后台方式按配置启动本节点,& 放到后台运行
[root@localhost redis-6.2.14]# redis-server redis.conf &

# 查看启动端口(6379为客户端端口,16379为集群总线端口);netstat -anpt | grep redis 查看redis监听端口,netstat显示网络连接,grep过滤redis相关行
[root@redis1 redis-6.2.14]# netstat -anpt | grep redis
tcp        0      0 0.0.0.0:6379            0.0.0.0:*               LISTEN      55036/redis-server     # 客户端端口6379处于监听状态
tcp        0      0 0.0.0.0:16379           0.0.0.0:*               LISTEN      55036/redis-server     # 集群总线端口16379处于监听状态(用于节点间通信,为客户端端口+10000)
tcp6       0      0 ::1:6379                :::*                    LISTEN      55036/redis-server
tcp6       0      0 ::1:16379               :::*                    LISTEN      55036/redis-server

任意一台即可创建集群:

bash 复制代码
# 创建集群,6个节点,每个主节点带1个从节点;redis-cli --cluster create 创建集群,后接6个节点的IP:端口,--cluster-replicas 1 指定每个主节点配备1个从节点
[root@redis1 redis-6.2.14]# redis-cli --cluster create 192.168.108.21:6379 192.168.108.22:6379 192.168.108.23:6379 192.168.108.24:6379 192.168.108.25:6379 192.168.108.26:6379 --cluster-replicas 1
>>> Performing hash slots allocation on 6 nodes...     # 工具开始在6个节点间分配16384个哈希槽
Master[0] -> Slots 0-5460
Master[1] -> Slots 5461-10922
Master[2] -> Slots 10923-16383
Adding replica 192.168.108.25:6379 to 192.168.108.21:6379
Adding replica 192.168.108.26:6379 to 192.168.108.22:6379
Adding replica 192.168.108.24:6379 to 192.168.108.23:6379
M: fcbf04a3649f4e9e6b381861a94393c31eb67e67 192.168.108.21:6379
   slots:[0-5460] (5461 slots) master     # [0-5460]是负责槽位0-5064,(5461 slots)是共5461个槽位
M: f3cb00b07a7836666bd3fd391040ff308ad35685 192.168.108.22:6379
   slots:[5461-10922] (5462 slots) master
M: 90507dea1ca425cfe6ca993f8c453b2377ded5f0 192.168.108.23:6379
   slots:[10923-16383] (5461 slots) master
S: 95fd02ecaf3fb34822fd56ed7ae61b05d782d892 192.168.108.24:6379
   replicates 90507dea1ca425cfe6ca993f8c453b2377ded5f0
S: 18b45e542ebbf318fd9a92cfe2adcde4c255c72b 192.168.108.25:6379
   replicates fcbf04a3649f4e9e6b381861a94393c31eb67e67
S: 8f5e04a03ae0984d843dab8fb9cd0cc4c4537624 192.168.108.26:6379
   replicates f3cb00b07a7836666bd3fd391040ff308ad35685
Can I set the above configuration? (type 'yes' to accept): yes     # 回答yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
55036:M 03 Feb 2026 11:35:48.414 # configEpoch set to 1 via CLUSTER SET-CONFIG-EPOCH
>>> Sending CLUSTER MEET messages to join the cluster
55036:M 03 Feb 2026 11:35:48.445 # IP address for this node updated to 192.168.108.21
Waiting for the cluster to join

55036:M 03 Feb 2026 11:35:49.510 * Replica 192.168.108.25:6379 asks for synchronization
55036:M 03 Feb 2026 11:35:49.510 * Partial resynchronization not accepted: Replication ID mismatch (Replica asked for '5ada9eaab85561721d9411eee29d9e15bf2b87b1', my replication IDs are '868d3acb992f408196b2367647b0cf6c3e02912a' and '0000000000000000000000000000000000000000')
55036:M 03 Feb 2026 11:35:49.510 * Replication backlog created, my new replication IDs are 'cef440df5f9f0706ac9ab7e60a450839e702f045' and '0000000000000000000000000000000000000000'
55036:M 03 Feb 2026 11:35:49.510 * Starting BGSAVE for SYNC with target: disk
55036:M 03 Feb 2026 11:35:49.606 * Background saving started by pid 55327
>>> Performing Cluster Check (using node 192.168.108.21:6379)
M: fcbf04a3649f4e9e6b381861a94393c31eb67e67 192.168.108.21:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: 8f5e04a03ae0984d843dab8fb9cd0cc4c4537624 192.168.108.26:6379
   slots: (0 slots) slave
   replicates f3cb00b07a7836666bd3fd391040ff308ad35685
S: 95fd02ecaf3fb34822fd56ed7ae61b05d782d892 192.168.108.24:6379
   slots: (0 slots) slave
   replicates 90507dea1ca425cfe6ca993f8c453b2377ded5f0
M: 90507dea1ca425cfe6ca993f8c453b2377ded5f0 192.168.108.23:6379
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: 18b45e542ebbf318fd9a92cfe2adcde4c255c72b 192.168.108.25:6379
   slots: (0 slots) slave
   replicates fcbf04a3649f4e9e6b381861a94393c31eb67e67
M: f3cb00b07a7836666bd3fd391040ff308ad35685 192.168.108.22:6379
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
55327:C 03 Feb 2026 11:35:49.733 * DB saved on disk
55327:C 03 Feb 2026 11:35:49.733 * RDB: 4 MB of memory used by copy-on-write
55036:M 03 Feb 2026 11:35:49.736 * Background saving terminated with success
55036:M 03 Feb 2026 11:35:49.737 * Synchronization with replica 192.168.108.25:6379 succeeded
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.     # 全部16384个槽位均已分配覆盖,集群创建成功
[root@redis1 redis-6.2.14]# 55036:M 03 Feb 2026 11:35:53.476 # Cluster state changed: ok
查看集群状态
bash 复制代码
# 查看集群节点信息;redis-cli 连接本机集群节点
[root@redis1 redis-6.2.14]# redis-cli

# cluster nodes 列出集群中所有节点的角色与槽位分配
127.0.0.1:6379> cluster nodes
8f5e04a03ae0984d843dab8fb9cd0cc4c4537624 192.168.108.26:6379@16379 slave f3cb00b07a7836666bd3fd391040ff308ad35685 0 1770089897000 2 connected
95fd02ecaf3fb34822fd56ed7ae61b05d782d892 192.168.108.24:6379@16379 slave 90507dea1ca425cfe6ca993f8c453b2377ded5f0 0 1770089900314 3 connected
90507dea1ca425cfe6ca993f8c453b2377ded5f0 192.168.108.23:6379@16379 master - 0 1770089898000 3 connected 10923-16383     # 主库.23,负责槽10923-16383
18b45e542ebbf318fd9a92cfe2adcde4c255c72b 192.168.108.25:6379@16379 slave fcbf04a3649f4e9e6b381861a94393c31eb67e67 0 1770089899000 1 connected
f3cb00b07a7836666bd3fd391040ff308ad35685 192.168.108.22:6379@16379 master - 0 1770089899267 2 connected 5461-10922     # 主库.22,负责槽5461-10922
fcbf04a3649f4e9e6b381861a94393c31eb67e67 192.168.108.21:6379@16379 myself,master - 0 1770089899000 1 connected 0-5460     # 当前节点(myself)为主库.21,负责槽0-5460

# 查看集群信息;cluster info 查看集群整体运行状态
127.0.0.1:6379> cluster info
cluster_state:ok     # 集群状态正常(ok)
cluster_slots_assigned:16384     # 已分配的槽位总数
cluster_slots_ok:16384     # 正常可用的槽位数
cluster_slots_pfail:0
cluster_slots_fail:0     # 故障槽位数量为0
cluster_known_nodes:6     # 集群已知节点总数为6
cluster_size:3     # 负责槽位的主库数量为3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:1134
cluster_stats_messages_pong_sent:1172
cluster_stats_messages_sent:2306
cluster_stats_messages_ping_received:1167
cluster_stats_messages_pong_received:1134
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:2306
创建数据

创建数据需要到提示的地方创建;或者在 redis-cli -c 的命令创建数据。

bash 复制代码
# redis1节点上创建数据,会提示跳转到对应槽位所在节点;set name gqd 在.21节点上写入键name
127.0.0.1:6379> set name gqd
(error) MOVED 5798 192.168.108.22:6379     # 键name归属槽5798,由.22负责,需跳转过去执行

# 提示在redis2操作

# redis2节点上创建数据;set name 'gqd' 在负责该槽的.22节点上写入键name
127.0.0.1:6379> set name 'gqd'
OK     # 写入成功

# get name 在.22节点上读取键name
127.0.0.1:6379> get name
"gqd"     # 读取到的值为gqd

# redis1节点上查询,也会提示跳转;get name 在.21节点上查询name,观察重定向提示
127.0.0.1:6379> get name     # 创建,查看都只能在redis2
(error) MOVED 5798 192.168.108.22:6379     # 槽5798归.22负责,需跳转

# 使用-c参数自动重定向,在任意节点即可操作;redis-cli -c 以集群模式启动客户端,-c 表示遇到MOVED时自动重定向到正确节点
[root@redis1 redis-6.2.14]# redis-cli -c

# set age 18 写入键age,客户端会自动重定向到负责该槽的节点
127.0.0.1:6379> set age 18
OK     # 写入成功

# get age 读取键age
127.0.0.1:6379> get age
"18"     # 读取到的值为18

# 在redis3节点上查询,自动重定向到对应节点;redis-cli -c 在.23节点上以集群模式连接
[root@redis3 redis-6.2.14]# redis-cli -c

# get age 在.23节点上读取age,观察自动重定向过程
127.0.0.1:6379> get age
-> Redirected to slot [741] located at 192.168.108.21:6379     # 客户端自动跳转到负责槽741的.21节点
"18"     # 读取到的值为18
模拟故障

关掉 redis1 节点,再次查看集群信息。

bash 复制代码
# 关闭redis1节点;init 0 关闭操作系统,即停掉redis1这台机器
[root@redis1 redis-6.2.14]# init 0

# 在redis2节点上查看集群信息;cluster info 在存活的.22节点上查看集群状态,验证故障转移
127.0.0.1:6379> cluster info
cluster_state:ok     # 集群仍为ok:.21的槽位已由其从库接管
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0     # 故障槽位为0,槽已由新主接管
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:7     # 故障转移后集群纪元号已递增
cluster_my_epoch:2
cluster_stats_messages_ping_sent:1486
cluster_stats_messages_pong_sent:1419
cluster_stats_messages_meet_sent:1
cluster_stats_messages_fail_sent:5
cluster_stats_messages_auth-ack_sent:1
cluster_stats_messages_sent:2912
cluster_stats_messages_ping_received:1419
cluster_stats_messages_pong_received:1487
cluster_stats_messages_fail_received:1
cluster_stats_messages_auth-req_received:1
cluster_stats_messages_received:2908
节点 IP 角色 负责槽 副本
192.168.108.21 Master 0 -- 5460 ← 由 .25 复制
192.168.108.25 Slave (无槽) 复制 .21
查看节点信息

结果可知,仍保持 3 台 master,原来的 redis5 节点被选举为新 master 节点。再把 redis 业务修复,看集群状态。

bash 复制代码
# 在redis2节点上查看集群节点;cluster nodes 查看各节点角色,确认故障转移结果
127.0.0.1:6379> cluster nodes
90507dea1ca425cfe6ca993f8c453b2377ded5f0 192.168.108.23:6379@16379 master - 0 1770091176000 3 connected 10923-16383
18b45e542ebbf318fd9a92cfe2adcde4c255c72b 192.168.108.25:6379@16379 master - 0 1770091177432 7 connected 0-5460     # 原.25从库已晋升为主库,接管槽0-5460
f3cb00b07a7836666bd3fd391040ff308ad35685 192.168.108.22:6379@16379 myself,master - 0 1770091175000 2 connected 5461-10922     # 当前节点.22为主库,负责槽5461-10922
8f5e04a03ae0984d843dab8fb9cd0cc4c4537624 192.168.108.26:6379@16379 slave f3cb00b07a7836666bd3fd391040ff308ad35685 0 1770091174000 2 connected
95fd02ecaf3fb34822fd56ed7ae61b05d782d892 192.168.108.24:6379@16379 slave 90507dea1ca425cfe6ca993f8c453b2377ded5f0 0 1770091176422 3 connected
fcbf04a3649f4e9e6b381861a94393c31eb67e67 192.168.108.21:6379@16379 master,fail - 1770091143695 1770091140000 1 connected     # 原主库.21已故障下线(fail)
完整过程

如果你的 Redis Cluster 中 192.168.108.21:6379(Master 1 )挂掉,而它的从节点 192.168.108.25:6379 仍然存活 ,那么整个集群会自动完成故障转移(failover) ,服务不会中断,数据也不会丢失。下面详细说明会发生什么:

🧩 当前角色回顾(基于你之前的 cluster nodes 输出):其他节点(.22/.26, .23/.24)正常运行。

🔥 如果 192.168.108.21 挂掉,会发生什么?

项目 故障前 故障后(.21 挂)
Master 节点 .21, .22, .23 .25, .22, .23
Slave 节点 .25, .26, .24 (.25 已晋升,剩下 .26, .24)
槽覆盖 0--16383 0--16383(完整)

✅ 第 1 步:故障检测(约 15 秒内)

其他 master 节点(.22 和 .23)通过 Gossip 协议 发现 .21 无响应。经过 cluster-node-timeout(默认 15 秒)后:多数 master(≥ 2/3)将其标记为 FAIL(客观下线)。

⏱️ 在这 15 秒内,访问槽 0--5460 的请求会失败或超时。

✅ 第 2 步:Slave .25 自动发起选举

.25(原 .21 的 slave)检测到 master 已 FAIL。等待一个短暂随机延迟(通常 < 1 秒),然后向其他 **master 节点(.22 和 .23)**请求投票。

✅ 第 3 步:获得多数票,晋升为新 Master

.22 和 .23 收到请求后,验证 .25 是合法副本。因为有 2 个 master 投票 (超过半数),.25 成功当选 。.25 执行 SLAVEOF NO ONE 并接管槽 0--5460。

✅ 第 4 步:集群拓扑更新,恢复服务

所有节点收到通知,更新集群视图:.25 变为 master,槽 0--5460 的 owner 变为 .25。集群状态重新变为 cluster_state: ok,cluster_slots_fail: 0。

客户端再次写入 key(如 SET user:1001 "Alice",假设其槽在 0--5460):自动重定向到 192.168.108.25:6379 操作成功!

📊 故障前后对比:

项目 故障前 故障后(.21 挂)
集群状态 ok 短暂 fail → 恢复为 ok
数据完整性 完整 完整(无丢失)
客户端影响 写入槽 0--5460 暂时失败(< 30 秒) 自动恢复
场景 集群是否可用 数据是否安全 是否自动恢复
仅 .21(master)挂 ✅ 是 ✅ 是 ✅ 是(由 .25 接管)
.21 + .25 同时挂 ❌ 否 ⚠️ 槽 0--5460 不可用 ❌ 否

✅ 整个过程全自动,无需人工干预!

❗ 极端情况:如果 .21 和 .25 同时挂掉?槽 0--5460 将无人负责 ,集群状态变为 cluster_state: fail,cluster_slots_fail: 5461。所有涉及这些槽的读写都会失败(返回 (error) CLUSTERDOWN The cluster is down)。必须手动恢复 .21 或 .25 才能恢复服务。

🔒 这就是为什么主从节点必须部署在不同物理机/可用区!

🔧 如何验证?(动手测试)

bash 复制代码
# 1. 在redis1 (.21) 上停止Redis;systemctl stop redis 用systemd停止.21上的Redis服务
systemctl stop redis

# 2. 在redis2 (.22) 上观察集群状态;redis-cli -c -h 192.168.108.22 -p 6379 cluster nodes:-c集群自动重定向,-h指定连接.22,-p指定端口6379,cluster nodes列出节点
redis-cli -c -h 192.168.108.22 -p 6379 cluster nodes

你会看到:

  • .21 状态变为 fail(→ fail#faile? 状态停留很短暂)
  • .25 角色从 slave 变为 master,并拥有 0-5460 槽
  • 几十秒后,cluster info 显示 cluster_state:ok

✅ 总结 :你当前的 3 主 3 从架构完全能容忍任意一个 master 宕机,这是 Redis Cluster 高可用的核心价值!

挂掉 radis5 测试:

bash 复制代码
# 关闭redis5节点;init 0 关闭redis5(.25)这台机器
[root@redis5 redis-6.2.14]# init 0

# 在redis2节点上查看集群节点(此时.25也挂了,槽0-5460无人负责);redis-cli -c -h 192.168.108.22 -p 6379 cluster nodes:-c自动重定向,-h指定.22,-p指定6379,查看节点状态
[root@redis2 redis-6.2.14]# redis-cli -c -h 192.168.108.22 -p 6379 cluster nodes
90507dea1ca425cfe6ca993f8c453b2377ded5f0 192.168.108.23:6379@16379 master - 0 1770092343225 3 connected 10923-16383
18b45e542ebbf318fd9a92cfe2adcde4c255c72b 192.168.108.25:6379@16379 master,fail - 1770092246338 1770092243211 7 connected 0-5460     # .25主库故障,其负责的槽0-5460已无人接管
f3cb00b07a7836666bd3fd391040ff308ad35685 192.168.108.22:6379@16379 myself,master - 0 1770092341000 2 connected 5461-10922
8f5e04a03ae0984d843dab8fb9cd0cc4c4537624 192.168.108.26:6379@16379 slave f3cb00b07a7836666bd3fd391040ff308ad35685 0 1770092342000 2 connected
95fd02ecaf3fb34822fd56ed7ae61b05d782d892 192.168.108.24:6379@16379 slave 90507dea1ca425cfe6ca993f8c453b2377ded5f0 0 1770092345259 3 connected
fcbf04a3649f4e9e6b381861a94393c31eb67e67 192.168.108.21:6379@16379 master,fail - 1770091143695 1770091140000 1 connected     # .21原主库仍处于故障状态

# 查看集群信息(状态为fail,5461个槽不可用);redis-cli -c -h 192.168.108.22 -p 6379 cluster info 查看集群状态
[root@redis2 redis-6.2.14]# redis-cli -c -h 192.168.108.22 -p 6379 cluster info
cluster_state:fail     # 集群状态变为fail

# 集群状态fail
cluster_slots_assigned:16384
cluster_slots_ok:10923     # 仅剩10923个槽可用
cluster_slots_pfail:0
cluster_slots_fail:5461     # 有5461个槽(0-5460)无主负责,不可用
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:7
cluster_my_epoch:2
cluster_stats_messages_ping_sent:2545
cluster_stats_messages_pong_sent:2417
cluster_stats_messages_meet_sent:1
cluster_stats_messages_fail_sent:5
cluster_stats_messages_auth-ack_sent:1
cluster_stats_messages_sent:4969
cluster_stats_messages_ping_received:2417
cluster_stats_messages_pong_received:2545
cluster_stats_messages_fail_received:2
cluster_stats_messages_auth-req_received:1
cluster_stats_messages_received:4965
恢复集群监控

将关掉的两台服务器开机,开启 redis 服务即可。

bash 复制代码
# 恢复后查看集群信息(状态恢复为ok);redis-cli -c -h 192.168.108.22 -p 6379 cluster info 查看恢复后的集群状态
[root@redis2 redis-6.2.14]# redis-cli -c -h 192.168.108.22 -p 6379 cluster info
cluster_state:ok     # 集群状态恢复为ok
cluster_slots_assigned:16384
cluster_slots_ok:16384     # 全部16384个槽恢复可用
cluster_slots_pfail:0
cluster_slots_fail:0     # 故障槽位清零
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:7
cluster_my_epoch:2
cluster_stats_messages_ping_sent:3130
cluster_stats_messages_pong_sent:2952
cluster_stats_messages_meet_sent:1
cluster_stats_messages_fail_sent:5
cluster_stats_messages_auth-ack_sent:1
cluster_stats_messages_sent:6089
cluster_stats_messages_ping_received:2952
cluster_stats_messages_pong_received:3130
cluster_stats_messages_fail_received:3
cluster_stats_messages_auth-req_received:1
cluster_stats_messages_received:6086

# 恢复后查看集群节点(.21变为.25的从节点);cluster nodes 查看重启后各节点角色
[root@redis2 redis-6.2.14]# redis-cli -c -h 192.168.108.22 -p 6379 cluster nodes
90507dea1ca425cfe6ca993f8c453b2377ded5f0 192.168.108.23:6379@16379 master - 0 1770092977782 3 connected 10923-16383
18b45e542ebbf318fd9a92cfe2adcde4c255c72b 192.168.108.25:6379@16379 master - 0 1770092978816 7 connected 0-5460
f3cb00b07a7836666bd3fd391040ff308ad35685 192.168.108.22:6379@16379 myself,master - 0 1770092977000 2 connected 5461-10922
8f5e04a03ae0984d843dab8fb9cd0cc4c4537624 192.168.108.26:6379@16379 slave f3cb00b07a7836666bd3fd391040ff308ad35685 0 1770092977000 2 connected
95fd02ecaf3fb34822fd56ed7ae61b05d782d892 192.168.108.24:6379@16379 slave 90507dea1ca425cfe6ca993f8c453b2377ded5f0 0 1770092976765 3 connected
fcbf04a3649f4e9e6b381861a94393c31eb67e67 192.168.108.21:6379@16379 slave 18b45e542ebbf318fd9a92cfe2adcde4c255c72b 0 1770092977000 7 connected     # .21重启后以从库身份复制新主库.25

容器部署 redis

docker 容器方式安装:

bash 复制代码
# 启动Redis容器,映射6379端口,数据挂载到/data/redis目录;docker run 启动容器,--name redis指定容器名为redis,-p 6379:6379把宿主6379端口映射到容器6379,-d 后台运行,-v /data/redis:/data把宿主目录挂载到容器/data,末尾redis为镜像名
[root@localhost ~]# docker run --name redis -p 6379:6379 -d -v /data/redis:/data redis

# 进入容器查看Redis服务信息;docker exec redis redis-cli info server:docker exec进入运行中的容器redis执行命令,redis-cli连接容器内Redis,info server查看服务信息
[root@localhost ~]# docker exec redis redis-cli info server

# Server
redis_version:8.6.0     # 容器内Redis版本号为8.6.0
redis_git_sha1:00000000
redis_git_dirty:1
redis_build_id:a7d515010e105f80
redis_mode:standalone     # 运行模式为单机(standalone)
os:Linux 4.18.0-553.6.1.el8.x86_64 x86_64
arch_bits:64
monotonic_clock:POSIX clock_gettime
multiplexing_api:epoll
atomicvar_api:c11-builtin
gcc_version:14.2.0
process_id:1     # Redis进程号为1(容器内的1号进程)
process_supervised:no
run_id:a2ee2aa49069ff42d23ca8d46f82d3ac61f1d385
tcp_port:6379     # Redis监听端口为6379
server_time_usec:1770950461439156
uptime_in_seconds:109     # 容器内Redis已运行109秒
uptime_in_days:0
hz:10
configured_hz:10
lru_clock:9342781
executable:/data/redis-server
config_file:
io_threads_active:0
listener0:name=tcp,bind=*,bind=-::*,port=6379

默认 Redis 容器可以支持远程连接。

bash 复制代码
# 在容器中设置键值;docker exec redis redis-cli set name ma:进入容器redis执行redis-cli,set写入键name,值为ma
[root@localhost ~]# docker exec redis redis-cli set name ma
OK     # 写入成功

# 再写入键age,值为18
[root@localhost ~]# docker exec redis redis-cli set age 18
OK     # 写入成功

# 获取键值;docker exec redis redis-cli get name:在容器内读取键name的值
[root@localhost ~]# docker exec redis redis-cli get name
ma     # 键name的值

# 读取键age的值
[root@localhost ~]# docker exec redis redis-cli get age
18     # 键age的值

# 手动保存数据到磁盘;docker exec redis redis-cli save:在容器内执行save,将数据同步持久化为RDB快照
[root@localhost ~]# docker exec redis redis-cli save
OK     # 保存成功

# 查看挂载目录下的持久化文件;ls /data/redis/ -1:查看宿主挂载目录,-1表示每行列出一个文件
[root@localhost ~]# ls /data/redis/ -1
dump.rdb     # RDB持久化快照文件

容器部署基础

bash 复制代码
# 编辑docker-compose编排文件;定义6节点Redis集群,3主3从,自定义bridge网段
[root@docker ~]# vim docker-compose.yml
yaml 复制代码
version: '2.27'

networks:
  redis-cluster-net:
    driver: bridge
    ipam:
      config:
        - subnet: 192.168.109.0/24

services:
  # Master1 192.168.109.21
  redis-21:
    image: redis:latest
    container_name: redis-21
    restart: always
    ports:
      - "6379:6379"
      - "16379:16379"
    networks:
      redis-cluster-net:
        ipv4_address: 192.168.109.21
    volumes:
      - ./node21/data:/data
    command: >
      redis-server
      --bind 0.0.0.0
      --port 6379
      --cluster-enabled yes
      --cluster-config-file nodes.conf
      --cluster-node-timeout 5000
      --cluster-announce-ip 192.168.109.21
      --cluster-announce-port 6379
      --cluster-announce-bus-port 16379
      --appendonly yes
      --protected-mode no

  # Master2 192.168.109.22
  redis-22:
    image: redis:latest
    container_name: redis-22
    restart: always
    ports:
      - "6380:6379"
      - "16380:16379"
    networks:
      redis-cluster-net:
        ipv4_address: 192.168.109.22
    volumes:
      - ./node22/data:/data
    command: >
      redis-server
      --bind 0.0.0.0
      --port 6379
      --cluster-enabled yes
      --cluster-config-file nodes.conf
      --cluster-node-timeout 5000
      --cluster-announce-ip 192.168.109.22
      --cluster-announce-port 6379
      --cluster-announce-bus-port 16379
      --appendonly yes
      --protected-mode no

  # Master3 192.168.109.23
  redis-23:
    image: redis:latest
    container_name: redis-23
    restart: always
    ports:
      - "6381:6379"
      - "16381:16379"
    networks:
      redis-cluster-net:
        ipv4_address: 192.168.109.23
    volumes:
      - ./node23/data:/data
    command: >
      redis-server
      --bind 0.0.0.0
      --port 6379
      --cluster-enabled yes
      --cluster-config-file nodes.conf
      --cluster-node-timeout 5000
      --cluster-announce-ip 192.168.109.23
      --cluster-announce-port 6379
      --cluster-announce-bus-port 16379
      --appendonly yes
      --protected-mode no

  # Slave1 192.168.109.25 主:192.168.109.21
  redis-25:
    image: redis:latest
    container_name: redis-25
    restart: always
    ports:
      - "6382:6379"
      - "16382:16379"
    networks:
      redis-cluster-net:
        ipv4_address: 192.168.109.25
    volumes:
      - ./node25/data:/data
    command: >
      redis-server
      --bind 0.0.0.0
      --port 6379
      --cluster-enabled yes
      --cluster-config-file nodes.conf
      --cluster-node-timeout 5000
      --cluster-announce-ip 192.168.109.25
      --cluster-announce-port 6379
      --cluster-announce-bus-port 16379
      --appendonly yes
      --protected-mode no

  # Slave2 192.168.109.26 主:192.168.109.22
  redis-26:
    image: redis:latest
    container_name: redis-26
    restart: always
    ports:
      - "6383:6379"
      - "16383:16379"
    networks:
      redis-cluster-net:
        ipv4_address: 192.168.109.26
    volumes:
      - ./node26/data:/data
    command: >
      redis-server
      --bind 0.0.0.0
      --port 6379
      --cluster-enabled yes
      --cluster-config-file nodes.conf
      --cluster-node-timeout 5000
      --cluster-announce-ip 192.168.109.26
      --cluster-announce-port 6379
      --cluster-announce-bus-port 16379
      --appendonly yes
      --protected-mode no

  # Slave3 192.168.109.24 主:192.168.109.23
  redis-24:
    image: redis:latest
    container_name: redis-24
    restart: always
    ports:
      - "6384:6379"
      - "16384:16379"
    networks:
      redis-cluster-net:
        ipv4_address: 192.168.109.24
    volumes:
      - ./node24/data:/data
    command: >
      redis-server
      --bind 0.0.0.0
      --port 6379
      --cluster-enabled yes
      --cluster-config-file nodes.conf
      --cluster-node-timeout 5000
      --cluster-announce-ip 192.168.109.24
      --cluster-announce-port 6379
      --cluster-announce-bus-port 16379
      --appendonly yes
      --protected-mode no
bash 复制代码
# 后台启动docker compose容器;创建并启动6个redis节点
[root@docker ~]# docker compose up -d
# 进入redis容器执行集群创建;--cluster-replicas 1 代表每个主节点1个从节点
[root@docker ~]# docker exec -it redis-21 redis-cli --cluster create \
> 192.168.109.21:6379 \
> 192.168.109.22:6379 \
> 192.168.109.23:6379 \
> 192.168.109.24:6379 \
> 192.168.109.25:6379 \
> 192.168.109.26:6379 \
> --cluster-replicas 1
>>> Performing hash slots allocation on 6 nodes...  # 对6个节点分配哈希槽
Master[0] -> Slots 0 - 5460                          # master0分配槽位0~5460
Master[1] -> Slots 5461 - 10922                      # master1分配槽位5461~10922
Master[2] -> Slots 10923 - 16383                     # master2分配槽位10923~16383
Adding replica 192.168.109.25:6379 to 192.168.109.21:6379   # 给21主分配从25
Adding replica 192.168.109.26:6379 to 192.168.109.22:6379   # 给22主分配从26
Adding replica 192.168.109.24:6379 to 192.168.109.23:6379   # 给23主分配从24
M: a3ce222784b489291224a2e9bd7f7119ceab88a2 192.168.109.21:6379
   slots:[0-5460] (5461 slots) master                 # 21为主节点,持有0-5460槽
M: 9e21f0ff2cf173f64018e40f981d1226cc941ece 192.168.109.22:6379
   slots:[5461-10922] (5462 slots) master             # 22为主节点,持有5461-10922槽
M: 7fbeeae0072a00c5125db1de7b264d63464740a9 192.168.109.23:6379
   slots:[10923-16383] (5461 slots) master            # 23为主节点,持有10923-16383槽
S: 3a4df342f4b660cbc7cc1dcd23ccce82a0000004 192.168.109.24:6379
   replicates 7fbeeae0072a00c5125db1de7b264d63464740a9 # 24是23的从节点
S: bac22197690ef5d24dedf7c348bbdf1c04410523 192.168.109.25:6379
   replicates a3ce222784b489291224a2e9bd7f7119ceab88a2 # 25是21的从节点
S: 7e7b6eb95bdc497ab018099ab9680a8f811da39a 192.168.109.26:6379
   replicates 9e21f0ff2cf173f64018e40f981d1226cc941ece # 26是22的从节点
Can I set the above configuration? (type 'yes' to accept): yes  # 输入yes确认集群配置
>>> Nodes configuration updated                     # 更新节点配置
>>> Assign a different config epoch to each node     # 为每个节点分配配置纪元
>>> Sending CLUSTER MEET messages to join the cluster # 发送meet消息,节点互相握手
Waiting for the cluster to join
.
>>> Performing Cluster Check (using node 192.168.109.21:6379) # 使用21节点校验集群
M: a3ce222784b489291224a2e9bd7f7119ceab88a2 192.168.109.21:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: 9e21f0ff2cf173f64018e40f981d1226cc941ece 192.168.109.22:6379
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
M: 7fbeeae0072a00c5125db1de7b264d63464740a9 192.168.109.23:6379
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: 7e7b6eb95bdc497ab018099ab9680a8f811da39a 192.168.109.26:6379
   slots: (0 slots) slave
   replicates 9e21f0ff2cf173f64018e40f981d1226cc941ece
S: 3a4df342f4b660cbc7cc1dcd23ccce82a0000004 192.168.109.24:6379
   slots: (0 slots) slave
   replicates 7fbeeae0072a00c5125db1de7b264d63464740a9
S: bac22197690ef5d24dedf7c348bbdf1c04410523 192.168.109.25:6379
   slots: (0 slots) slave
   replicates a3ce222784b489291224a2e9bd7f7119ceab88a2
[OK] All nodes agree about slots configuration. # 所有节点槽位配置一致
>>> Check for open slots...                     # 检查未分配的槽位
>>> Check slots coverage...                     # 校验槽位完整覆盖
[OK] All 16384 slots covered.                  # 全部16384个哈希槽分配完成,集群就绪

验证

bash 复制代码
# 进入redis-21容器的redis-cli客户端;连接集群节点
[root@docker ~]# docker exec -it redis-21 redis-cli
127.0.0.1:6379> cluster info
cluster_state:ok                           # 集群状态正常ok
cluster_slots_assigned:16384               # 已分配哈希槽总数
cluster_slots_ok:16384                     # 正常可用槽位全部16384
cluster_slots_pfail:0                      # 疑似故障槽数量
cluster_slots_fail:0                       # 确认故障槽数量
cluster_known_nodes:6                      # 集群识别总节点数6个
cluster_size:3                             # 集群主节点数量3
cluster_current_epoch:6                    # 集群当前配置纪元
cluster_my_epoch:1                         # 当前节点配置纪元
cluster_stats_messages_ping_sent:182        # 发送ping消息数量
cluster_stats_messages_pong_sent:189        # 发送pong消息数量
cluster_stats_messages_sent:371             # 发送集群消息总数
cluster_stats_messages_ping_received:184   # 收到ping消息数量
cluster_stats_messages_pong_received:185    # 收到pong消息数量
cluster_stats_messages_meet_received:5      # 收到meet握手消息数量
cluster_stats_messages_received:374         # 接收集群消息总数
total_cluster_links_buffer_limit_exceeded:0
cluster_slot_migration_active_tasks:0
cluster_slot_migration_active_trim_running:0
cluster_slot_migration_active_trim_current_job_keys:0
cluster_slot_migration_active_trim_current_job_trimmed:0
cluster_slot_migration_stats_active_trim_started:0
cluster_slot_migration_stats_active_trim_completed:0
cluster_slot_migration_stats_active_trim_cancelled:0

127.0.0.1:6379> cluster nodes
9e21f0ff2cf173f64018e40f981d1226cc941ece 192.168.109.22:6379@16379 master - 0 1791537158961 2 connected 5461-10922  # redis22主节点,槽5461-10922
7fbeeae0072a00c5125db1de7b264d63464740a9 192.168.109.23:6379@16379 master - 0 1791537158000 3 connected 10923-16383 # redis23主节点,槽10923-16383
7e7b6eb95bdc497ab018099ab9680a8f811da39a 192.168.109.26:6379@16379 slave 9e21f0ff2cf173f64018e40f981d1226cc941ece 0 1791537159000 2 connected # redis26,22的从节点
a3ce222784b489291224a2e9bd7f7119ceab88a2 192.168.109.21:6379@16379 myself,master - 0 0 1 connected 0-5460 # 当前节点redis21,主节点,槽0-5460
3a4df342f4b660cbc7cc1dcd23ccce82a0000004 192.168.109.24:6379@16379 slave 7fbeeae0072a00c5125db1de7b264d63464740a9 0 1791537159566 3 connected # redis24,23的从节点
bac22197690ef5d24dedf7c348bbdf1c04410523 192.168.109.25:6379@16379 slave a3ce222784b489291224a2e9bd7f7119ceab88a2 0 1791537159970 1 connected # redis25,21的从节点
127.0.0.1:6379> 

#关闭redis-22测试;模拟主节点故障,验证集群自动故障转移
[root@docker ~]# docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS          PORTS                                                                                      NAMES
259842ca88ac   redis:latest   "docker-entrypoint.s..."   17 minutes ago   Up 17 minutes   0.0.0.0:6382->6379/tcp, :::6382->6379/tcp, 0.0.0.0:16382->16379/tcp, :::16382->16379/tcp   redis-25
b9d2dd1c92fc   redis:latest   "docker-entrypoint.s..."   17 minutes ago   Up 17 minutes   0.0.0.0:6384->6379/tcp, :::6384->6379/tcp, 0.0.0.0:16384->16379/tcp, :::16384->16379/tcp   redis-24
d7dab0f3763e   redis:latest   "docker-entrypoint.s..."   17 minutes ago   Up 17 minutes   0.0.0.0:6381->6379/tcp, :::6381->6379/tcp, 0.0.0.0:16381->16379/tcp, :::16381->16379/tcp   redis-23
ecd46faaa291   redis:latest   "docker-entrypoint.s..."   17 minutes ago   Up 17 minutes   0.0.0.0:6380->6379/tcp, :::6380->6379/tcp, 0.0.0.0:16380->16379/tcp, :::16380->16379/tcp   redis-22
c79770bcdfd5   redis:latest   "docker-entrypoint.s..."   17 minutes ago   Up 17 minutes   0.0.0.0:6383->6379/tcp, :::6383->6379/tcp, 0.0.0.0:16383->16379/tcp, :::16383->16379/tcp   redis-26
c41129c6226f   redis:latest   "docker-entrypoint.s..."   17 minutes ago   Up 17 minutes   0.0.0.0:6379->6379/tcp, :::6379->6379/tcp, 0.0.0.0:16379->16379/tcp, :::16379->16379/tcp   redis-21

# 停止redis-22容器;模拟主节点宕机故障
[root@docker ~]# docker stop redis-22
redis-22

# 查看全部容器,包括停止状态;确认redis22已经退出
[root@docker ~]# docker ps -a
CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS                      PORTS                                                                                      NAMES
259842ca88ac   redis:latest   "docker-entrypoint.s..."   18 minutes ago   Up 18 minutes               0.0.0.0:6382->6379/tcp, :::6382->6379/tcp, 0.0.0.0:16382->16379/tcp, :::16382->16379/tcp   redis-25
b9d2dd1c92fc   redis:latest   "docker-entrypoint.s..."   18 minutes ago   Up 18 minutes               0.0.0.0:6384->6379/tcp, :::6384->6379/tcp, 0.0.0.0:16384->16379/tcp, :::16384->16379/tcp   redis-24
d7dab0f3763e   redis:latest   "docker-entrypoint.s..."   18 minutes ago   Up 18 minutes               0.0.0.0:6381->6379/tcp, :::6381->6379/tcp, 0.0.0.0:16381->16379/tcp, :::16381->16379/tcp   redis-23
ecd46faaa291   redis:latest   "docker-entrypoint.s..."   18 minutes ago   Exited (0) 11 seconds ago                                                                                              redis-22
c79770bcdfd5   redis:latest   "docker-entrypoint.s..."   18 minutes ago   Up 18 minutes               0.0.0.0:6383->6379/tcp, :::6383->6379/tcp, 0.0.0.0:16383->16379/tcp, :::16383->16379/tcp   redis-26
c41129c6226f   redis:latest   "docker-entrypoint.s..."   18 minutes ago   Up 18 minutes               0.0.0.0:6379->6379/tcp, :::6379->6379/tcp, 0.0.0.0:16379->16379/tcp, :::16379->16379/tcp   redis-21

# 再次查看集群节点信息;观察故障转移结果,redis26升级为主节点
127.0.0.1:6379> cluster nodes
9e21f0ff2cf173f64018e40f981d1226cc941ece 192.168.109.22:6379@16379 master,fail - 1791537638186 1791537637000 2 connected # redis22标记fail故障
7fbeeae0072a00c5125db1de7b264d63464740a9 192.168.109.23:6379@16379 master - 0 1791537745000 3 connected 10923-16383 # redis23主节点正常
7e7b6eb95bdc497ab018099ab9680a8f811da39a 192.168.109.26:6379@16379 master - 0 1791537745392 7 connected 5461-10922 # 原从节点redis26自动升级为主,接管5461-10922槽
a3ce222784b489291224a2e9bd7f7119ceab88a2 192.168.109.21:6379@16379 myself,master - 0 0 1 connected 0-5460 # 当前节点redis21主节点正常
3a4df342f4b660cbc7cc1dcd23ccce82a0000004 192.168.109.24:6379@16379 slave 7fbeeae0072a00c5125db1de7b264d63464740a9 0 1791537746403 3 connected # redis24为23从节点
bac22197690ef5d24dedf7c348bbdf1c04410523 192.168.109.25:6379@16379 slave a3ce222784b489291224a2e9bd7f7119ceab88a2 0 1791537745897 1 connected # redis25为21从节点
相关推荐
救救孩子把1 小时前
AI面试八股文-03_3-RAG 系统面试题
人工智能·redis·面试·agent·rag
hweiyu002 小时前
Redis命令:LPUSHX
redis·缓存
hweiyu002 小时前
Redis命令:LRANGE
redis·缓存
蓝胖的四次元口袋2 小时前
Redis知识梳理(5)
数据库·redis·缓存
for_ever_love__2 小时前
半同步复制:数据安全和服务可用之间怎么权衡
redis·mysql·缓存·一致性·数据·半同步复制·方式
Anastasiozzzz3 小时前
InnoDB 为什么不用跳表,Redis 为什么不用 B+树?一场“磁盘”与“内存”的终极对决
数据库·redis·b树
Thomas.Sir5 小时前
第29课:微服务异步调用、线程池优化 & 事件驱动开发
java·redis·微服务
java_nnnn5 小时前
Redis 初识:从单体架构到分布式架构的演进
redis·分布式·架构
Tsuki_tl5 小时前
Redis过期策略
redis·缓存·过期策略·内存淘汰机制·分布式开发·惰性删除·定期删除