【Redis 03】Redis常用数据对象类型

Redis常见对象有8种:

  • 字符串对象 -string
  • 列表对象 - list
  • 哈希对象-hash
  • 集合对象-set
  • 有序集合对象-zset
  • Bitmap
  • Geo
  • Hyperloglog

0、Redis对象介绍

Redis中的键值都是用对象表示的,新建键值时,每次都会至少创建两个对象:键对象、值对象。

Redis中的对象redisObject定义如下:

typedef struct redisObject {
    unsigned type:4; // 对象类型, string、list、hash、set、zset
    unsigned encoding:4; //  编码方式
    unsigned lru:LRU_BITS; 
    int refcount; //引用次数
    void *ptr; // 数据指针
} robj;
  • 类型type:使用TYPE key,会分别输出: string、list、hash、set、zset,对应5种类型的对象。
  • encoding:有字符串(int、embstr、raw)、字典(hashtable)、双端链表(linkedlist)、压缩列表(ziplist)、整数集合(intset)、跳跃表和字典(skiplist)。

一、字符串对象(stirng)

字符串对象的编码可以是int、raw、embstr 3种。

1、int:当字符串对象保存整数值时,使用int编码方式,值会保存到ptr属性中(省去了ptr指针占用的空间):
在这里插入图片描述

2、raw:当字符串对象保存字符串值,且字符串 > 39字节,使用SDS简单动态字符串来保存,编码方式为raw:
在这里插入图片描述

3、embstr:用于保存短字符串(<=39字节)。

与raw编码区别:raw编码会调用两次内存分配函数创建redisObject和sds。embstr只需要调用一次内存分配函数来分配一块连续的空间(减少内存碎片)。
在这里插入图片描述

常用操作命令:

set key value
get key
mset key1 value1 keys2 value2
mget key1 key2...
incrby key integer :将key的值 + integer值
dncrby key integer :将key的值 - integer值  

二、列表对象(list)

列表对象编码是 ziplist 或 linkedlist。

当列表对象保存的是字符串元素,且:

  • 字符串长度 < 64字节
  • 字符串个数 < 512个

两者必须满足才会使用ziplist,否则用linkedlist。

1、ziplist (压缩列表)

RPUSH numbers 1 "three" 5
在这里插入图片描述

2、linkedlist (双端链表)
在这里插入图片描述

list对象常用命令:

l - left  r - right
lpush key value1 [value2]
rpush key value1 [value2]

三、哈希对象(hash)

哈希对象编码可以是 ziplist 或 hashtable。

当哈希对象保存的键值对 键值字符串长度均 < 64字节;键值对个数都小于 < 512个,使用ziplist;否则会使用hashtable。

1、ziplist

使用压缩列表保存哈希对象时,先保存键对象,再保存值对象。新添加的元素放到表尾。
在这里插入图片描述

如 集合key为player,集合value中的key、value可以是 basketball - James; baskterball - Jordan; football - belly

即:hset player baskterball James

​ hset player baskterball Jordan

	hset player football  belly

=> hset key k v

2、hashtable

键值都是用字符串对象进行保存:
在这里插入图片描述

常用命令:

存储:hset book name "Java"
			hset book price "100"
获取:hget book name ==> Java
     hget book price ==> 100

四、集合对象(set)

集合对象编码使用intset 和 hashtable。

当集合对象保存的元素都是整数值 && 元素个数不超过512个时采用intset,否则使用hashtable。

1、intset:都是存储整数时,使用该编码方式;
在这里插入图片描述

2、hashtable:只使用字典键保存,每个键都是字符串对象,值设置为null。

常用命令:

sadd numbers 1 2 3
获取集合所有元素:smembers numbers
删除元素:srem numbers 2
获取集合个数:scard numbers 

五、有序集合对象(zset)

有序集合编码使用ziplist和 skiplist。

元素个数< 128 && 长度小于64字节时,使用skiplist编码。

1、ziplist

压缩列表将元素值和分数一起保存。压缩列表中的元素按分值从小到大排序,分值较小的元素放在靠近表头的位置。

如:zadd price 8.5 apple 7.2 banana 2 cherry,key为price, value为[8.5-apple]、[7.2-banana]、[2-chery]。
在这里插入图片描述

2、skiplist

zset同时使用 字典+跳跃表的方式实现有序集合:zsl + dict。

(1)zsl:跳跃表节点object属性保存元素,score保存分值。通过跳跃表可以对有序集合进行范围查询,便于范围查找。

(2)dict:保存了对象 与 分值的映射,目的:便于查找,查找O(1)
在这里插入图片描述

注:为什么有序集合同时使用跳跃表和字典?

=》 如果只使用zsl,在查找某个成员时时间复杂度会从O(1)变为O(logn)。如果只使用dict,在进行范围查找时,需要先遍历所有元素分值排序,最好时间复杂度是O(nlogn),同时需要额外的空间复杂度O(k)进行保存。

特殊对象类型

  • BitMap
  • Geo
  • HyperLogLog

1、BitMap - 省空间、适用于2值状态统计,即0或1统计如签到,要么签了,要么没签到

位图不是一个真实的数据类型,而是定义在字符串类型上的面向位的操作的集合。由于字符串类型是二进制安全的二进制大对象,并且最大长度是 512MB,适合于设置 2^32个不同的位。

位图操作是用来操作比特位的,其优点是节省内存空间。为什么可以节省内存空间呢?假如我们需要存储100万个用户的登录状态,使用位图的话最少只需要100万个比特位(比特位1表示登录,比特位0表示未登录)就可以存储了,而如果以字符串的形式存储,比如说以userId为key,是否登录(字符串“1”表示登录,字符串“0”表示未登录)为value进行存储的话,就需要存储100万个字符串了,相比之下使用位图存储占用的空间要小得多,这就是位图存储的优势。

BitMap底层通过对字符串的操作来实现。String 类型是会保存为二进制的字节数组,所以,Redis 就把字节数组的每个 bit 位利用起来,用来表示一个元素的二值状态。你可以把 Bitmap 看作是一个 bit 数组。

语法:SETBIT key offset value

比如统计某用户一年打卡的天数。key为该用户id,value为365位的Bitmap,每一个bit对应该用户当天的签到情况,1-代表已签到,11000011代表用户号,0 1 2…360代偏移量,分别代表第1、2、3、360天。

比如第一天:setbit user:sign:11000011:2020 0 1

第2天:setbit user:sign:11000011:2020 1 1,

第3天:setbit,

最后统计该用户一年的打卡次数:

BITCOUNT uid:sign:11000011:2020

再比如,统计1亿个用户10月份的签到情况。key为每一天的日期,31个key;value为1亿位的Bitmap,1代表已签到。

10.01:

// 3个用户在10月1号的签到状态
setbit user:sign:20201001 user01  1
setbit user:sign:20201001 user02  0
setbit user:sign:20201001 user03  1

10.02:

// 3个用户在10月2号的签到状态
setbit user:sign:20201002 user01  1
setbit user:sign:20201002 user02  0
setbit user:sign:20201003 user03  1

统计对31个Bitmap进行与操作,得到最后的Bitmap,通过bitcount获取结果。

BITCOUNT uid:sign:20201001 
+
BITCOUNT uid:sign:20201002
+
BITCOUNT uid:sign:20201003

1个1亿为的Bitmap占用的内存为:10^8 bit = 10 ^8 / 8 = 1.25 * 10^7 字节 = 1.2207 * 10^4 KB = 12 MB

https://mp.weixin.qq.com/s?spm=a2c6h.12873639.0.0.5c245c15Ac1CVJ&__biz=MzAxNjM2MTk0Ng==&mid=2247484427&idx=1&sn=cb810acc286b9f85796ef4dc35587309&chksm=9bf4b4beac833da86eff09b2f68195930e5fd1c374448c7b22b16725a4ec717dacac63ba1da7&scene=21#wechat_redirect - 如何优雅地使用Redis之位图操作

2、HyperLogLog

HyperLogLog 是一种用于统计基数的数据集合类型,它的最大优势就在于,当集合元素数量非常多时,它计算基数所需的空间总是固定的,而且还很小。

在 Redis 中,每个 HyperLogLog 只需要花费 12 KB 内存,就可以计算接近 2^64 个元素的基数。你看,和元素越多就越耗费内存的 Set 和 Hash 类型相比,HyperLogLog 就非常节省空间。

比如我们统计一个网页页面访问人数,可以使用Set类型去重统计。但是随着元素越多占用的内存就越大,可以使用HyperLogLog进行统计:

PFADD page1:uv user1 user2 user3 user4 user5,查询:PFCOUNT page1:uv

实现原理:HyperLogLog算法、https://blog.csdn.net/u011489043/article/details/78727128

大概思想是:统计一组数据中不重复元素的个数,集合中每个元素经过hash函数后可以表示成0和1构成的二进制数字串。二进制串中0和1出现的次数就有点像抛硬币实现,通过局部不同的数组估算整体。会有误差,在0.8左右。

3、Geo

Geo底层基于Sorted Set实现,GEO 类型是把经纬度所在的区间编码作为 Sorted Set 中元素的权重分数。

如何编码?

采用GeoHash,即”二分区间,区间编码“的方法,对一组经纬度分别对经度和纬度编码,最终合成一个最终编码。

对经纬度区间不断二分,落在左区间的设置为0,落在右区间的设置为1.
在这里插入图片描述

最终编码的规则是:最终编码值的偶数位上依次是经度的编码值,奇数位上依次是纬度的编码值,其中,偶数位从 0 开始,奇数位从 1 开始。

1110011101。

总结、几种对象使用的数据结构
在这里插入图片描述

几种对象应用场景

  1. string:最常见的一种数据类型,普通的KV存储。

    比如存放用户信息,key为用户id,value可以json格式存储:set userid {“name”:“aaa”,“age”:“123”}

    唯一id生成:先初始化:set uniqueid 1,每次获取都执行incr uniqueid返回递增后的id值,单线程处理不用考 虑并发;

  2. list:

    重试队列,通过list添加任务;执行任务时不断查询list有无待执行任务。

  3. set:

    主要是去重。一些需要去重的场景可以使用set,比如统计、过滤功能。小数据量的统计可以使用set。

  4. zset:

    排行榜。如电影排行榜:zadd movie_rank_board 1 功夫 2 逃学威龙 3 大话西游

  5. hash:

    秒杀场景下,不同商家的的不同商品限购xx件。可以采用 商家id作为key;参与抢购的商品id作为field,对应抢购数量作为value存储。如: hmset 781287 product-001 100 product-002 50, 抢购时,执行扣减操作:

内存回收

Redis在对象上加上引用计数器实现内存回收机制:

  • 创建一个对象时,引用计数器refCount = 1;
  • 当对象被引用时,refCount++;
  • 使用完了之后,refCount–;
  • refCount = 0时会被释放。

值对象共享- 只针对整数值的对象字符串才会进行对象共享

当键A创建一个整数值100的字符串对象作为值对象时, A <===> 100;键B也需要创建一个值100的字符串对象作为值对象时,Redis会将键A、B指向同一个字符串对象100,同时100这个字符串对象的refCount++ (=3,初始为1,被服务器引用)。
在这里插入图片描述

注:为什么不共享字符串数组的对象?

==》 Redis想要将一个共享对象设置为某个键的值对象时,需要检查这个共享对象是否是想要的。

因此这个共享对象保存的值不能太复杂,否则在验证的时候时间复杂度会过高。目前Redis只对包含整数值的字符串对象进行共享,验证时间复杂度是O(1)。


参考:

《Redis设计与实现》

《Redis核心技术与实战 》

猜你喜欢

转载自blog.csdn.net/noaman_wgs/article/details/114989655