redis的数据结构

登录redis客户端
[root@tspvggdb01-test1-rgtj1-tj1 bin]# ./redis-cli
127.0.0.1:6379> auth 123456

查看redis数据类型
127.0.0.1:6379> type name

查看redis底层数据类型:
127.0.0.1:6379> object encoding name

产看list数据结构提供的API
127.0.0.1:6379> help @list

一、string类型

数据结构:

在 Redis 3.2 版本以前,SDS 的结构如下:

struct sdshdr { 
	unsigned int len; //len  4字节,表示 buf 中已占用的字节数
	unsigned int free;//free 4字节,表示 buf 中剩余可用字节数。
	char buf[]; //buf 表示数据空间,用于存储字符串
};

4 字节的 len,可表示的字符串长度为 2^32,而在实际应用中,存放于 Redis 中的字符串往往没有这么长;在 Redis 3.2 版本之后,Redis 将 SDS 划分为 5 种类型: vim sds.h

struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* 已经使用长度8位 */
    uint8_t alloc; /*总长度8位 excluding the header and null terminator */
    unsigned char flags; /*前3位类型,后五位预留位 */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

编码格式:

如果value是一个字符类型的数据,当长度小于45的时候编码格式是embstr,大于等45的时候编码格式是raw

如果value是数值类型,当机器是64为操作系统,他的默认大小是一个64位的有符号数,如果数据长度小于20时编码格式是int,长度大于20时使用编码格式embstr

总结:对于string类型

  • 如果设置的值是一个可以转为long(整型)的数据,且长度小于20,redis会采用int类型的编码格式
  • 如果设置的值就是一个普通的字符串,当长度小于45的时候编码格式是embstr,长度大于等45的时候编码格式是raw

embstr编码:(长度小于44),embstr的创建只需分配一次内存,embstr的objet和sds放在一起,更好地利用缓存带来的优势
raw编码:(长度大于44),raw创建为两次一次为sds分配对象,另一次为objet分配对象redisObject内存不在连续,采用指针的形式,实现连接

字符串的扩容:当前字符串实际分配的空间,一般是要高于实际字符串长度的len。当字符串长度小于1M的时候,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩容1M的空间,需要注意的是字符串的最大长度为512M。

字符串的应用:

  • string串操作数值类型:可以利用reids的 incr、incrby、decr、decrby 等api做计数、统计功能,如秒杀存库存,统计点赞量
  • string可以操作字符串:用来做session共享,key-velue的缓存等
  • string可以操作bitmap:可以来用很小的空间,处理较大的数据;例如:统计上亿用户某天的登录情况,等等

二、List类型

list的特点:

  • 双向链表,便于头尾操作数据时间复杂度0(1),对于中间位置数据的操作时间复杂度0(n)
  • 按照数据的加入顺序

list的底层结构:quickList

  • quickList是一个以zipList类型为节点的双向链表,即每个节点是一个zipList类型;
  • ziplist是一个经过特殊编码的双向链表,它的设计目标就是为了提高存储效率。ziplist可以用于存储字符串或整数,其中整数是按真正的二进制表示进行编码的,而不是编码成字符串序列。它能以O(1)的时间复杂度在表的两端提供push和pop操作。实际上,ziplist充分体现了Redis对于存储效率的追求。一个普通的双向链表,链表中每一项都占用独立的一块内存,各项之间用地址指针(或引用)连接起来。这种方式会带来大量的内存碎片,而且地址指针也会占用额外的内存。而ziplist却是将表中每一项存放在前后连续的地址空间内,一个ziplist整体占用一大块内存。它是一个表(list),但其实不是一个链表(linked list)。另外,ziplist为了在细节上节省内存,对于值的存储采用了变长的编码方式,大概意思是说,对于大的整数,就多用一些字节来存储,而对于小的整数,就少用一些字节来存储。
  • quickList与zipList的关系,比如,一个包含3个节点的quicklist,如果每个节点的ziplist又包含4个数据项,那么对外表现上,这个list就总共包含12个数据项。

redis为什么要设计quicklist的结构:

  • 双向链表便于在表的两端进行push和pop操作,但是它的内存开销比较大。首先,它在每个节点上除了要保存数据之外,还要额外保存两个指针;其次,双向链表的各个节点是单独的内存块,地址不连续,节点多了容易产生内存碎片。
  • ziplist由于是一整块连续内存,所以存储效率很高。但是,它不利于修改操作,每次数据变动都会引发一次内存的realloc。特别是当ziplist长度很长的时候,一次realloc可能会导致大批量的数据拷贝,进一步降低性能。
  • redis就是综合了这两种结构的特点,设计了quickList

如何合理的调整ziplist的大小?

  • 每个quicklist节点上的ziplist越短,则内存碎片越多。内存碎片多了,有可能在内存中产生很多无法被利用的小碎片,从而降低存储效率。这种情况的极端是每个quicklist节点上的ziplist只包含一个数据项,这就蜕化成一个普通的双向链表了。
  • 每个quicklist节点上的ziplist越长,则为ziplist分配大块连续内存空间的难度就越大。有可能出现内存里有很多小块的空闲空间(它们加起来很多),但却找不到一块足够大的空闲空间分配给ziplist的情况。这同样会降低存储效率。这种情况的极端是整个quicklist只有一个节点,所有的数据项都分配在这仅有的一个节点的ziplist里面。这其实蜕化成一个ziplist了。通过调整list-max-ziplist-size可以控制每个ziplist的大小

优化列表:

  • list-max-ziplist-size 表示每个ziplist的字节大小。默认为-2 表示8KB
  • list-compress-depth 表示一个quicklist两端不被压缩的节点个数。默认是0 表示不压缩;2: 表示quicklist两端各有2个节点不压缩,中间的节点压缩。

list的使用:

redis 做消息队列的缺点:

  • 消息已经消费,就会在服务端删除,如果消费端失败,就会丢失
  • 做消费者确认ACK麻烦
  • 队列不去重(不能严格意义说是缺点)

三、hash结构

redis的hash数据类型的底层存储使用ziplist(压缩列表)和hashtable。当hash对象可以同时满足以下两个条件时,使用的是ziplist

  • hash-max-ziplist-entries 512  当hash中的数据项的数目超过512的时候,也就是ziplist数据项超过1024的时候。
  • hash-max-ziplist-value 64      当hash中插入的任意一个value的长度超过了64的时候

先修改配置文件 hash-max-ziplist-entries=5

当 key的数据条目大于5时数据类型转为hashtable

ziplist转hashtable:当hash转为hashtable时会占用较大的内存,并且在hashtable扩容时也会影响一定的性能

redis中的set与hash:

  • set以普通的key-value键值对的方式存储,可以设置过期时间,时间复杂度为O(1),每执行一个set就会在Redis中多出一个key
  • hset是以哈希散列表的形式存储,超时时间只能设置在键key上,单个域field不能设置过期时间。时间复杂度为O(n)n是单个哈希上的field域个数。所以,单个哈希并不适合存储大量的字段field,过多的字段field会比较消耗CPU。但优点在于散列表存储会比较节省内存。通过 hash-max-ziplist-entries 可以控制ziplist上每个field于的个数
  • 由此我们知道为什么对于同一个对象结构的数据建议使用hash而不是set 多个属性

四、set结构

set的特点:无序的、自动去重的集合数据类型,并且可以处理多个集合的交集、并集、差集,Set底层用两种数据结构存储,一个是hashtable,一个是inset。

set使用inset作为存储结构的条件:

  • 结合对象保存的所有元素都是整数值
  • 集合对象保存的元素数量不超过512个(默认值)(set-max-intset-entries 512)

总结:Redis中set数据类型当存储整数集合并且数据量较小的情况下Redis会使用intset作为set的底层实现;当数据量较大或者集合元素为字符串时则会使用dict(hashtable)实现set。

intset:intset底层是一个整数类型的数组intset将整数元素按顺序存储在数组里,并通过二分法降低查找元素的时间复杂度。数据量大时,依赖于“查找”的命令(如SISMEMBER)就会由于O(logn)的时间复杂度而遇到一定的瓶颈,所以数据量大时会用dict(hashtable)来代替intset。但是intset的优势就在于比dict更省内存,而且数据量小的时候O(logn)未必会慢于O(1)的hash function。

set中的hashtable:就是key作为set的值,value=null,类似java中的hashset

set的应用场景:

1. 好友/关注/粉丝/感兴趣的人集合

  • sinter命令可以获得A和B两个用户的共同好友
  • sismember命令可以判断A是否是B的好友
  • scard命令可以获取好友数量
  • 关注时,smove命令可以将B从A的粉丝集合转移到A的好友集合
  • 在实战中使用Redis Cluster时,sinter,smove这两个个命令其实是不适合作用于两个不同用户对应的集合的,因为在sinter,smove 操作的集合需要都在同一个slot(槽位)中

2、随机抽取(抽奖):

点击参与抽奖加入集合
SADD  key  {userID}

查看参与抽奖所有用户
SMEMBERS key

抽取count名中奖者
SRANGEMEMBER key [count]  #不会从原集合中删除元素,适合一次性抽取所有中奖用户

STOP key [count] #会从原集合中删除元素,适合分批抽取中奖用户

3、黑名单/白名单:经常有业务出于安全性方面的考虑,需要设置用户黑名单、ip黑名单、设备黑名单等,set类型适合存储这些黑名单数据,sismember命令可用于判断用户、ip、设备是否处于黑名单之中

五、zset类型

zset 有序(默认字典顺序也可以设置评分,按照评分排序) 、无重复元素;zset底层的存储结构包括ziplist或skiplist,在同时满足以下两个条件的时候使用ziplist,其他时候使用skiplist:

  • zset-max-ziplist-entries 5 有序集合保存的元素数量小于128个(默认值)
  • zset-max-ziplist-value 64  有序集合保存的所有元素的长度小于64字节

ziplist:ziplist编码的有序集合使用紧挨在一起的压缩列表节点来保存,第一个节点保存member,第二个保存score。ziplist内的集合元素按score从小到大排序,score较小的排在表头位置。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值