一.概述:
Redis从大的方面来说,就是一个K-V数据库(或cache);但是redis还提供了对复杂数据结构的操作,比如set/list/map,因此它需要具备对复杂数据的高效查询;此外它还提供了故障恢复特性,因此它需要具备数据持久化(文件操作)能力。
##如下为Reis顶层数据结构,redisDB实例表示为一个"database",任何K-V/expire信息均隶属一个db ##一个redis可以有多个databse,参见配置文件 //redis.h(源码) typedef struct redisDb { dict *dict; /* The keyspace for this DB */ dict *expires; /* Timeout of keys with a timeout set */ dict *blocking_keys; /* Keys with clients waiting for data (BLPOP) */ dict *ready_keys; /* Blocked keys that received a PUSH */ dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */ int id; /*DB 索引号*/ } redisDb;
此数据结构将会在redis与client交互中/以及后台worker中不断的调整和修改。顶层数据结构维护了K-V表/过期key集合/基于阻塞操作的Key集合/基于事务的watch-key集合等。其中K-V表就是一个hashtable,此表维护了“k-v对”集合,无论是查询还是插入,均基于HASH,HASH的默认尺寸为2,此后会根据“需要”做rehash操作(2*N),对于HASH冲突的解决(参见其他关于rehash原理),V的直接数据结构是linkedlist。dict数据结构可以参见dict.h源码
key就是简单的string,key似乎没有长度限制,不过原则上应该尽可能的短小且可读性强,无论是否基于持久存储,key在服务的整个生命周期中都会在内存中,因此减小key的尺寸可以有效的节约内存,同时也能优化key检索的效率。
value在redis中,存储层面仍然基于string,在逻辑层面,可以是string/set/list/map,不过redis为了性能考虑,使用不同的“encoding”数据结构类型来表示它们。(例如:linkedlist,ziplist等)。
Redis-server与client交互过程中以及value值的保存,使用的string称为sds:Simple Dynamic Strings,即简单动态字符串,它的实现原理和java中StringBuffer/StringBuilder如出一辙,这种结构很适合字符串长度无法预期或者允许区间操作的场景,这只是一种技巧。
key默认为“不过期”,可以通过“EXPIRE”/“PEXPIRE”来指定key过期的时间(秒/毫秒),具有“过期”时间的key将会被添加到expires集合中,如果redis为“持久存储”,那么每个key的过期信息也将被序列化到rdb文件中;可以通过“PERSIST”来移除key的过期控制,此后key将处于“永不过期”状态。可以通过“TTL”/“PTTL”来查看key尚能“存活”的时间(剩余时间值)。
redis将过期时间转换成时间戳而保存起来,时间戳的计算将使用本地时间,对于key过期检测是通过将“过期时间”与本地时间(戳)比较,因此随意调整本地时间,将有可能导致redis对“过期控制”出现意外,比如将时间前进一天将会导致部分key直接过期;在数据恢复时,也会检测key是否过期,如果redis停机时间过久,那么会导致大量key过期,对于过期的key将直接丢弃;如果你的数据恢复文件来自其他server,且两个server的本地时间差距较大,也会导致上述问题。(参见db.c)“DEL”操作将会导致key删除,同时也会在expries集合中删除。在redis中(包括memcached)只有set/getset操作会重置“过期控制”,其他的任何读取/修改操作都不会“触及”(touch)过期时间(当然“expire”/“persist”除外).Redis对于过期检测,有2种方式,一个主动检测,一个是被动检测;在redis和client交互过程中,对于任何数据的操作,都会首先检测key是否已经过期,这是被动检测;主动检测是Redis启动的后台线程中,不间断的随机扫描一定量的key(randomKey),并对key进行过期检测。对于过期的key,将会被直接丢弃(伴生“DEL”操作)。因为slave不具备主动检测机制,master对于过期的key将会以一个“DEL”操作同步到salve中;如果采取的是AOF方式,也是以“DEL”指令append到文件中。
二.文件存储格式
如果你开启了snapshot功能,那么数据将会间歇性的同步到rdb文件中(binary文件)。
#摘自redis# FE 00 # FE = code that indicates database selector. db number = 00 ----------------------------# Key-Value pair starts FD $unsigned int # FD indicates "expiry time in seconds". After that, expiry time is read as a 4 byte unsigned int $value-type # 1 byte flag indicating the type of value - set, map, sorted set etc. $string-encoded-key # The key, encoded as a redis string $encoded-value # The value. Encoding depends on $value-type
可以通过“vim”指令查看此文件,不过阅读起来不是很方便。每条数据,都以特殊的字节标记开头,数据中包括“过期时间”/“value数据类型”“key”/“value数据”;基本上可以通过有序的读取字节序列的方式,即可恢复结构化的K-V逻辑结构。K-V字节存储结构图:
##过期时间:秒 [FD][过期时间戳:4个字节][value类型:一个字节][key字符串][value字符串] ##过期时间:毫秒 [FC][过期时间戳:8个字节][value类型:一个字节][key字符串][value字符串] ##无过期 [value类型:一个字节][key字符串][value字符串] ##其中FD/FC是一个标记前缀,一个字节,16进制表示,读取数据是,如果第一个字节不是FD/FC,那么它就是一个“无过期时间”的K-V。
1) value类型:
##摘自redis 0 = “String Encoding” 1 = “List Encoding” 2 = “Set Encoding” 3 = “Sorted Set Encoding” 4 = “Hash Encoding” 9 = “Zipmap Encoding” 10 = “Ziplist Encoding” 11 = “Intset Encoding” 12 = “Sorted Set in Ziplist Encoding” 13 = “Hashmap in Ziplist Encoding”
value类型用来指示文件解析者,将当前“K-V”以何种数据结构来表示。在Redis中目前有这13种数据结构,稍后逐一介绍。
2) key字符串:
因为基于字节流的方式解析,所以我们需要知道key的长度(后驱偏移量):[位计算:前缀与长度][字符串值]
可能考虑节约空间或者其他考虑,key的解析有点奇怪,首先读取一个字节,并检测此字节的前2位(bit),这2位数据是用来标记key的长度特征:
00: 此字节的剩余6位则表示“长度”
01: 此字节的剩余6位以及下一个字节的8位,共14位来表示“长度”
10: 此字节不参与计算,将直接读取4个字节,这4个字节表示“长度”
11: 一种特殊格式,剩余的6位用来表示格式信息,此种情况出现在value中,表示此value为一个integer类型数据:
##如果剩余6位的值为如下: 0: 8位integer 1: 16位integer 2: 32位integer
key的特征已经分析完毕,接下来直接读取一定“长度”的字节并编码成string即可,此string就是KEY。
3) value字符串:
此前已经有一个字节表示value-type,那么读取value就非常简单,那照2)中读取key字符串的方式读取出value的字符串,此后就可以根据value-type对字符串进行相应的解析,并构建逻辑数据结构。
在redis文件解析中,有2中方式:length-encoding,string-encoding;length-encoding主要的目的就是将字节码(不可读)数据使用“字节码成帧”手段,按照“[字节长度][字节序列]”的方式,读取“字节序列”并按照“字符编码(UTF-8)”的方式转换成字符串的过程。string-encoding,是对字符串使用“格式约束”手段解析成特定逻辑API的过程,例如将“1,2,3,4”转换成一个数组{1,2,3,4}.
三.数据结构概述
1)查看value类型:
redis 127.0.0.1:6379> lpush testlist 0 (integer) 1 redis 127.0.0.1:6379> type testlist list redis 127.0.0.1:6379> object encoding testlist "ziplist"
”type“指令用来查看value的类型,“object encoding”用来查看value的编码类型。在Redis中type有如下几种值: