1. Redis初印象:为什么它成了“瑞士军刀”?
如果你刚接触后端开发或者系统架构,听到“Redis”这个词的频率,可能仅次于“MySQL”。但和关系型数据库那种规规矩矩的表结构不同,Redis给人的第一感觉是“快”和“灵活”。你可以把它理解成一个超级高效、功能多样的“内存工作台”。所有数据主要放在内存里操作,所以读写速度能达到微秒级,比基于磁盘的数据库快几个数量级。但这不仅仅是快,它的价值在于提供了丰富的数据结构(比如字符串、列表、哈希、集合等),让你能用更贴合业务逻辑的方式来存储和操作数据,而不用像在关系型数据库里,什么都得先拆成行和列。
我刚开始用Redis时,主要就拿它做缓存,把一些查询耗时的结果(比如用户信息、热门文章列表)丢进去,下次请求直接取,数据库压力瞬间就下来了。但用着用着就发现,它的能力远不止于此。比如用它的List做个简单的消息队列,用Set做点赞去重,用Sorted Set做实时排行榜,用HyperLogLog估算UV(独立访客数)…… 它就像一把“瑞士军刀”,在需要高性能、实时性的场景里,总能找到趁手的工具。而熟练使用这些工具的第一步,就是掌握它的基本命令。这不仅仅是记住几个单词,更是理解其设计哲学和适用场景,避免“拿着锤子看什么都像钉子”。
2. 环境准备与连接:迈出第一步
在开始敲命令之前,我们得先有个Redis服务可以操作。虽然生产环境通常是独立的服务器,但本地学习和测试,最方便的就是自己安装一个。
2.1 快速搭建Redis测试环境
对于大多数开发者,我推荐直接用Docker来跑Redis,干净又省事,不用担心污染本地环境。如果你还没安装Docker,去官网下载安装就好,这里假设你已经有了。
打开终端,一行命令就能拉起一个最新版的Redis服务:
docker run -d --name my-redis -p 6379:6379 redis:latest这条命令做了几件事:-d表示后台运行,--name my-redis给容器起个名字方便管理,-p 6379:6379把容器内的Redis默认端口6379映射到本机的6379端口,最后指定使用redis:latest镜像。
启动后,你可以用下面命令进入容器内的Redis命令行界面(CLI):
docker exec -it my-redis redis-cli看到127.0.0.1:6379>这个提示符,就说明你已经成功连接上Redis服务了。这是我们的主战场。
注意:生产环境绝不会这么简单。你需要配置密码(通过
requirepass)、考虑持久化方案(RDB/AOF)、设置内存上限(maxmemory)和淘汰策略(maxmemory-policy),可能还需要搭建主从复制或集群。但对于学习和命令练习,这个单机无认证的实例足够了。
2.2 命令行客户端(redis-cli)的基本操作
连接上之后,我们先熟悉一下这个命令行环境。它支持一些基本的交互操作:
ping:测试服务是否存活。服务器会回复PONG。select <db_index>:切换数据库。Redis默认有16个数据库(编号0-15),默认使用0号库。这个功能现在不太推荐了,因为容易混淆,更好的实践是用不同的Key前缀来区分业务。auth <password>:如果服务端配置了密码,需要用这个命令认证。quit或exit:退出CLI。
一个常见的检查流程是:连上后先ping一下,确保连接通畅。
3. 核心数据结构与命令全解析
Redis的强大,根基在于其五种核心数据结构。理解它们,是灵活运用Redis的关键。我们一个一个来看,并配上最常用的命令。
3.1 String(字符串):最简单的键值对
String是Redis最基本的数据类型,一个Key对应一个Value。但这个Value不仅是文本,也可以是数字(整数或浮点数)甚至是二进制数据(如图片序列化后的字节流)。
常用命令:
SET key value:设置键值对。例如SET user:1001:name “张三”。GET key:获取键对应的值。例如GET user:1001:name。DEL key:删除键。例如DEL user:1001:name。INCR key/DECR key:将键存储的整数值加一/减一。常用于计数器,如INCR article:100:view_count。INCRBY key increment/DECRBY key decrement:按指定步长增减。例如INCRBY inventory:item_001 5。SETEX key seconds value:设置键值对并指定过期时间(秒)。这是实现缓存最常用的命令之一,比如SETEX session:abc123 3600 “{userId: 1001}”表示这个会话数据1小时后自动消失。MSET key1 value1 key2 value2 .../MGET key1 key2 ...:批量设置/获取多个键值,能有效减少网络往返开销,提升性能。
实操心得:
- Key的设计艺术:好的Key名是自描述的。我习惯用冒号分隔,形成一种伪命名空间,比如
业务:对象:ID:属性(user:1001:profile)。这比无规律的字符串清晰得多,也方便用KEYS或SCAN命令按模式查找(尽管生产环境慎用KEYS)。 - 数字的妙用:
INCR命令是原子性的,这意味着即使多个客户端同时对一个Key执行INCR,也不会出现竞争条件导致计数错误。这让它成为实现分布式系统里全局计数器的完美选择。 - SETEX是缓存标配:一定要为缓存数据设置合理的过期时间,这既是内存管理的要求,也能保证数据的最终时效性,避免脏数据一直留存。
3.2 Hash(哈希表):存储对象
Hash适合存储对象,比如用户信息、商品信息。它可以将一个对象的多个字段和值存储在一个Redis键下,类似于编程语言里的Map或dict。
常用命令:
HSET key field value:设置哈希表中字段的值。例如HSET user:1001 name “张三” age 30。HGET key field:获取哈希表中指定字段的值。例如HGET user:1001 name。HGETALL key:获取哈希表中所有字段和值。它会以交替的字段名、值的形式返回。HDEL key field1 field2 ...:删除哈希表中的一个或多个字段。HINCRBY key field increment:为哈希表中的整数字段值增加指定步长。例如HINCRBY user:1001 score 10。
实操心得:
- 与String的取舍:是把一个用户对象序列化成JSON字符串用
SET存,还是用Hash分字段存?我的经验是,如果需要频繁更新对象的部分字段,或者需要单独访问某些字段,Hash更高效,因为它可以独立操作每个字段,不用像String那样每次都要序列化/反序列化整个对象。但如果对象字段很少,或者总是整体存取,那么用String序列化可能更简单。 - HGETALL的陷阱:
HGETALL会返回哈希的所有内容。如果这个Hash非常大(字段成千上万),这个操作可能会阻塞Redis较长时间,并消耗大量网络带宽。在生产环境中,对于大的Hash,更推荐使用HSCAN命令进行增量迭代,或者明确使用HMGET获取你真正需要的几个字段。
3.3 List(列表):有序的元素集合
List是一个按照插入顺序排序的字符串元素列表。你可以从左侧(头部)或右侧(尾部)添加、弹出元素,实现栈(后进先出)或队列(先进先出)的功能。
常用命令:
LPUSH key value1 value2 .../RPUSH key value1 value2 ...:将一个或多个值插入列表的头部(左边)/尾部(右边)。LPOP key/RPOP key:移除并返回列表的头部/尾部元素。LRANGE key start stop:获取列表中指定范围内的元素。0 -1表示获取所有元素。LLEN key:获取列表长度。BLPOP key1 key2 ... timeout/BRPOP ...:阻塞式弹出。如果列表为空,命令会阻塞连接,直到等待超时(timeout秒)或有其他客户端向列表插入元素为止。这是实现简单消息队列的核心。
实操心得:
- 实现消息队列:生产者用
LPUSH向列表(如task_queue)尾部添加任务,消费者用BRPOP从列表头部阻塞获取任务。这种方式简单可靠,但注意它没有ACK确认机制,消息被弹出就没了,适合允许少量丢失的场景。 - 最新N条记录:用
LPUSH加入新元素,然后用LTRIM key 0 N-1来修剪列表,只保留最新的N个元素。这常用于实现“最近访问记录”、“最新评论”等功能。 - 性能注意:
List的底层实现是“快速列表”(quicklist),在元素不多时表现很好。但要注意,LRANGE一个非常大的列表(比如获取全部)同样有性能风险。
3.4 Set(集合):无序且唯一
Set是一个无序的字符串集合,其特点是元素不重复。它支持交集、并集、差集等集合运算,功能非常强大。
常用命令:
SADD key member1 member2 ...:向集合添加一个或多个成员。SREM key member1 member2 ...:移除集合中一个或多个成员。SMEMBERS key:返回集合中的所有成员。SISMEMBER key member:判断成员是否在集合中。SINTER key1 key2 ...:返回多个集合的交集。SUNION key1 key2 ...:返回多个集合的并集。SDIFF key1 key2 ...:返回第一个集合与其他集合的差集。
实操心得:
- 去重与判存:这是Set最直观的用途。比如存储文章的所有点赞用户ID
SADD article:100:likes user:1 user:2,可以天然保证同一个用户不会重复点赞。用SISMEMBER可以快速检查某个用户是否点过赞。 - 标签系统:为每篇文章存储一个标签集合(
SADD article:100:tags tech database),同时为每个标签存储一个包含文章ID的集合(SADD tag:tech:articles 100)。这样,要找带有“tech”和“database”两个标签的文章,只需对两个标签的文章ID集合求交集(SINTER tag:tech:articles tag:database:articles),效率极高。 - 共同关注:在社交网络中,计算两个用户的共同关注,就是对他们“关注的人”集合求交集。
- SMEMBERS的警告:和
HGETALL类似,SMEMBERS也会一次性返回所有成员。对于大集合,请使用SSCAN命令进行迭代。
3.5 Sorted Set(有序集合):带权重的Set
Sorted Set在Set的基础上,为每个元素关联了一个分数(score),元素根据分数从小到大排序。分数可以重复,但元素(member)不能重复。
常用命令:
ZADD key score1 member1 score2 member2 ...:向有序集合添加一个或多个成员,或者更新已存在成员的分数。ZRANGE key start stop [WITHSCORES]:按分数从低到高返回指定排名区间的成员。WITHSCORES选项会同时返回分数。ZREVRANGE key start stop [WITHSCORES]:按分数从高到低返回指定排名区间的成员。ZRANK key member/ZREVRANK key member:返回成员在集合中的正序/逆序排名(从0开始)。ZSCORE key member:返回成员的分数。ZINCRBY key increment member:为成员的分数增加指定值。ZRANGEBYSCORE key min max [WITHSCORES]:返回分数在指定区间内的成员。
实操心得:
- 排行榜的天然实现:这是Sorted Set的杀手级应用。比如游戏玩家得分榜:
ZADD leaderboard 2500 “player:A” 1800 “player:B”。要取前三名:ZREVRANGE leaderboard 0 2 WITHSCORES。要更新玩家分数:ZINCRBY leaderboard 100 “player:A”。所有操作都是原子且高效的。 - 时间轴:将时间戳作为分数,事件内容作为成员,可以构建一个按时间排序的事件流或消息时间线。
- 范围查询:
ZRANGEBYSCORE可以用来做范围筛选,比如查找分数在80到90之间的所有学生。 - 分数设计:分数是双精度浮点数,可以表示整数、小数,甚至是负数和很大的数。在设计排行榜时,如果需要“分数相同按时间先后排”,一个常见的技巧是把实际分数乘以一个很大数(如1e10),再加上一个由时间戳转换的递减序列号,组合成一个新的分数。
4. 键管理、生存时间与持久化窥探
掌握了核心数据结构的操作,我们还需要一些全局性的命令来管理这些键,以及理解Redis如何管理数据生命周期。
4.1 键的通用操作
KEYS pattern:查找所有符合给定模式pattern的键。例如KEYS user:*查找所有以user:开头的键。严重警告:
KEYS命令在生产环境必须禁用或极其谨慎使用!因为它会遍历数据库中的所有键,在键数量巨大时,会导致Redis服务短暂阻塞(卡住)。替代方案是使用SCAN命令,它以游标方式增量迭代,不会阻塞服务。SCAN cursor [MATCH pattern] [COUNT count]:安全地迭代数据库中的键。cursor是迭代游标,从0开始,一次调用返回下一个游标和部分键。当返回的游标为0时,迭代结束。EXISTS key:检查键是否存在。TYPE key:返回键所存储的值的类型。RENAME key newkey:重命名键。DEL key1 key2 ...:删除一个或多个键。UNLINK key1 key2 ...:异步删除键(Redis 4.0+)。它与DEL不同,它会在后台线程中回收内存,对于删除大键不会阻塞主线程,更友好。
4.2 生存时间(TTL)与过期
Redis可以为任何键设置生存时间,到期后键会自动被删除。这是实现缓存、验证码过期、限时活动等功能的基础。
EXPIRE key seconds:为键设置过期时间,单位秒。PEXPIRE key milliseconds:为键设置过期时间,单位毫秒。TTL key:以秒为单位返回键的剩余生存时间。返回-2表示键不存在,-1表示键存在但没有设置过期时间。PTTL key:以毫秒为单位返回键的剩余生存时间。PERSIST key:移除键的过期时间,使其永久有效。
实操心得:
- 过期删除策略:Redis采用“惰性删除”+“定期删除”两种策略。惰性删除指在访问一个键时检查它是否过期,如果过期就删除。定期删除是Redis每隔一段时间随机检查一批键并删除过期的。这意味着,一个键即使到了过期时间,也可能不会立刻被删除,直到它被再次访问或轮到定期检查时才会被清理。这在大多数场景下没问题,但如果你对过期时间的精确性有严格要求(比如秒杀库存),就需要在业务逻辑上做额外保障。
SETEXvsSET+EXPIRE:SETEX是原子操作,而先SET再EXPIRE是两个命令,中间可能发生故障导致键没有设置过期时间。在需要保证一致性的场景,优先使用SETEX或SET命令的EX选项(如SET key value EX 60)。
4.3 持久化相关命令浅析
虽然持久化配置主要在服务端(redis.conf),但了解相关命令有助于排查问题。
SAVE:同步保存数据到磁盘(RDB文件)。这会阻塞所有客户端请求,直到保存完成。生产环境几乎不用。BGSAVE:后台异步保存数据到磁盘。Redis会fork一个子进程来执行保存操作,主进程继续服务客户端。这是常用的手动触发RDB快照的方式。LASTSAVE:返回最近一次成功将数据保存到磁盘的Unix时间戳。可以用来检查后台保存是否成功。INFO persistence:这个命令可以查看详细的持久化相关信息,如RDB/AOF的状态、上次保存时间、重写情况等,是监控Redis健康状态的重要信息来源。
5. 事务与管道:提升效率与保证批次操作
5.1 事务(Multi/Exec)
Redis的事务和关系型数据库的ACID事务不太一样。它更像一个命令的打包批处理:用MULTI开启一个事务,之后输入的命令都会被放入队列,最后用EXEC一次性原子性地执行队列中的所有命令。
MULTI SET balance:alice 100 DECRBY balance:alice 20 INCRBY balance:bob 20 EXEC重要特性与陷阱:
- 原子性:
EXEC执行时,队列中的命令会作为一个整体、按顺序执行,不会被其他客户端的命令打断。这保证了批次操作的原子性。 - 没有回滚(Rollback):这是Redis事务最特别的一点。如果在
EXEC执行前,用DISCARD可以取消事务。但如果在EXEC执行过程中,某条命令失败了(比如对字符串执行了HINCRBY),Redis不会回滚之前已执行的命令,而是会继续执行队列中的剩余命令。开发者需要自己确保入队的命令语法正确、类型匹配。 - 乐观锁(Watch):
WATCH命令可以实现CAS(Check-And-Set)操作。它用于监视一个或多个键,如果在EXEC执行前,这些被监视的键被其他客户端修改了,那么整个事务将会失败(EXEC返回nil)。这可以用来实现简单的乐观锁控制。
5.2 管道(Pipeline)
管道是另一种提升性能的技术。它的原理是将多个命令打包,一次性发送给Redis服务器,然后再一次性读取所有回复。这极大地减少了网络往返时间(RTT)的开销。
在没有管道的情况下,执行N条命令需要N次网络往返。使用管道后,理论上只需要1次网络往返(发送一批命令 + 接收一批回复)。
如何使用管道:在编程中,几乎所有Redis客户端库都支持管道。在命令行中,你可以通过将命令写入文件,然后用管道符重定向来模拟:
(echo -en “PING\r\nSET key1 hello\r\nGET key1\r\n”; sleep 1) | nc localhost 6379或者更简单地,在redis-cli中,使用--pipe选项。
管道 vs 事务:
- 目的不同:管道主要为了提升性能(减少RTT);事务主要为了保证批次操作的原子性(不被其他命令打断)。
- 可以结合:你可以在
MULTI…EXEC事务块中使用管道,这样既保证了原子性,又提升了网络效率。
实操心得:
- 对于需要连续执行多个不相关命令的场景(比如初始化一批数据),优先使用管道,性能提升立竿见影。
- 对于需要保证“要么全做,要么全不做”的关联操作(比如转账),使用事务(
MULTI/EXEC),并结合WATCH来防止竞态条件。 - 管道打包的命令数量也不是越多越好,因为服务器需要一次性为所有命令分配输出缓冲区。通常一个管道包含几百到几千个命令是比较合适的。
6. 运维与调试常用命令实录
当你把Redis用起来之后,总会遇到需要查看状态、排查问题的时候。下面这些命令就是你的“手术刀”。
6.1 信息获取与监控
INFO [section]:这是最强大的诊断命令。不加参数会返回所有信息,内容非常多。通常按模块查看:INFO server:服务器基本信息,版本、运行时间、进程ID等。INFO clients:客户端连接信息。INFO memory:内存使用详情,包括总内存、使用峰值、碎片率等,是排查内存问题的关键。INFO stats:一般统计信息,命令处理数、连接数、网络流量等。INFO replication:主从复制信息。INFO cpu:CPU消耗统计。
CLIENT LIST:列出所有连接到服务器的客户端详细信息,包括连接ID、地址、空闲时间、正在执行的命令等。可以用来排查异常连接或找出执行慢查询的客户端。SLOWLOG GET [n]:获取慢查询日志。Redis可以记录执行时间超过指定阈值(slowlog-log-slower-than配置)的命令。这个命令对于发现性能瓶颈至关重要。MONITOR:这是一个调试命令,它会实时打印出服务器接收到的所有命令。警告:在生产环境运行MONITOR会严重降低性能,因为它会输出大量信息,只应在绝对必要时临时使用。
6.2 连接与配置管理
CLIENT SETNAME connection-name:为当前连接设置一个名字,方便在CLIENT LIST中识别。CONFIG GET parameter:获取运行时的配置参数。例如CONFIG GET maxmemory。CONFIG SET parameter value:动态修改运行时的配置参数。例如CONFIG SET maxmemory 2gb。注意:不是所有参数都能动态修改,且CONFIG SET的修改在重启后会失效,永久修改需要改redis.conf文件。CONFIG REWRITE:将当前通过CONFIG SET设置的配置持久化到redis.conf文件中。
6.3 数据备份与迁移
BGSAVE:如前所述,手动触发RDB持久化,生成数据快照文件(通常是dump.rdb)。这个文件可以用于备份、迁移或复制。- 在命令行中,直接复制RDB文件或AOF文件是最简单的冷备份方式。迁移时,在新服务器上启动Redis并加载这些文件即可。
7. 常见问题与排查技巧实录
在实际使用中,你肯定会遇到各种“坑”。下面是我总结的一些典型问题和解决方法。
7.1 内存使用异常高
问题现象:INFO memory显示used_memory接近或超过maxmemory,但实际业务数据量感觉没这么大。
排查思路:
- 检查数据是否真的多:用
INFO keyspace看看各个数据库的键数量。也可以用SCAN粗略估算。 - 检查是否有大Key:使用
redis-cli --bigkeys命令(或编写脚本用SCAN配合TYPE、MEMORY USAGE命令)扫描找出占用内存过大的Key。大Key(如一个Hash有百万字段,或一个String有几十MB)不仅占用内存,在删除、过期时也会引起阻塞。 - 检查内存碎片率:
INFO memory中的mem_fragmentation_ratio(碎片率)。如果这个值远大于1.5(比如超过2),说明内存碎片比较严重。可以尝试重启Redis实例(如果允许)来回收碎片,或者使用Redis 4.0+的MEMORY PURGE命令(如果支持)。 - 检查客户端输出缓冲区:某些连接(如长时间订阅Pub/Sub或Monitor)可能导致输出缓冲区积压大量数据,占用内存。用
CLIENT LIST查看omem(输出缓冲区内存)字段。 - 检查过期键清理:大量已过期的键如果未被及时清理,也会占用内存。可以手动执行
DEBUG OBJECT key查看某个键的serializedlength,或观察INFO stats中的expired_keys和evicted_keys数量。
7.2 响应变慢
问题现象:客户端请求延迟明显增加。
排查思路:
- 检查慢查询:立刻执行
SLOWLOG GET 10查看最近10条慢查询命令。分析是哪些命令慢,是否使用了KEYS、HGETALL、SMEMBERS(对大集合)等危险命令,或者是否是对大Key的操作。 - 检查持久化影响:如果正在生成RDB快照(
BGSAVE)或重写AOF文件(BGREWRITEAOF),尤其是内存很大时,fork子进程的过程可能会导致主进程短暂阻塞(与内存量和系统有关)。观察INFO persistence中的rdb_bgsave_in_progress和aof_rewrite_in_progress。 - 检查网络和系统:使用
top、iostat等系统命令,检查服务器CPU、内存、磁盘I/O是否饱和。网络是否拥堵。 - 检查连接数:
INFO clients中的connected_clients是否过多?连接数过多会消耗资源。检查CLIENT LIST中是否有空闲时间(idle)很长的连接,考虑设置timeout配置让Redis自动关闭空闲连接。
7.3 键丢失或数据不一致
问题现象:明明设置了键,过一会儿就没了,或者值不对。
排查思路:
- 确认过期时间(TTL):用
TTL key检查键是否设置了较短的过期时间。 - 检查内存淘汰策略:当内存达到
maxmemory时,Redis会根据maxmemory-policy(如allkeys-lru,volatile-lru)淘汰一些键。用INFO stats查看evicted_keys计数是否在增长。确保你理解的淘汰策略和配置一致。 - 主从复制延迟:如果使用了主从架构,从库的数据可能会有延迟。在从库上读取的数据可能是旧的。检查
INFO replication中的master_repl_offset和slave_repl_offset的差距。 - 事务使用不当:回顾事务部分,确认是否因为事务中某条命令失败,但其他命令执行了,导致部分数据被修改。
- 应用程序逻辑错误:这是最常见的原因。检查代码中是否有覆盖写、条件判断错误等逻辑Bug。可以使用
MONITOR命令(在测试环境)跟踪一段时间内对特定Key的所有操作。
7.4 连接失败或超时
问题现象:客户端无法连接到Redis,或连接频繁断开。
排查思路:
- 检查基础网络:
ping服务器IP/端口,用telnet或nc测试6379端口是否通畅。 - 检查Redis配置:
bind:是否绑定了正确的IP地址(默认127.0.0.1只允许本地连接)。protected-mode:如果是非本地连接且未配置密码,保护模式会拒绝连接。requirepass:是否配置了密码,客户端连接时是否提供了正确的AUTH。maxclients:是否达到了最大连接数上限。
- 检查系统限制:服务器的防火墙(如iptables, firewalld)是否放行了6379端口。操作系统的最大文件描述符限制是否够用(
ulimit -n)。 - 检查客户端配置:客户端的连接池配置、超时时间设置是否合理。不合理的超时设置可能导致连接被服务端主动关闭(
timeout配置)。
掌握这些基本命令和排查思路,你就能应对Redis日常使用中80%以上的场景和问题。记住,命令是工具,理解其背后的数据结构和设计思想,才能让你在合适的场景选择最合适的工具,真正发挥出Redis这把“瑞士军刀”的威力。