redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
1 内部数据结构
1.1 简单动态字符串sds:
Sds (Simple Dynamic String,简单动态字符串)是Redis 底层所使用的字符串表示,它被用在几乎所有的Redis 模块中。
1.1.1 sds的用途:
a.实现字符串对象(StringObject):数据库的键总是包含一个sds值,而数据库的值保存的是String类型的时候值中包含sds值,否则包含的是long类型的值。
b.在redis程序内部用作char*类型的替代品:char*类型的功能单一抽象层次低不能支持redis的一些常用操作(长度计算和追加操作),
1.1.2 redis中的字符串:
在C 语言中,字符串可以用一个 结尾的char 数组来表示。比如说,hello world 在C 语言中就可以表示为"hello world " 。这种简单的字符串表示在大多数情况下都能满足要求,但是,它并不能高效地支持长度计算和追加(append)这两种操作:
• 每次计算字符串长度(strlen(s))的复杂度为θ(N) 。
• 对字符串进行N 次追加,必定需要对字符串进行N 次内存重分配(realloc)。
在Redis 内部,字符串的追加和长度计算并不少见,而APPEND 和STRLEN 更是这两种操作在Redis 命令中的直接映射,这两个简单的操作不应该成为性能的瓶颈。Redis 除了处理C 字符串之外,还需要处理单纯的字节数组,以及服务器协议等内容,所以为了方便起见,Redis 的字符串表示还应该是二进制安全的:程序不应对字符串里面保存的数据做任何假设,数据可以是以 结尾的C 字符串,也可以是单纯的字节数组,或者其他格式的数据。(这就是redis用sds替换char*的原因:sds可以高效的实现追加和长度计算,并且还是二进制安全的)
typedef char *sds; struct sdshdr { // buf 已占用长度 int len; // buf 剩余可用长度 int free; // 实际保存字符串数据的地方 char buf[]; };
其实类型sds是char*的别名,而结构sdshdr则保存了len、free、bug这三个参数属性。通过len 属性,sdshdr 可以实现复杂度为θ(1) 的长度计算操作。另一方面,通过对buf 分配一些额外的空间,并使用free 记录未使用空间的大小,sdshdr 可以让执行追加操作所需的内存重分配次数大大减少
1.1.3 优化追加操作:
当执行追加操作时,比如现在给key=‘Hello World’的字符串后追加‘ again!’则这时的len=18,free由0变成了18,此时的buf='Hello World again! ',也就是buf的内存空间是18+18+1=37个字节,其中‘