Redis数据结构之string

一、String 类型概述

String 是 Redis 最基础的数据类型:

  • 所有 key 都是字符串;
  • value 本质是一段连续的、二进制安全的字节序列(SDS),不关心内容是文本、整数还是图片/视频的二进制;
  • 单个 value 最大 512MB。

虽然理论上能存图片、视频,但实践中不这么做:内存昂贵、大 value 会阻塞单线程、还有 512MB 上限。大文件放对象存储(OSS/MinIO/硬盘),Redis 只存 URL 或小缩略图。


二、String 的内部编码

内部编码 触发条件
int 值是 long 范围内的整数(64 位有符号:-2⁶³ ~ 2⁶³-1)
embstr 字符串长度 ≤ 44 字节
raw 字符串长度 > 44 字节
复制代码
127.0.0.1:6379> SET k1 12345
127.0.0.1:6379> OBJECT ENCODING k1          # "int"
127.0.0.1:6379> SET k2 hello
127.0.0.1:6379> OBJECT ENCODING k2          # "embstr"
127.0.0.1:6379> SET k3 aaaaa...(45个字符)
127.0.0.1:6379> OBJECT ENCODING k3          # "raw"

注意:int 编码一旦执行 APPEND 等字符串操作,会退化成 raw;embstr 被修改也会转 raw。


三、基础读写命令(回顾)

命令 作用
SET key value 设置(可加 EX/PX/NX/XX 等选项)
GET key 获取
MSET k1 v1 k2 v2 ... 批量设置,原子
MGET k1 k2 ... 批量获取
SETEX key s v / SETNX key v 设值+过期 / 不存在才设置
EXISTS / DEL / EXPIRE / TTL 通用键命令

四、数值增减命令(计数器核心)

4.1 INCR / DECR

复制代码
INCR key        # 原子 +1,返回自增后的值
DECR key        # 原子 -1
  • 要求 value 是 long 范围内的整数 ,否则报错 value is not an integer or out of range;
  • key 不存在时,自动从 0 开始:对不存在的 key 执行 INCR,结果是 1。

4.2 INCRBY / DECRBY(指定步长)

复制代码
INCRBY key 5       # +5
INCRBY key -5      # 加负数 = 减
DECRBY key 5       # -5
DECRBY key -5      # 减负数 = 加

步长也必须是整数。

4.3 INCRBYFLOAT(浮点增减)

复制代码
INCRBYFLOAT key 1.5
INCRBYFLOAT key -1.5
INCRBYFLOAT key +1.5
  • 参数是浮点数,支持正负号;
  • 注意两点:
    1. 内部用 IEEE754 double 计算,有精度误差 (典型如 0.1+0.2 ≠ 0.3),金额场景要用整数(存"分")或字符串精确值,别用它;
    2. 浮点执行后,值按字符串保存,编码变为 embstr/raw;若结果恰好是整数形式(如 113),后续仍可 INCR,是 111.5 则不能 INCR。

4.4 思考题:两个连接同时发 INCR,最终自增几次?

答案:自增 2 次,结果准确(假设初值 113,最终 115),不会丢更新。

原因在于 Redis 命令执行是单线程的,两条 INCR 排队串行执行,每条 INCR 在 Redis 内部是一个不可分割的"读---改---写"原子操作:

复制代码
连接A的 INCR:读取113 → 加1 → 写回114   (整条命令执行期间,不会被连接B打断)
连接B的 INCR:读取114 → 加1 → 写回115

对比你在 C/C++ 里写的 i++,它在 CPU 层面不是原子的,对应三条指令:

复制代码
mov  rax, [i]     ; ① 从内存读到寄存器
inc  rax          ; ② 寄存器 +1
mov  [i], rax     ; ③ 写回内存

多线程下两个线程可能在①②之间交错(都读到旧值、各自 +1、互相覆盖),导致"加了两次结果只 +1"。要在多核上保证正确,必须加锁或用带 LOCK 前缀的原子指令(如 lock xadd)。

而 Redis 单线程天然把整个 INCR 串行化,无需加锁就是原子的------这正是 Redis 适合做计数器、秒杀扣库存、限流的根本原因。


五、字符串操作命令

5.1 APPEND:尾部追加

复制代码
APPEND key value      # 返回追加后的字符串长度
  • key 不存在时,等同于 SET;

  • 对整数 int 编码追加,会先转成字符串再追加:

    SET key1 113
    APPEND key1 22 # 返回 5,值变成 "11322"(编码变 raw)

5.2 STRLEN:字节长度

复制代码
STRLEN key            # 返回字节数,不存在返回 0

⚠️ 注意是字节长度不是字符数 :STRLEN "你好" 在 UTF-8 下返回 6(每个汉字 3 字节)。

5.3 GETSET:设置新值并返回旧值

复制代码
GETSET key value
  • 返回旧值;key 不存在返回 nil(并设置新值);
  • 典型用途:重置计数器同时取旧值;
  • 6.2+ 可用 SET key value GET 替代。

5.4 GETRANGE:按字节截取

复制代码
GETRANGE key start end
  • 截取范围是闭区间 start, end ,单位是字节(不是字符、也不是长度);
  • 支持负数下标:-1 表示最后一个字节,0 -1 表示全部;
  • 早期命令名叫 SUBSTR。

5.5 SETRANGE:按字节覆盖

复制代码
SETRANGE key offset value     # 从 offset 字节处开始覆盖,返回修改后的总长度
  • 你的示例:key3="value3"(位置 0~5),从位置 2 覆盖 5 个 b:

    复制代码
    v a l u e 3
    v a b b b b b        → "vabbbbb",长度 7
  • 如果 offset 超过当前字符串长度,中间会用零字节 \x00 填充:

    复制代码
    SET k ab
    SETRANGE k 5 xy       # 结果是 "ab\x00\x00\x00xy",长度 7

GETRANGE/SETRANGE 都按字节操作,对中文等多字节字符可能切到半个字符,产生乱码,处理文本时慎用。


六、二进制安全与字节视角(重点)

6.1 Redis 存字符串不做任何字符集转换

客户端发过来什么字节,服务端就原样存什么字节,不存在"解码---转码---重新编码"的过程。这和 MySQL 形成对比:

  • MySQL 有字符集概念(utf8mb3/utf8mb4 等),文本类按字符处理,读写有字符集转换;
  • Redis 只把值当字节流,按字节处理,不认识字符,也就没有服务端转码。

6.2 Linux默认编码和utf8mb3

  • utf8mb3/utf8mb4 是 MySQL 的字符集名称 ;Linux 系统的字符编码叫 locale ,现代发行版一般是 UTF-8(en_US.UTF-8 / C.UTF-8);
  • UTF-8 本身是变长 1~4 字节编码。常用汉字在 BMP 平面内,所以每个汉字占 3 字节,这和 MySQL 的 utf8mb3 只是"恰好都是 3 字节",不是一回事。

6.3 为什么 GET 中文显示成 \xHH

复制代码
127.0.0.1:6379> SET key2 你好
127.0.0.1:6379> GET key2
"\xe4\xbd\xa0\xe5\xa5\xbd"
  • 内存里就是 6 个字节:你=E4 BD A0、好=E5 A5 BD;
  • redis-cli 默认对可打印 ASCII(0x20~0x7E)原样显示 ,其余字节用 \x 加两位十六进制转义显示,所以看到的是字节的十六进制形式,不是真乱码。

6.4 --raw 选项(注意拼写)

复制代码
redis-cli --raw
127.0.0.1:6379> GET key2
你好

加了之后 redis-cli 不做转义,直接输出原始字节,由你的终端按它自己的编码渲染。

6.5 按字节切中文的后果

复制代码
127.0.0.1:6379> GETRANGE key2 0 -1     # 全部 → 你好
127.0.0.1:6379> GETRANGE key2 1 -2     # 切出乱码

字节序列是 E4 BD A0 E5 A5 BD(下标 0~5),1 -2 截取的是 BD A0 E5 A5,这不是合法的 UTF-8 序列,终端只能渲染成乱码------这直观说明了 Redis 按字节处理,不按字符处理。

6.6 乱码的真正来源

因为 Redis 服务端不转码,乱码只可能出现在两端编码不一致:A 终端用 GBK 写入,B 终端用 UTF-8 读取,字节按不同字符集解释就会乱。统一客户端编码(都用 UTF-8)即可。


七、危险命令:FLUSHALL / FLUSHDB

复制代码
FLUSHDB       # 清空当前逻辑库
FLUSHALL      # 清空所有库(16 个库全删)
  • 生产环境严禁使用,误执行基本等于"删库跑路",原文的警告完全正确;
  • 8.0 支持后台异步清空 避免长时间阻塞:FLUSHALL ASYNC(由后台线程释放内存,效果依然是清空,只是不阻塞);
  • 权限上应通过 rename-command 在配置中禁用或改名。

八、String 典型应用场景

场景 用法
缓存对象 整条 JSON 存 String,或用 Hash
计数器 INCR/DECR:文章阅读量、点赞数、库存、接口限流
秒杀扣库存 DECR 原子扣减,配合判断返回值
分布式锁 SET key value NX PX 毫秒
全局唯一 ID INCR 生成全局递增整数
在线/签到统计 Bitmap(SETBIT/BITCOUNT,String 的位操作)
验证码 SETEX code 60 验证码
相关推荐
cc_瀚海知行1 小时前
资料检索 1|直接放入全文、按需读文件和先检索,有什么区别?
java·人工智能·ai
BioRunYiXue1 小时前
使用docker分析高通量测序数据
java·数据库·人工智能·spring cloud·docker·tomcat·html
Shaoshing2 小时前
缓存一致问题及常见解决方案
java·缓存
聚美智数2 小时前
文档转换 API‑文档转 PDF‑PDF 转文档‑异步文档处理‑文件格式迁移‑转换结果回调推送 API 接口介绍
android·java·pdf
从零数据分析2 小时前
零基础学SQL 23:和上月比总对不上?LAG/LEAD 一步取出上一次(附 4 个模板)
java·数据库·sql·mysql·oracle·sqlite·database
码士集团小青2 小时前
Redis 缓存击穿(失效)、缓存穿透、缓存雪崩怎么解决?
数据库·redis·缓存
yuzhiboyouye2 小时前
com.fasterxml.jackson.annotation 里最常用高频的注解,可以按“控制字段、控制格式、控制结构、控制流程”来记
java·开发语言
码士集团小青2 小时前
硬核 | Redis 布隆(Bloom Filter)过滤器原理与实战
数据库·redis·缓存
苜蓿muxu2 小时前
简述 ConcurrentHashMap 和 CopyOnWriteArrayList 的区别以及各自适用场景
java·编程语言