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 具有过期时间控制等操作,能够更好的应用于各个场景,并易于调用。