Redis 7.x 系列【6】数据类型之字符串(String)

有道无术,术尚可求,有术无道,止于术。

本系列Redis 版本 7.2.5

源码地址:https://gitee.com/pearl-organization/study-redis-demo

文章目录

    • [1. 前言](#1. 前言)
    • [2. 常用命令](#2. 常用命令)
      • [2.1 SET](#2.1 SET)
      • [2.2 GET](#2.2 GET)
      • [2.3 MSET](#2.3 MSET)
      • [2.4 MGET](#2.4 MGET)
      • [2.5 GETSET](#2.5 GETSET)
      • [2.6 STRLEN](#2.6 STRLEN)
      • [2.7 SETEX](#2.7 SETEX)
      • [2.8 SETNX](#2.8 SETNX)
      • [2.9 INCR](#2.9 INCR)
      • [2.10 DECR](#2.10 DECR)
    • [3. SDS(简单动态字符串)](#3. SDS(简单动态字符串))
      • [3.1 二进制安全](#3.1 二进制安全)
      • [3.2 查询字符串长度效率高](#3.2 查询字符串长度效率高)
      • [3.3 缓冲区溢出保护](#3.3 缓冲区溢出保护)
      • [3.4 空间预分配](#3.4 空间预分配)
      • [3.5 惰性空间释放](#3.5 惰性空间释放)
    • [4. 应用场景](#4. 应用场景)
      • [4.1 存储常规数据](#4.1 存储常规数据)
      • [4.2 计数器](#4.2 计数器)
      • [4.3 分布式锁](#4.3 分布式锁)

1. 前言

官方文档中,可以看到 Redis 支持很多种数据类型,以解决不同场景下的各种问题。接下来,会详细介绍 Redis 中的各种数据类型, 主要是介绍一些常用的,包括:

  • String
  • List
  • Hash
  • Set
  • Sorted setZSet
  • Bitmap
  • HyperLogLog
  • Geospatial
  • Stream

String字符串是编程语言里最常用的数据类型,使用双引号表示。

比如JAVA

java 复制代码
	String str="1234";

比如C语言:

java 复制代码
    char str[] = "1234";

Redis 中, String 类型是最基本的数据类型,是二进制安全的,可以存储任意类型的数据,文本、数字、图片或者序列化的对象 。

注意Key的类型只能为字符串,Value的类型为字符串时,默认情况下的最大值是 512M

2. 常用命令

String 相关的所有命令:

命名 描述
APPEND value 追加到 key 原来的值的末尾
DECR key 中储存的数字值减一
DECRBY key 所储存的值减去给定的减量值 ( decrement )
GET 设置指定 key 的值
GETDEL 获取 key 的值并删除该 key
GETEX 获取 key 的值,并可选择设置其过期时间
GETRANGE 返回 key 中字符串值的子字符
GETSET 将给定 key 的值设为 value ,并返回 key 的旧值
INCR key 中储存的数字值增一
INCRBY key 所储存的值加上给定的增量值 ( increment )
INCRBYFLOAT key 所储存的值加上给定的浮点增量值 ( increment )
LCS 实现了最长公共子序列算法,可用于评估字符串的相似程度
MGET 获取所有(一个或多个)给定 key 的值
MSET 同时设置一个或多个 key-value
MSETNX 同时设置一个或多个 key-value
PSETEX 以毫秒为单位设置 key 的生存时间
SET 设置指定 key 的值
SETEX 设置 key 的值为 value 同时将过期时间设为 seconds
SETNX 只有在 key 不存在时设置 key 的值
SETRANGE 从偏移量 offset 开始用 value 覆写给定 key 所储存的字符串值
STRLEN 返回 key 所储存的字符串值的长度
SUBSTR 返回字符串值的子字符串,由偏移量开始和结束(两者都包含在内)决定

2.1 SET

用于给指定的 Key 设置字符串 值,如果 Key 已经保存了一个值,那么这个操作会直接覆盖原来的值,并且忽略原始类型。当 SET 命令执行成功后,之前设置的过期时间都将失效。

示例:

bash 复制代码
127.0.0.1:6379> SET mykey "Hello World"
OK
127.0.0.1:6379> SET mykey "Hello World" EX 60
OK

2.2 GET

用于获取指定 Key 的字符串值,如果 Key 不存在, 那么返回特殊值 nil, 如果键 Key的值不是字符串类型, 返回错误, 因为 GET 命令只能用于字符串值。

示例:

bash 复制代码
127.0.0.1:6379> GET mykey
"Hello World"
127.0.0.1:6379> GET mykey_nil
(nil)

2.3 MSET

SET 命令一样,会用新值替换旧值, MSET 是原子操作,所有 Key 的值同时设置,客户端不会看到有些 Key 值被修改,而另一些 Key 值没变。总是返回 OK ,因为 MSET 不会失败。

基本语法:

bash 复制代码
MSET key1 value1 key2 value2 .. keyN valueN 

示例:

bash 复制代码
127.0.0.1:6379> MSET key1 "Hello" key2 "World"
OK
127.0.0.1:6379> GET key1
"Hello"
127.0.0.1:6379> GET key2
"World"

2.4 MGET

用于获取所有给定 Key 的值,返回一个列表,值的类型是字符串,如果某个 Key 不存在或者值不是字符串,那么这个 Key 返回特殊值 nil

基本语法:

bash 复制代码
MGET KEY1 KEY2 .. KEYN

示例:

bash 复制代码
127.0.0.1:6379> MGET key1 key2 nonexisting
1) "Hello"
2) "World"
3) (nil)

2.5 GETSET

用于给指定的 Key 设置字符串 值, 并返回设置之前的旧值。如果 Key 没有旧值返回 nil,如果 Key 对应的值存在但不是字符串类型时,返回一个错误。

示例:

bash 复制代码
127.0.0.1:6379> SET mykey "Hello"
OK
127.0.0.1:6379> GETSET mykey "World"
"Hello"
127.0.0.1:6379> GET mykey
"World"

2.6 STRLEN

用于获取指定 Key 所储存的字符串值的长度,当储存的不是字符串类型时,返回错误。

基本语法:

bash 复制代码
STRLEN KEY_NAME

示例:

bash 复制代码
127.0.0.1:6379> SET mykey "Hello world"
OK
127.0.0.1:6379> STRLEN mykey
(integer) 11
127.0.0.1:6379> STRLEN mykey_nil
(integer) 0

2.7 SETEX

用于给指定的 Key 设置字符串 值,并将 Key 的生存时间设置为多少秒。如果键 Key 已经存在,将覆盖已有的值。

基本语法:

bash 复制代码
SETEX key seconds value

示例:

bash 复制代码
127.0.0.1:6379> SETEX mykey 60 "Hello"
OK
127.0.0.1:6379> TTL mykey
(integer) 52
127.0.0.1:6379> GET mykey
"Hello"

2.8 SETNX

SETNXSET if Not eXists 的缩写,在指定的 Key 不存在时,设置指定的值,这种情况下等同 SET 命令,当 Key 存在时,什么也不做。

返回值为整数:

  • 1Key 存在
  • 0Key 不存在

SETNX 命令是原子性的,常用于实现分布式锁,添加成功表示获取到锁,添加失败表示未获取到锁(后续详解介绍)。

示例:

bash 复制代码
redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"

2.9 INCR

用于将指定的 Key 储存的数字值增一,如果 Key 不存在,那么值会先被初始化为 0 ,然后再执行 INCR 操作,返回值为执行操作之后 Key 的值。如果值包含错误的类型,或字符串类型的值不能表示为数字,将会返回一个错误 ERR ERR hash value is not an integer

操作的值限制在 64 位(bit)有符号数字表示之内。本质上这是一个字符串操作,因为 Redis 没有专门的整数类型,储在 Key 中的字符串被转换为十进制有符号整数,在此基础上加 1 。常用于计数器、限流(后续详解介绍)。

基本语法:

bash 复制代码
 INCR KEY_NAME 

示例:

bash 复制代码
127.0.0.1:6379> SET mykey "10"
"OK"
127.0.0.1:6379> INCR mykey
(integer) 11
127.0.0.1:6379>  GET mykey
"11"

2.10 DECR

用于将指定的 Key 储存的数字值减去一,如果 Key 不存在,那么值会先被初始化为 0 ,然后再执行 DECR 操作。如果值包含错误的类型,或字符串类型的值不能表示为数字,将会返回一个错误。操作的值限制在 64 位(bit)有符号数字表示之内,返回值为执行操作之后 Key 的值

基本语法:

bash 复制代码
 DECR KEY_NAME 

示例:

bash 复制代码
127.0.0.1:6379> DECR mykey
(integer) 9
127.0.0.1:6379> SET mykey "234293482390480948029348230948"
"OK"
127.0.0.1:6379>DECR mykey
ERR ERR value is not an integer or out of range

3. SDS(简单动态字符串)

SDSSimple Dynamic String 的首字母缩写,翻译为简单的动态字符串,是 Redis 自定义的一种表示字符串的特殊数据结构。相较于传统的C语言字符串,SDS 具有更多的功能和更高的性能,当前也会占用更多的内存。

Redis 3.2 之前的版本:

java 复制代码
struct sdshdr {
    int len; // 记录buf数组中已使用字节的数量,即字符串的实际长度
    int free; // 存储剩余(空闲)的空间
    char buf[]; // 存储实际数据
};

Redis 3.2 之后的版本,SDS 变成了 5 种数据结构,不同类型的存储空间大小不一样:

java 复制代码
/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; // 已用空间的长度,占 4 个字节,不包括 '\0';
    uint8_t alloc; // 实际分配长度,占 4 个字节,不包括 '\0';
    unsigned char flags; // 标记当前 buf[] 类型( sdshdr8/16/32/64)
    char buf[]; // 存储实际数据
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

相比于C语言字符串,SDS 有以下优点:

  • 二进制安全
  • 查询字符串长度效率高
  • 缓冲区溢出保护
  • 空间预分配
  • 惰性空间释放

3.1 二进制安全

C 语言中,用字符 \0 表示字符串的结束,如果字符串中本身就有 \0 字符,字符串就会被截断,即非二进制安全,例如 hello \0 world 会被截断解析为 hello

SDS 不受 \0 字符影响,能够准确存储任意二进制数据,因为它基于 len 属性而非遇到 \0 来判断字符串的结束位置。

3.2 查询字符串长度效率高

C 语言中,获取一个字符串长度时,需对整个字符串进行遍历,直至遇到结束符号。在高并发场景下频繁遍历字符串,势必会造成性能瓶颈。

SDS 中于 len 属性记录了字符串的长度,可以直接通过 STRLEN 命令获取长度即可。

3.3 缓冲区溢出保护

C 语言中,字符串本身不记录可用空间大小,在进行字符串拼接等操作时,如果没有检查就可能会导致缓冲区溢出。

SDS 中于 free 属性记录了额外未使用的空间大小,每次进行字符串操作前都会检查剩余空间是否足够,不足时会自动扩容,有效避免了缓冲区溢出问题。

3.4 空间预分配

C 语言中,每次修改字符串长度(增加或减少)时,通常需要重新分配内存,这可能导致频繁的内存操作。

SDS 优化了字符串增长操作,当修改字符串并需对 SDS 的空间进行扩展时,不仅会为 SDS分配修改所必要的空间,还会分配额外的未使用空间(free属性),下次再修改就先检查未使用空间free是否满足,满足则不用在扩展空间。有效的减少字符串连续增长操作,减少所产生的内存重分配次数。

3.5 惰性空间释放

惰性空间释放策略则用于优化 SDS 字符串缩短 操作,当缩短 SDS 字符串后,并不会立即执行内存重分配来回收多余的空间,而是用 free 属性将这些空间记录下来,如果后续有增长操作,则可直接使用。

4. 应用场景

4.1 存储常规数据

String 可以存储任意类型的数据,文本、数字、图片或者序列化的对象 。

常用实际场景:

  • 图形、短信验证码
  • 令牌

例如,存储短信验证码并设置过期时间为 60 秒:

bash 复制代码
localhost:0>SET 13688889999 80906 EX 60
"OK"
localhost:0>GET 13688889999
"80906"

4.2 计数器

INCR 用于将指定的 Key 储存的数字值增一, DECR 用于将指定的 Key 储存的数字值减去一。

常用实际场景:

  • 阅读数
  • 点赞
  • 分布式ID
  • 网站访问量
  • 记录接口访问次数,实现防止重复提交、简单限流

例如,使用 INCR 记录网站每日访问量:

bash 复制代码
localhost:0>SET web_views:20240617 0
"OK"
localhost:0>INCR web_views:20240617
"1"
localhost:0>GET web_views:20240617
"1"

4.3 分布式锁

SETNX 命令是原子性的,常用于实现分布式锁,添加成功表示获取到锁,添加失败表示未获取到锁(后续详解介绍)。

例如,执行 SETNX 添加锁,返回 1 表示成功获取到锁:

bash 复制代码
localhost:0>SETNX my_lock yes
"1"

其他线程执行SETNX,返回 0 表示获取锁失败:

bash 复制代码
localhost:0>SETNX my_lock yes
"0"

执行完成后,删除锁,其他线程再次获取则成功:

java 复制代码
localhost:0>DEL my_lock
"1"
localhost:0>SETNX my_lock yes
"1"
相关推荐
只因在人海中多看了你一眼36 分钟前
分布式缓存 + 数据存储 + 消息队列知识体系
分布式·缓存
Dlwyz2 小时前
redis-击穿、穿透、雪崩
数据库·redis·缓存
工业甲酰苯胺4 小时前
Redis性能优化的18招
数据库·redis·性能优化
Oak Zhang6 小时前
sharding-jdbc自定义分片算法,表对应关系存储在mysql中,缓存到redis或者本地
redis·mysql·缓存
门牙咬脆骨7 小时前
【Redis】redis缓存击穿,缓存雪崩,缓存穿透
数据库·redis·缓存
门牙咬脆骨7 小时前
【Redis】GEO数据结构
数据库·redis·缓存
墨鸦_Cormorant9 小时前
使用docker快速部署Nginx、Redis、MySQL、Tomcat以及制作镜像
redis·nginx·docker
Dlwyz12 小时前
问题: redis-高并发场景下如何保证缓存数据与数据库的最终一致性
数据库·redis·缓存
飞升不如收破烂~13 小时前
redis的List底层数据结构 分别什么时候使用双向链表(Doubly Linked List)和压缩列表(ZipList)
redis
吴半杯14 小时前
Redis-monitor安装与配置
数据库·redis·缓存