redis 源码见闻录
redis定义
redis是一个KV(键值)类型的数据库。redis的值可以有多种类型(string,array,list),键总是字符串对象。
字符串对象
一个字符串对象里如果包含了字符串值,那么这个字符串的类型称为sds类型。(对象里可能还会有long类型的值,那个就不是sds类型)。
SDS类型
SDS(Simple Dynamic String)简单动态字符串,是C语言char*的替代品。能高效地支持长度计算和追加(append)这两种操作。
SDS类型高效的原因
从数据结构来进行分析
struct sdshdr {
// buf 已占用长度
int len;
// buf 剩余可用长度
int free;
// 实际保存字符串数据的地方
char buf[];
};
首先我们发现里结构体里面有存储长度字段,那么它取长度的时间复杂度就是O(1),而C原生的长度查询依赖strlen(),复杂度是O(N)。又因为不依赖C的函,直接取长度,所以说它是二进制安全的。
二进制安全(不对数据做解析,将数据当作二进制数据流处理。如C的char*,会将\0解析成结尾。而二进制安全则不进行任何转义)。)
其次我们发现,C原生对字符串进行 N 次追加,必定需要对字符串进行 N 次内存重分配(realloc)。而SDS结构里带有free和buf,会在APPEND的时候给buf一些额外的空间,并将可用内存空间记录在free里。这样下次APPEND的时候,就可以直接进行追加,不涉及内存重新分配。
关于buf的空间分配规则:在目前版本的 Redis 中, SDS_MAX_PREALLOC 的值为 1024 * 1024 , 也就是说, 当大小小于 1MB 的字符串执行追加操作时, sdsMakeRoomFor 就为它们分配多于所需大小一倍的空间; 当字符串的大小大于 1MB , 那么 sdsMakeRoomFor 就为它们额外多分配 1MB 的空间。
优化:将SDS_MAX_PREALLOC的值调低。
优点:节省内存,比如调整到一半512kb,那么每多一个APPEND超过512kb的操作,就会节省超出512kb部分的内存空间出来。
缺点:可能的性能损耗(如果再次APPEND超过512kb的时候,会不走buf,进行内存重分配)
所以还是要结合业务,根据APPEND的频率和大小来选出最适合的buf空间。
buf的内存回收规则:可以修改配置,定时释放(或者重启redis会自动释放)
思考:
所有处理 sdshdr 的函数,都必须正确地更新 len 和 free 属性,否则就会造成 bug。(这里就会导致redis暂时只能单线程执行,如果多线程的话,要考虑len和free在并发操作下的一致性)。