【Redis6快速深入学习04】Redis字符串(String)的使用和原理

2023-11-29 14:04

本文主要是介绍【Redis6快速深入学习04】Redis字符串(String)的使用和原理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Redis字符串(String)

4.1 Redis 中 String 的简介

String 是Redis五种最基本的类型之一,在使用时可以理解成与Memcached一模一样的类型,一个key对应一个value。

String 类型是二进制安全的。意味着 Redis 的 string 可以包含任何数据。比如 jpg 图片或者序列化的对象。

String 类型是 Redis 最基本的数据类型,一个 Redis 中字符串 value 最多可以是 512M

4.2 常用命令

  1. set <key> <value>: 添加键值对

  2. get <key>:查询对应键值

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> get k1
"v1"
  1. append <key> <value> 将给定的 <value> 追加到原值的末尾
127.0.0.1:6379> append k1 100
(integer) 5
127.0.0.1:6379> get k1
"v1100"
  1. strlen <key>:获得值的长度
127.0.0.1:6379> strlen k1
(integer) 5
  1. setnx <key> <value>:只有在 key 不存在时,设置 key 的值,而key存在时,不对key对应的value值作修改,之前的 set 命令则会覆盖原来的值
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> keys *
1) "k2"
2) "k1"
127.0.0.1:6379> set k1 v110
OK
127.0.0.1:6379> get k1
"v110"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> setnx k2 v210
(integer) 0
127.0.0.1:6379> get k2
"v2"
  1. incr <key>:将 key 中储存的数字值增1,只能对数字值操作,如果为空,新增值为1
127.0.0.1:6379> set k3 1
OK
127.0.0.1:6379> incr k3
(integer) 2
127.0.0.1:6379> get k3
"2"
127.0.0.1:6379> incr k3
(integer) 3
127.0.0.1:6379> incr k3
(integer) 4
127.0.0.1:6379> get k3
"4"
  1. decr <key>:将 key 中储存的数字值减1,只能对数字值操作,如果为空,新增值为-1
127.0.0.1:6379> decr k3
(integer) 3
127.0.0.1:6379> get k3
"3"
127.0.0.1:6379> decr k3
(integer) 2
127.0.0.1:6379> decr k3
(integer) 1
127.0.0.1:6379> get k3
"1"
  1. incrby/decrby <key> <步长>将 key 中储存的数字值增减。自定义步长。
127.0.0.1:6379> incrby k3 5
(integer) 6
127.0.0.1:6379> incrby k3 5
(integer) 11
127.0.0.1:6379> incrby k3 5
(integer) 16
127.0.0.1:6379> get k3
"16"
127.0.0.1:6379> decrby k3 3
(integer) 13
127.0.0.1:6379> decrby k3 3
(integer) 10
127.0.0.1:6379> get k3
"10"

注意:incr key 是一种原子操作,所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。

(1)在单线程中, 能够在单条指令中完成的操作都可以认为是"原子操作",因为中断只能发生于指令之间。

(2)在多线程中,不能被其它进程(线程)打断的操作就叫原子操作。

Redis单命令的原子性主要得益于Redis的单线程。

案例:

java中的i++是否是原子操作?不是,java是多线程的

i = 0;两个线程分别对i进行++100次,值是多少? 2~200

i++ 的步骤(1)取值,(2)++,(3)赋值

Thread-0Thread-1
i=0
……
i++
i=99
i++
i=1
i=1
i++
i=100
i++
i=2

mset <key1> <value1> <key2> <value2> .....:连续设置多个键值对

mget <key1> <key2> <key3> .....:同时获取一个或多个 value

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"

msetnx <key1> <value1> <key2> <value2> ..... :同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。且 msetnx 有一个失败其他都是注入无效的

127.0.0.1:6379> msetnx k1 v110 k4 v110
(integer) 0
127.0.0.1:6379> mget k1 k2 k3 k4
1) "v1"
2) "v2"
3) "v3"
4) (nil)
127.0.0.1:6379> msetnx k1 v110
(integer) 0
127.0.0.1:6379> mget k1 k2 k3 k4
1) "v1"
2) "v2"
3) "v3"
4) (nil)
127.0.0.1:6379> msetnx k4 v110
(integer) 1
127.0.0.1:6379> mget k1 k2 k3 k4
1) "v1"
2) "v2"
3) "v3"
4) "v110"

getrange <key> <起始位置> <结束位置> :获得值的范围,类似java中的substring,前包,后包

127.0.0.1:6379> getrange k4 0 -1
"v110"
127.0.0.1:6379> getrange k4 0 2
"v11"

setrange <key> <起始位置> <value>:用 覆写所储存的字符串值,从<起始位置>开始(索引从0开始)。

127.0.0.1:6379> getrange k4 0 -1
"v110"
127.0.0.1:6379> setrange k4 1 000
(integer) 4
127.0.0.1:6379> get k4
"v000"

setex <key> <过期时间> <value>:设置键值的同时,设置过期时间,单位秒。

127.0.0.1:6379> setex k5 40 v5
OK
127.0.0.1:6379> ttl k5
(integer) 35
127.0.0.1:6379> ttl k5
(integer) 32
127.0.0.1:6379> ttl k5
(integer) 1
127.0.0.1:6379> ttl k5
(integer) -2

getset <key> <value>:以新换旧,设置了新值同时获得旧值。

127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> getset k1 v111
"v1"
127.0.0.1:6379> get k1
"v111"

4.3 底层逻辑

String类型的数据结构存储方式有三种int、raw、embstr。那么这三种存储方式有什么区别呢?

4.3.1 int

Redis中规定假如存储的是 整数型值,比如 set k1 1 这样的类型,就会使用 int的存储方式进行存储,在redisObject的ptr属性中就会保存该值。

4.3.2 SDS

结论先行:假如存储的字符串是一个字符串值并且长度大于44个字节就会使用 SDS(simple dynamic string) 方式进行存储,并且encoding设置为raw;若是字符串长度小于等于44个字节就会将encoding改为emdstr来保存字符串。

127.0.0.1:6379> set k1 qwertyuiopqwertyuiopqwertyuiopqwertyuiopqwert
OK
127.0.0.1:6379> debug object k1
Value at:0x7f70b1ca0dc0 refcount:1 encoding:raw serializedlength:21 lru:16311423 lru_seconds_idle:4
127.0.0.1:6379> set k2 qwertyuiopqwertyuiopqwertyuiopqwertyuiopqwer
OK
127.0.0.1:6379> debug object k2
Value at:0x7f70b1c22d80 refcount:1 encoding:embstr serializedlength:21 lru:16311924 lru_seconds_idle:2

k1 和 k2 只差了一个字符,存储形式 encoding 就发生了变化。对于较长的k1,其 encoding 为 raw ;对于较短的 k2,其 encoding 为 embstr。

在了解存储格式的区别之前,首先了解下RedisObject结构体。也对上面的结构体做解释。

所有的 Redis 对象都有一个 Redis 对象头结构体

struct RedisObject { int4 type; // 4bits  类型int4 encoding; // 4bits 存储格式int24 lru; // 24bits 记录LRU信息int32 refcount; // 4bytes void *ptr; // 8bytes,64-bit system 
} robj;

不同的对象具有不同的类型 type ,占4个bit。

同一个类型的 type 会有不同的存储形式 encoding,也占4个bit。

为了记录对象的 LRU 信息,使用了 24 个 bit 的 lru 来记录 LRU 信息。

每个对象都有个引用计数 refcount,当引用计数为零时,对象就会被销毁,内存被回收。

ptr 指针将指向对象内容 (body) 的具体存储位置。

所以,一个 RedisObject 对象头共需要占据 16 字节的存储空间。

再看一下RedisObject的10种存储格式——encoding

//这两个宏定义申明是在server.h文件中
#define OBJ_ENCODING_RAW 0     /* Raw representation */
#define OBJ_ENCODING_INT 1     /* Encoded as integer */
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */

而Redis 的字符串共有两种存储方式,在长度特别短时,使用 emb 形式存储 (embedded),当长度超过 44 时,使用 raw 形式存储。那么为什么是 44 字节呢?

embstr 存储形式是这样一种存储形式,它将 RedisObject 对象头和 SDS 对象连续存在一起,使用 malloc 方法一次分配。

而 raw 存储形式不一样,它需要两次 malloc,两个对象头在内存地址上一般是不连续的。

在字符串比较小时,SDS 对象头的大小是capacity+3——SDS结构体的内存大小至少是 3。意味着分配一个字符串的最小空间占用为 19 字节 (16+3)。

如果总体超出了 64 字节,Redis 认为它是一个大字符串,不再使用 emdstr 形式存储,而该用 raw 形式。而64-19-结尾的**\0**,所以empstr只能容纳44字节。

4.3.3 SDS的扩容策略和扩容

SDS_MAX_PREALLOC的容量大小定义在sds.h文件中,默认是 1024 * 1024,也就是1MB。

//扩容分配策略
newlen = (len+addlen);
// 如果新长度小于最大预分配长度则分配扩容为2倍
// 如果新长度大于最大预分配长度则仅追加SDS_MAX_PREALLOC长度
if (newlen < SDS_MAX_PREALLOC)newlen *= 2;
elsenewlen += SDS_MAX_PREALLOC;

通过源代码可以看出,扩容策略是字符串在长度小于 SDS_MAX_PREALLOC 之前,扩容空间采用加倍策略,也就是保留 100% 的冗余空间。当长度超过 SDS_MAX_PREALLOC 之后,为了避免加倍后的冗余空间过大而导致浪费,每次扩容只会多分配 SDS_MAX_PREALLOC大小的冗余空间。

4.5 String类型应用(存储图片)

(1)首先要把上传得图片进行编码,这里写了一个工具类把图片处理成了Base64得编码形式,具体得实现代码如下:

/*** 将图片内容处理成Base64编码格式* @param file* @return*/
public static String encodeImg(MultipartFile file) {byte[] imgBytes = null;try {imgBytes = file.getBytes();} catch (IOException e) {e.printStackTrace();}BASE64Encoder encoder = new BASE64Encoder();return imgBytes==null?null:encoder.encode(imgBytes );
}

(2)第二步就是把处理后的图片字符串格式存储进Redis中,实现得代码如下所示:

/*** Redis存储图片* @param file* @return*/
public void uploadImageServiceImpl(MultipartFile image) {String imgId = UUID.randomUUID().toString();String imgStr= ImageUtils.encodeImg(image);redisUtils.set(imgId , imgStr);// 后续操作可以把imgId存进数据库对应的字段,如果需要从redis中取出,只要获取到这个字段后从redis中取出即可。
}

这篇关于【Redis6快速深入学习04】Redis字符串(String)的使用和原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/433117

相关文章

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

Redis中Stream详解及应用小结

《Redis中Stream详解及应用小结》RedisStreams是Redis5.0引入的新功能,提供了一种类似于传统消息队列的机制,但具有更高的灵活性和可扩展性,本文给大家介绍Redis中Strea... 目录1. Redis Stream 概述2. Redis Stream 的基本操作2.1. XADD

Spring StateMachine实现状态机使用示例详解

《SpringStateMachine实现状态机使用示例详解》本文介绍SpringStateMachine实现状态机的步骤,包括依赖导入、枚举定义、状态转移规则配置、上下文管理及服务调用示例,重点解... 目录什么是状态机使用示例什么是状态机状态机是计算机科学中的​​核心建模工具​​,用于描述对象在其生命

使用Python删除Excel中的行列和单元格示例详解

《使用Python删除Excel中的行列和单元格示例详解》在处理Excel数据时,删除不需要的行、列或单元格是一项常见且必要的操作,本文将使用Python脚本实现对Excel表格的高效自动化处理,感兴... 目录开发环境准备使用 python 删除 Excphpel 表格中的行删除特定行删除空白行删除含指定

深入理解Go语言中二维切片的使用

《深入理解Go语言中二维切片的使用》本文深入讲解了Go语言中二维切片的概念与应用,用于表示矩阵、表格等二维数据结构,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧... 目录引言二维切片的基本概念定义创建二维切片二维切片的操作访问元素修改元素遍历二维切片二维切片的动态调整追加行动态

prometheus如何使用pushgateway监控网路丢包

《prometheus如何使用pushgateway监控网路丢包》:本文主要介绍prometheus如何使用pushgateway监控网路丢包问题,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录监控网路丢包脚本数据图表总结监控网路丢包脚本[root@gtcq-gt-monitor-prome

Python通用唯一标识符模块uuid使用案例详解

《Python通用唯一标识符模块uuid使用案例详解》Pythonuuid模块用于生成128位全局唯一标识符,支持UUID1-5版本,适用于分布式系统、数据库主键等场景,需注意隐私、碰撞概率及存储优... 目录简介核心功能1. UUID版本2. UUID属性3. 命名空间使用场景1. 生成唯一标识符2. 数

SpringBoot中如何使用Assert进行断言校验

《SpringBoot中如何使用Assert进行断言校验》Java提供了内置的assert机制,而Spring框架也提供了更强大的Assert工具类来帮助开发者进行参数校验和状态检查,下... 目录前言一、Java 原生assert简介1.1 使用方式1.2 示例代码1.3 优缺点分析二、Spring Fr

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

java使用protobuf-maven-plugin的插件编译proto文件详解

《java使用protobuf-maven-plugin的插件编译proto文件详解》:本文主要介绍java使用protobuf-maven-plugin的插件编译proto文件,具有很好的参考价... 目录protobuf文件作为数据传输和存储的协议主要介绍在Java使用maven编译proto文件的插件