44. Redis字符串操作:SET/GET、MSET/MGET、INCR/DECR、APPEND、GETRANGE/SETRANGE

Redis的字符串类型,说白了就是最基础也最常用的数据结构。我做了这么多年嵌入式数据库相关的工作,Redis的字符串操作几乎每天都在用。今天咱们就把这些核心命令掰开揉碎了讲清楚。

SET与GET:最基础的读写

SET和GET就像数据库里的"写"和"读"。简单到不能再简单,但坑也不少。

// 设置一个键值对
redisCommand(ctx, "SET user:1:name \"张三\"");
// 读取
redisCommand(ctx, "GET user:1:name");

嗯,这里要注意:SET命令有个可选参数,很多人不知道。

参数 说明 我踩过的坑
EX seconds 设置过期时间(秒) 忘记加EX,缓存永远不过期
PX milliseconds 设置过期时间(毫秒) 高精度场景下用这个
NX 键不存在时才设置 分布式锁的核心
XX 键存在时才设置 更新场景很实用
我的习惯:做缓存时,我习惯用SET key value EX 3600 NX。这样既能设置过期时间,又能避免覆盖已有数据。有一次线上事故就是因为没加NX,两个服务同时写同一个key,数据全乱了。

MSET与MGET:批量操作的艺术

你想想看,如果一次要读写100个key,用SET/GET循环100次,网络开销得多大?这时候MSET/MGET就派上用场了。

// 批量设置
redisCommand(ctx, "MSET user:1:name \"张三\" user:1:age \"28\" user:1:city \"北京\"");
// 批量读取
redisCommand(ctx, "MGET user:1:name user:1:age user:1:city");

我个人建议:只要涉及多个key的读写,优先考虑批量命令。我在项目中遇到过,一个接口要查用户的基本信息、扩展信息、权限信息,原来写了三个GET,后来改成MGET,响应时间从15ms降到了3ms。

注意:MSET是原子操作,要么全部成功,要么全部失败。但MGET不是原子操作,只是批量发送请求。如果中间某个key不存在,返回的是nil,不会影响其他key的读取。

INCR与DECR:计数器的最佳拍档

Redis的INCR/DECR是原子操作,这在并发场景下太重要了。我曾经用C语言自己实现过计数器,各种锁、各种竞态条件,烦得很。Redis一句话就搞定。

// 自增1
redisCommand(ctx, "INCR page:view:1001");
// 自增指定步长
redisCommand(ctx, "INCRBY page:view:1001 10");
// 自减
redisCommand(ctx, "DECR page:view:1001");
redisCommand(ctx, "DECRBY page:view:1001 5");

为什么会这么设计?因为Redis是单线程模型,所有命令串行执行,天然避免了并发问题。你想想看,如果两个请求同时INCR,传统数据库得加锁,Redis直接搞定。

实战场景:
  • 文章阅读量统计
  • 用户积分增减
  • 库存扣减(配合WATCH实现乐观锁)
  • 限流计数器(配合EXPIRE)

APPEND:字符串拼接的利器

APPEND命令,说白了就是在字符串末尾追加内容。如果key不存在,就相当于SET。

// 第一次调用,相当于SET
redisCommand(ctx, "APPEND log:001 \"[2024-01-01 10:00:00] 系统启动\"");
// 第二次调用,追加内容
redisCommand(ctx, "APPEND log:001 \"\\n[2024-01-01 10:00:05] 用户登录\"");

我记得有一次做日志收集,客户端不断往同一个key里追加日志。后来发现日志越来越长,读取性能急剧下降。嗯,这里要提醒大家:APPEND虽然方便,但别无限制追加。Redis的字符串最大512MB,真到了那个量级,读写都会很慢。

我的建议:如果要做日志收集,最好按时间分片。比如每小时一个key,或者每天一个key。这样单个key不会太大,也方便清理过期数据。

GETRANGE与SETRANGE:子串操作

这两个命令让我想起了C语言的指针操作。GETRANGE可以截取字符串的一部分,SETRANGE可以覆盖指定位置的内容。

// 假设key "msg" 的值是 "Hello, Redis World!"
// 截取从0到4的字符
redisCommand(ctx, "GETRANGE msg 0 4");  // 返回 "Hello"
// 从第7个字符开始覆盖
redisCommand(ctx, "SETRANGE msg 7 \"C语言\"");  
// 结果变成 "Hello, C语言 World!"

你可能会问:这有什么用?我在项目中用过一次:存储固定格式的协议数据,比如报文头+报文体。用SETRANGE可以直接修改报文头的某个字段,不用把整个字符串读出来再写回去。

注意:SETRANGE如果指定的偏移量超过了字符串长度,Redis会用空字符(\x00)填充中间部分。我曾经因为这个bug排查了半天,数据中间突然多了一堆空字符,解析的时候全乱了。

知识体系总览

下面这张图把Redis字符串操作的核心命令和它们的关系梳理清楚了。我建议你把它保存下来,写代码前看一眼,思路会清晰很多。

Redis字符串操作知识体系 Redis String SET / GET MSET / MGET INCR / DECR APPEND GETRANGE / SETRANGE EX / NX / XX 核心特性 原子操作 | 单线程模型 | 最大512MB | O(1)时间复杂度 适用场景:缓存、计数器、分布式锁、会话管理

实战经验总结

做了这么多年,我总结了几条铁律:

  1. 能用批量就别用单个——网络开销是性能杀手
  2. 计数器用INCR/DECR——别自己实现,Redis的原子操作比你写的锁靠谱
  3. APPEND别贪心——单个key别超过10MB,否则读写都慢
  4. SETRANGE小心偏移——偏移量算错了,数据就毁了
  5. 过期时间别忘了——我见过太多缓存撑爆内存的案例

一句话总结:Redis字符串操作看着简单,但用好了能解决80%的缓存和计数问题。关键是理解每个命令的特性,别在简单的地方翻车。


公众号:蓝海资料掘金营,微信deep3321