Redis数据类型全解析《三》

发表时间: 2023-10-09 17:20

往期系列文章:

1.彻底搞懂Redis系列《一》Redis简介&安装步骤&架构演变过程

2.彻底搞懂Redis系列《二》全量配置文件详细说明

目录

一、概述二、详解		1、字符串(string)		2、哈希(hash)		3、列表(List)		4、集合(set)		5、有序集合(sorted set)		6、位图(Bitmaps)		7、基数统计(HyperLogLog)		8、地理位置(GEO)		9、自定义数据类型三、总结

一、概述


redis中支持的数据类型主要分为三大类:五大基本数据类型、三大扩展数据类型、自定义数据类型:

Redis 所有的数据结构都是以唯一的key 字符串作为名称,然后通过这个唯一 key 值来获取相应的 value 数据。不同类型的数据结构的差异就在于 value 的结构不一样。

二、详解

1、字符串(string)

基本使用

string是redis最基本的类型,一个key对应一个value,一个key最大能存储512MB的value。

string类型是二进制安全的,在redis中的string可以包含任何数据,比如jpg图片或者序列化的对象 。下面演示用命令操作string类型的读写:

[root@XXX ~]# redis-cli -h 127.0.0.1 -p 6379127.0.0.1:6379> set key1 'hello redis'OK127.0.0.1:6379> get key1"hello redis"127.0.0.1:6379>

在以上实例中小郭使用了 Redis 的 SETGET 命令。键为‘key1’,对应的值为‘hello redis’。

底层结构

Redis 中的字符串是可以修改的字符串,在内存中它是以字节数组的形式存在的。Redis 的字符串叫「SDS」,也就是 Simple Dynamic String。它的结构是一个带长度信息的字节数组。

struct SDS<T> {T capacity; //表示所分配数组的长度,也就是数组最大容量 使用泛型表示的T len; // 表示字符串的实际占用长度 使用泛型表示的 byte flags; // 特殊标识位,不理睬它byte[] content; // 存储了真正的字符串内容 字节数组}

上面的 SDS 结构使用了范型 T,为什么不直接用 int 呢 ?

这是因为当字符串比较短时,len 和 capacity 可以使用 byte 和 short 来表示,Redis 为了对内存做非常极致的优化,不同长度的字符串使用不同的结构体来表示。

如上图所示,有几个点需要说明

  • redis内部为当前字符串实际分配的空间 capacity 一般要高于实际字符串长度 len.

初始创建字符串时 len 和 capacity 一样长,不会多分配冗余空间,这是因为绝大多数场景下我们不会使用 append 操作来修改字符串。

  • 当字符串长度小于 1M 时,扩容都是加倍现有的空间
  • 超过 1M,扩容时一次只会多扩 1M 的空间
  • 字符串最大长度为 512M
  • 字符串是由多个字节组成,每个字节又是由 8 个 bit 组成,可以说在redis中一个字符串是很多 bit 的组合

String 在 Redis 中有三种编码方式: int、embstr、raw 。其中, raw 和 embstr 类型,都是基于动态字符串(SDS)实现的,int类型是当存储内容为整数时使用:

127.0.0.1:6379> set key2 aOK127.0.0.1:6379> get key2"a"127.0.0.1:6379> object encoding key2"embstr"127.0.0.1:6379> set key2 1OK127.0.0.1:6379> object encoding key2"int"127.0.0.1:6379> set key2 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaOK127.0.0.1:6379> object encoding key2"raw"

在redis中使用 string 类型时,尽可能让其长度小于 44 字节,或者使用整数表示,使其使用 EMBSTR 和 INT 编码。

关于redis编码类型的相关知识,接下来会有专门的章节来总结。

常用命令

命令语法

描述

SET key value

设置指定 key 的值

GET key

获取指定 key 的值

GETRANGE key start end

返回 key 中字符串值的子字符

GETSET key value

将给定 key 的值设为 value ,并返回 key 的旧值(old value)

GETBIT key offset

对 key 所储存的字符串值,获取指定偏移量上的位(bit)

MGET key1 [key2..]

获取所有(一个或多个)给定 key 的值

SETBIT key offset value

对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)

SETEX key seconds value

将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。

SETNX key value

只有在 key 不存在时设置 key 的值

SETRANGE key offset value

用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。

STRLEN key

返回 key 所储存的字符串值的长度。

MSET key value [key value ...]

同时设置一个或多个 key-value 对。

MSETNX key value [key value ...]

同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。

PSETEX key milliseconds value

这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。

INCR key

将 key 中储存的数字值增一。

INCRBY key increment

将 key 所储存的值加上给定的增量值(increment) 。

INCRBYFLOAT key increment

将 key 所储存的值加上给定的浮点增量值(increment) 。

DECR key

将 key 中储存的数字值减一。

DECRBY key decrement

key 所储存的值减去给定的减量值(decrement) 。

APPEND key value

如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。

2、哈希(hash)

基本使用

Redis hash就是一个键值对集合,对应一个string类型的field和value的映射表。在 hash 类型中,field 与 value 一一对应,且不允许重复。

redis 本身就是一个 key-value 型数据库,因此 hash 数据结构相当于在 value 中又套了一层 key-value 型数据。

由于对象都有多个字段属性,hash特别适合用于存储对象,每个字段在映射表中对应一个field。使用示例如下:

127.0.0.1:6379> HMSET user username gyd age 18 phone 123OK127.0.0.1:6379> HGETALL user1) "username"2) "gyd"3) "age"4) "18"5) "phone"6) "123"127.0.0.1:6379>

以上示例中 hash 数据类型存储了包含用户脚本信息的用户对象。 我们使用了 Redis HMSET, HEGTALL 命令,user 为键值,映射表中了三个field分别是usernameagephone

每个 hash 可以存储 2^32^ - 1 键值对(40多亿)。

底层结构

hash 类型是 Redis 常用数据类型之一,其底层存储结构有两种实现方式。

1)第一种,当存储的数据量较少的时,hash 采用 ziplist (压缩列表)作为底层存储结构,需要同时满足以下两个条件:

  • 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节
  • 哈希对象保存的键值对数量小于512个

ziplist的数据结构主要包括两层,第一层ziplist和第二层zipEntry。

  • ziplist包括ZIP HEADER、ZIP ENTRY、ZIP END三个模块。
  • ZIP ENTRY由prevlen、encoding&length、value三部分组成。
  • prevlen主要是指前面zipEntry的长度,encoding&length是指编码字段长度和实际- 存储value的长度,value是指真正的内容。
  • 每个key/value存储结果中key用一个zipEntry存储,value用一个zipEntry存储。

上述每一部分在内存中都是紧密相邻的,并承担着不同的作用,具体含义总结如下:

  • ZIP_BYTES是一个无符号整数,表示当前 ziplist 占用的总字节数;
  • ZIP_TAIL指的是压缩列表尾部元素相对于压缩列表起始元素的偏移量。
  • ZIP_LENGTH指 ziplist 中 entry 的数量。当 zllen 比2^16 - 2大时,需要完全遍历 entry 列表来获取 entry 的总数目。
  • ZIP_Entry用来存放具体的数据项(member),长度不定,可以是字节数组或整数,entry 会根据成员的数量自动扩容。
  • ZIP_END是一个单字节的特殊值,等于 255,起到标识 ziplist 内存结束点的作用。

2)当无法满足上述条件时,hash 就会采用第二种方式来存储数据,也就是 dict(字典结构),也称为哈希表(hashtable),该结构类似于 Java 的 HashMap,是一个无序的字典,并采用了数组和链表相结合的方式存储数据。在 Redis 中,dict 是基于哈希表算法实现的,因此其查找性能非常高效,其时间复杂度为 O(1)。

哈希表又称散列表,其初衷是将数据映射到数组中的某个位置上,这样就能够通过数组下标来访问该数据,从而提高数据的查找效率。下面通过一个示例,了解一下到底什么是哈希表。

假设现在有 1/5/8/ 这三个业务数据数字,需要把这三个数字映射到数组中,由于哈希表规定必须使用下标来访问数据,因此需要构建一个 0 到 8 的数组,如下所示:

如上图所示,通常我们把待查找的数字,在相应的下标数组上标记出来,它们之间一一对应。

虽然这样做能实现元素的查找,但却很浪费存储空间,并且查找效率也不高。Redis采用了哈希表,我们只需要申请一个长度为 3 的数组(与待查找的元素个数相同),如下图所示:

如上图所示,将 1/5/8 分别对数组长度 3 做取模运算,然后把它们指向运算结果对应的数组槽位,这样就把一组离散的数据映射到了连续的空间中,从而在最大限度上提高了空间的利用率,并且也提高了元素的查找效率。但是会出现一个问题,数字 5、8 竟然映射到同一个槽位上,这样就导致其中一个数字无法查找到。上述这种情况在实际中也会遇到,我们习惯把它称为“哈希冲突”或者“哈希碰撞”。

有许多方法可以解决“哈希冲突”,比如开放地址法、链表地址法,再次散列法等,而 Redis 采用是链表地址法。如下图所示:

如上图所示,即使发生了冲突,Redis也可以将数据存储在一起,最后,通过遍历链表的方式就找到上述发生“冲突”的数据。如下所示:

常用命令

命令语法

描述

HDEL key field1 [field2]

删除一个或多个哈希表字段

HEXISTS key field

查看哈希表 key 中,指定的字段是否存在

HGET key field

获取存储在哈希表中指定字段的值

HGETALL key

获取在哈希表中指定 key 的所有字段和值

HINCRBY key field increment

为哈希表 key 中的指定字段的整数值加上增量 increment

HINCRBYFLOAT key field increment

为哈希表 key 中的指定字段的浮点数值加上增量 increment

HKEYS key

获取哈希表中的所有字段

HLEN key

获取哈希表中字段的数量

HMGET key field1 [field2]

获取所有给定字段的值

HMSET key field1 value1 [field2 value2 ]

同时将多个 field-value (域-值)对设置到哈希表 key 中

HSET key field value

将哈希表 key 中的字段 field 的值设为 value

HSETNX key field value

只有在字段 field 不存在时,设置哈希表字段的值

HVALS key

获取哈希表中所有值

HSCAN key cursor [MATCH pattern] [COUNT count]

迭代哈希表中的键值对,cursor 表示游标,默认为 0。

3、列表(List)

基本使用

Redis列表是简单的字符串列表,按照插入顺序排序,支持添加一个元素到列表的头部(左边)或者尾部(右边)。

一个列表最多可以包含 2^32^ - 1 个元素 (4294967295, 每个列表超过40亿个元素)。

Redis 的列表也常被用作异步处理。可以被当做栈、队列来使用,如果列表的元素是“左进右出”那就是队列模型;如果元素是“右进右出”那就是栈模型,例如:一个线程将需要异步处理的任务序列化成字符串,并从左侧“放”进 Redis 列表中,而另外一个线程则以轮询的方式从该列表右侧中读取“任务”,这就实现了先进先出的队列效果。

底层结构

Redis的列表相当于 Java 语言中的 LinkedList 结构,是一个双向链表而非数组,其插入、删除元素的时间复杂度为 O(1),但是查询速度欠佳,时间复杂度为 O(n)。

它的底层存储结构,其实是一个被称为快速链表(quicklist,双向链表)的结构。当列表中存储元素较少时,Redis 会直接使用一块连续的内存来存储这些元素,这个连续的结构被称为 ziplist(压缩列表,压缩列表是 Redis 为节省内存而开发的,它是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表了可以包含任意多个节点,每个节点都可以保存一个字符数组或者整数值),它将所有的元素紧挨着一起存储。如果元素非常多时,Redis 列表就会优化成用 quicklist(快速链表)存储元素。

正是因为单独使用普通链表存储元素时,所需的空间较大,会造成存储空间的浪费。Redis 设计者巧妙的采用了链表和压缩列表这两种方法相结合的方式来存储元素,也就是 quicklist + ziplist,结构如下图:

如上图 1 所示,将多个 ziplist 使用双向指针串联起来,这样既能满足快速插入、删除的特性,又节省了一部分存储空间。

常用命令

命令语法

描述

BLPOP key1 [key2 ] timeout

移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止

BRPOP key1 [key2 ] timeout

移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止

BRPOPLPUSH source destination timeout

从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止

LINDEX key index

通过索引获取列表中的元素

LINSERT key BEFORE | AFTER pivot value

在列表的指定元素前或者后插入元素

LLEN key

获取列表长度

LPOP key

移出并获取列表的第一个元素

LPUSH key value1 [value2]

将一个或多个值插入到列表头部

LPUSHX key value

将一个值插入到已存在的列表头部

LRANGE key start stop

获取列表指定范围内的元素

LREM key count value

移除列表元素

LSET key index value

通过索引设置列表元素的值

LTRIM key start stop

对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除

RPOP key

移除列表的最后一个元素,返回值为移除的元素

RPOPLPUSH source destination

移除列表的最后一个元素,并将该元素添加到另一个列表并返回

RPUSH key value1 [value2]

在列表中添加一个或多个值到列表尾部

RPUSHX key value

为已存在的列表添加值

4、集合(set)

基本使用

Redis的Set是string类型的无序集合,有点类似于 Java 中的 HashSet 。

Redis set 是通过哈希映射表实现的,所以它的添加、删除、查找操作的时间复杂度为 O(1)。集合中最多可容纳 2^32 - 1 个成员(40 多亿个)。set有一个非常重要的特性就是“自动去重”,这使得它可以适用于许多场景,比如过滤掉已中奖用户的 id,保证该用户不会被第二次抽中。

我们可以基于 Set 轻易实现交集、并集、差集的操作,比如可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。这样的话,Set 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程,redis都提供了基本的功能命令。

底层结构

set的底层存储结构有两种实现方式,分别是 intset(整型数组)与 hash table(哈希表)。

1)当 set 存储的数据满足以下要求时,使用 intset 结构:

  • 集合内保存的所有成员都是整数值;
  • 集合内保存的成员数量不超过 512 个。

Redis 中 intset 的结构体定义如下:

typedf struct inset{//指定编码方式,默认为INSET_ENC_INT16//共有三种,分别是 INTSET_ENC_INT16、INSET_ENC_INT32 和 INSET_ENC_INT64,它们对应不同的数值范围。Redis 为了尽可能地节省内存,它会根据插入数据的大小来选择不同的编码格式。uint32_t encoding;//集合内成员的数量,记录 contents 数组中共有多少个成员uint32_t length;//实际存储成员的数组,数组中的成员从小到大依次排列,且不允许重复。int8_t contents[];//实际存储成员的数组,并且数组中的数值从小到大依次排列}inset;

2)当不满足上述要求时,则使用 hash table 结构。

哈希表原理都大同小异,在前面总结哈希(hash)的时候已经说明过, set 的哈希表与其相似,这里不再重复总结。

常用命令

命令语法

描述

SADD key member1 [member2]

向集合添加一个或多个成员

SCARD key

获取集合的成员数

SDIFF key1 [key2]

返回第一个集合与其他集合之间的差异

SDIFFSTORE destination key1 [key2]

返回给定所有集合的差集并存储在 destination 中

SINTER key1 [key2]

求两个或多个集合的交集

SINTERSTORE destination key1 [key2]

求两个或多个集合的交集,并将结果保存到指定的集合中

SISMEMBER key member

判断 member 元素是否是集合 key 的成员

SMEMBERS key

返回集合中的所有成员

SMOVE source destination member

将 member 元素从 source 集合移动到 destination 集合

SPOP key

移除并返回集合中的一个随机元素

SRANDMEMBER key [count]

随机从集合中返回指定数量的元素,默认返回 1个

SREM key member1 [member2]

删除一个或者多个元素,若元素不存在则自动忽略

SUNION key1 [key2]

求两个或者多个集合的并集

SUNIONSTORE destination key1 [key2]

求两个或者多个集合的并集,并将结果保存到指定的集合中

SSCAN key cursor [MATCH pattern] [COUNT count]

迭代集合中的元素

5、有序集合(sorted set)

有序集合通常也被称为zset,下面的内容都使用zset来进行描述。

基本使用

zset和set类似,set该有的特点它都具备,唯一不同的一点是zset是有序的,set 是无序的,这是因为zset中每个成员都会关联一个 double(双精度浮点数)类型的 score (分数值),Redis 正是通过 score 实现了对集合成员的排序。

我们可以使用如下语法来创建一个zset:

127.0.0.1:6379> ZADD key score member [score member ...]
  • key:指定一个键名
  • score:分数值,用来描述 member,它是实现排序的关键,可以重复
  • member:要添加的成员(元素),不可以重复

zset中的成员(member)是唯一存在的,但是分数(score)却可以重复。zset的最大成员数为 2^32 - 1 (大约 40 多亿个)。

zset非常适用于排行榜类型的业务场景,比如 用户贡献榜、用户活跃度排行榜、QQ 音乐排行榜等。在音乐排行榜单中,我们可以将歌曲的点击次数作为 score 值,把歌曲的名字作为 value 值,通过对 score 排序就可以得出歌曲“热度榜单”。

底层结构

zset底层也使用了两种不同的存储结构,分别是 zipList(压缩列表)和 skipList(跳跃列表)。

1)当 zset 满足以下条件时使用压缩列表ziplist:

  • 成员的数量小于128 个;
  • 每个 member (成员)的字符串长度都小于 64 个字节

ziplist的结构在前面的哈希(hash)数据类型介绍时已经说明过,唯一不同的一点是:

在zset中,ZIP_Entry用来存放具体的数据项(score和member),而哈希hash的ZIP_Entry只存了member。

示例:

下面执行ZADD命令添加两个成员:gyd1(代码小郭1) 的存款是 2100.0;gyd2(代码小郭2)的存款是3400.0

[root@XXX ~]# redis-cli -h 127.0.0.1 -p 6379127.0.0.1:6379> zadd deposit 2100 gyd1 3400 gyd2(integer) 2127.0.0.1:6379>

内存中布局是如下的情况:

当 zset 使用上面的压缩列表ziplist保存数据时,zipentry 的第一个节点保存 成员member,第二个节点保存分数 score。依次类推,集合中的所有成员最终会按照 score 从小到大排列。

2)当zset不满足使用压缩列表的条件时,就会使用 跳跃表(skipList) 结构来存储数据。

跳跃列表(skipList)又称“跳表”是一种基于链表实现的随机化数据结构,其插入、删除、查找的时间复杂度均为 O(logN)。从名字可以看出“跳跃列表”,并不同于一般的普通链表,它的结构较为复杂,本节仅介绍最基础的知识。

在Redis 中一个 skipList 节点最高可以达到 64 层,一个“跳表”中最多可以存储 2^64 个元素,每个节点都是一个 skiplistNode(跳表节点)。skipList 的结构体定义如下:

typedf struct zskiplist{//指向 skiplist 的头节点指针,通过它可以直接找到跳表的头节点,时间复杂度为 O(1)struct zskiplistNode *header;//指向 skiplist 的尾节点指针,通过它可以直接找到跳表的尾节点,时间复杂度为 O(1)struct zskiplistNode *tail;// 记录 skiplist 的长度,也就跳表中有多少个元素,但不包括头节点unsigned long length;//记录当前跳表内所有节点中的最大层数(level)int level;}zskiplist;

跳跃列表的每一层都是一个有序的链表,链表中每个节点都包含两个指针,一个指向同一层的下了一个节点,另一个指向下一层的同一个节点。最低层的链表将包含 zset 中的所有元素。如果说一个元素出现在了某一层,那么低于该层的所有层都将包含这个元素,也就说高层是底层的子集

下图演示一个上下共四层的跳跃列表结构:

跳跃列表中的每个节点都存储着 S:V(即 score/value),示意图显示了使用跳跃列表查找 S:V 节点的过程。跳跃列表的层数由高到低依次排列,最低层是 L0 层,最高层是 L3 层,共有 4 层。

如上图所示,首先从最高层开始遍历找到第一个S:V节点,然后从此节点开始,逐层下降,通过遍历的方式找出每一层的 S:V 节点,直至降至最底层(L0)才停止。在这个过程中找到所有 S:V 节点被称为期望的节点。跳跃列表把上述搜索一系列期望节点的过程称为“搜索路径”,这个“搜索路径”由搜索到的每一层的期望节点组成,其本质是一个列表。

常用命令

命令语法

描述

ZADD key score1 member1 [score2 member2]

用于将一个或多个成员添加到有序集合中,或者更新已存在成员的 score 值

ZCARD key

获取有序集合中成员的数量

ZCOUNT key min max

用于统计有序集合中指定 score 值范围内的元素个数

ZINCRBY key increment member

用于增加有序集合中成员的分值

ZINTERSTORE destination numkeys key [key ...]

求两个或者多个有序集合的交集,并将所得结果存储在新的 key 中

ZLEXCOUNT key min max

当成员分数相同时,计算有序集合中在指定词典范围内的成员的数量

ZRANGE key start stop [WITHSCORES]

返回有序集合中指定索引区间内的成员数量

ZRANGEBYLEX key min max [LIMIT offset count]

返回有序集中指定字典区间内的成员数量

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT]

返回有序集合中指定分数区间内的成员

ZRANK key member

返回有序集合中指定成员的排名

ZREM key member [member ...]

移除有序集合中的一个或多个成员

ZREMRANGEBYLEX key min max

移除有序集合中指定字典区间的所有成员

ZREMRANGEBYRANK key start stop

移除有序集合中指定排名区间内的所有成员

ZREMRANGEBYSCORE key min max

移除有序集合中指定分数区间内的所有成员

ZREVRANGE key start stop [WITHSCORES]

返回有序集中指定区间内的成员,通过索引,分数从高到低

ZREVRANGEBYSCORE key max min [WITHSCORES]

返回有序集中指定分数区间内的成员,分数从高到低排序

ZREVRANK key member

返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序

ZSCORE key member

返回有序集中,指定成员的分数值

ZUNIONSTORE destination numkeys key [key ...]

求两个或多个有序集合的并集,并将返回结果存储在新的 key 中

ZSCAN key cursor [MATCH pattern] [COUNT count]

迭代有序集合中的元素(包括元素成员和元素分值)

6、位图(Bitmaps)

基本使用

Redis Bitmap 通过类似 map 结构存放 0 或 1 ( bit 位 ) 作为值。Redis 官方模拟了一个拥有 1 亿 2 千 8 百万用户的系统,然后使用 Redis 的位图来统计“日均用户数量”,最终所用时间的约为 50ms,且仅仅占用 16 MB内存。

Redis Bitmap适用于一些特定场景, 比如日活是否浏览过某个东西、用户每日签到、用户登录次数等。相比于直接使用字符串而言,位图中的每一条记录仅占用一个 bit 位,从而大大降低了内存空间使用率。

Redis 的bitmap数组是自动扩展的,如果设置了某个偏移位置超出了现有的内容范围,bitmap数组容量就会自动扩充。

下面演示如何使用bitmap。

我们设置一个key名称叫"keyone",值设置为"he", 字符"h"的八位二进制码是"01101000",字符"e"的八位二进制码是"01100101",两者的二进制连接起来是16位的二进制码0110100001100101,第一位的下标是 0,依次递增至 15,然后将二进制码中数字为 1 的位置标记出来,得到 位置1/2/4/9/10/13/15,我们把这组数字称为位的“偏置数”,最后按照上述偏置数对keyone 进行如下位图操作:

注意,key 的初始二进制位全部为 0。

127.0.0.1:6379> setbit keyone 1 1(integer) 0127.0.0.1:6379> setbit keyone 2 1(integer) 0127.0.0.1:6379> setbit keyone 4 1(integer) 0127.0.0.1:6379> get keyone"h"127.0.0.1:6379> setbit keyone 9 1(integer) 0127.0.0.1:6379> setbit keyone 10 1(integer) 0127.0.0.1:6379> setbit keyone 13 1(integer) 0127.0.0.1:6379> setbit keyone 15 1(integer) 0127.0.0.1:6379> get keyone"he"127.0.0.1:6379>

底层结构

bitmap本质上就是一个普通的字节串,也就是 bytes 数组。我们可以使用getbit/setbit命令来处理这个位数组,位图的结构如下所示:

如上图所示,bitmap本质上就是一个普通的字符串(字节串),也就是 bytes 数组。Redis 中一个字符串类型的值最多能存储 512 MB 的内容,每个字符串由多个字节组成,每个字节又由 8 个 Bit 位组成。bitmap结构正是使用“位”来实现存储的,它通过将比特位设置为 0 或 1来达到数据存取的目的,这大大增加了 value 存储数量,它存储上限为2^32^。bitmap操作的优势,相比于字符串而言,它不仅效率高,而且还非常的节省空间。

常用命令

命令语法

描述

SETBIT key offset value

用来设置或者清除某一位上的值,其返回值是原来位上存储的值。key 在初始状态下所有的位都为 0 ,其中 offset 表示偏移量,从 0 开始

GETBIT

用来获取某一位上的值,当偏移量 offset 比字符串的长度大,或者当 key 不存在时,返回 0

BITCOUNT key [start end]

统计指定位区间上,值为 1 的个数。通过指定的 start 和 end 参数,可以让计数只在特定的字节上进行。start 和 end 参数和 GETRANGE 命令的参数类似,都可以使用负数,比如 -1 表示倒数第一个位, -2 表示倒数第二个

7、基数统计(HyperLogLog)

Redis 2.8.9 版本中新增了 HyperLogLog 类型


基数定义:一个集合中不重复的元素个数就表示该集合的基数,比如集合 {1,2,3,1,2} ,它的基数集合为 {1,2,3} ,所以基数为 3。HyperLogLog 正是通过基数估计算法来统计输入元素的基数。

基本使用

HyperLogLog非常适用于海量数据的计算、统计,可以接受多个元素作为输入,并给出输入元素的基数估算值,其特点是:

  • 占用空间小

即使输入元素的数量或者体积非常非常大,计算基数所需的空间总是固定的、并且是很小的, HyperLogLog 只会根据输入元素来计算 基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素

  • 计算速度快

HyperLogLog采用了一种基数估计算法,因此,最终得到的结果会存在一定范围的误差(标准误差为 0.81%)。每个 HyperLogLog key 只占用 12 KB 内存,理论上可以存储大约2^64个值,而 set(集合)则是元素越多占用的内存就越多,两者形成了鲜明的对比 。

HyperLogLog 适合特定的使用场景,例如统计网站用户月活量,或者网站页面的 UV(网站独立访客)数据等。

当一个网站拥有巨大的用户访问量时,我们就可以使用 Redis 的 HyperLogLog 来统计网站的 UV (网站独立访客)数据,它提供的去重计数方案,虽说不精确,但 0.81% 的误差足以满足 UV 统计的需求。

使用示例:

[root@XXX ~]# redis-cli -h 127.0.0.1 -p 6379127.0.0.1:6379> PFADD uv:20231009 user1 user2 user3(integer) 1127.0.0.1:6379> PFCOUNT uv:20231009(integer) 3127.0.0.1:6379> PFADD uv:20231008 user4(integer) 1127.0.0.1:6379> PFCOUNT uv:20231008(integer) 1127.0.0.1:6379> PFMERGE uv:all uv:20231009 uv:20231008OK127.0.0.1:6379> PFCOUNT uv:all(integer) 4127.0.0.1:6379> PFCOUNT uv:20231009(integer) 3127.0.0.1:6379> PFADD uv:20231009 user1(integer) 0127.0.0.1:6379> PFCOUNT uv:20231009(integer) 3127.0.0.1:6379>

底层结构

HyperLogLog 的内部原理较为复杂,不建议大家深入研究,只要会用即可。小郭也不打算深入学习它啦!

常用命令

命令语法

描述

PFADD key element [element ...]

添加指定元素到 HyperLogLog key 中。

PFCOUNT key [key ...]

返回指定 HyperLogLog key 的基数估算值。

PFMERGE destkey sourcekey [sourcekey ...]

将多个 HyperLogLog key 合并为一个 key。

8、地理位置(GEO)

Redis GEO (全称geographic)主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增。

基本使用

GEO 有很多应用场景,比如外卖APP上会显示“店家距离你有多少米,打车APP上会显示司机师傅距离你有多远,类似这种功能就可以使用 Redis GEO 实现。数据库中存放着商家所处的经纬度,你的位置则由手机定位获取,这样 APP 就计算出了最终的距离。

微信中附近的人、摇一摇、实时定位等功能都依赖地理位置实现。

底层结构

GEO的底层通过 Redis 有序集合(zset)实现。不过它并没有与 zset 共用一套的命令,而是拥有自己的一套命令。

常用命令

1)GEOADD

语法:

GEOADD key longitude latitude member [longitude latitude member ...]

描述:添加地理位置的坐标

  • longitude:位置地点所处的经度;
  • latitude:位置地点所处的纬度;
  • member:位置名称

GEOADD命令能够记录的坐标数量是有限的,如果位置非常接近两极(南极/北极)区域,那么将无法被索引到。因此当您输入经纬度时,需要注意以下规则:

  • 有效的经度介于 -180 度至 180 度之间
  • 有效的纬度介于 -85.05112878 度至 85.05112878 度之间

2)GEODIST

语法:

GEODIST key member1 member2 [unit]

描述:

计算两个位置之间的距离,返回值为双精度浮点数,计算举例时存在 0.5% 左右的误差,这是由于 Redis GEO 把地球假设成了完美的球体。
参数 unit 是表示距离单位,取值如下所示:

  • m 表示单位为米;如果没有指出距离单位,那么默认取值为m
  • km 表示单位为千米;
  • mi 表示单位为英里;
  • ft 表示单位为英尺

3)GEORADIUS

语法:

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC]

描述:

以给定的经纬度为中心,计算出 key 包含的地理位置元素与中心的距离不超过给定最大距离的所有位置元素,并将其返回。

  • WITHDIST :在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。
  • WITHCOORD :返回位置元素的经度和维度。
  • WITHHASH :采用 GEOHASH 对位置元素进行编码,以 52 位有符号整数的形式返回有序集合的分值,该选项主要用于底层调试,实际作用不大。
  • COUNT:指定返回位置元素的数量,在数据量非常大时,可以使用此参数限制元素的返回数量,从而加快计算速度。

注意:该命令默认返回的是未排序的位置元素。通过 ASC 与 DESC 可让返回的位置元素以升序或者降序方式排列。

4)GEORADIUSBYMEMBER

语法:

GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DES]

描述:

根据给定的地理位置坐标(即经纬度)获取指定范围内的位置元素集合。

georadiusbymember 和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 georadiusbymember 的中心点是由给定的位置元素决定的, 而不是使用经度和纬度来决定中心点。

  • m :米,默认单位;
  • km :千米(可选);
  • mi :英里(可选);
  • ft :英尺(可选);
  • ASC:根据距离将查找结果从近到远排序;
  • DESC:根据距离将查找结果从远到近排序。

5)GEOHASH

语法:

GEOHASH key member [member ...]

描述:

Redis GEO 使用 geohash 来保存地理位置的坐标。geohash返回一个或多个位置元素的哈希字符串,该字符串具有唯一 ID 标识,它与给定的位置元素一一对应。

6)ZREM

语法:

zrem KEY member1 member2 member...

描述:

用于删除指定的地理位置元素

7)ZPOS

语法:

GEOPOS key member [member ...]

描述:

geopos 用于从给定的 key 里返回所有指定名称(member)的位置(经度和纬度),不存在的返回 nil。

9、自定义数据类型

这块在小郭的工作经历里从来没涉及过,这里就暂时不做总结啦,有兴趣的小伙伴们可以查阅官方文档了解。

三、总结

本文主要总结了redis的各种数据类型,概念比较多。

大家可千万不能死记硬背,小郭的习惯就是先把所有知识点按自己的方式总结成文章,然后在后续使用过程中再来查找对应的部分内容进行使用,大家也可以点赞关注收藏备用哦!!