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

介绍

在 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.xdnf.cn/news/1537187.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Linux基础---07文件传输及解决yum安装失效的方法

Linux文件传输地图如下,先选取你所需的场景,若你是需要Linux和Linux之间传输文件就查看SCP工具即可。 一.下载网站文件 前提是有网: 检查网络是否畅通命令:ping www.baidu.com,若有持续的返回值就说明网络畅通。Ctr…

新手必学:如何从github下载项目正确配置环境和运行起来!

第一步:先去github找到你需要的代码,然后点击code进行下载,下载时可以选择下载压缩包! 第二步:解压后将项目放入pycharm中,如果你使用了anaconda的虚拟环境,那就将pycharm的编译环境改为你自己创…

【C++前后缀分解 动态规划】2100. 适合野炊的日子|1702

本文涉及知道点 C前后缀分解 C动态规划 LeetCode2100. 适合野炊的日子 你和朋友们准备去野炊。给你一个下标从 0 开始的整数数组 security ,其中 security[i] 是第 i 天的建议出行指数。日子从 0 开始编号。同时给你一个整数 time 。 如果第 i 天满足以下所有条件…

健身器材识别系统源码分享

健身器材识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

【JavaSE】--数组的定义与使用

文章目录 1. 数组的基本概念1.1 什么是数组1.2 数组的创建及初始化1.2.1 数组的创建1.2.2 数组的初始化 1.3 数组的使用1.3.1 数组中元素访问1.3.2 遍历数组 2. 数组是引用类型2.1 初识JVM的内存分布2.2 基本类型变量与引用类型变量的区别2.3 再谈引用变量2.4 认识null 3. 数组…

腾讯地图SDK Android版开发 11 覆盖物示例 4 线

腾讯地图SDK Android版开发 11 覆盖物示例 4 线 前言线的属性介绍ColorType 和 LineTypeColorTypeLineType 与颜色有关的属性填充色和线宽描边颜色和描边的宽度分段颜色渐变色擦除颜色 与纹理相关属性内置纹理自定义颜色纹理线上叠加纹理 虚线 界面布局MapPolyline类常量成员变…

redis简单使用与安装

redis redis 是什么 Redis 是一个开源的,使用 C 语言编写的,支持网络交互的,内存中的Key-Value 数据结构存储系统,支持多种语言,它可以用作数据库、缓存和消息中间件。 一、存储系统特性 内存存储与持久化 Redis 主要将数据存储在内存中,这…

不善言辞的程序员适合做项目经理吗?

项目经理的角色需要承担多重任务,包括团队协调、资源调配、风险管理、沟通与汇报等。因此,很多人认为项目经理需要较强的沟通能力和外向性格。然而,不善言辞的程序员是否适合这一职位,实际上取决于多个因素。以下从不同角度进行分…

嵌入式开发—CAN通信协议详解与应用(上)

文章目录 1.CAN简介CAN协议的诞生背景CAN协议的发展历程CAN协议的影响CAN通信的主要特点 2.CAN数据帧的帧格式CAN标准数据帧的帧格式CAN标准数据帧的帧格式结构图CAN扩展帧的帧格式CAN遥控帧的帧格式CAN错误帧的帧格式 3.CAN数据传输中的位填充位填充的概念位填充的作用位填充的…

5款录屏软件电脑版,哪一款更适合你?

身边不少做行政的小伙伴,经常需要制作一些培训视频、会议记录或是演示文稿。这就要求他们必须掌握一款好用的录屏软件。作为一个经常搜索各种办公软件的人,今天,我就来分享一下我使用过的五款录屏软件在录制电脑屏幕时的表现。 1、福昕录屏大…

红外成像人员检测数据集

红外成像人员检测数据集YOLO格式介绍 红外成像技术是一种非接触式的温度测量技术,通过探测物体发出的红外辐射来生成图像。这种技术在人员检测领域有着广泛的应用,尤其是在夜间监控、安全防范、医疗诊断、环境监测等方面。本文将详细介绍一个红外成像人…

anaconda下载安装教程

anaconda是python的包管理器,通过它来安装python库比较方便快捷,可以使用conda或者pip命令进行安装。 微智启软件工作室最常用的是Anaconda3-2021.11-Windows-x86_64.exe这一个版本,当然如果你使用其他版本也可以,其他版本特别是最…

android设置实现广告倒计时功能

文章目录 CountDownTimer基本使用增加基础BaseActivity增加固定活动 在Android中,CountDownTimer 是一个用于计时的类,它允许你在指定的时间段内执行某些操作。通常用于倒计时功能,例如显示一个倒计时进度条或者在倒计时结束后执行某个动作。…

【吊打面试官系列-MySQL面试题】简述在 MySQL 数据库中 MyISAM 和 InnoDB 的区别?

大家好,我是锋哥。今天分享关于【简述在 MySQL 数据库中 MyISAM 和 InnoDB 的区别?】面试题,希望对大家有帮助; 简述在 MySQL 数据库中 MyISAM 和 InnoDB 的区别? MyISAM: 不支持事务,但是每次查…

1863. 找出所有子集的异或总和再求和

目录 一:题目: 二:代码: 三:结果: 一:题目: 一个数组的 异或总和 定义为数组中所有元素按位 XOR 的结果;如果数组为 空 ,则异或总和为 0 。 例如&#x…

Java高级Day43-类加载

117.类加载 静态和动态加载 反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强 动态加载:运行时加载需要的类,如果运行时不用该类…

php部署到apach服务器上遇到的问题

php部署到apach服务器上遇到的问题 问题描述解决方案 问题描述 参考环境搭建文章: 链接: Windows本地搭建PHP环境 第六步的第二条中出现无法正常访问http://localhost:8888/index.php的情况。 解决方案 思路:之前的http://localhost:8888是可以正常访…

任嘉伦新剧《流水迢迢》:卫昭多层人设引关注

近日,由晋江文学城同名小说改编的武侠古装爱情传奇剧《流水迢迢》即将开播,这部由任嘉伦主演的新剧,在原著和阵容的双双加持下热度直线上涨,宣传阶段就已备受网友期待,预约人数截止9月13日已达到206万,上升…

C/C++笔记

C/CPP笔记 杂记 struct msg_train和typedef struct msg_train 大小不一样 cstdio和stdio #include <stdio.h>int main() {printf("Hello, World!\n");return 0; } #include <cstdio>int main() {std::printf("Hello, World!\n");return 0; } 命…

【例题】lanqiao301 实现基数排序

输入输出样例 输入 6 7 1 4 8 5 2输出 1 2 4 5 7 8解题思路 翻译&#xff1a;就是从个位到十位、……比较大小。 代码 nint(input()) alist(map(int,input().split())) a.sort() print( .join(map(str,a)))