Redis 数据类型
信息来源
官方文档:https://redis.io/docs/data-types
Java 全栈知识体系:https://www.pdai.tech/md/db/nosql-redis/db-redis-data-types.html
前言
对 redis 来说,所有的 key(键)都是字符串。我们在谈基础数据结构时,讨论的是存储值的数据类型,主要包括常见的 5 种数据类型,分别是:String、List、Set、Zset、Hash。
除了上述常见的数据类型,Redis 还提供了其他一些特殊的数据类型,如地理位置(Geospatial)、位图(Bitmaps)和流(Stream)等,这些数据类型可以满足更复杂的应用需求。
基础数据类型:
String
:字符串是最基本的数据类型,可以存储任意类型的数据,例如文本、数字等。字符串类型支持各种操作,如设置值、获取值、追加、增减等。List
:列表是一个按照插入顺序排序的字符串元素集合。可以在列表的两端进行元素的插入和删除操作,支持类似栈(LIFO)或队列(FIFO)的操作。Hash
:以无序的键值对形式存储数据,类似于 Java 的 HashMap,每个哈希对象可以存储多个字段(field)和对应的值(value),字段和值之间是一一对应的关系。Set
:集合是一个无序且唯一的字符串集合。集合类型提供了判断成员是否存在、添加、删除、求交集、并集、差集等操作,适用于需要快速查找、去重或者计算集合间的交并差等场景。Sorted Set
:又叫 ZSet,是一个有序且唯一的字符串集合。与集合类型相比,有序集合每个成员都关联一个分数,用于进行排序。有序集合可用于实现排行榜、优先级队列等功能。
特殊数据类型:
Geospatial
:Redis 的地理位置数据类型可以存储地理位置坐标,并支持进行地理位置的查询和计算。通过使用地理位置数据类型,可以实现附近的人、地点搜索以及地理围栏等功能。Bitmaps
:位图是由二进制位组成的数据结构,可以对某个对象的单个位进行设置、获取和操作。位图类型常用于记录用户在线状态、统计访问频次、判断是否存在等应用场景。Stream
:流是 Redis 5.0 版本中引入的一种高级数据结构,它允许按时间顺序存储多个事件,并支持按范围查询、消费者群组、消息发布与订阅等功能。流类型可用于构建消息队列、日志处理等系统。HyperLogLog
:HyperLogLog 是一种概率性数据结构,用于进行基数(不重复元素个数)估计。通过使用 HyperLogLog,可以在占用很小内存的情况下,快速统计一组元素的基数。Bitfields
:一种在 Redis 中进行位级操作的数据类型,用于存储和操作二进制数据。
String
Redis 的字符串类型是最简单、最常用的数据类型之一,它可以存储任意二进制数据,不限于文本字符。它可以存储例如 JSON、序列化对象、图像等各种形式的数据。最大支持 512MB 的字符串长度。
对字符串类型的值进行读取和设置的操作是原子性的,保证了并发环境下的数据一致性。
除了作为普通的数据存储之外,还可以用于实现缓存、计数器、分布式锁等功能。
String 常用命令
# 设置指定 key 的值。
SET key value
# 获取指定 key 的值
GET key
# 返回 key 中字符串值的子字符,start 从 0 开始
GETRANGE key start end
# 获取所有(一个或多个)给定 key 的值。
MGET key1 [key2..]
# 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。
SETEX key seconds value
# 以毫秒为单位设置 key 的生存时间
PSETEX key milliseconds value
# 只有在 key 不存在时设置 key 的值。
SETNX key value
# 返回 key 所储存的字符串值的长度。
STRLEN key
# 同时设置一个或多个 key-value 对。
MSET key value [key value ...]
# 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。
MSETNX key value [key value ...]
# 将 key 中储存的数字值增一。
INCR key
# 将 key 所储存的值加上给定的增量值(increment) 。
INCRBY key increment
# 将 key 所储存的值加上给定的浮点增量值(increment) 。
INCRBYFLOAT key increment
# 将 key 中储存的数字值减一。
DECR key
# key 所储存的值减去给定的减量值(decrement) 。
DECRBY key decrement
# 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。
APPEND key value
如果该 key 储存的不是数字,却使用加减命令,则会提示:ERR value is not an integer or out of range
使用场景
- 缓存:经典使用场景,把常用信息,字符串,图片或者视频等信息放到 redis 中,redis 作为缓存层,mysql 做持久化层,降低 mysql 的读写压力。
- 计数器:redis 是单线程模型,一个命令执行完才会执行下一个,同时数据可以一步落地到其他的数据源。
- session:常见方案 spring session + redis 实现 session 共享,
- 分布式锁:SETNX 可以实现分布式锁
List
List 是一个有序的字符串集合(使用双端链表实现),它按照插入顺序排序。列表允许在列表的两端进行快速的插入和删除操作,因此非常适合用于实现队列、栈以及其他需要按顺序处理元素的场景。
List 常用命令
# 将一个或多个值插入到列表的头部
LPUSH key value1
LPUSH key value2 value3
# 将一个或多个值追加到列表的尾部
RPUSH key value4
RPUSH key value5 value6
# 移除并返回列表的头部元素
LPOP key
# 移除并返回列表的尾部元素
RPOP key
# 返回列表中指定索引位置的元素
LINDEX key index
# 返回列表中指定范围内的元素,start 从0开始
LRANGE key start stop
# 返回列表的长度
LLEN key
# 从列表中移除指定数量的与 value 值相等的元素,如果 count 是正数,则从列表的头部开始向尾部搜索,如果 count 是负数,则从列表的尾部开始向头部搜索,如果 count 是零,则移除列表中所有与 value 相等的元素。
LREM key count value
# 在列表中,基于某个元素的前面插入新元素,pivot 为基准元素,是元素的值
LINSERT key BEFORE pivot value
# 在列表中,基于某个元素的后面插入新元素,pivot 为基准元素,是元素的值
LINSERT key AFTER pivot value
# 根据索引设置列表中某个位置的元素的值
LSET key index value
# 将一个值插入到已存在的列表尾部(最右边)。如果列表不存在,操作无效。
RPUSHX KEY_NAME VALUE1..VALUEN
使用列表的技巧:
- lpush+lpop=Stack(栈)
- lpush+rpop=Queue(队列)
- lpush+ltrim=Capped Collection(有限集合)
- lpush+brpop=Message Queue(消息队列)
SET
无序的唯一字符串集合,支持添加、删除、查找元素以及对多个集合执行交集、并集、差集等操作。
Set 常用命令
# 向集合添加一个或多个成员
SADD key member1
SADD key member2 member3
# 返回集合中的所有成员
SMEMBERS key
# 判断成员是否属于集合
SISMEMBER key member
# 获取集合中的成员数量
SCARD key
# 从集合中随机移除并返回一个或多个成员
SPOP key [count]
# 移除集合中的一个或多个成员
SREM key member1
SREM key member2 member3
# 返回给定多个集合的交集
SINTER key1 key2
SINTER key1 key2 key3
# 返回给定多个集合的并集
SUNION key1 key2
SUNION key1 key2 key3
# 返回给定多个集合的差集
SDIFF key1 key2
SDIFF key1 key2 key3
# 将给定集合之间的交集、并集或差集存储在一个新的集合中
SINTERSTORE destination key1 key2
SUNIONSTORE destination key1 key2
SDIFFSTORE destination key1 key2
Hash
Redis 的 Hash 数据类型是一种键值对集合,其中键与值之间是一对一的关系。类似于 Java 中的 HashMap
Hash 中的字段和值都是字符串类型的,字段用于唯一标识和访问值。
# 设置一个或多个字段和值到哈希表中
HSET key field1 value1
HSET key field2 value2
HSET key field3 value3
# 获取指定哈希表中指定字段的值
HGET key field
# 获取所有给定哈希表的字段和值
HGETALL key
# 检查指定的字段是否存在于哈希表中
HEXISTS key field
# 删除一个或多个哈希表字段
HDEL key field1
HDEL key field2 field3
# 增加指定字段的整数值
HINCRBY key field increment
# 获取指定哈希表中所有字段的名字
HKEYS key
# 获取指定哈希表中所有字段的值
HVALS key
# 获取哈希表中字段的数量
HLEN key
# 批量设置多个字段和值到哈希表中
HMSET key field1 value1 field2 value2 field3 value3
# 批量获取指定哈希表中的多个字段的值
HMGET key field1 field2 field3
Zset
Redis 的 Zset(有序集合)是一种集合数据类型,它与普通的集合相比,每个元素都关联一个 double(双精度浮点数)类型的分数(score),通过分数可以对元素进行排序,并且支持快速的范围查找操作。
有序集合的成员是唯一的,但分数 (score) 却可以重复。
Zset 常用命令
# 添加一个或多个有序集合成员
ZADD key score1 member1
ZADD key score2 member2
ZADD key score3 member3
# 获取有序集合中成员的数量
ZCARD key
# 计算指定分数范围内成员的数量
ZCOUNT key min max
# 为有序集合中指定成员的分数增加增量
ZINCRBY key increment member
# 获取有序集合中指定排名范围内的成员
ZRANGE key start stop [WITHSCORES]
# 获取有序集合中指定分数范围内的成员
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
# 获取有序集合中指定成员的排名(从小到大)
ZRANK key member
# 移除有序集合中的一个或多个成员
ZREM key member1
ZREM key member2
# 移除有序集合中指定排名范围的成员
ZREMRANGEBYRANK key start stop
# 移除有序集合中指定分数范围的成员
ZREMRANGEBYSCORE key min max
# 获取有序集合中指定成员的分数
ZSCORE key member
# 获取有序集合中指定成员的排名(从大到小)
ZREVRANK key member
# 获取有序集合中指定排名范围内的成员(从大到小)
ZREVRANGE key start stop [WITHSCORES]
HyperLogLog
参考:
https://www.jianshu.com/p/00b25ec577a1
https://www.pdai.tech/md/db/nosql-redis/db-redis-data-type-special.html#hyperloglogs-基数统计
Redis 在 2.8.9 版本添加了 HyperLogLog 结构。
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。
什么是基数呢:举个例子,A = {1, 2, 3, 4, 5},B = {3, 5, 6, 7, 9};那么基数(不重复的元素)= 1, 2, 4, 6, 7, 9; (允许容错,即可以接受一定误差)
说白了就是在大数据量级的情况下能够在很小的空间中进行元素去重统计。如果使用我们平常的数据结构比如 Set
,HashMap
,等,虽然也可以实现去重统计的工作,但是当数据量上升到一定级别之后,其占用的空间也是非常的大。
在 Redis 里面,每个 HyperLogLog
键只需要花费 12 KB
内存,就可以计算接近 2^64
个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog
只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog
不能像集合那样,返回输入的各个元素。
一个大型的网站,每天 IP 比如有 100 万,粗算一个 IP 消耗 15 字节,那么 100 万个 IP 就是 15M。而 HyperLogLog 在 Redis 中每个键占用的内容都是 12K,理论存储近似接近 2^64 个值,不管存储的内容是什么,它一个基于基数估算的算法,只能比较准确的估算出基数,可以使用少量固定的内存去存储并识别集合中的唯一元素。而且这个估算的基数并不一定准确,是一个带有 0.81% 标准错误的近似值(对于可以接受一定容错的业务场景,比如 IP 数统计,UV 等,是可以忽略不计的)。
常用命令
# 添加指定元素到 HyperLogLog 中
PFADD key element [element ...]
# 返回给定 HyperLogLog 的基数估算值。
PFCOUNT key [key ...]
# 将多个 HyperLogLog 合并为一个 HyperLogLog
PFMERGE destkey sourcekey [sourcekey ...]
Bitmap
内容来 (转载) 自:
https://juejin.cn/post/6998348240441573412
Bitmap,即位图,是一串连续的二进制数组(0 和 1),可以通过偏移量(offset)定位元素。BitMap 通过最小的单位 bit 来进行 0|1 的设置,表示某个元素的值或者状态,时间复杂度为 O(1)。由于 bit 是计算机中最小的单位,使用它进行储存将非常节省空间,特别适合一些数据量大且使用二值统计的场景。
这里的二值状态就是指集合元素的取值就只有 0 和 1 两种。例如在签到打卡的场景中,我们只用记录签到(1)或未签到(0),所以它就是非常典型的二值状态。在签到统计时,每个用户一天的签到用 1 个 bit 位就能表示,一个月(假设是 31 天)的签到情况用 31 个 bit 位就可以,而一年的签到也只需要用 365 个 bit 位,根本不用太复杂的集合类型。这个时候,我们就可以选择 Bitmap。
Bitmap 不属于 Redis 的基本数据类型,而是基于 String 类型进行的位操作。而 Redis 中字符串的最大长度是 512M,所以 BitMap 的 offset 值也是有上限的,其最大值是:8 * 1024 * 1024 * 512 = 2^32 = 4294967296
示例:
# 设置位图第 0、2、4、6、8 个位为 1
BITSET mybitmap 0 1
BITSET mybitmap 2 1
BITSET mybitmap 4 1
BITSET mybitmap 6 1
BITSET mybitmap 8 1
# 获取位图的指定位值
GETBIT mybitmap 0 # 返回 1
GETBIT mybitmap 3 # 返回 0
# 统计位图中值为 1 的位的数量
BITCOUNT mybitmap # 返回 5
# 获取范围内值为 1 的位的数量(从第 0 位到第 8 位)
BITCOUNT mybitmap 0 8 # 返回 5
命令详解
SETBIT key offset value
针对 key 存储的字符串值,设置或清除指定偏移量 offset 上的位 (bit)。
位的设置或清除取决于 value 值,即 1 或 0 当 key 不存在时,会创建一个新的字符串。
而且这个字符串的长度会伸展,直到可以满足指定的偏移量 offset(0 ≤offset< 2^32
),在伸展过程中,新增的位的值被设置为 0
返回值为整数,是偏移量 offset 位置的原始值
警告!
如果设置较大的 offset,内存分配可能会导致 Redis 阻塞。
如果 key 对应的字符串不存在或长度较短,但是设置的 offset 较大(比如最大为 2^32 -1),Redis 需要对中间的位数进行内存分配,Redis 可能会阻塞。
拿 2010 MacBook Pro 举例,offset = 2^32 -1 (分配 512MB 内存),需要耗时 300ms 左右;offset = 2^30 -1 (分配 128 内存),需要耗时 80ms 左右;offset = 2^28 -1 (分配 32MB 内存),需要耗时 30ms 左右;offset = 2^26 -1 (分配 8MB 内存),需要耗时 80ms 左右。
第一次分配内存后,后续对该 key 的相同操作不会再有内存分配开销。
GETBIT key offset
返回 key 对应的字符串,offset 位置的位(bit)
当 offset 大于值的长度时,返回 0
当 key 不存在时,可以认为 value 为空字符串,此时 offset 肯定大于空字符串长度,参考上一条,也返回 0
返回值为整数是偏移量 offset 位置的 bit 值
BITCOUNT key [start end]
统计给定字符串中,比特值为 1 的数量
默认会统计整个字符串,同时也可以通过指定 start 和 end 来限定范围
start 和 end 也可以是负数,-1 表示最后一个字节,-2 表示倒数第二个字节。注意这里是字节,1 字节= 8 比特
如果 key 不存在,返回 0
BITPOS key bit [start [end]]
返回字符串中,从左到右,第一个比特值为 bit(0 或 1)的偏移量
默认情况下会检查整个字符串,但是也可以通过指定 start 和 end 变量来指定字节范围,与 BITCOUNT 中的范围描述一致 SETBIT 和 GETBIT 指定的都是比特偏移量,BITCOUNT 和 BITPOS 指定的是字节范围
不论是否指定查询范围,该命令返回的偏移量都是基于 0 开始的
如果 key 不存在,认为是空字符串
返回值:
整数:第一个比特值为指定 bit(0 或 1)的偏移量
如果命令中参数 bit=1,但是字符串为空,此时返回 -1;
如果命令中参数 bit=0,但是字符串中所有的比特值都为 1,此时命令返回字符串最大的 offset+1。例如字符串对应的比特值为’11111111‘,那么此时会返回 8。
默认情况下,如果查询 bit=0,且没有指定范围,或者只指定了 start,命令默认在字符串后面补 0 用于查询 bit=0 的 offset。但是如果指定了 start 和 end,且范围内所有值都为 0,此时会返回 -1,因为用户指定了范围且范围内没有 0,不会在后面补充
如果查询 bit=1,始终不会在字符串后面补充 1,查询不到就会返回 -1
总结:
SETBIT:设置比特位
GETBIT:查询比特值
BITCOUNT:统计比特值为 1 的数量
BITPOS:查询第一个比特值为 0 或 1 的偏移量
BITOP:对 Bitmap 做逻辑与、或、异或、非操作
BITFIELD:将 Bitmap 看作由多个整数组成的,对其中的整数操作
Geospatial
在 Redis 3.2 版本中加入了地理空间 (geospatial) 以及索引半径查询的功能
把某个具体的位置信息(经度,纬度,名称)添加到指定的 key 中,数据将会用一个 sorted set 存储,以便稍后能使用 GEORADIUS 和 GEORADIUSBYMEMBER 命令来根据半径来查询位置信息。
这个命令 (指 GEOADD) 的参数使用标准的 x,y 形式,所以经度(longitude)必须放在纬度(latitude)之前,对于可被索引的坐标位置是有一定限制条件的:非常靠近极点的位置是不能被索引的,在 EPSG:900913 / EPSG:3785 / OSGEO:41001
指定如下:
- 有效的经度是 -180 度到 180 度
- 有效的纬度是 -85.05112878 度到 85.05112878 度
如果使用了超出有效范围的经纬度,此命令会返回一个错误。
提示:之所以没有 GEODEL 命令,是因为你可以用 ZREM 来删除一个元素,GEO 索引结构实际上就是一个 sorted set。
在 Redis 中,geospatial 功能是通过 Geohash 算法实现的,Geohash 将地理位置信息编码成字符串,使得可以方便地存储和查询。Redis 的 geospatial 提供了一系列命令来操作地理空间数据,下面是一些常用的命令示例:
# 添加地理位置
GEOADD key longitude latitude member [longitude latitude member ...]
# 例如
GEOADD cities 116.397128 39.916527 "Beijing" 121.4737 31.2304 "Shanghai" 113.2644 23.1291 "Guangzhou"
# 获取两个地理位置的距离,其中 unit 参数可选,默认为米(m),还可以是千米(km)、英里(mi)、英尺(ft)等。
GEODIST key member1 member2 [unit]
# 例如
GEODIST cities Beijing Shanghai km
# 根据指定位置获取附近的地理位置,radius 是距离范围,WITHCOORD 返回位置的经纬度信息,WITHDIST 返回距离信息,ASC 或 DESC 用于排序,COUNT 限制返回结果数量。
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [ASC|DESC] [COUNT count]
# 例如:
GEORADIUS cities 116.4039 39.9149 100 km WITHDIST
# 跟 GEORADIUS类似,只是中心点不是指定经纬度,而是指定已添加的某个位置作为中心
GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]
# 获取指定地理位置的经纬度
GEOPOS key member [member ...]
# 例如
GEOPOS cities Beijing Shanghai Guangzhou
# 获取指定地理位置的 geohash 值:
GEOHASH key member [member ...]
# 例如
GEOHASH cities Beijing Shanghai Guangzhou