2018/4/7
从今天开始,记录学习Redis源码的一些笔记,写一些自己的理解与总结。主要以概念和自己觉得重要的东西为主。希望能在一个月内看完。
首先开篇先看redis中sds链表的实现。
数据结构
其数据结构异常简单
typedef char *sds;
就一个字符串指针。
/* Note: sdshdr5 is never used, we just access the flags byte directly.
* However is here to document the layout of type 5 SDS strings. */
//__attribute__ ((__packed__)) 保证了内存不对齐,即每次存储该结构时连续存储,保证后面api能够使用指针,直接访问想要的数据结构。
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
};//长度最长为2的5次方 即32个字符 已经被放弃使用。
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* used */
uint8_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};//长度最长为2的8次 即256个字符 下面以次类推
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* used */
uint16_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* used */
uint32_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* used */
uint64_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
其内存中的存储结构为
uint_* len | uint_* alloc | unsigned char flags | char buf [ ] |
连续的内存空间用于存储 *根据使用的位数来决定
unsigned char 固定为两个字节 八个bit。
那么拿到一个sds其实是指向的是buf的位置的指针。
由于拥有不同长度的字符串类型需要用flag进行判断,以下为所有type存储在flag的值。利用flag&SDS_TYPE_MASK即可得到type。
#define SDS_TYPE_5 0
#define SDS_TYPE_8 1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4
#define SDS_TYPE_MASK 7
知道了数据结构后,来看看其重要的一些操作。个人认为最重要的操作其实就是两个,扩容和压缩。
sds扩容
废话不多说,直接上代码。
sds sdsMakeRoomFor(sds s, size_t addlen) { //s 与要增容的长度
void *sh, *newsh;
size_t avail = sdsavail(s);//allo 获取sds剩余空间大小
size_t len, newlen;
char type, oldtype = s[-1] & SDS_TYPE_MASK; //如前面所说的&7 则为类型
int hdrlen;
/* Return ASAP if there is enough space left. */
if (avail >= addlen) return s; //如果新增的长度,小于剩余空间,则直接返回
len = sdslen(s);//获取现在长度
sh = (char*)s-sdsHdrSize(oldtype); //sh指向len 即指向了最前的位置
newlen = (len+addlen); //增容后的长度
if (newlen < SDS_MAX_PREALLOC) //小于 1024*1024即1M时*2增加, 避免多次增加,因为没增加一次都需要realloc 遍历一遍。
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC; //大于1m 时新的长度增加1m
type = sdsReqType(newlen);//更新type
/* Don't use type 5: the user is appending to the string and type 5 is
* not able to remember empty space, so sdsMakeRoomFor() must be called
* at every appending operation. */
if (type == SDS_TYPE_5) type = SDS_TYPE_8; //淘汰了5bit的类型
hdrlen = sdsHdrSize(type);//获取增容后的头长度
if (oldtype==type) {
newsh = s_realloc(sh, hdrlen+newlen+1);//type若没有变化,则表头结构都不需要重新初始化,直接realloc
if (newsh == NULL) return NULL; //分配失败则返回NULL
s = (char*)newsh+hdrlen; //新的开始 即newsh+头的长度
} else {
/* Since the header size changes, need to move the string forward,
* and can't use realloc */
newsh = s_malloc(hdrlen+newlen+1);//新分配长度
if (newsh == NULL) return NULL;
memcpy((char*)newsh+hdrlen, s, len+1);//拷贝表头后的所有数据
s_free(sh);//释放原有空间 realloc 会自动做这一步
s = (char*)newsh+hdrlen;//s调整指向buf数据段
s[-1] = type;//设置flag
sdssetlen(s, len);//设置占用的总长度
}
sdssetalloc(s, newlen);//设置分配的总空间
return s;
}
压缩
sds sdsRemoveFreeSpace(sds s) {
void *sh, *newsh;
char type, oldtype = s[-1] & SDS_TYPE_MASK;//一样取type
int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
size_t len = sdslen(s);//使用的长度
sh = (char*)s-oldhdrlen;//指向len的指针 |len|alloc|flag|buf|
/* Check what would be the minimum SDS header that is just good enough to
* fit this string. */
type = sdsReqType(len);//符合实际长度的新的type
hdrlen = sdsHdrSize(type);//新的type
/* If the type is the same, or at least a large enough type is still
* required, we just realloc(), letting the allocator to do the copy
* only if really needed. Otherwise if the change is huge, we manually
* reallocate the string to use the different header type. */
if (oldtype==type || type > SDS_TYPE_8) {//若两者一样则直接调用realloc 原type变化不大则只是realloc 将容量缩减,不变更type
newsh = s_realloc(sh, oldhdrlen+len+1);//重新分配容量
if (newsh == NULL) return NULL;
s = (char*)newsh+oldhdrlen;//指向buf
} else {
newsh = s_malloc(hdrlen+len+1);//更换type 重新分配空间
if (newsh == NULL) return NULL;
memcpy((char*)newsh+hdrlen, s, len+1);//拷贝数据
s_free(sh);
s = (char*)newsh+hdrlen;//指向新的buf
s[-1] = type;//设置type
sdssetlen(s, len);//设置加上头加上数据的总长度
}
sdssetalloc(s, len);//设置分配的长度即使用的长度
}
除了两个重要的函数,sds.c还实现了一些字符串的操作函数,个人觉得不是很重要。