【Redis】String的常用命令及图解String使用场景

本文将详细介绍 Redis String 类型的常见命令及其使用场景,包括缓存、计数器、共享会话、手机验证码、分布式锁等场景,并且配图和伪代码进一步方便理解和使用。

命令 执行效果 时间复杂度
set key value [key value…] 设置key的值是value O(k),k是键个数
get key 获取key的值 O(1)
del key [key …] 删除指定的key O(k),k是键个数
mset key value [key value …] 批量设置指定的key和value O(k),k是键个数
mget key [key …] 批量获取key的值 O(k),k是键个数
incr key 指定的key的值+1 O(1)
decr key 指定的key的值-1 O(1)
incrby key n 定的key的值+n O(1)
decrby key n 指定的key的值-n O(1)
incrbyfloat key n 指定的key的值+n O(1)
append key value 指定的key的值追加value O(1)
strlen key 获取指定key的值的长度 O(1)

setrange key offset value

覆盖指定key的从offset开始的部分值

O(n),n是字符串长度,通常视为0(1)

getrange key start end

获取指定key的从start到end的部分值

O(n),n是字符串长度,通常视为O(1)

 1. 相关命令演示

# SET 命令
# 设置 key1 的值为 "Hello"
# 时间复杂度: O(1)
127.0.0.1:6379> SET key1 "Hello"
OK

# 设置 key2 的值为 "Redis"
# 时间复杂度: O(1)
127.0.0.1:6379> SET key2 "Redis"
OK

# GET 命令
# 获取 key1 的值
# 时间复杂度: O(1)
127.0.0.1:6379> GET key1
"Hello"

# DEL 命令
# 删除 key1
# 时间复杂度: O(1) 对单个键
127.0.0.1:6379> DEL key1
(integer) 1

# 尝试获取已经被删除的 key1 的值
# 时间复杂度: O(1)
127.0.0.1:6379> GET key1
(nil)

# MSET 命令
# 批量设置多个键值对
# 时间复杂度: O(k), k 是键的个数
127.0.0.1:6379> MSET key1 "Hello" key2 "Redis" key3 "!"
OK

# MGET 命令
# 批量获取多个键的值
# 时间复杂度: O(k), k 是键的个数
127.0.0.1:6379> MGET key1 key2 key3
1) "Hello"
2) "Redis"
3) "!"

# INCR 命令
# 将 counter 的值加 1
# 时间复杂度: O(1)
127.0.0.1:6379> SET counter 10
OK
127.0.0.1:6379> INCR counter
(integer) 11

# DECR 命令
# 将 counter 的值减 1
# 时间复杂度: O(1)
127.0.0.1:6379> DECR counter
(integer) 10

# INCRBY 命令
# 将 counter 的值加 5
# 时间复杂度: O(1)
127.0.0.1:6379> INCRBY counter 5
(integer) 15

# DECRBY 命令
# 将 counter 的值减 3
# 时间复杂度: O(1)
127.0.0.1:6379> DECRBY counter 3
(integer) 12

# INCRBYFLOAT 命令
# 将 counter 的值加 2.5(浮点数)
# 时间复杂度: O(1)
127.0.0.1:6379> INCRBYFLOAT counter 2.5
"14.5"

# APPEND 命令
# 将 " World" 追加到 key1 的值后
# 时间复杂度: O(1)
127.0.0.1:6379> APPEND key1 " World"
(integer) 11
127.0.0.1:6379> GET key1
"Hello World"

# STRLEN 命令
# 获取 key1 的值的长度
# 时间复杂度: O(1)
127.0.0.1:6379> STRLEN key1
(integer) 11

# SETRANGE 命令
# 从偏移量 6 开始,将 key1 的值替换为 "Redis"
# 时间复杂度: O(n),n 是字符串的长度,通常视为 O(1)
127.0.0.1:6379> SET key1 "Hello World"
OK
127.0.0.1:6379> SETRANGE key1 6 "Redis"
(integer) 11
127.0.0.1:6379> GET key1
"Hello Redis"

# GETRANGE 命令
# 获取 key1 从偏移量 0 到 4 的部分值
# 时间复杂度: O(n),n 是字符串的长度,通常视为 O(1)
127.0.0.1:6379> GETRANGE key1 0 4
"Hello"

2. 内部编码

Redis 中的字符串(String)类型是最基本的数据类型。为了高效地存储和操作字符串数据,Redis对字符串类型进行了多种内部编码优化。具体来说,字符串的内部编码主要有以下三种:intembstrraw。

int:8 个字节的⻓整型。
embstr:⼩于等于 39 个字节的字符串。 【Redis 3.2 及之前版本
raw:⼤于 39 个字节的字符串。 【Redis 3.2 及之前版本

Redis 4.0 及之后版本

  • embstr 编码:用于长度小于等于 44 字节的字符串。
  • raw 编码:用于长度大于 44 字节的字符串。

2.1 int

当一个字符串的值可以表示为 64 位(8字节)带符号整数时,Redis 会将该字符串编码为 int 类型。这种编码方式节省了内存,并且可以更高效地执行诸如 INCRDECR 等操作。

127.0.0.1:6379> SET myint 100
OK
127.0.0.1:6379> OBJECT ENCODING myint
"int"

2.2. embstr

当字符串的长度小于等于 44 字节时,Redis 会使用 embstr 编码。这种编码将 Redis 对象和实际字符串数据存储在连续的内存块中,因此在内存分配和访问上更加高效。embstr 编码在创建时效率很高,但一旦需要修改字符串内容,它会转换为 raw 编码。

127.0.0.1:6379> SET shortstr "Hello, Redis!"
OK
127.0.0.1:6379> OBJECT ENCODING shortstr
"embstr"

2.3 raw

当字符串长度大于 44 字节或需要进行修改操作时,Redis 会使用 raw 编码。raw 编码将 Redis 对象和字符串数据分开存储。这种方式适用于较长字符串或频繁修改的字符串,因为在这种情况下,内存分配和管理会更加高效。

127.0.0.1:6379> SET longstr "This is a very long string that exceeds the embstr limit."
OK
127.0.0.1:6379> OBJECT ENCODING longstr
"raw"

来看一下我使用的版本

 验证一下是39字节还是44字节发生内部编码类型的变化

测试长度为 39 字节的字符串

 测试长度为 44 字节的字符串

测试长度为 45 字节的字符串

 3 典型使用场景

3.1 缓存(Cache)功能

 下图是比较典型的缓存使用场景,其中 Redis作为缓冲层,MySQL作为存储层,绝大部分请求的数据都是从 Redis 中获取。由于 Redis 具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。

 下面的伪代码模拟了图 2-10 的业务数据访问过程

1. 假设业务是根据用户 uid 获取用户信息

UserInfo getUserInfo(long uid) {
 ...
}

2. 首先从 Redis 获取用户信息,我们假设用户信息保存在"user:info:<uid>" 对应的键中

1 // 根据 uid 得到 Redis 的键
2 String key = "user:info:" + uid;
3
4 // 尝试从 Redis 中获取对应的值
5 String value = Redis 执行命令:get key;
7 // 如果缓存命中(hit)
8 if(value != null){
9     //假设我们的用户信息按照 JSON 格式存储
10    UserInfo userInfo = JSON 反序列化(value);
11    return userInfo:
12 }

3. 如果没有从 Redis 中得到用户信息,及缓存 miss,则进一步从 MVSOL中获取对应的信息,随后写入缓存并返回:

// 如果缓存未命中(miss)
if (value == null) {
     // 从数据库中,根据 uid 获取⽤⼾信息
     UserInfo userInfo = MySQL 执⾏ SQL:select * from user_info where uid = <uid>
 
     // 如果表中没有 uid 对应的⽤⼾信息
     if (userInfo == null) {
         响应 404
         return null;
     }
 
     // 将⽤⼾信息序列化成 JSON 格式
     String value = JSON 序列化(userInfo);
 
     // 写⼊缓存,为了防⽌数据腐烂(rot),设置过期时间为 1 ⼩时(3600 秒)
     Redis 执⾏命令:set key value ex 3600
 
     // 返回⽤⼾信息
     return userInfo;
}

3.2 计数器功能

许多应用都会使用 Redis 作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数据可以异步处理或者落地到其他数据源。如下图所示,例如视频网站的视频播放次数可以使用Redis 来完成:用户每播放一次视频,相应的视频播放数就会自增 1

 3.3 共享会话

如图所示,一个分布式Web 服务将用户的 Session 信息(例如用户登录信息)保存在各自的服务器中,但这样会造成一个问题:出于负载均衡的考虑,分布式服务会将用户的访问请求均衡到不同的服务器上,并且通常无法保证用户每次请求都会被均衡到同一台服务器上,这样当用户刷新一次访问是可能会发现需要重新登录,这个问题是用户无法容忍的。

为了解决这个问题,可以使用 Redis 将用户的 Session 信息进行集中管理,如下图所示,在这种模式下,只要保证 Redis 是高可用和可扩展性的,无论用户被均衡到哪台 Web 服务器上,都集中从Redis 中查询、更新 Session 信息。

3.4 手机验证码

很多应用出于安全考虑,会在每次进行登录时,让用户输入手机号并且配合给手机发送验证码,然后让用户再次输入收到的验证码并进行验证,从而确定是否是用户本人。为了短信接口不会频繁访会限制用户每分钟获取验证码的频率,例如一分钟不能超过5次,如图所示。

 此功能可以⽤以下伪代码说明基本实现思路

// 发送验证码并处理发送频率限制
String 发送验证码(String phoneNumber) {
    // 构造用于限制短信发送频率的 Redis 键
    String key = "shortMsg:limit:" + phoneNumber;

    // 尝试在 Redis 中设置键值对,设置过期时间为 1 分钟(60 秒),只在键不存在时才能成功(NX 参数)
    boolean r = Redis.执行命令:set key 1 ex 60 nx;

    if (!r) {
        // 如果设置失败,说明之前已经设置过该手机号的发送频率限制
        // 增加计数器,表示又发送了一次验证码
        long c = Redis.执行命令:incr key;

        if (c > 5) {
            // 如果超过了一分钟内的 5 次发送限制,限制发送,返回 null
            return null;
        }
    }

    // 生成随机的 6 位数验证码
    String validationCode = 生成随机的 6 位数的验证码();

    // 设置验证码的存储键,有效期设置为 5 分钟(300 秒)
    String validationKey = "validation:" + phoneNumber;
    Redis.执行命令:set validationKey validationCode ex 300;

    // 返回生成的验证码,后续通过手机短信发送给用户
    return validationCode;
}

// 验证用户输入的验证码是否正确
boolean 验证验证码(String phoneNumber, String validationCode) {
    // 构造存储验证码的 Redis 键
    String validationKey = "validation:" + phoneNumber;

    // 从 Redis 中获取存储的验证码值
    String value = Redis.执行命令:get validationKey;

    if (value == null) {
        // 如果没有找到该手机号的验证码记录,验证失败
        return false;
    }

    // 比较用户输入的验证码是否和存储的验证码一致,返回相应的验证结果
    return value.equals(validationCode);
}

3.5 分布式锁

在 Redis 中,String 类型可以用于实现简单的分布式锁:

  • 原子性操作: Redis 的命令是原子性的,即 Redis 单个命令的执行是不可中断的,要么全部执行成功,要么全部不执行,不存在部分执行的情况。例如,SETNX 命令(设置值并仅在键不存在时设置成功)和 DEL 命令(删除键值对)都是原子操作。这种特性确保了在高并发环境下,对分布式锁的获取和释放操作是可靠的。

  • 并发控制Redis 是单线程的,通过事件循环和非阻塞 I/O 实现高并发处理。虽然 Redis 本身是单线程的,但其内部使用了时间片轮转机制来实现多个客户端的请求处理。这使得 Redis 能够高效处理大量的并发请求,适合作为分布式锁的存储和管理工具。

  • 过期时间可以为 Redis 的 String 类型设置过期时间(Expiration),即在设置键值对时可以指定键的生存时间。这一特性对于分布式锁尤为重要,可以避免因为客户端异常退出而造成的死锁情况。设置合适的过期时间可以确保即使锁未显式释放,也能在一定时间后自动释放,从而避免资源长时间被锁定。

 此功能可以⽤以下伪代码说明基本实现思路

// 示例中的参数
String lockKey = "resource_lock";
String clientId = UUID.randomUUID().toString();

// 执行 SETNX 命令
boolean lockAcquired = Redis.执行命令:setnx(lockKey, clientId);

if (lockAcquired) {
    // 如果成功获得锁
    try {
        // TODO: 执行业务逻辑
    } finally {
        // 释放锁
        Redis.执行命令:del(lockKey);
    }
} else {
    // 获取锁失败的处理逻辑
    // TODO: 处理获取锁失败的情况
}

命令说明

  • 当执行 SETNX lockKey clientId 命令时,Redis 将尝试设置键 lockKey 的值为 clientId
  • 如果 lockKey 已经存在(即已被其他客户端设置),SETNX 命令将会设置失败,返回 0
  • 如果 lockKey 不存在,SETNX 命令将设置成功,返回 1 表示获取了锁。

注意事项

  • 获取锁后,一定要确保最终释放锁,以避免锁被长时间持有而造成资源无法访问。
  • 可以结合设置过期时间的方式来实现自动释放锁,避免因客户端异常退出而导致的死锁问题。

以上介绍了使用Redis的字符串数据类型可以使用的几个场景,但其适用场景远不止于此,开发人员可以结合字符串类型的特点以及提供的命令,充分发挥自己的想象力,在自己的业务中去找到合适的场景去使用Redis的字符串类型。


码字不易,如果有用还请三连支持哦。

相关推荐

  1. SynchronousQueue 场景使用示例

    2024-06-16 14:08:02       13 阅读
  2. CyclicBarrier 场景使用示例

    2024-06-16 14:08:02       7 阅读
  3. Git 基本概念、使用方式命令

    2024-06-16 14:08:02       33 阅读
  4. Git命令场景及其实例

    2024-06-16 14:08:02       37 阅读
  5. 网络命令使用

    2024-06-16 14:08:02       17 阅读
  6. docker使用以及命令

    2024-06-16 14:08:02       10 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-16 14:08:02       10 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-16 14:08:02       12 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-16 14:08:02       11 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-16 14:08:02       14 阅读

热门阅读

  1. [AIGC] 深入浅出 Python中的`enumerate`函数

    2024-06-16 14:08:02       9 阅读
  2. 刷题 ——反转链表(若有其它解法,继续补充)

    2024-06-16 14:08:02       10 阅读
  3. wordpress站群搭建1需求分析

    2024-06-16 14:08:02       7 阅读
  4. git子模块应用和常用用法

    2024-06-16 14:08:02       6 阅读
  5. MySQL每日备份

    2024-06-16 14:08:02       6 阅读
  6. C++ 取近似值

    2024-06-16 14:08:02       9 阅读
  7. GO语言容器大全(附样例代码)

    2024-06-16 14:08:02       6 阅读
  8. linux下nvidia驱动安装-ubuntu22.04安装2060-notebook驱动

    2024-06-16 14:08:02       8 阅读
  9. 如何基于Redis实现消息队列

    2024-06-16 14:08:02       5 阅读
  10. JVM-GC-基础知识

    2024-06-16 14:08:02       7 阅读
  11. 差分,LeetCode 2779. 数组的最大美丽值

    2024-06-16 14:08:02       8 阅读
  12. Oracle锁机制之分类和死锁

    2024-06-16 14:08:02       7 阅读