redis 学习笔记-string 原理

1. 前言

在分布式缓存中,Redis 作为一个高性能的键值存储系统,被广泛地应用于各种场景。而字符串(string)作为 Redis 中最基础的数据结构之一,是使用最为频繁、最为常见的一种数据类型,本文将介绍 Redis string 的相关原理和使用方法。

2. Redis String 的定义

Redis 中,string 是二进制安全的,它们的最大长度是 512MB。Redis 中每一个 key 都对应一个 value,因此当一个 key 关联一个 string 类型的 value 时,就可以使用相关的命令对这个 value 进行操作了。

2.1 命令格式

在 Redis 中,我们可以使用以下命令对 string 进行操作:

SET key value [EX seconds] [PX milliseconds] [NX|XX]

GET key

GETRANGE key start end

2.2 SET 命令

SET 命令可以将 key 与 value 进行绑定,如果 key 已经存在,则会覆盖原来的 value。同时,可以对 key 进行过期时间的设置,将 key 明确设置为永久,或者在指定的秒数或毫秒数之后过期。

SET key value [EX seconds] [PX milliseconds] [NX|XX]

例如,将 key 对应的 value 设置为 Hello Redis,过期时间为 10 秒:

SET key "Hello Redis" EX 10

2.3 GET 命令

GET 命令可以用于获取指定 key 的 value 值。

GET key

例如,获取 key 对应的 value:

GET key

2.4 GETRANGE 命令

GETRANGE 命令可以用于获取指定字符串区间的值,对应于 C 语言的 sprintf 函数,可以返回指定字符串的子集。

GETRANGE key start end

例如,获取 key 对应的 value 中,从第 3 个字符开始的 4 个字符对应的子串:

GETRANGE key 2 5

3. Redis String 的实现原理

相对于其他数据类型,Redis String 的实现操作相对简单。Redis 将 String value 存储在一个内部数据结构 redisObject 中,redisObject 中的类型为 OBJ_STRING,其中存储着字符数组和字符串长度信息。

struct redisObject {

// 类型信息

unsigned type:4;

// 编码信息

unsigned encoding:4;

// LRU 时间

unsigned lru:LRU_BITS;

// 引用计数

int refcount;

// 实际值

void *ptr;

};

String 在 Redis 中的实现,主要考虑以下两个因素:

在内存中划定一定的空间,并存储字符数组和字符串长度信息。

支持字符数组的动态增长。

3.1 内存分配策略

Redis String 类型底层实现使用的是动态数组。为了高效利用内存空间,Redis 设计了一种 sds (Simple Dynamic String) 字符串,支持动态分配内存并控制分配的内存大小。sds 字符串主要有以下优点:

与 C 字符串相比,sds 更加安全,内存分配和释放完全由 Redis 控制。

当字符串长度小于 1MB 时,采取预先分配的策略,能够减少字符串扩容(realloc)时的内存分配和数据移动次数。

采用惰性空间释放(realloc 策略),在 sds 的查找、清空等操作中,能够减少系统对内存的调用,提高整个 Redis 的性能。

3.2 动态数组实现

动态数组是一种在内存中自动分配空间的数组,它可以随时调整大小。Redis 中的 String 类别,采用了类似于 C++ STL 中的 vector 来实现字符串动态增长。

Redis 为字符数组的内存分配使用了类似于 C++ STL 中的 vector,也就是指数级增长。在初始化时,字符数组的长度为 0,分配的内存空间为 16 字节。当长度增长时,每当字符数组长度增加一倍时,Redis 则会动态地分配一块两倍大的额外内存,将字符数组的原始内容复制到新内存之中,随后释放原有的内存空间。

#define SDS_HDR_SIZE sizeof(struct sdshdr)

#define SDS_MAX_PREALLOC (1024*1024)

struct sdshdr {

// 字符串长度

int len;

// 申请的空间大小

int free;

// 字符数组,以 \0 结尾

char buf[];

};

通过如上 sdshdr 结构体可以了解到,实现位置包含三个部分:字符串长度、申请的空间大小以及字符数组。其中 free 记录字符数组的未使用空间。在 Redis 内部,字符串是以 sdshdr 形式存储的,Redis 通过 sdshdr 的字符串长度,知道了这个 String 的长度和占用空间大小,从而对其进行操作,最大长度不超过 512MB 。

4. Redis String 的使用方法

Redis String 的操作具有对应的命令,命令能够与字符串的常用操作相对应。

4.1 命令简介

SET:设置 key 对应的 value。

GET:获取指定 key 的 value。

GETRANGE:获取指定字符串区间的值。

MSET:一次设置多个 key 对应的 value。

MGET:一次获取多个指定 key 的 value。

APPEND:将指定字符串附加到原有字符串之后。

INCRBY:将指定 key 所存储的值加上给定的增量值。当然,也支持它们的 DECRBY 命令:将值减水。

4.2 实例分析

以下调用 Redis String 存储方式中的原理与命令作用等内容,是从下面这个例子分析过来的。在此例中,使用 Node.js 调用 Redis Client 进行操作。

4.2.1 Redis String 的字符串拼接

如果使用的是 SET 命令更新 String 的 value,那么这个 value 将会被覆盖。但是,Redis 提供了 APPEND 命令来实现字符串拼接,即在字符串尾部追加 value。

redisClient.set('name', 'Tom', function(err, reply) {

// 如果 name 键不存在,则设置会设置成功

redisClient.append('name', ' Lu', function(err, reply) {

// reply 为追加后的 name 值 => 'Tom Lu'

});

});

4.2.2 Redis String 的数值类型转换

利用 INCRBY 和 DECRBY 命令,可以对既定的数字类型字符串,进行数值型操作(自增、自减)。

redisClient.set('pageView', 1, function(err, reply) {

incrPageView();

});

function incrPageView() {

redisClient.incrby('pageView', 1, function(err, reply) {

// reply 为自增后的 pageView 值 => '2'

});

}

4.2.3 Redis String 的过期时间控制

使用 SET 命令时,可以通过 EX 参数或者 PX 参数来指定过期时间,单位分别为秒和毫秒。在 Redis 中,能够通过 TTL 命令来获取 key 的过期时间。

redisClient.set('name', 'Tom Lu', 'EX', 10, function(err, reply) {

redisClient.ttl('name', function(err, reply) {

// reply 为 name 过期的剩余时间,单位为秒

});

});

总结

Redis String 是 Redis 中最基础,最为常见的一种数据类型,主要用于在内存中存储和操作二进制安全的数据。Redis String 的实现较为简单,通过内存中的定长数组存储字符,兼顾了性能和安全性;通过动态数组实现了字符串的动态增长,提供了更为灵活的存储空间。此外,Redis String 具有过期时间控制等操作,能够更好的应用于各个场景,并易于调用。

数据库标签