Redis的魅力:
缓存分为两类,一种是应用内缓存,比如Map(简答的数据结构),EH Cache(java第三方库);另一种就是缓存组件,比如Memached,redis;redis(remote dictionary server 远程服务字典),是一个基于key-value的高性能的存储系统,通过提供多种键值数据类型来适应不同场景下的缓存域存储需求。
数据类型
字符串类型
字符串类型是最基本的数据类型,它能存储任何形式的字符串,包括二进制数据。你可以用它存储用户的邮箱,json化的对象甚至图片,一个字符类型允许存储最大容量是512M。
内部数据结构
在redis内部,String类型通过int,SDS(simple dynamic string)作为结构存储,int用来存放整数型,SDS存放字节/字符串和浮点数类型,在C的标准字符串结构进行了封装,同类提升基本操作的性能,同时也充分利用已有的C的标准库,简化实现逻辑,可以在redis的源码中sds.h中看到sds的结构如下:
//8表示字符串最大长度是2^8-1
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; 表示当前sds的长度
uint8_t alloc; 表示已为sds分片的内存大小(单位是字节)
unsigned char flags;用一个字节表示当前sdshdr的类型,因为sdshdr有五种类型,所以至少需要三位来表示,000:shshdr5,001:shshdr8,010:sdshdr16,011:sdshdr32,100:sdshdr64,高五位用不到所以都是0
char buf[];sds实际存放的位置
}
列表类型List
可以存储一个有序的有序的字符串列表,常用的操作时向列表两端添加元素或者获得列表的某一个片段,列表类型内部使用双向链表实现,所有向列表两端添加元素的时间复杂度为O(1),获取越接近两端的元素速度就越快,这意味着即使是一个有几千万个元素的列表,获取头部或者尾部的10条记录也是很快的。
数据结构
在3.2之前,List类型的value对象以linkedList和zipList来实现,当list的元素个数和单个元素的长度比较小的时候,redis会采用zipList来实现,减少内存的占用,否则就会用linkedList结构。
在3.2之后,采用的是一种quickList的数据结构来存储list,列表的底层由quickList(quick意为敏捷)实现,者两种存储方式都有优缺点,双向链表在链表两端进行push和pop操作,在插入节点上复杂度比较低,但是内存开销比较大,zipList存在一段连续的内存上,所以存储效率很高,但是插入和删除都需要频繁申请和释放内存。
quickList任然是一个双向链表,只是列表的每个节点都是一个zipList,其实就是zipList和LinkedList的结合,quickList中每个节点zipList都能够存储多个数据元素,在源码中的文件为quicklist.c在源码中第一行的解释为:A double linked list of ziplist 意思为一个有ziplist组成的双向链表。
Hash类型
map提供两种结构来存储,一种是hashtable,另一种是前面讲的zipList,数据量小的时候用zipList,在redis中,哈希表分为三层。
typedef struct dictEntry{
void *key;
union{
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;//下一个节点的地址,用来处理碰撞,所以分到到同一个索引元素
//通过next连接起来形成链表,key和value都可以保存多种类型的数据
} dictEntry
dictht
实现一个hash表会使用一个buckets存放dictEntry的地址,一般情况下通过hash(key)%len得到的值就是buckets的索引,这个值决定了我们要将此dictEntry节点放入buckets的哪个索引里,这个buckets实际上就是我们说的hash表,dict.h的dictht结构中table存放的就是buckets的地址。
typedef struct dictht{
dictEntry **table;//buckets的地址
unsigned long size;//buckets的大小,总保持2^n
unsigned long sizemask;//掩码,用来计算hash值对应的buckets索引
unsinged long used;//当前dictht有多少个dictEntry节点
}dictht;
dictht实际上就是hash的核心,但是只有一个dictht还不够,比如rehash、遍历hash等操作,索引redis定了一个叫dict的结构以支持字典的各种操作,当dictht需要扩容和缩容时,用来管理dictht的迁移,以下是他的数据结构。
typedef struct dict{
dictType *type;//dictType里存放的是一堆工具函数的函数指针,
void *privdata;//保存type中的某些函数需要作为参数的数据
dictht ht[2];//两个dictht,ht[0]平时用,ht[1]rehash时用
long rehashidx;//当前rehash到buckets的哪个索引,-1表示非rehash状态
int iterators;//安全迭代计数
}dict;
比如我们要将一个数据存储在hash表中,那么会先将key对应的hashcode计算出来,然后根据hashcode取模得到bucket的位置,再插入到链表中。
集合类型Set
集合类型中,每个元素都是不同的,也就是不能有重复的元素,同时集合类型中的数据是无序的,一个集合类型键可以存储至多232-1个,集合类型和列表类型最大的区别就是有序性和唯一性。
结合类型的常用操作和向集合中假如或删除元素,判断某个元素是否存在,由于集合类型在redis内部是使用的值为空的散列表(hash table),所有这些操作的时间复杂度都是O(1);
数据结构
Set在底层数据接收以intset或者hashtable来存储,当set中只包含整数型的元素时 ,采用intset来存储,否则采用hashtable存储。
有序集合sorted set
有序集合类型,顾名思义,和前面讲的结合类型的区别就是多了有序的功能
在集合类型的基础上,有序集合类型为集合中的每个元素都关联了一个分数,这使我们不仅可以完成插入,删除和判断元素是否存在等集合类型支持的操作,虽然集合中每个元素都是不同的,但是他们的分数却可以相同。
数据结构
zset类型的结构就比较复杂一点,内部是以ziplist或者skiplist或者skip+hashtable来实现,这里面核心的一个结构就是skipList,也就是跳跃表。