设为首页 加入收藏

TOP

Redis数据类型使用场景及有序集合SortedSet底层实现详解(一)
2019-09-17 18:18:46 】 浏览:77
Tags:Redis 数据 类型 使用 场景 有序 集合 SortedSet 底层 实现 详解

  Redis常用数据类型有字符串String、字典dict、列表List、集合Set、有序集合SortedSet,本文将简单介绍各数据类型及其使用场景,并重点剖析有序集合SortedSet的实现。

  List的底层实现是类似Linked List双端链表的结构,而不是数组,插入速度快,不需要节点的移动,但不支持随机访问,需要顺序遍历到索引所在节点。List有两个主要的使用场景:

  1. 记住用户最新发表的博文,每次用户发表了文章,将文章id使用LPUSH加入到列表中,用户访问自己的主页时,使用LRANGE 0 9获取最新10条博文(使用LTRIM 0 9可以取出最新10条文章的同时,删除旧的文章),而不用使用order by sql语句去后端数据库取数据。
  2. 生产者/消费者模式,生产者往List中加入数据,消费者从List中取数据。当List为空时,消费者rpop返回为NULL,这是会进行轮询,等待一段时间继续去取。轮询模式有如下缺点:
    1. 客户端和redis耗费cpu和网络带宽等资源执行无效命令。
    2. 取回NULL后,sleep会使有新数据时,客户端消费不够及时。

  为了解决轮询的问题,Redis提供了brpop和blpop实现Blocking读,当List为空时,等待一段时间再返回,当有数据时,按请求顺序返回给各客户端。(当List为空时,可以将请求Blocking读命令的客户端加入此List的Blocking读列表中,有数据时按列表序返回)

  集合Set的底层实现是类似Hash,不过value全为NULL,set有求并、交、差集及随机取的功能。使用场景如下:

  1. 表示对象之间的联系,比如求拥有标签1、2、10的新闻,使用sinter tag:1:news tag:2:news tag:10:news。
  2. 随机发牌,使用spop,spop随机返回集合中的元素,比如给每位玩家发五张牌,每位玩家调用五次spop即可,为了下次发牌不需要再将牌加入set,可以在这次发牌前调用sunionstore将牌复制。

  有序集合SortedSet(t_zset.c),集合中的每个值都带有分数,按分数排序,底层实现较为复杂,用到了ziplist、skiplist和dict数据结构,后文将进行详细介绍。使用场景如下:

  1. 排行榜问题,比如游戏排行榜,按用户分数排序,并取top N个用户。

  在redis中,所有数据类型都被封装在一个redisObject结构中,用于提供统一的接口,结构如下表1:

 表1 redisObject

redisObject源码(server.h)
typedef struct redisObject {
    unsigned type:4;//对象类型,用于分辨字符串、列表、
//集合、有序集合、字典,有序集合为REDIS_ZSET
unsigned encoding:4;//编码,标识底层数据结构,
//有序集合有REDIS_ENCODING_ZIPLIST(压缩列表)、REDIS_ENCODING_SKIPLIST(跳表)
//记录键最近一次被访问的时间,长时间不被访问的对象可被内存回收 unsigned lru:LRU_BITS;

/* LRU time (relative to global lru_clock) or * LFU data (least significant 8 bits frequency * and most significant 16 bits access time). */ int refcount;//引用计数,用于对象内存回收,
//当为0时回收内存,引用计数可实现不同键相同值的共享,
//事实上,redis会初始化创建0到9999个整数对象用于共享,从而节约内存
void *ptr;//指向底层数据结构实例的指针 } robj;

 

 

   有序列表有压缩列表ziplist和跳表skiplist两种实现方式,通过encoding识别,当数据项数目小于zset_max_ziplist_entries(默认为128),且保存的所有元素长度不超过zset_max_ziplist_value(默认为64)时,则用ziplist实现有序集合,否则使用zset结构,zset底层使用skiplist跳表和dict字典。创建有序集合的关键代码如下表2:

表2 创建有序集合

zaddGenericCommand函数
if (server.zset_max_ziplist_entries == 0 ||
server.zset_max_ziplist_value < sdslen(c->argv[scoreidx+1]->ptr))
        {
            zobj = createZsetObject(); //创建zset
        } else {
            zobj = createZsetZiplistObject();//创建ziplist
        }

 

  ziplist是一个内存连续的特殊双向链表LinkList,减少了内存碎片和指针的占用,用于节省内存,但对ziplist进行操作会导致内存的重新分配,影响性能,故在元素较少时用ziplist。ziplist内存布局如下:

<zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>

表3 ziplist在内存中各字节含义

Field
含义
zlbytes(uint32_t)
ziplist占用的内存字节数,包括zlbytes本身
zltail(uint32_t)
最后一个entry的offset偏移值
zllen(uint16_t)
数据项entry的个数
entry(变长)
数据项
zlend(uint8_t)
标识ziplist的结束,值为255

  数据项entry的内存结构如下:<prevlen> <encoding> <entry-data>,当保存的是小整型数据时,entry没有entry-data域, encoding本身包含了整型元素值。Entry各字节含义如下表4:

表4 entry各Field含义

Field
含义
prevlen
上一个数据项entry的长度。当长度小于254字节,则prevlen占1字节,当长度大于或等于254字节,则prevlen占5字节,首字节值为254,剩下4字节表示上一entry长度。
encoding
encoding的值依赖于数据entry-data。首字节的前两个bit为00、01、10,标识entry-data为字符串,同时表示encoding的长度分别为1、2、5字节,除前两个bit,剩下的bit表示字符串长度;前两个bit为11,表示entry-data为整型,接下来的2 bit表示整数类型。entry-data不同类型及encoding如下:
1)       |00pppppp| - 1 byte,字符串且长度小于等于63字节(6bit)
首页 上一页 1 2 3 4 5 下一页 尾页 1/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇高性能索引策略二 下一篇MySQL----navicat for mysql(破..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目