Redis实战(一)

 

redis是什么?

REmote DIctionary Server(redis)远程字典服务器,是一个key-value的存储系统。redis是一个开源的、使用ANSI① C语言编写、遵守BSD协议②、支持网络、可基于内存亦可持久化③的日志型、key-value数据库,并提供多种语言的API。

 

数据结构

redis数据结构,也就是Value的值的类型。包括(String,hash,list,set,zset)。下面让我们了解下每种类型在底层如何存储,以及其应用场景。

 

 

String(字符串)

在redis内部,string类型有两种底层存储结构。redis会根据存储的数据以及用户的操作指令自动选择合适的结构 

  • int:存放整数类型
  • SDS:存放浮点、字符串、字节类型
SDS:简单动态字符串 simple dynamic string
typedef struct sdshdr { 
  // buf中已经占用的字符长度 
  unsigned int len; 
  // buf中剩余可用的字符长度 
  unsigned int free; 
  // 数据空间 
  char buf[]; 
}

根据上述底层数据结构可见,其底层是一个char数组。buf最大容量512M,里面可以放字符串、浮点数和字节。所以你甚至可以放一张序列化后的图片。以及项目中常用的序列化对象等等

 

 

思考?为什么它没有直接使用数组?而是对数组进行了包装?

假定底层结构为数组:当value为hello的时候,底层结构如下,但是当存储的值改为hello word时呢?需要重新开辟一块内存空间,效率很低

 

 

 

包装数组的意义

buf的扩容方式分为以下两种情况,修改后的大小小于1M以及修改后的大小大于1M

  • 修改后的len小于1M,此时分配给free的大小和len一样,比如说修改过后为10字节,那么给free也是10字节,buf 的实际长度变成了 10 + 10 + 1 = 21b (不要问我为什么加1)
  • 修改后的len大于1M,此时分配给的free长度为1M,比如修改过后为10M,那么free是1M,buf的实际长度变成了10M + 1M + 1b

buff空间释放:惰性空间释放,当字符串缩短时,并没有真正的缩容,而是移动free指针。这样将来字符串长度增加时,就不用重新分配内存了。但是这样会导致内存浪费,SDS提供了方法来真正释放内存

sdsfree 函数释放内存 

 

 

Hash(散列)

hash底层有两种实现:压缩列表和字典(dict)。

 

压缩列表

压缩列表有点类似于数组,通过一片连续的内存空间来存储数据。不过,它跟数组不同的一点是,它允许存储的数据大小不同。每个节点上增加一个length属性来记录这个节点的长度,这样比较方便地得到下一个节点的位置。

struct ziplist<T> {
  int32 zlbytes;          //整个压缩列表占用字节数
  int32 zltail_offset;    //最后一个元素距离压缩列表起始位置的偏移量,用来快速定位到最后一个节点 
  int16 zllength;         //元素的个数
  T[] entries;            //元素内容列表,依次紧凑存储
  int8 zlend;             //标志元素列表的结束,值恒为0xFF
}

 

快速列表

redis3.2版本之后,对列表数据结构进行了改造,使用quicklist代替了ziplist和linkedlist。简单来说就是链表+压缩列表,每个链表的节点都是一个压缩列表。

struct quicklistNode {
  quicklistNode* prev;
  quicklistNode* next;
  ziplist* zl;     //指向压缩列表
  int32 size;      //ziplist的 字节总数
  int16 count;     //ziplist中的元素个数
  int2 encoding;   //存储形式
  ……
}


struct quicklist {
  quicklistNode* prev;
  quicklistNode* next;
  long count;      //元素总个数
  int nodes;       //ziplist 的节点个数
  int compressDepth; //LZF 算法压缩深度
}

 

字典

说是字典,其实就是我们熟悉的hashmap(散列表)。只不过redis的字典值只能是字符串,而且rehash的方式不一样。

 

rehash

我们都知道,hashmap的几个参数 DEFAULT_INITIAL_CAPACITY(数组容量)、DEFAULT_LOAD_FACTOR(影响因子)两个参数相乘得到一个阈值threshold,当数组容量达到此值时,hashmap会进行扩容,扩容后会进行一次性rehash(这里我就不深入分析了,大家感兴趣可以自己了解下)。

 

渐进式rehash

与上述不同的是,我们redis中存储的数据量可能较大,采用rehash一次性扩容的方式可能耗时过多(简直对不起我们redis快的称号)。所以redis才用了渐进式rehash,当负载因子达到阈值之后,只申请空间,但不将老的数据迁移到新的散列表中。当有数据插入时,将新数据插入到新散列表中,并且从老的散列表中拿出一个数据放入新的散列表。每次插入数据都重复上述过程,久而久之,老的散列表中的数据就全部搬到新的散列表中了。这样一个一个的迁移,rehash的过程就变得无感了。这个过程就叫做渐进式rehash。

 

List(列表)

list底层也有两种数据结构:链表linkedlist以及压缩列表ziplist。当list元素个数少切元素内容长度不大时使用ziplist,反之使用linkedList。

 

链表

redis采用双向链表。为了方便操作,使用了一个list结构来持有该链表。

typedef struct list{ 
  //头节点 
  listNode *head; 
  //尾节点 
  listNode *tail; 
  //链表所包含的节点数量 
  unsigned long len; 
  //节点值复制函数 
  void *(*dup)(void *ptr); 
  //节点值释放函数 
  void *(*free)(void *ptr); 
  //节点值对比函数 
  int (*match)(void *ptr,void *key); 
}list;

 

压缩列表

压缩列表同hash的压缩列表

 

Set(集合)

set是没有重复数据的集合。set也有两种数据结构。intset和字典

 

intset

intset,一种特殊的set数据结构,由多个整形元素组成。intset也是一个有序的整型集合,数据结构如下

typedef struct intset {
    //字符编码  三种类型 int16_t(默认) int32_t  nt64_t 分别占 2  4  8字节
    uint32_t encoding;
    //元素大小
    uint32_t length;
    //元素数组
    int8_t contents[];
} intset;

intset是一个有序的集合,所以插入时间复杂度On,查找采用二分查找,速度还是挺快的。感兴趣的可以看下其中的方法。这里我不做过多介绍了

 

字典

字典同hash的字典

 

Zset(有序集合)

zset是可排序的set。采用跳表的数据结构。与hash的实现方式类似,如果元素个数不多且不大,就是用压缩列表ziplist来存储。不过由于zset包含了score的排序信息,所以在ziplist的内部,是按照score排序递增来存储的。所以每次插入都要移动之后的数据。

 

跳表

redis的zset是一个复合结构,一方面他需要一个hash结构来存储value和score的关系,另一方面需要按照score排序,并且能够指定score的来获取列表。这就需要一个跳跃列表结构。

struct zslnode{
    string value;
    double score;
    zslnode*[] forwards;         //多层连接指针
    zslnode* backward;           //回溯指针
}


struct zsl {
    zslnode* header;             //跳跃列表头指针
    int maxLevel;                //跳跃列表当前最高层
    map<string, zslnode*> ht;    //hash结构的所有键值对
}

如果所示,跳表的作用就是为了减少查询次数,就类似与二分查找,只不过这里是分层记录中间值。比如我们要查找8,先在最上层L2查找,发现在1和9之间;然后去L1层查找,发现在5和9之间;然后去L0查找,发现在7和9之间,然后找到8。

 

总结

redis基本数据类型讲到这里就结束了,我们了解了redis底层真正存储数据的5中结构,分别是string、hash、list、set以及zset,那么redis的常用命令以及使用场景是怎么样的呢?期待下次分享。

关注不迷路     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值