redis
- redis 是一个开源(BSD许可)的,数据结构为key-value的存储系统。它可以用作分布式数据库、缓存
和消息队列(消息中间件)。数据保存在内存中,存取速度快,并发能力强。
 
- redis 支持多种数据结构,如字符串、map(哈希)、列表、集合、有序集合等数据类型。数据都是原子操作,
数据都缓存在内存中,会定期将数据更新到磁盘或者修改操作写入追加记录。再次基础上实现了主从(master-slave)
 
redis 数据结构
字符串(string)类型
- 字符串类型是redis最基础的数据结构,可以存储简单的字符串也可存储复杂的xml、json、二进制数据(如图像、音频)
 
- value内部以保存整数的int和保存字符的sds组成的数据存储结构。sds内部结构为:
 
struct sdshdr {
    int len;    // 记录着buf中也使用的字符串数量
    int free;   // 记录着buf中未使用的字符串数量
    char buh[]; // 字符串数组,用于保存字符串
}
	
		
	
	
字符串类型特性
- 分配冗余空间:采用预先分配冗余空间的方式来减少内存的频繁分配
 
- 自动扩容: 当字符串所占空间小于1MB时,redis会按照字符串(len + addLen)*2倍数存储空间增加,
当字符串的存储空间超过所占空间的1MB时,每次自会增加1MB的存储空间扩容,最大扩容为512MB的存储空间
 
- 二进制的安全性,兼容c语言函数库中字符串以\0结束。
 
字符串常用的场景
- 缓存功能: 基于redis作为缓存再配合其他的数据库最为存储层,利用redis的数据存储在内存和支持高并发的特点,可以大大加快
系统的读写速度以及降低后端数据库的压力。(单值缓存、对象缓存、分布式锁等)
 
- 计数器: 使用redis作为系统的实时计数器,可以加快计数和查询功能。
 
- 共享用户的session:利用redis将用户的session集中管理,这种模式确保redis的高可用。每次用户的session的更新和获取快速完成
 
列表(list)
- list类型的value对象内部采用的quicklist(快速列表)或者ziplist(压缩列表)承载。当list的元素和单个元素较小时采用ziplist实现来减少内存的
占用否则采用quicklist结构进行存储。
 
- ziplist所有内容都存放在来连续的内存中。zipbytes表示ziplist的总长度, zltail表示指向最末的元素,zllen表示元素个数, entryX表示元素自身内容, zlend是ziplist的定界符
ziplist的内部结构为:
 
typedef struct ziplist{
     /*ziplist分配的内存大小*/
     uint32_t bytes;
     /*达到尾部的偏移量*/
     uint32_t tail_offset;
     /*存储元素实体个数*/
     uint16_t length;
     /*存储内容实体元素*/
     unsigned char* content[];
     /*尾部标识*/
     unsigned char end;
}ziplist;
/*元素实体所有信息, 仅仅是描述使用, 内存中并非如此存储*/
typedef struct zlentry {
     /*前一个元素长度需要空间和前一个元素长度*/
    unsigned int prevrawlensize, prevrawlen;
     /*元素长度需要空间和元素长度*/
    unsigned int lensize, len;
     /*头部长度即prevrawlensize + lensize*/
    unsigned int headersize;
     /*元素内容编码*/
    unsigned char encoding;
     /*元素实际内容*/
    unsigned char *p;
}zlentry;
	
		
	
	
- quicklist是底层是ziplist的双向链表,结构为:
 
struct quicklistNode {
    quicklistNode *prev;
    quicklistNode *next;
    ziplist *zl;         // 指向压缩列表
    int32 size;          // ziplist字节总数
    int16 count;         // ziplist中元素数量
    int2 encoding;       // 存储形式,表示原生字节数组还是LZF压缩存储
    ...
} quicklistNode;
// 快速列表
struct quicklist {
    quicklistNode *head;
    quicklistNode *next;
    long count;           // 元素总数
    int nodes;            // ziplist节点个数
    int compressDepth;    // LZF算法压缩深度
}
quicklist;
	
		
	
	
- redis的list支持存储2的32次方个元素,可以通过下标获取指定元素和莫个范围的元素集
 
列表常用场景
- 消息队列:redis的链表式结构,可以轻松实现阻塞队列,可以使用左进右出的命令完成队列的设计
 
- 文章列表或者数据分页展示的应用
 
map类型(hash 散列)
- map主要由hashtable和ziplist两种承载方式承载。当存储数据较小时,采用ziplist实现
 
hashtable组成
- dict: 当dictht需要扩容/缩容时,用于管理桶的迁移,结构图下
 
typedef struct dict{
  // 类型特定函数
  dictType *type;
  // 私有数据
  void *private;
  // 哈希表
  dictht ht[2];
  // rehash 索引,当当前的字典不在 rehash 时,值为-1
  int trehashidx;
}
- dictht: 维护哈希表的说有桶连,结构如下:
 
typedef struct dictht{
     //哈希表数组
     dictEntry **table;
     //哈希表大小
     unsigned long size;
     //哈希表大小掩码,用于计算索引值
     //总是等于 size-1
     unsigned long sizemask;
     //该哈希表已有节点的数量
     unsigned long used;
 
}dictht
- dictEntry:管理一个k-v对,同时保留着一个桶中相邻元素的指针,一次维护哈希桶内部相连,结构如下
 
typedef struct dictEntry{
     void *key;
     union{
          void *val;
          uint64_tu64;
          int64_ts64;
     }v;
     //下一个
     struct dictEntry *next;
}dictEntry
	
		
	
	
ziplist
- map中对应的ziplist的entry的个数一定是2的整数倍。即奇数存放着key,偶数存放着value
 
map类型的特性
- map内部的key和value不能在嵌套map,只能是string,int或着是float
 
- 负载因子 = 哈希表中已有的元素/哈希桶数
 
- 扩容:通过负载因子判断是否需要增加桶数。即当负载因子小于1时,一定不扩容,
当负载因子大于1,且在没有在执行BGSAVE进行或当负载因子大于5时,一顶会进行扩容,
扩容时,新桶的数目是现有桶数目的两倍。
 
- 缩容:当负载因子小于0.1时,就进行缩容。
 
- 扩/缩容都是通过新建哈希表的方式实现,扩容时新建现有桶的2倍的新表,缩容时是新建原表的一半新建哈希表。
将原表的桶逐步迁移到目标表(新建的哈希表)
 
map常用场景
- 利用键值对的特性,用来存储关系性数据库中表的记录
 
- 存储用户信息,优化用户信息的来源,提高系统的性能
 
集合(set)类型
- set是由inset或者hashtable来存储,hashtable的value永远为nil,只有int类型是使用inset进行存储
 
- inset:其核心是一个字符数组
 
set类型的特性
- 允许重复元素,没有顺序,没有下表,支持集合内的增删改查,并支持多个集合的交、并、差集等操作
 
set常用场景
- 根据set的特性,适合于聚合分类,如标签、好友共同喜好等场景
 
- 利用set中元素的唯一性,适合于数据的唯一性,如统计网站的独立ip
 
有序集合(sorted-set)
- sorter-set是的内部结构是以压缩列表(ziplist)或者跳跃表(skiplist)+hashtable构成
 
- sorted-set类似于map的键值对(key-value)且有序,value是一个浮点数称之为score,是排序的根据
 
- 跳跃表(skiplist)其核心点主要是包括一个dict对象和一个skiplist对象,结构如下:
 
typedef struct zset{
     //跳跃表
     zskiplist *zsl;
     //字典
     dict *dice;
} zset
/* ZSETs use a specialized version of Skiplists */
/*
 * 跳跃表节点
 */
typedef struct zskiplistNode {
    // 成员对象
    robj *obj;
    // 分值
    double score;
    // 后退指针
    struct zskiplistNode *backward;
    // 层
    struct zskiplistLevel {
        // 前进指针
        struct zskiplistNode *forward;
        // 跨度
        unsigned int span;
    } level[];
} zskiplistNode;
/*
 * 跳跃表
 */
typedef struct zskiplist {
    // 表头节点和表尾节点
    struct zskiplistNode *header, *tail;
    // 表中节点的数量
    unsigned long length;
    // 表中层数最大的节点的层数
    int level;
} zskiplist;
	
		
	
	
有序集合(sorted-set)特性
- sorted-set是集合的基础上,增加了给每一个元素设置分数(score),利用该分数进行排序,集合元素是唯一的,分数是不唯一的。
 
有序集合(sorted-set)常用创景
- 利用它的特性,可以适合于排序的场景,如排行榜等
 
- 根据集合的每个元素都设置了一个score分数的特性,做带权的队列,如:普通的信息的权重为1,重要的信息的权重为2,
应用线程可以根据权重调用。
 
参考文献:
Redis底层数据结构详解