- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章Redis中的动态字符串学习教程由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
sds 的用途 Sds 在 Redis 中的主要作用有以下两个:
实现字符串对象(StringObject); 在 Redis 程序内部用作 char* 类型的替代品; 以下两个小节分别对这两种用途进行介绍.
实现字符串对象 。
Redis 是一个键值对数据库(key-value DB), 数据库的值可以是字符串、集合、列表等多种类型的对象, 而数据库的键则总是字符串对象.
对于那些包含字符串值的字符串对象来说, 每个字符串对象都包含一个 sds 值.
“包含字符串值的字符串对象”,这种说法初听上去可能会有点奇怪, 但是在 Redis 中, 一个字符串对象除了可以保存字符串值之外, 还可以保存 long 类型的值, 所以为了严谨起见, 这里需要强调一下: 当字符串对象保存的是字符串时, 它包含的才是 sds 值, 否则的话, 它就是一个 long 类型的值。 举个例子, 以下命令创建了一个新的数据库键值对, 这个键值对的键和值都是字符串对象, 它们都包含一个 sds 值:
1
2
3
4
5
|
redis> SET book "Mastering C++ in 21 days"
OK
redis> GET book
"Mastering C++ in 21 days"
|
以下命令创建了另一个键值对, 它的键是字符串对象, 而值则是一个集合对象:
1
2
3
4
5
6
7
|
redis> SADD nosql
"Redis"
"MongoDB"
"Neo4j"
(integer) 3
redis> SMEMBERS nosql
1)
"Neo4j"
2)
"Redis"
3)
"MongoDB"
|
用 sds 取代 C 默认的 char* 类型 。
因为 char* 类型的功能单一, 抽象层次低, 并且不能高效地支持一些 Redis 常用的操作(比如追加操作和长度计算操作), 所以在 Redis 程序内部, 绝大部分情况下都会使用 sds 而不是 char* 来表示字符串.
性能问题在稍后介绍 sds 定义的时候就会说到, 因为我们还没有了解过 Redis 的其他功能模块, 所以也没办法详细地举例说那里用到了 sds , 不过在后面的章节中, 我们会经常看到其他模块(几乎每一个)都用到了 sds 类型值.
目前来说, 只要记住这个事实即可: 在 Redis 中, 客户端传入服务器的协议内容、 aof 缓存、 返回给客户端的回复, 等等, 这些重要的内容都是由 sds 类型来保存的.
redis 中的字符串 在 C 语言中,字符串可以用一个 \0 结尾的 char 数组来表示.
比如说, hello world 在 C 语言中就可以表示为 "hello world\0" .
这种简单的字符串表示,在大多数情况下都能满足要求,但是,它并不能高效地支持长度计算和追加(append)这两种操作:
每次计算字符串长度(strlen(s))的复杂度为 θ(N) 。 对字符串进行 N 次追加,必定需要对字符串进行 N 次内存重分配(realloc)。 在 Redis 内部, 字符串的追加和长度计算很常见, 而 APPEND 和 STRLEN 更是这两种操作,在 Redis 命令中的直接映射, 这两个简单的操作不应该成为性能的瓶颈.
另外, Redis 除了处理 C 字符串之外, 还需要处理单纯的字节数组, 以及服务器协议等内容, 所以为了方便起见, Redis 的字符串表示还应该是二进制安全的: 程序不应对字符串里面保存的数据做任何假设, 数据可以是以 \0 结尾的 C 字符串, 也可以是单纯的字节数组, 或者其他格式的数据.
考虑到这两个原因, Redis 使用 sds 类型替换了 C 语言的默认字符串表示: sds 既可高效地实现追加和长度计算, 同时是二进制安全的.
sds 的实现 。
在前面的内容中, 我们一直将 sds 作为一种抽象数据结构来说明, 实际上, 它的实现由以下两部分组成:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
typedef
char
*sds;
struct
sdshdr {
// buf 已占用长度
int
len;
// buf 剩余可用长度
int
free
;
// 实际保存字符串数据的地方
char
buf[];
};
|
其中,类型 sds 是 char * 的别名(alias),而结构 sdshdr 则保存了 len 、 free 和 buf 三个属性.
作为例子,以下是新创建的,同样保存 hello world 字符串的 sdshdr 结构:
1
2
3
4
5
|
struct
sdshdr {
len = 11;
free
= 0;
buf =
"hello world\0"
;
// buf 的实际长度为 len + 1
};
|
通过 len 属性, sdshdr 可以实现复杂度为 θ(1) 的长度计算操作.
另一方面, 通过对 buf 分配一些额外的空间, 并使用 free 记录未使用空间的大小, sdshdr 可以让执行追加操作所需的内存重分配次数大大减少, 下一节我们就会来详细讨论这一点.
当然, sds 也对操作的正确实现提出了要求 —— 所有处理 sdshdr 的函数,都必须正确地更新 len 和 free 属性,否则就会造成 bug .
数据类型定义 与sds实现有关的数据类型有两个,一个是 sds:
1
2
|
// 字符串类型的别名
typedef
char
*sds;
|
另一个是 sdshdr:
1
2
3
4
5
6
7
8
9
|
// 持有sds的结构
struct
sdshdr {
// buf中已经被使用的字符串空间数量
int
len;
// buf中预留字符串的空间数量
int
free
;
// 实际存储字符串的地方
char
buf[];
};
|
其中,sds只是字符串数组类型char*的别名,而sdshdr用于持有和保存sds的信息 。
比如,sdshdr.len可以用于在O(1)的复杂度下获取sdshdr.buf中存储的字符串的实际长度,而sdshdr.free则用于保存sdshdr.buf中还有多少预留空间 。
(这里sdshdr应该是sds handler的缩写) 。
将sdshdr用作sds sds模块对sdshdr结构使用了一点小技巧:通过指针运算,它使得sdshdr结构可以像sds类型一样被传值和处理,并在需要的时候恢复成sdshdr类型 。
通过下面的函数定义来理解这个技巧 。
sdsnewlen 函数返回一个新的sds值,实际上,它创建的却是一个sdshdr结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
sds sdsnewlen(
const
void
*init,
size_t
initlen)
{
struct
sdshdr *sh;
if
(init) {
// 创建
sh =
malloc
(
sizeof
(
struct
sdshdr) + initlen + 1);
}
else
{
// 重分配
sh =
calloc
(1,
sizeof
(
struct
sdshdr) + initlen + 1);
}
if
(sh == NULL)
return
NULL;
sh->len = initlen;
sh->
free
= 0;
// 刚开始free为0
if
(initlen && init) {
memcpy
(sh->buf, init, initlen);
}
sh->buf[initlen] =
'\0'
;
// 只返回sh->buf这个字符串部分
return
(
char
*)sh->buf;
}
|
通过使用变量持有一个sds的值,在遇到那些只处理sds值本身的函数时,可以直接将sds传给它们。比如说,sdstoupper 函数就是其中的一个例子:
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
static
inline
size_t
sdslen(
const
sds s)
{
// 从sds中计算出相应的sdshdr结构
struct
sdshdr *sh = (
void
*)(s - (
sizeof
(
struct
sdshdr)));
return
sh->len;
}
void
sdstoupper(sds s)
{
int
len = sdslen(s), j;
for
(j = 0; j < len; j ++)
s[j] =
toupper
(s[j]);
}
|
这里有一个技巧,通过指针运算,可以从sds值中计算出相应的sdshdr结构:
sds虽然是指向char *的buf(ps:并且空数组不占用内存空间,数组名即为内存地址),但是分配的时候是分配sizeof(struct sdshdr) + initlen + 1的,通过sds - sizeof(struct sdshdr)可以计算出struct sdshdr的首地址,从而可以得到len和free的信息 。
sdsavail 函数就是使用这中技巧的一个例子:
。
1
2
3
4
5
6
|
static
inline
size_t
sdsavail(
const
sds s)
{
struct
sdshdr *sh = (
void
*)(s - (
sizeof
(
struct
sdshdr)));
return
sh->
free
;
}
|
内存分配函数实现 和Reids 的实现决策相关的函数是 sdsMakeRoomFor :
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
sds sdsMakeRoomFor(sds s,
size_t
addlen)
{
struct
sdshdr *sh, *newsh;
size_t
free
= sdsavail(s);
size_t
len, newlen;
// 预留空间可以满足本地拼接
if
(
free
>= addlen)
return
s;
len = sdslen(s);
sh = (
void
*)(s - (
sizeof
(
struct
sdshdr)));
// 设置新sds的字符串长度
// 这个长度比完成本次拼接实际所需的长度要大
// 通过预留空间优化下次拼接操作
newlen = (len + addlen);
if
(newlen < 1024 * 1024)
newlen *= 2;
else
newlen += 1024;
// 重新分配sdshdr
newsh =
realloc
(sh,
sizeof
(
struct
sdshdr) + newlen + 1);
if
(newsh == NULL)
return
NULL;
newsh->
free
= newlen - len;
// 只返回字符串部分
return
newsh->buf;
}
|
这种内存分配策略表明,在对sds 值进行扩展(expand)时,总会预留额外的空间,通过花费更多的内存,减少了对内存进行重分配(reallocate)的次数,并优化下次扩展操作的处理速度 。
再把redis的如果实现对sds字符串扩展的方法贴一下,很不错的思路:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
/**
* 按长度len扩展sds,并将t拼接到sds的末尾
*/
sds sdscatlen(sds s,
const
void
*t,
size_t
len)
{
struct
sdshdr *sh;
size_t
curlen = sdslen(s);
// O(N)
s = sdsMakeRoomFor(s, len);
if
(s == NULL)
return
NULL;
// 复制
memcpy
(s + curlen, t, len);
// 更新len和free属性
sh = (
void
*)(s - (
sizeof
(
struct
sdshdr)));
sh->len = curlen + len;
sh->
free
= sh->
free
- len;
// 终结符
s[curlen + len] =
'\0'
;
return
s;
}
/**
* 将一个char数组拼接到sds 末尾
*/
sds sdscat(sds s,
const
char
*t)
{
return
sdscatlen(s, t,
strlen
(t));
}
|
最后此篇关于Redis中的动态字符串学习教程的文章就讲到这里了,如果你想了解更多关于Redis中的动态字符串学习教程的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我有一个关于 Redis Pubsub 的练习,如下所示: 如果发布者发布消息但订阅者没有收到服务器崩溃。订阅者如何在重启服务器时收到该消息? 请帮帮我,谢谢! 最佳答案 在这种情况下,消息将永远消失
我们正在使用 Service Stack 的 RedisClient 的 BlockingDequeue 来保存一些数据,直到它可以被处理。调用代码看起来像 using (var client =
我有一个 Redis 服务器和多个 Redis 客户端。每个 Redis 客户端都是一个 WebSocket+HTTP 服务器,其中包括管理 WebSocket 连接。这些 WebSocket+HTT
我有多个 Redis 实例。我使用不同的端口创建了一个集群。现在我想将数据从预先存在的 redis 实例传输到集群。我知道如何将数据从一个实例传输到集群,但是当实例多于一个时,我无法做到这一点。 最佳
配置:三个redis集群分区,跨三组一主一从。当 Master 宕机时,Lettuce 会立即检测到中断并开始重试。但是,Lettuce 没有检测到关联的 slave 已经将自己提升为 master
我想根据从指定集合中检索这些键来删除 Redis 键(及其数据集),例如: HMSET id:1 password 123 category milk HMSET id:2 password 456
我正在编写一个机器人(其中包含要禁用的命令列表),用于监视 Redis。它通过执行禁用命令,例如 (rename-command ZADD "")当我重新启动我的机器人时,如果要禁用的命令列表发生变化
我的任务是为大量听众使用发布/订阅。这是来自 docs 的订阅的简化示例: r = redis.StrictRedis(...) p = r.pubsub() p.subscribe('my-firs
我一直在阅读有关使用 Redis 哨兵进行故障转移的内容。我打算有1个master+1个slave,如果master宕机超过1分钟,就把slave变成master。我知道这在 Sentinel 中是
与仅使用常规 Redis 和创建分片相比,使用 Redis 集群有哪些优势? 在我看来,Redis Cluster 更注重数据安全(让主从架构解决故障)。 最佳答案 我认为当您需要在不丢失任何数据的情
由于 Redis 以被动和主动方式使 key 过期, 有没有办法得到一个 key ,即使它的过期时间已过 (但 在 Redis 中仍然存在 )? 最佳答案 DEBUG OBJECT myKey 将返回
我想用redis lua来实现monitor命令,而不是redis-cli monitor。但我不知道怎么办。 redis.call('monitor') 不起作用。 最佳答案 您不能从 Redis
我读过 https://github.com/redisson/redisson 我发现有几个 Redis 复制设置(包括对 AWS ElastiCache 和 Azure Redis 缓存的支持)
Microsoft.AspNet.SignalR.Redis 和 StackExchange.Redis.Extensions.Core 在同一个项目中使用。前者需要StackExchange.Red
1. 认识 Redis Redis(Remote Dictionary Server)远程词典服务器,是一个基于内存的键值对型 NoSQL 数据库。 特征: 键值(key-value)型,value
1. Redis 数据结构介绍 Redis 是一个 key-value 的数据库,key 一般是 String 类型,但 value 类型多种多样,下面就举了几个例子: value 类型 示例 Str
1. 什么是缓存 缓存(Cache) 就是数据交换的缓冲区,是存贮数据的临时地方,一般读写性能较高。 缓存的作用: 降低后端负载 提高读写效率,降低响应时间 缓存的成本: 数据一致性成本 代码维护成本
我有一份记录 list 。对于我的每条记录,我都需要进行一些繁重的计算,因为我要在Redis中创建反向索引。为了达到到达记录,需要在管道中执行多个redis命令(sadd为100 s + set为1
我有一个三节点Redis和3节点哨兵,一切正常,所有主服务器和从属服务器都经过验证,并且哨兵配置文件已与所有Redis和哨兵节点一起更新,但是问题是当Redis主服务器关闭并且哨兵希望选举失败者时再次
我正在尝试计算Redis中存储的消息之间的响应时间。但是我不知道该怎么做。 首先,我必须像这样存储chat_messages的时间流 ZADD conversation:CONVERSATION_ID
我是一名优秀的程序员,十分优秀!