Redis系列 - Redis基本数据类型

Redis系列 - Redis基本数据类型与操作

  • 字符串String
  • 散列Hash(key-filed-value)
  • 列表List
  • 集合Set
  • 有序集合sorted set(zSet)

字符串String


字符串是Redis中最常用的类型,是一个由字节组成的序列,它在Redis中是二进制安全的,这便意味着该类型可以接受任何格式的数据。Value最多可以容纳的数据长度为512MB。

  • set (set key value)
  • get (get key)
  • setnx(setnx key value)   如果key存在则设置失败
  • mset(mset key1 value1 key2 value2 ... )   批量设置键值对
  • msetnx(mset key1 value1 key2 value2 ... )   批量设置键值,如果key存在则设置失败
  • getset(getset key newValue)   获取key的值然后设置新的值
  • mget(mget key1 key2, key3)   批量获取
  • incr(incr key)   自增1
  • incrby(incrby key num)   指定增加的数量
  • decr(decr key)   自减1
  • decrby(decby key num)   指定减少的数量
  • append(append key value)   给指定的字符串追加value的值
  • strlen(strlen key)   获取指定的key对应的值长度

当我们存储一个string类型的键值对时会占用多少的空间呢?Redis 会使用一个全局哈希表保存所有键值对,哈希表的每一项是一个 dictEntry 的结构体,用来指向一个键值对。dictEntry 结构中有三个 8 字节的指针,分别指向 key、value 以及下一个 dictEntry,三个指针共 24 字节,但是会申请32字节,RedisObject的元数据和ptr指针分别占用8字节,共16字节,简单动态字符串(SDS)的len占用4字节,alloc占用4字节,buf存储实际数据,以raw编码的string为例,示意图如下:

为什么dicEntry会申请32字节呢?

jemalloc 在分配内存时,会根据我们申请的字节数 N,找一个比 N 大,但是最接近 N 的 2 的幂次数作为分配的空间,这样可以减少频繁分配的次数。举个例子。如果你申请 6 字节空间,jemalloc 实际会分配 8 字节空间;如果你申请 24 字节空间,jemalloc 则会分配 32 字节。所以,在我们刚刚说的场景里,dictEntry 结构就占用了 32 字节。

当embstr和raw类型的存储的string数据时需要消耗80字节的额外存储空间,而int类型的需要消耗48字节的额外的存储空间,所以当需要存储的key-value的数据很小时(如key=5,value=10这种单值的键值对),string类型会消耗更多的空间,string类型就不再适合了,具体用什么存储在本文后面的章节会详细介绍。

散列Hash


Redis中的散列可以看成具有String key和String value的map容器,可以将多个key-value存储到一个key中。每一个Hash可以存储4294967295个键值对。

  • hset(hset key field value)   给指定的key添加key-value元素
  • hget(hget key field)   获取指定的key中field字段的值
  • hsetnx(hsetnx key field value)   如果key和field不存在进行插入,如果key和field都存在不进行插入
  • hexists(hexists key field)   判断指定的key中是否存在field这个字段
  • hlen(hlen key)   获取哈希表中字段的数量
  • hdel(hdel key field1 field2 ... )   删除指定的key中的指定的字段和对应的值
  • hincrby(hincrby key field count)   给指定的key中的field的字段添加或减去count这个值
  • hgetall(hgetall key)   获取key中所有的键值对,返回的是一个键值对
  • hkeys(hkeys key)   获取指定的key中的所有字段
  • hmget(hmget key field1 field2 ...)   获取指定的key中的指定字段的值
  • hmset(hmset key field1 value1 field2 value2 ...)   同时设置多个键值对数据
  • hvals(hvals key)   获取指定的key中所有的value

在Redis常用的五种数据类型与底层数据结构对应关系图中,Redis Hash 类型的两种底层实现结构,分别是压缩列表和哈希表。

那么,Hash 类型底层结构什么时候使用压缩列表,什么时候使用哈希表呢?其实,Hash 类型设置了用压缩列表保存数据时的两个阈值,一旦超过了阈值,Hash 类型就会用哈希表来保存数据了。

这两个阈值分别对应以下两个配置项:

  • hash-max-ziplist-entries:表示用压缩列表保存时哈希集合中的最大元素个数。
  • hash-max-ziplist-value:表示用压缩列表保存时哈希集合中单个元素的最大长度。

如果我们往 Hash 集合中写入的元素个数超过了 hash-max-ziplist-entries,或者写入的单个元素大小超过了 hash-max-ziplist-value,Redis 就会自动把 Hash 类型的实现结构由压缩列表转为哈希表。一旦从压缩列表转为了哈希表,Hash 类型就会一直用哈希表进行保存,而不会再转回压缩列表了。在节省内存空间方面,哈希表就没有压缩列表那么高效了。

当存储单值键值对时,我们可以使用Hash散列的方式进行存储,只要hash-max-ziplist-entries和hash-max-ziplist-value不超过设置的值,那么基本存储单值键值对只需要16字节就可以完成,避免string类型需要使用64字节浪费空间。

列表List


Redis的集合是无序不可重复的,此处的无序是数据不能重复。和列表一样,在执行插入和删除以及判断是否存在某元素时,效率是很高的。集合最大的优势在于可以进行交集并集差集操作。Set可包含的最大元素数量是4294967295。

  • lpush (lpush key value1 value2 ...)   往list集合中压入元素
  • linsert (insert key before/after value newValue)   往指定元素前或后插入元素
  • lset (lset key index newValue)   设置指定下标下的值
  • lrem (lrem key count value)   删除count个与value相同的元素,count>0从开始位置进行删除,count<0从末尾开始删除,count=0删除所有
  • ltrim (ltrim key startIndex endIndex)   删除指定范围内以外的元素,保留指定范围内的元素
  • lpop (lpop key)   从list的头部删除元素
  • lindex (lindex key index)   返回指定索引处的元素
  • llen (llen key)   返回列表的长度
  • rpush (rpush key value)   从末尾压入元素
  • rpop (rpop key)   从末尾删除元素
  • rpoplpush (rpoplpush key1 key2)   从key1链表中弹出最后一个元素然后压入到key2链表中

创建新列表时 redis 默认使用 redis_encoding_ziplist 编码, 当以下任意一个条件被满足时, 列表会被转换成 redis_encoding_linkedlist 编码:

  • 试图往列表新添加一个字符串值,且这个字符串的长度超过 server.list_max_ziplist_value (默认值为 64 )。
  • ziplist 包含的节点超过 server.list_max_ziplist_entries (默认值为 512 )。

这两种存储方式的优缺点

  • 双向链表linkedlist便于在表的两端进行push和pop操作,在插入节点上复杂度很低,但是它的内存开销比较大。首先,它在每个节点上除了要保存数据之外,还要额外保存两个指针;其次,双向链表的各个节点是单独的内存块,地址不连续,节点多了容易产生内存碎片。
  • ziplist存储在一段连续的内存上,所以存储效率很高。但是,它不利于修改操作,插入和删除操作需要频繁的申请和释放内存。特别是当ziplist长度很长的时候,一次realloc可能会导致大批量的数据拷贝。

Redis3.2+ list的新实现quickList

Redis中的列表list,在版本3.2之前,列表底层的编码是ziplist和linkedlist实现的,但是在版本3.2之后,重新引入 quicklist,列表的底层都由quicklist实现。

在版本3.2之前,当列表对象中元素的长度比较小或者数量比较少的时候,采用ziplist来存储,当列表对象中元素的长度比较大或者数量比较多的时候,则会转而使用双向列表linkedlist来存储。

可以认为quickList,是ziplist和linkedlist二者的结合;quickList将二者的优点结合起来。
quickList是一个ziplist组成的双向链表。每个节点使用ziplist来保存数据。本质上来说,quicklist里面保存着一个一个小的ziplist。结构如下:

quickList就是一个标准的双向链表的配置,有head 有tail。
每一个节点是一个quicklistNode,包含prev和next指针。
每一个quicklistNode 包含 一个ziplist,*zp 压缩链表里存储键值。
所以quicklist是对ziplist进行一次封装,使用小块的ziplist来既保证了少使用内存,也保证了性能。

集合Set


Redis的集合是无序不可重复的,此处的无序是数据不能重复。和列表一样,在执行插入和删除以及判断是否存在某元素时,效率是很高的。集合最大的优势在于可以进行交集并集差集操作。Set可包含的最大元素数量是4294967295。

  • sadd (sadd key member1 member2 ...)
  • scard (scard key)
  • sismembers (sismembers key)
  • smembers (smembers key)
  • spop (spop key)
  • srem (srem key member1 member2 ...)
  • smove (smove source desition member)
  • sdiff (sdiff key1 key2)
  • sdiffstore (sdiffstore destionset key1 key2)
  • sinter (sinter key1 key2 ...)
  • sinterstore (sinterstore destionset key1 key2 ...)
  • sunion (sunion key1 key2 ...)
  • sunionstore (sunionstore destionset key1 key2)

集合对象 set 是 string 类型(整数也会转换成string类型进行存储)的无序集合。注意集合和列表的区别:集合中的元素是无序的,因此不能通过索引来操作元素;集合中的元素不能有重复。

集合对象set有两种实现:哈希表(hashtable)和整数数组(intset)。

intset 编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都被保存在整数集合中。

hashtable 编码的集合对象使用 字典作为底层实现,字典的每个键都是一个字符串对象,这里的每个字符串对象就是一个集合中的元素,而字典的值则全部设置为 null。这里可以类比Java集合中HashSet 集合的实现. HashSet 集合是由 HashMap 来实现的,集合中的元素就是 HashMap 的key,而 HashMap 的值都设为 null。

何时使用整数集合(intset),何时使用哈希表(hashtable):

  • 集合对象中所有元素都是整数
  • 集合对象所有元素数量不超过512

当满足上面的两个条件中的任何一个时会使用整数集合,否则使用哈希表。

有序集合sorted set:


和set很像,都是字符串的集合,都不允许重复的成员出现在一个set中。他们之间差别在于有序集合中每一个成员都会有一个分数(score)与之关联,Redis正是通过分数来为集合中的成员进行从小到大的排序。尽管有序集合中的成员必须是唯一的,但是分数(score)却可以重复。

  • zadd (zadd key score1 member1 score2 member2 ...)   添加成员
  • zcard (zcard key)   计算元素个数
  • zincrby (zincrby key number member)   给指定的member的分数添加或减去number这个值
  • zcount (zcount key min max)   获取分数在min和max之间的成员和数量;默认是闭区间
  • zrange (zrange key start stop [WITHSCORES])   返回指定排名之间的成员(结果是分数由低到高)
  • zrevrange (zrevrange key start stop [WITHSCORES])   返回指定排名之间的成员(结果是分数由高到低)
  • zrangebyscore (zrangebyscore key min max [withscores] [limit offset count])   根据分数的范围获取成员,按照分数由低到高
  • zrevrangebyscore (zrevrangebyscore key min max [withscores] [limit offset count])   根据分数的范围获取成员,按照分数由高到低
  • zrank (zrank key member)   返回一个成员的排名,从低到高
  • zrevrank (zrevrank key member)   返回一个成员的排名,从高到低
  • zscore (zscore key member)   获取一个成员的分数
  • zrem (zrem key member1 member2 ...)   删除指定的成员

有序集合sorted set有两种实现方式:压缩列表和跳表。那么这两种数据结构是如何使用的呢?

首先,我们来看这两个参数:

  • zset-max-ziplist-entries 128
  • zset-max-ziplist-value 64

ziplist 这种数据结构,顾名思义:压缩列表,怎么个压缩法,简单来说就是对于大的数据会用比较多点字节来存储,对于小的数据就用比较少的字节来存储。

当field-value对的数量大于ziplist.entries.size或者任意一个filed或value长度大于zset-max-ziplist-value时会使用zset进行存储

typedef struct zset {
    dict *dict;
    zskiplist *zsl;
} zset;

zset 结构体里有两个元素,一个是 dict,用来维护 数据 到 分数 的关系,一个是 zskiplist,用来维护分数所在链表的关系。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值