Redis底层是靠C语言实现的,想看源码实现直接看src下的代码即可
.h文件和.c文件作用
xx.h文件 .h中一般放的是同名.c文件中定义的变量、数组、函数的声明,需要让.c外部使用的声明。
xx.c文件 .c文件一般放的是变量、数组、函数的具体定义
核心就是server.h,redisDb以及redisObject就是在server.h中。
typedef struct redisDb {
dict *dict; /* k-v结构就存在这里边 */
dict *expires; /* 过期时间*/
dict *blocking_keys; /* 阻塞队列的一些处理*/
dict *ready_keys; /* 对应的client连接 */
dict *watched_keys; /* 事物处理 */
int id; /* 索引ID */
long long avg_ttl; /* Average TTL, just for stats */
unsigned long expires_cursor; /* Cursor of the active expire cycle. */
list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;
typedef struct redisObject {
/* 4bits 表示对象的类型,包括5种基本类型 */
unsigned type:4;
/* 4bits 表示值对象的内部编码 */
unsigned encoding:4;
/* 24bits 记录对象被命令访问的最后一次时间,和内存回收有很大关系 */
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
/* 4bytes = 32bits 记录被引用的次数 */
int refcount;
/* 8bytes,64-bit system 指向真正内容的指针 */
void *ptr;
} robj;
学过C的都晓得,C语言存储字符串,是 char data[] 数组形式。
eg: char data[]=“guojia\0”;
\0表示结束
如果有一个字符串guo\0jia,正常结果应该是guojia,但是由于有换行符,就有可能变为guo.这就有可能导致数据不准确,所以redis自定义了字符串:
SDS :simple dynamic string
一、特点:
- 1、二进制安全的数据结构
- 2、提供了内存预分配,避免了频繁的内存分配
- 3、兼容C语言函数库
二、扩容方式:
二倍扩容(空间换时间)
sds:
free:0 空余空间为0
len:6 使用空间为6
char buf[]=“huohuo” ->“huohuo123”
修改后的结果为:huohuo123,需使用9个空间
len:6
addlen:3
(len+addlen)*2=18个字节
free:9
len:9
char buf[]=“huohuo123”
由于剩余空间还为9,再次修改,空间够用不会扩容,空间不够将会再次2倍方式扩容。
什么场景下不会采用二倍扩容方式?
当len长度达到1M的时候,也就是1024*1024的长度
扩容源码位于sds.h中
sds sdscatlen(sds s, const void *t, size_t len) {
/* 原字符串长度 */
size_t curlen = sdslen(s);
/* 按需调整空间,如果 capacity 不够容纳追加的内容,就会重新分配字节数组并复制原字符串的内容到新数组中 */
s = sdsMakeRoomFor(s,len);
/* 内存不足 */
if (s == NULL) return NULL;
/* 追加目标字符串的内容到字节数组中 */
memcpy(s+curlen, t, len);
/* 设置追加后的长度值 */
sdssetlen(s, curlen+len);
/* 让字符串以\0 结尾,便于调试打印 */
s[curlen+len] = '\0';
return s;
}
sds sdsMakeRoomFor(sds s, size_t addlen) {
void *sh, *newsh;
size_t avail = sdsavail(s);
size_t len, newlen;
/* 首先计算出原SDS还剩多少可分配空间 */
char type, oldtype = s[-1] & SDS_TYPE_MASK;
int hdrlen;
/* Return ASAP if there is enough space left. */
/* 已经够用的情况下直接返回 */
if (avail >= addlen) return s;
len = sdslen(s);
sh = (char*)s-sdsHdrSize(oldtype);
/*扩容分配策略 */
newlen = (len+addlen);
assert(newlen > len); /* Catch size_t overflow */
/* 如果新长度小于最大预分配长度则分配扩容为2倍 */
/* 如果新长度大于最大预分配长度则仅追加SDS_MAX_PREALLOC长度 */
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
type = sdsReqType(newlen);
/* 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. */
/* 由于SDS_TYPE_5没有记录剩余空间(用多少分配多少),所以是不合适用来进行追加的 */
/* 为了防止下次追加出现这种情况,所以直接分配SDS_TYPE_8类型 */
if (type == SDS_TYPE_5) type = SDS_TYPE_8;
hdrlen = sdsHdrSize(type);
assert(hdrlen + newlen + 1 > len); /* Catch size_t overflow */
if (oldtype==type) {
/* 类型没变化则直接使用原起始地址重新分配下内存即可 */
newsh = s_realloc(sh, hdrlen+newlen+1);
if (newsh == NULL) return NULL;
s = (char*)newsh+hdrlen;
} else {
/* Since the header size changes, need to move the string forward,
* and can't use realloc */
/* 头部类型有变化则重新开辟一块内存并将原先整个SDS拷贝一份过去 */
newsh = s_malloc(hdrlen+newlen+1);
if (newsh == NULL) return NULL;
memcpy((char*)newsh+hdrlen, s, len+1);
/* 旧的已经没用了 */
s_free(sh);
s = (char*)newsh+hdrlen;
/* 配置新类型 */
s[-1] = type;
sdssetlen(s, len);
}
/* 设置新对分配对总长度 */
sdssetalloc(s, newlen);
return s;
}
SDS_MAX_PREALLOC的容量大小定义在sds.h文件中,默认是 1024 * 1024,也就是1MB。
三、
三、reidsDb有多少DB?
0-15,一共16个redisDb