没有合适的资源?快使用搜索试试~ 我知道了~
温馨提示
内容概要:本篇文章深入研究了Redis源码的细节。从源码层面分析Redis的技术架构,详细介绍Redis的核心数据结构,例如字符串、整数集合、压缩列表及其对象类型,事务处理,Pub/Sub系统的运行方式及其实现细节。探讨Redis的两种持久化技术之一——RDB的工作机制。此外,还分析了Redis的主从复制技术和虚拟内存(VM)系统的特点,以便进一步理解和利用该系统的能力。 适用人群:适合希望深入了解和掌握Redis工作机制及开发经验的专业人士,尤其是具备一定编程基础的软件开发工程师和技术爱好者。 使用场景及目标:本资源适用于想要理解Redis数据结构优化手段及其高级特性的技术人员。有助于更好地进行故障排除和优化Redis应用程序。 使用说明:阅读此内容是为了获得对Redis设计及其实现的理解,从而提高解决实际工程问题的能力。建议对照相关代码和官方资料进行研究以验证所学知识。
资源推荐
资源详情
资源评论
《redis源码分析》系列分享专栏
简介
主要是从源码的级别分析redis,介绍reids中相关的数据结构,分析redis中订阅与发布,redis事务以及作为数据库的基本功能的介绍
文章
redis源码分析----序言
redis数据结构之字符串
redis数据结构之整数集合
redis数据结构之压缩列表
redis数据结构之对象
redis数据库之事务
redis数据库之订阅和发布
redis数据库之rdb持久化
redis数据库之主从复制
redis数据库之VM(虚拟内存)
redis数据结构之字符串
redis字符串相对比标准c的字串是二进制安全的,也就是说如果redis的字符串中包含'\0'字符的话,还是能计算出相应字符串的长度。下面来看下redis字符串的定义:
typedef char *sds;
struct sdshdr {
// buf 已占长度
int len;
// buf 剩余可用的长度
int free;
// 实际存放字符串的地方
char buf[];
};
看到这里也许有人会问,那个sds有什么作用?不用着急,先来看下生成字符串的函数:
sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;
sh = malloc(sizeof(struct sdshdr)+initlen+1);
#ifdef SDS_ABORT_ON_OOM
if (sh == NULL) sdsOomAbort();
#else
if (sh == NULL) return NULL;
#endif
sh->len = initlen;
sh->free = 0;
if (initlen) {
if (init) memcpy(sh->buf, init, initlen);
else memset(sh->buf,0,initlen);
}
sh->buf[initlen] = '\0';
return (char*)sh->buf;
}
sds sdsnew(const char *init) {
size_t initlen = (init == NULL) ? 0 : strlen(init);
return sdsnewlen(init, initlen);
}
redis生成字符串也是通过char*做模板的,而且从sdsnew中可以看到,计算长度的时候也用到了strlen,这样看来以某个字符串为模板新建字符串时,字符串并不是二进制安全的
。为什么要这样设计呢?
我们具体来看下sdsnewlen这个函数,redis其实额外使用sdshdr这个结构体来描述字符串的属性,也就是如上所述,字符串已有的长度,字符串空间还剩多少空间,以及字符串
真正的存放地址。而且sdsnewlen最后返回的也是char*。上面说到的sds这个的作用就是也在于此,代码风格问题吧。我们也可以通过sds来获取sdshdr这个结构体的地址,如下
:
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
得到了sdshdr我们就能获取到字符串相应的信息。
redis字符串的设计与nginx字符串设计是有点类似的。nginx字符串设计:
typedef struct {
size_t len;
u_char *data;
} ngx_str_t;
字符串这样设计的好处就是省去了调用strlen的消耗。这样性能也得到了一定的提升。
由于字符串比较简单,其余的函数,大家也能很轻易的看懂。
redis数据结构之整数集合
redis的整数集合实质上是动态的数组。reids的整数集合是可以根据整数的值,自动选择用什么长度来存储的。例如:如果插入的值可以用int16_t类型来保存,那所有的元素都可
以用int16_t类型来保存。所以可以看出保存的类型应该有这几种:
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))
其实还有种int8_t,但是redis在创建整数集合的时候就默认最小使用int16_t,所以int8_t就是一个保留的类型。下面来看下整数集合的定义:
typedef struct intset {
// 保存元素所使用的类型的长度
uint32_t encoding;
// 元素个数
uint32_t length;
// 保存元素的数组
int8_t contents[];
} intset;
encoding就是上面说的类型;
有人对对contents的类型为int8_t 有疑惑:下面截取redis设计和实现这本书里的一段话来进行解释:
contents 数组的int8_t 类型声明比较容易让人误解,实际上,intset 并不使用int8_t 类型来保存任何元素,结构中的这个类型声明只是作为一个占位符使用:在对contents 中的元素进行读取或者写入时
,程序并不是直接使用contents 来对元素进行索引,而是根据encoding的值,对contents 进行类型转换和指针运算,计算出元素在内存中的正确位置。在添加新元素,进行内存分配时,分配的容量也
是由encoding 的值决定。
下面用具体的代码来进行解释下:
static int64_t _intsetGetEncoded(intset *is, int pos, uint8_t enc) {
int64_t v64;
int32_t v32;
int16_t v16;
if (enc == INTSET_ENC_INT64) {
memcpy(&v64,((int64_t*)is->contents)+pos,sizeof(v64));
memrev64ifbe(&v64);
return v64;
} else if (enc == INTSET_ENC_INT32) {
memcpy(&v32,((int32_t*)is->contents)+pos,sizeof(v32));
memrev32ifbe(&v32);
return v32;
} else {
memcpy(&v16,((int16_t*)is->contents)+pos,sizeof(v16));
memrev16ifbe(&v16);
return v16;
}
}
上面的函数就是获取整数集合的某个整数,可以看出获取整数前都是把content的类型转换成相应的encode类型,再通过encode类型和整数在整数集合中的位置(pos)找到内存中
地址,再拷贝过来。
整数集合中的encode类型必须一致,也就是说如果新插入的值的类型比较大的话会自动升级到相应的encode类型。例如:新元素的长度为int32_t ,那么这个intset 就会自动进行“
升级” :先将集合中现有的所有元素从int16_t 类型转换为int32_t 类型,接着再将新元素加入到集合中。
根据需要,intset 可以自动从int16_t 升级到int32_t 或int64_t ,或者从int32_t 升级到int64_t 。
整数集合的升级是通过intsetUpgradeAndAdd这个函数来进行升级。这个函数最主要的一部分如下:
while(length--)
_intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));
这个就是把原先整数集合中的类型升级到现在的类型,升级的示意图如下:
在升级之前,内存示意图如下:
重新分配空间后的内存示意图:
升级过程的内存示意图:
上面就是整个升级的过程。
注意:整数集合是一个有序的集合,所以redis搜索的时候采用的是二分查找。
剩余25页未读,继续阅读
资源评论
天涯学馆
- 粉丝: 2348
- 资源: 436
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
最新资源
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功