图解Redis 02 | String数据类型的原理及应用场景

embedded/2024/9/20 3:53:16/ 标签: redis, 数据库, 缓存

介绍

在 Redis 中,String 是一种重要的数据类型,是最基本的 key-value 结构,在这个结构中, value 是一个字符串。value 所能容纳的数据最大长度为512M

需要注意的是,这里的字符串不只指文本数据,它还可以是数字、JSON 对象、二进制数据等。

内部实现

String 类型的底层数据结构在 Redis 中主要由整数(int)和 SDS(Simple Dynamic String)实现。

SDS 与我们熟知的 C 语言字符串有所不同。Redis 选择 SDS 而非 C 语言的字符串实现,主要是因为 SDS 具有以下特点:

  • SDS 不仅可以保存文本数据,还可以保存二进制数据。这是因为 SDS 使用 len 属性来判断字符串的结束位置,而不是依靠空字符 (\0) 标记结束。同时,SDS 的所有 API 都会将 buf[] 数组中的数据作为二进制来处理。因此,SDS 不仅可以存储文本,还能存储图片、音频、视频、压缩文件等二进制数据。

  • SDS获取字符串长度的时间复杂度为O(1)。由于C语言的字符串没有记录自身的长度,所以获取长度的复杂度为O(n);而SDS结构体中的len属性记录了字符串长度,所以复杂度为O(1)。

  • Redis 的 SDS API 是安全的,拼接字符串不会导致缓冲区溢出。因为 SDS 会在拼接字符串前检查空间是否足够,如果空间不足,会自动扩展,避免缓冲区溢出的问题。

在 Redis 中,字符串对象的内部编码(encoding)有三种:int、raw 和 embstr,关系如下:

如果字符串对象存储的是一个整数值,并且这个整数值可以用 long 类型表示,那么该字符串对象会将这个整数值存储在RedisObject的 ptr 属性中(将 void* 转换为 long),并将其编码设置为 int。

注意:redisObject是Redis中用于表示数据结构(如字符串、列表、集合、哈希表和群体集合)的一个核心数据结构。通俗来讲,可以把它redisObject想象成一个“包装器”或“容器”,用于封装和管理存储在Redis内存中的数据。后面会有文章详细介绍。

如果字符串对象存储的是一个长度不超过 32 字节的字符串(在 Redis 3.2 版本及以后改成44字节),Redis 会使用简单动态字符串(SDS)来存储这个字符串,并将编码设置为 embstr。

embstr 编码是一种专门为存储短字符串而优化的编码方式,这种编码方式将字符串数据直接嵌入到 redisObject 结构体内部,redisObject 和 SDS 数据都是在同一块内存区域内,这意味着 Redis 只需分配一次内存空间,从而提高了内存管理效率和操作性能。

如果字符串对象存储的是一个长度超过 32 字节的字符串(在 Redis 3.2 版本及以后改成44字节),Redis 会使用简单动态字符串(SDS)来存储这个字符串,并将编码设置为 raw。raw 编码需要分配两次内存空间(分别为redisObject和sds)。

注意,不同版本的 Redis 中,embstr 编码与 raw 编码的界限长度有所不同:

  • 在 Redis 2.x 版本中,界限为 32 字节。
  • 在 Redis 3.0 到 4.0 版本中,界限为 39 字节。
  • 在 Redis 5.0 版本中,界限为 44 字节。

这里再总结一下,embstr 编码和 raw 编码都使用 SDS来保存字符串值,但它们在内存分配和数据存储方式上有所不同:

1. 内存分配
  • “raw”编码:需要进行两次内存分配,分别为Redis对象(“redisObject”)和SDS(Simple Dynamic String)分配内存空间。
  • “embstr”编码:只进行一次内存分配,同时为Redis对象和SDS分配连续的内存空间。
2. 数据存储
  • “raw”编码:Redis对象和SDS分别存储在不同的内存区域。
  • “embstr”编码:Redis对象和SDS存储在连续的内存区域中。
3. 修改操作
  • “embstr”编码的字符串是只读的,一旦需要修改字符串,就会转换为“raw”编码。
  • 可以直接修改“raw”编码的字符串。
4. 性能影响
  • “embstr“ 编码:在创建和释放内存时的开销较小,因为只需要进行一次内存操作。
  • “raw“ 编码:在修改字符串时性能更好,因为不需要进行编码转换。

选择使用哪种编码取决于具体的应用场景和需求。如果字符串长度较短且不经常修改,embstr 编码可能更为适合;而如果字符串较长或需要频繁修改,则 raw 编码可能是更好的选择。

常用命令

基本操作
# 设置value> SET name Sea 
OK 
# 根据key获取对应的value。> GET name 
"Sea" 
# 判断某个key是否存在。> EXISTS name 
( integer ) 1 
# 返回key存储的字符串值的长度。> STRLEN name 
( integer ) 3 
# 删除某个key对应的value。> DEL name 
( integer ) 1#不存在则设置
>SETNX key value
(integer) 1
批量操作
# 批量设置key-value类型的值
> MSET k1 v1 k2 v2 k3 v3 
OK 
# 批量获取多个key对应的值
> MGET k1 k2 
1) "v1"
2) "v2"
计数器操作(当字符串内容为整数时可使用)
# 设置键值类型的值。> SET number 0 
OK 
# 将键中存储的数字值增加一。> INCR number 
( integer ) 1 
# 将键中存储的数字值加 100。> INCRBY number 100 
( integer ) 101 
# 将键中存储的数字值减少一。> DECR number 
( integer ) 100 
# 将键中存储的数字值减少 50。> DECRBY number 50 
( integer ) 50
# 设置key的 过期时间为30秒(此命令是 为已经存在的key设置 过期时间)
> EXPIRE name 30 
(integer) 1
# # 检查数据还有多长时间过期
> TTL name 
(integer) 23
# 设置key- value的同时设置过期时间
> SET key value EX 30
OK
> SETEX key 30 value
OK

应用场景

1. 统计计数

由于 Redis 是单线程处理命令的,执行命令的过程是原子的,因此 String 数据类型非常适合用于计数场景,如计算访问次数、点赞次数等。等等。

例如,我们可以使用 Redis 的 INCR 命令来计算一篇文章的访问次数:每当用户访问这篇文章时,我们就调用 INCR 命令增加该文章的访问次数。由于 Redis 的 INCR 命令是原子的,这保证了每次访问都会正确地更新计数值,而不会出现并发冲突的问题。

# 初始化文章访问次数为  0
> SET aritcle:visit:count:1000001 0 
OK 
# 访问量 +1> INCR aritcle:readcount:1000001 
( integer ) 1 
# 访问量 +1> INCR aritcle:readcount:1000001 
( integer ) 2 
# 获取相应文章的访问量
> GET aritcle:readcount:1000001 
"2"

2. 缓存对象

使用 String 缓存对象的常见方式有两种:

1. 直接缓存整个对象的 JSON

将对象转换为 JSON 字符串,然后将这个 JSON 字符串作为值存储在 Redis 的 String 类型中。这种方式简单直观,但当需要修改对象的某个字段时,就必须将整个 JSON 字符串取出,进行反序列化,修改字段后再重新序列化存储。

这可能会导致较大的性能开销,因为每次修改都涉及到整个对象的序列化和反序列化过程。

SET user:1 '{"name":"Bob", "age":18}'
2. 将对象的属性分开存储

将对象的各个属性分别存储在不同的 key 中,每个 key 对应一个属性的值。这种方式提供了更大的灵活性,允许你单独访问和修改对象的某个属性,而不需要处理整个对象。然而,这也意味着需要对多个 key 进行操作,从而可能增加操作的复杂性和开销。

MSET user:1:name Bob user:1:age 18 user:2:name Jerry user:2:age 20

3. 分布式锁

在 Redis 中,可以使用 String 数据结构来实现分布式锁。利用 SET 命令的 NX 参数,可以实现“仅当 key 不存在时才插入”的功能,从而用来实现分布式锁:

  • 如果 key 不存在,Redis 会插入 key 并返回成功,这表示锁获取成功。
  • 如果 key 已经存在,Redis 会插入失败,表示锁获取失败。

通常,为了确保锁不会因异常情况而长时间占用,还会给分布式锁设置一个过期时间。分布式锁的命令如下:

SET lock_key unique_value NX PX 10000
  • lock_key 是锁对应的key;
  • unique_value 是客户端生成的唯一标识,用于标识锁的拥有者;
  • NX 表示仅在 lock_key 不存在时才设置;
  • PX 10000 表示将 lock_key 的过期时间设置为 10 秒,以防客户端异常无法释放锁。

在解锁时,操作的步骤是删除 lock_key 键。但是,为了确保只有持有锁的客户端才能删除 lock_key,需要先验证 unique_value 是否匹配。如果匹配,则可以删除该键。

由于解锁过程涉及两个操作(验证和删除),需要确保这两个操作是原子的。这可以通过使用 Lua 脚本来实现,因为 Redis 在执行 Lua 脚本时,会以原子方式执行整个脚本,从而保证了解锁操作的原子性。

# 释放锁时,先比较unique_value是否相等,避免错误释放锁。
if redis.call("get",KEYS[1]) == ARGV[1] thenreturn redis.call("del",KEYS[1])
elsereturn 0
end

这样就通过SET命令结合Lua脚本完成了单个Redis节点上分布式锁的加锁与解锁。

4. 共享会话信息

通常我们在开发登录模块的时候,都会使用Session来保存用户的登录状态,这些Session信息会保存在服务器端,但是这只适用于单系统应用。

在分布式系统中,这种模式就不再适用了。由于用户的请求可能会被分配到不同的服务器上,这会导致 Session 信息丢失,导致用户需要频繁重新登录。

例如,假设用户1第一次的登录请求是落到服务器A上,因此其 Session 信息保存在服务器A上。但是当用户1第二次访问的时候,可能会被分配到了服务器B上,而服务器B没有用户1的Session信息,那么就会提示用户1需要登录,这样的用户体验是极度不友好的。

为了解决这个问题,通常需要采用集中式的 Session 存储方案,例如使用 Redis 来存储 Session 信息,这样无论用户的请求被分配到哪台服务器,服务器都会去同一个Redis中获取相关的Session信息,样就解决了分布式系统中Session存储的问题。


http://www.ppmy.cn/embedded/113324.html

相关文章

【Kubernetes】常见面试题汇总(十六)

目录 48.简述 Kubernetes PodsecurityPolicy 机制能实现哪些安全策略? 49.简述 Kubernetes 网络模型? 50.简述 Kubernetes CNl 模型? 48.简述 Kubernetes PodsecurityPolicy 机制能实现哪些安全策略? 在 PodSecurityPolicy 对象…

LSTM文本预测(Pytorch版)

任务:基于 flare 文本数据,建立 LSTM 模型,预测序列文字 1.完成数据预处理,将文字序列数据转化为可用于LSTM输入的数据 2.查看文字数据预处理后的数据结构,并进行数据分离操作 3.针对字符串输入(" fla…

【基于C++的产品入库管理系统】

基于C的产品入库管理系统可以用来跟踪产品的入库、出库和库存情况。这种系统通常包括产品信息的录入、查询、更新以及库存管理等功能。下面是一个简化的产品入库管理系统的设计方案及其代码示例。 系统设计概览 产品管理:包括产品的基本信息(如名称、规…

个人电脑可以当服务器用吗?

服务器和普通电脑的主要区别体现在以下几个方面: 1.CPU处理性能 服务器的CPU配置通常是多核的,普通电脑的CPU往往只包含单个核心,因此在数据处理能力上远远不如服务器。 2.安全性能 服务器具备更高的可靠性、安全性、容错能力和安全保护能…

【计算机网络 - 基础问题】每日 3 题(一)

✍个人博客:Pandaconda-CSDN博客 📣专栏地址:http://t.csdnimg.cn/fYaBd 📚专栏简介:在这个专栏中,我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话,欢迎点赞👍收藏&…

JVM垃圾回收算法

JVM垃圾回收算法是Java虚拟机中自动管理内存的关键机制,它通过智能识别和回收无用对象,有效防止内存泄露,提升系统性能,是Java语言高效、稳定运行的基石。让我们一同探索这一神奇算法,感受它如何为Java世界注入活力&am…

程序化自动交易,合约量化交易软件用哪个比较好

炒股自动化:申请官方API接口,散户也可以 python炒股自动化(0),申请券商API接口 python炒股自动化(1),量化交易接口区别 Python炒股自动化(2):获取…

面试真题-TCP的三次握手

TCP的基础知识 TCP头部 面试题:TCP的头部是多大? TCP(传输控制协议)的头部通常是固定的20个字节长,但是根据TCP选项(Options)的不同,这个长度可以扩展。TCP头部包含了许多关键的字…

力扣100题——贪心算法

概述 贪心算法(Greedy Algorithm)是一种在解决问题时,按照某种标准在每一步都选择当前最优解(局部最优解)的算法。它期望通过一系列局部最优解的选择,最终能够得到全局最优解。 贪心算法的核心思想 贪心算…

Nginx实用篇:实现负载均衡、限流与动静分离

Nginx实用篇:实现负载均衡、限流与动静分离 | 原创作者/编辑:凯哥Java | 分类:Nginx学习系列教程 Nginx 作为一款高性能的 HTTP 服务器及反向代理解决方案,在互联网架构中扮演着至关重要的角色。它…

如何搭建一个ip池用来做数据抓取用

在当今的数据驱动时代,数据抓取成为了获取网络信息的重要手段。然而,频繁的数据抓取活动可能会触发网站的安全机制,导致IP被封禁。为了维持数据抓取的持续性和稳定性,构建一个有效的IP池变得至关重要。本文将详细介绍如何搭建一个…

好用的XML解析库——fast-xml-parser

有时候需要在前台用到xml的解析,并且可能需要解析后再重新生成xml字符串,这个时候就可以用到fast-xml-parser了。 优点 使用简单,主要有两个对象,分别是XMLParser和XMLBuilder。 主要需要关注的属性有 ignoreAttributes 和 supp…

ASR(自动语音识别)识别文本效果的打分总结

ASR(自动语音识别)识别文本效果的打分总结 1. 词错误率(WER, Word Error Rate)2. 字正确率(W.Corr, Word Correct)3. 编辑距离(Edit Distance)4. 特定错误率5. 句子错误率(SER, Sentence Error Rate)6. 基于模型的评估方法对于ASR(自动语音识别)识别文本效果的打分…

HtmlRender - c++实现的html生成类

HtmlRender 在CppTinParser/render.hpp中定义和实现。 使用c实现的简易Html编辑类。 简介 目前,c有几个Html解析器,而少见便捷规范的html生成器,HtmlRender则提供了一个简单的、规范的html内容生成器。用c实现html内容生成器,…

vue3项目实现全局国际化

本文主要梳理vue3项目实现全项目格式化,例如在我前面文章使用若依创建vue3的项目中,地址:若依搭建vue3项目在导航栏中切换,页面中所有的组件的默认语言随之切换,使用的组件库依旧是element-plus,搭配vue-i1…

Java应用压测工具JMeter

目录 1、下载JMeter 2、配置环境变量 3、配置语音 4、使用 1、下载JMeter Apache JMeter - Apache JMeter™ 千万别下载这个,会报错、 千万别下载这个,会报错、 千万别下载这个,会报错 下载这个,失败多下载几次 2、配置环…

中国数据中心服务器CPU行业发展概述

2024中国服务器CPU行业概览:信创带动服务器CPU国产化 AA体系是一种基于ARM指令系统和Android操作系统的体系结构,主要用于移动设备。与Wintel体系不同,AA体系中CPU厂商对芯片或系统厂商进行指令系统或IP核授权,操作系统厂商提供基…

【乐企-业务篇】开票前置校验服务-规则链服务接口实现(纳税人基本信息)

开票前置校验服务-规则链服务接口实现(纳税人基本信息) 代码 import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3

C++使用Socket编程实现一个简单的HTTP服务器

C使用Socket编程实现一个简单的HTTP服务器 在现代网络编程中,HTTP服务器是一个非常重要的组件。通过实现一个简单的HTTP服务器,可以帮助我们更好地理解网络通信的基本原理。本文将详细介绍如何使用C进行Socket编程,实现一个简单的HTTP服务器…

Cartographer源码理解

一、前言 最近一个半月,利用空余时间对Cartographer源码进行了简单的阅读,在这里做了个简单梳理,和大家分享交流。 cartographer源码量其实是有点大的,逐行逐句去解释实在是有心无力了,而且已经有大佬做了类似的事情…