概述
- string(字符串)
- hash(哈希)
- list(列表)
- set(集合)
- zset(有序集合)
- stream(流)
- geospatial(地理)
- bitmap(位图)
- bitfield(位域)
- hyperloglog(基数统计)
String
概述
string 是 Redis 最基本的数据类型,它可以存储任意类型的数据,比如文本、数字、图片或者序列化的对象。一个 string 类型的键最大可以存储 512 MB 的数据。
- embstr:占用64Bytes的空间,存储44Bytes的数据
- raw:存储大于44Bytes的数据
- int:存储整数类型
embstr和raw存储字符串数据,int存储整型数据
应用场景
- 缓存数据,提高访问速度和降低数据库压力。
- 计数器,利用 incr 和 decr 命令实现原子性的加减操作。
- 分布式锁,利用 setnx 命令实现互斥访问。
- 限流,利用 expire 命令实现时间窗口内的访问控制。
底层原理
embstr结构
embstr 结构存储小于等于44个字节的字符串,embstr 每次开辟64个byte的空间
- 前19个byte用于存储embstr 结构
- 中间的44个byte存储数据
- 最后为
\0
符号
raw结构
embstr和raw的转换
- 如果长度小于等于44个字节,就会选择embstr 结构
- 如果长度大于44个byte,就会选择raw结构
127.0.0.1:6379> object encoding str
"embstr"
# str赋值44个字节的字符串
127.0.0.1:6379> set str 1234567890123456789012345678901234567890abcd
OK
127.0.0.1:6379> object encoding str
"embstr"
# str2赋值45个字节的字符串
127.0.0.1:6379> set str2 1234567890123456789012345678901234567890abcde
OK
127.0.0.1:6379> object encoding str2
"raw"
127.0.0.1:6379> set num 123
OK
127.0.0.1:6379> object encoding num
"int"
Hash
概述
hash 是一个键值对集合,它可以存储多个字段和值,类似于编程语言中的 map 对象。一个 hash 类型的键最多可以存储 2^32 - 1 个字段。
-
listpack
:紧凑列表,在Redis7.0之后,listpack
正式取代ziplist
。同样的,当 hash达到一定的阈值时,会自动转换为hashtable
结构 -
hashtable
:哈希表,类似map
ziplist
:压缩列表,当 **hash **达到一定的阈值时,会自动转换为hashtable
结构
应用场景
hash 类型的应用场景主要是存储对象,比如:
- 用户信息,利用 hset 和 hget 命令实现对象属性的增删改查。
- 购物车,利用 hincrby 命令实现商品数量的增减。
- 配置信息,利用 hmset 和 hmget 命令实现批量设置和获取配置项。
底层原理
- ziplist(redis7.0之前使用)和listpack(redis7.0之后使用)
- hashTable
从ziplist/listpack编码转换为hashTable编码是通过判断元素数量或单个元素Key或Value的长度决定的:
-
hash-max-ziplist-value
:表示当 **hash **中的每个元素的key
和value
的长度都小于或等于该值时,使用 **ziplist **编码,否则使用 **hashtable **编码。默认值为 64。
hash-max-ziplist-entries
:表示当 **hash **中的元素数量小于或等于该值时,使用 **ziplist **编码,否则使用 **hashtable 编码。ziplist 是一种压缩列表,它可以节省内存空间,但是访问速度较慢。hashtable **是一种哈希表,它可以提高访问速度,但是占用内存空间较多。默认值为 512。
ziplist与listpack
ziplist/listpack都是hash结构用来存储少量数据的结构。从Redis7.0后,hash默认使用**ziplist **结构。因为 ziplist 有一个致命的缺陷,就是连锁更新,当一个节点的长度发生变化时,可能会导致后续所有节点的长度字段都要重新编码,这会造成极差的性能
ziplist结构
Redis中ziplist源码
typedef struct {
/* 当使用字符串时,slen表示为字符串长度 */
unsigned char *sval;
unsigned int slen;
/* 当使用整形时,sval为NULL,lval为ziplistEntry的value */
long long lval;
} ziplistEntry;
listpack结构
zipList的连锁更新问题
- 如果前一节点的长度小于254个byte,则采用1个byte来保存这个长度值
- 如果前一节点的长度大于等于254个byte,则采用5个byte来保存这个长度值,第一个byte为0xfe,后四个byte才是真实长度数据
假设,现有有N个连续、长度为250~253个byte的entry,因此entry的previous_entry_length属性占用1个btye
previous_entry_length变为5个bytes,第二节的长度由250变为254。而第二节长度的增加必然会影响第三节的previous_entry_length。ziplist这种特殊套娃的情况下产生的连续多次空间扩展操作成为连锁更新。新增、删除都可能导致连锁更新的产生。
listpack是如何解决的
- 由于ziplist需要倒着遍历,所以需要用previous_entry_length记录前一个entry的长度。而listpack可以通过total_bytes和end计算出来。所以previous_entry_length不需要了。
- listpack 的设计彻底消灭了 ziplist 存在的级联更新行为,元素与元素之间完全独立,不会因为一个元素的长度变长就导致后续的元素内容会受到影响。
- 与ziplist做对比的话,牺牲了内存使用率,避免了连锁更新的情况。从代码复杂度上看,listpack相对ziplist简单很多,再把增删改统一做处理,从listpack的代码实现上看,极简且高效。
hashTable
Redis中hashTable源码
struct dict {
dictType *type;
dictEntry **ht_table[2];
unsigned long ht_used[2];
long rehashidx; /* 当进行rehash时,rehashidx为-1 */
int16_t pauserehash; /* 如果rehash暂停,pauserehash则大于0,(小于0表示代码错误)*/
signed char ht_size_exp[2]; /* 哈希桶的个数(size = 1<<exp */
};
typedef struct dict {
dictEntry **table;
dictType *type;
unsigned long size;
unsigned long sizemask;
unsigned long used;
void *privdata;
} dict;
typedef struct dictEntry {
void *key;
void *val;
struct dictEntry *next;
} dictEntry;
ziplist和hashTable的转换
127.0.0.1:6379> hset h2 id 123456789012345678901234567890123456789012345678901234567890abcd
(integer 1
127.0.0.1:6379> object encoding h2
"ziplist"
127.0.0.1:6379> hset h3 id 123456789012345678901234567890123456789012345678901234567890abcde
(integer 1
127.0.0.1:6379> object encoding h3
"hashtable"
ziplist的废弃
显然是ziplist在field个数太大、key太长、value太长三者有其一的时候会有以下问题:
- ziplist每次插入都有开辟空间,连续的
- 查询的时候,需要从头开始计算,查询速度变慢
hashTable变得越来越长怎么办
rehash。rehash
会重新计算哈希值,且将哈希桶的容量扩大。
rehash 步骤
rehash来完成,这其中就涉及到了空间的分配和释放,主要经过以下五步:
- 为字典dict的ht[1]哈希表分配空间,其大小取决于当前哈希表已保存节点数(即:
ht[0].used
):
- 如果是扩展操作则ht[1]的大小为2的n次方中第一个大于等于
- 如果是收缩操作则ht[1]大小为 2 的 n 次方中第一个大于等于
ht[0].used
的值
ht[0].used * 2
属性的值(比如used=3,此时ht[0].used * 2=6
,故2的3次方为8就是第一个大于used * 2的值(2 的 2 次方 6))。
rehashidx
的值设置为0,表示正在执行rehash
操作rehash
之后rehashidx的值需要自增1rehashidx
设置为-1,表示此次rehash
操作结束,等待下一次rehash
渐进式 rehash
rehash,而是分多次来慢慢的将ht[0]中的键值对rehash
到ht[1],故而这种操作也称之为渐进式rehash
。渐进式rehash
可以避免集中式rehash
带来的庞大计算量,是一种分而治之的思想。
rehash过程中,因为还可能会有新的键值对存进来,此时Redis的做法是新添加的键值对统一放入ht[1]中,这样就确保了ht[0]键值对的数量只会减少。
rehash操作时,如果服务器收到来自客户端的命令请求操作,则会先查询ht[0],查找不到结果再到ht[1]中查询
List
概述
redis3.2
以后,list 类型的底层实现只有一种结构,就是quicklist。版本不同时,底层实现是不同的,下面会讲解。应用场景
- 消息队列,利用 lpush 和 rpop 命令实现生产者消费者模式。
- 最新消息,利用 lpush 和 ltrim 命令实现固定长度的时间线。
- 历史记录,利用 lpush 和 lrange 命令实现浏览记录或者搜索记录。
底层原理
在讲解list结构之前,需要先说明一下list结构编码的更替,如下
- 在
- 在
Redis3.2~Redis7.0
之间,list使用的是quickList
,是linkedlist
和ziplist
的结合 - 在
Redis7.0
之后,list使用的也是quickList
,只不过将ziplist
转为listpack
,它是listpack、linkedlist结合版
Redis3.2
之前,list使用的是linkedlist
和ziplist
linkedlist与ziplist
Redis3.2之前,linkedlist
和ziplist
两种编码可以选择切换,它们之间的转换关系如图
list-max-ziplist-entries 512
list-max-ziplist-value 64
quickList(ziplist、linkedlist结合版)
quicklist
存储了一个双向列表,每个列表的节点是一个ziplist
,所以实际上quicklist
并不是一个新的数据结构,它就是linkedlist
和ziplist
的结合,然后被命名为快速列表。
ziplist内部entry个数可在redis.conf配置list-max-ziplist-size -2 # -5: 每个ziplist最多为 64 kb <-- 影响正常负载,不推荐 # -4: 每个ziplist最多为 32 Kb <-- 不推荐 # -3: 每个ziplist最多为 16 Kb <-- 最好不要使用 # -2: 每个ziplist最多为 8 Kb <-- 好 # -1: 每个ziplist最多为 4 Kb <-- 好 # 正数为ziplist内部entry个数
ziplist通过特定的LZF压缩算法来将节点进行压缩存储,从而更进一步的节省空间,而很多场景都是两端元素访问率最高,我们可以通过配置
list-compress-depth
来排除首尾两端不压缩的entry个数。list-compress-depth 0 # - 0:不压缩(默认值) # - 1:首尾第 1 个元素不压缩 # - 2:首位前 2 个元素不压缩 # - 3:首尾前 3 个元素不压缩 # - 以此类推
quickList(listpack、linkedlist结合版)
和Hash结构一样,因为
ziplist
有连锁更新问题,redis7.0
将ziplist
替换为listpack
,下面是新quickList的结构图Redis中listpack源码
typedef struct quicklist { quicklistNode *head; quicklistNode *tail; unsigned long count; /* 所有列表包中所有条目的总数,占用16 bits,最大65536 */ unsigned long len; /* quicklistNode 的数量 */ signed int fill : QL_FILL_BITS; /* 单个节点的填充因子 */ unsigned int compress : QL_COMP_BITS; /* 不压缩的端节点深度;0=off */ unsigned int bookmark_count: QL_BM_BITS; quicklistBookmark bookmarks[]; } quicklist;
typedef struct quicklistNode { struct quicklistNode *prev; struct quicklistNode *next; unsigned char *entry; size_t sz; /* 当前entry占用字节 */ unsigned int count : 16; /* listpack元素个数,最大65535 */ unsigned int encoding : 2; /* RAW==1 or LZF==2 */ unsigned int container : 2; /* PLAIN==1 or PACKED==2 */ unsigned int recompress : 1; /* 当前listpack是否需要再次压缩 */ unsigned int attempted_compress : 1; /* 测试用 */ unsigned int extra : 10; /* 备用 */ } quicklistNode;
listpack内部entry个数可在redis.conf配置
List-Max-listpack-size -2 # -5: 每个listpack最多为 64 kb <-- 影响正常负载,不推荐 # -4: 每个listpack最多为 32 Kb <-- 不推荐 # -3: 每个listpack最多为 16 Kb <-- 最好不要使用 # -2: 每个listpack最多为 8 Kb <-- 好 # -1: 每个listpack最多为 4 Kb <-- 好 # 正数为listpack内部entry个数
Set
概述
set
是一个无序的字符串集合,它不允许重复的元素。一个set
类型的键最多可以存储 2^32 - 1 个元素。set 类型的底层实现有两种:
-
hashtable
(哈希表)。哈希表和 hash 类型的哈希表相同,它将元素存储在一个数组中,并通过哈希函数计算元素在数组中的索引
intset
,整数集合
typedef struct intset {
uint32_t encoding;
uint32_t length;
int8_t contents[];
} intset;
应用场景
set 类型的应用场景主要是利用集合的特性,比如:
- 去重,利用 sadd 和 scard 命令实现元素的添加和计数。
- 交集,并集,差集,利用 sinter,sunion 和 sdiff 命令实现集合间的运算。
- 随机抽取,利用 srandmember 命令实现随机抽奖或者抽样。
底层原理
- 在
- 在
Redis7.2
之后,set使用的是intset
、listpack
、hashtable
Redis7.2
之前,set使用的是intset
和hashtable
intset
intset
是一种紧凑的数组结构,它只保存int
类型的数据,它将所有的元素按照从小到大的顺序存储在一块连续的内存中。intset
会根据传入的数据大小,encoding
分为int16_t
、int32_t
、int64_t
127.0.0.1:6379> sadd set 123 (integer 1 127.0.0.1:6379> object encoding set "intset" 127.0.0.1:6379> sadd set abcd (integer 1 127.0.0.1:6379> object encoding set "hashtable"
intset 和 hashtable 的转换
在
Redis7.2
之前,当一个集合满足以下两个条件时,Redis 会选择使用intset
编码:
- 集合对象保存的所有元素都是整数值
- 集合对象保存的元素数量小于等于
512
个(默认)
set-max-intset-entries 512
为什么加入了listpack
在redis7.2
之前,sds
类型的数据会直接放入到编码结构式为hashtable
的set
中。其中,sds
其实就是redis
中的string
类型。
redis7.2之后,sds类型的数据,首先会使用listpack
结构当 set
达到一定的阈值时,才会自动转换为hashtable
。
listpack结构是为了提高内存利用率和操作效率,因为 hashtable 的空间开销和碰撞概率都比较高。
hashtable 的空间开销高
空间开销高是因为它需要预先分配一个固定大小的数组来存储键值对,而这个数组的大小通常要大于实际存储的元素个数,以保证较低的装载因子。装载因子是指 hashtable 中已经存储的元素个数和数组大小的比值,它反映了 hashtable 的空间利用率
- 如果装载因子过高,那么 hashtable 的性能会下降,因为碰撞的概率会增加
- 如果装载因子过低,那么 hashtable 的空间利用率会下降,因为数组中会有很多空闲的位置
hashtable 的碰撞概率高
hashtable
的碰撞概率高是因为它使用了一个散列函数来将任意长度的键映射到一个有限范围内的整数,作为数组的索引举例说明,假设有一个大小为8的
hashtable
,使用取模运算作为散列函数,即h(k = k mod 8。现在有四个键:5,13,21,29,它们都被散列到索引1
处
- 散列函数的选择不合适,没有充分利用hashtable的空间。
- 键的分布不均匀,有些区间的键出现的频率更高。
- hashtable的大小太小,不能容纳所有的键。
为了解决碰撞,redis
采用了链地址法。就是在每个索引处维护一个链表,存储所有散列到该索引的键。但是,如果链表过长,查找效率会降低。因此,一般建议保持hashtable的负载因子(即键的数量除以hashtable的大小)在一定范围内,比如0.5到0.75之间。如果负载因子过高或过低,可以通过扩容或缩容来调整hashtable的大小
intset 、listpack和hashtable的转换
set的编码和阈值决定的。
-
- 当前
set
的编码为intset
:如果没有超过阈值,转换为listpack
;否则,直接转换为hashtable
- 当前
set
的编码为listpack
:如果超过阈值,就转换为hashtable
- 当前
set
的编码为hashtable
:直接插入,编码不会进行转换
阈值条件为:
set-max-listpack-entries
:最大元素个数,默认128set_max_listpack_value
:最大元素大小,默认64
以上两个条件需要同时满足才能进行编码转换 - 当前
set的编码为intset
,如果超过阈值由intset
直接转为hashtable
set-max-intset-entries
,intset
最大元素个数,默认512
ZSet
概述
Redis 中的 zset
是一种有序集合类型,它可以存储不重复的字符串元素,并且给每个元素赋予一个排序权重值(score
)。Redis
通过权重值来为集合中的元素进行从小到大的排序。zset
的成员是唯一的,但权重值可以重复。一个 zset
类型的键最多可以存储 2^32 - 1 个元素。
Redis中zset源码
typedef struct zskiplistNode {
sds ele;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned long span;
} level[];
} zskiplistNode;
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;
typedef struct zset {
dict *dict;
zskiplist *zsl;
} zset;
应用场景
zset 类型的应用场景主要是利用分数和排序的特性,比如:
- 排行榜,利用 zadd 和 zrange 命令实现分数的更新和排名的查询
- 延时队列,利用 zadd 和 zpopmin 命令实现任务的添加和执行,并且可以定期地获取已经到期的任务
- 访问统计,可以使用 zset 来存储网站或者文章的访问次数,并且可以按照访问量进行排序和筛选。
底层原理
Redis在存储zset
结构的数据,为了达到内存和性能的平衡,针对少量存储和大量存储分别设计了两种结构,分别为:
skiplist
ziplist
(redis7.0
之前使用)和listpack(redis7.0
之后使用)
zset 中的元素个数和元素值的长度比较小的时候,Redis
使用ziplist/listpack
来节省内存空间。当 zset
中的元素个数和元素值的长度达到一定阈值时,Redis
会自动将ziplist/listpack
转换为skiplist
,以提高操作效率
zset 同时满足以下两个条件时,会使用 listpack
作为底层结构:
- 元素个数小于
- 元素值的长度小于
zset_max_listpack_value
,默认值为 64
zset_max_listpack_entries
,默认值为 128
zset 中不满足以上两个条件时,会使用 skiplist
作为底层结构。
skiplist
优点:
- 实现简单,易于理解和调试
- 插入和删除操作只需要修改局部节点的指针,不需要像平衡树那样进行全局调整
- 可以利用空间换时间,通过增加索引层来提高查找效率
- 支持快速的范围查询,可以方便地返回指定区间内的所有元素
- 空间复杂度较高,需要额外存储多级索引
- 随机性太强,性能不稳定,最坏情况下可能退化成链表
- 不支持快速的倒序遍历,需要额外的指针来实现
redis的skiplist
skiplist
有一个层数上的问题,当层数过多,会影响查询效率。而Redis
使用了一个随机函数来决定每个节点的层数,这个随机函数的期望值是1/(1-p
,其中p
是一个概率常数,Redis
中默认为0.25
。这样可以保证跳跃表的平均高度为log (1/p n
,其中n
是节点数。Redis 还限制了跳跃表的最大层数为 32,这样可以避免过高的索引层造成空间浪费Stream
概述
stream 类型的底层实现是 rax(基数树),它是一种压缩的前缀树结构,它将所有的键值对按照 ID 的字典序存储在一个树形结构中。rax 可以快速地定位、插入、删除任意位置的键值对
应用场景
- 消息队列,利用 xadd 和 xread 命令实现生产者消费者模式。
- 操作日志,利用 xadd 和 xrange 命令实现操作记录和回放。
- 数据同步,利用 xadd 和 xreadgroup 命令实现多个消费者组之间的数据同步。
底层原理
Rax Tree
rax tree是一种基于基数树(radix tree)的变体,也叫做压缩前缀树(compressed prefix tree),它被应用于redis stream中,用来存储streamID,其数据结构为
typedef struct raxNode {
uint32_t iskey:1; /* Does this node contain a key? */
uint32_t isnull:1; /* Associated value is NULL (don't store it. */
uint32_t iscompr:1; /* 前缀是否压缩 */
uint32_t size:29; /* Number of children, or compressed string len. */
unsigned char data[];
} raxNode;
-
isnull
:是否存储value值 -
iscompr
:前缀是否压缩。决定了size
存储的是什么和data
的数据结构 -
size
:-
iscompr=0
:节点为非压缩节点,size
是孩子节点的数量 -
iscompr=1
:节点为压缩节点,size
是已压缩的字符串长度
-
-
data
:iscompr=0
:节点为非压缩节点,数据格式为[header strlen=0][abc][a-ptr][b-ptr][c-ptr](value-ptr?
。其有size个字符, -
iscompr=1
:节点为压缩节点,数据格式为[header strlen=3][xyz][z-ptr](value-ptr?
。
iskey
:是否包含key
为了便于理解,设定一些场景举例说明
场景一:只插入foot
z-ptr指向的叶子节点的iskey=1
,标识foot
这个key。下图为使用树状图的形式来展现其数据结构
场景二:插入foot后,插入footer
- 与foot节点中每个字符进行比较,获得最大公共前缀
- 将er作为foot的子节点,其
iskey=1
,标识foot
这个key - 将er的子节点的
iskey=1
,标识footer
这个key
foot
下图为使用树状图的形式来展现其数据结构
场景三:插入foot后,插入fo
- 与foot节点中每个字符进行比较,获得最大公共前缀
- 将foot拆成fo和ot
- 将ot作为fo的子节点,其
iskey=1
,标识fo
这个key - 设置ot的子节点的
iskey=1
,标识foot
这个key
fo
下图为使用树状图的形式来展现其数据结构
场景四:插入foot后,插入foobar
- 与foot节点中每个字符进行比较,获得最大公共前缀
- 将foot拆成foo和t
- 将footbar拆成foo、b、ar
- 将t、b作为foo的子节点
- 设置ot的子节点的
iskey=1
,标识foot
这个key - 将ar作为b的子节点
- 设置ar的子节点的
iskey=1
,标识footbar
这个key
foo
下图为使用树状图的形式来展现其数据结构
Stream
stream的底层使用了rax tree
和listpack
两种结构,rax tree
用来存储streamID,而listpack
用来存储对应的值,结构图如下:
Hyperloglog
概述
hyperloglog 类型的底层实现是 SDS(simple dynamic string),它和 string 类型相同,只是在操作时会使用一种概率算法来计算基数。hyperloglog 的误差率为 0.81%,也就是说如果真实基数为 1000,那么 hyperloglog 计算出来的基数可能在 981 到 1019 之间
应用场景
- 统计网站的独立访客数(UV)
- 统计在线游戏的活跃用户数(DAU)
- 统计电商平台的商品浏览量
- 统计社交网络的用户关注数
- 统计日志分析中的不同事件数
假如需要统计某商品的用户关注数,可以通过以下方式:
> PFADD goodA "1"
1
> PFADD goodA "2"
1
> PFADD goodA "3"
1
> PFCOUNT goodA
3
GEO
概述
geospatial 是一种用于存储和查询地理空间位置的数据类型,它基于 sorted set 数据结构实现,利用 geohash 算法将经纬度编码为二进制字符串,并作为 sorted set 的 score 值。Redis geospatial 提供了一系列的命令来添加、删除、搜索和计算地理空间位置,例如:
-
GEOPOS key member [member …]:返回一个或多个地理空间位置的经纬度
-
GEODIST key member1 member2 [unit]:返回两个地理空间位置之间的距离,可以指定单位(m, km, mi, ft)
-
GEORADIUS key longitude latitude radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]:返回指定圆心和半径内的地理空间位置,可以指定返回坐标、距离、哈希值、数量、排序方式等,也可以将结果存储到另一个 key 中
-
GEORADIUSBYMEMBER key member radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]: 返回以指定成员为圆心的指定半径内的地理空间位置,其他参数同
GEORADIUS
GEOADD key longitude latitude member [longitude latitude member …]:将一个或多个地理空间位置(经度、纬度、名称)添加到指定的 key 中
应用场景
- 统计某个区域内的商家或用户数量
- 查询某个位置附近的餐馆或酒店
- 计算两个位置之间的距离或行驶时间
- 显示某个位置周围的景点或活动
Bitmap
概述
bitmap
不是一个独立的数据类型,而是一种特殊的 string 类型,它可以将一个 string 类型的值看作是一个由二进制位组成的数组,并提供了一系列操作二进制位的命令。一个 bitmap 类型的键最多可以存储 2^32 - 1 个二进制位。应用场景
bitmap 类型的应用场景主要是利用二进制位的特性,比如:
- 统计用户活跃度,利用 setbit 和 bitcount 命令实现每天或每月用户登录次数的统计。
- 实现布隆过滤器,利用 setbit 和 getbit 命令实现快速判断一个元素是否存在于一个集合中。
- 实现位图索引,利用 bitop 和 bitpos 命令实现对多个条件进行位运算和定位
首先,需要规定**bitmap **的格式,假设为{userid}:{年份}:{第几天} {秒数} {是否登录}
userid为100的用户,记录他在2024年第100天中第1秒,是否登录
SETBIT 1000:2024:100 1 1
0
将
userid
为100的用户,记录他在2024年第100天中第10240 秒,是否登录
SETBIT 1000:2024:100 10240 1
0
将
userid
为100的用户,记录他在2024年第100天中第86400 秒,是否登录
SETBIT 1000:2024:100 86400 1
0
统计
userid
为100的用户,在2024年第100天的登录次数
BITCOUNT 1000:2024:100
3
Bitfield
概述
bitfield
结构是基于字符串类型的一种扩展,可以让你对一个字符串中的任意位进行设置,增加和获取操作,就像一个位数组一样无符号的1位整数到有符号的63位整数。这些值是使用二进制编码的
Redis
字符串来存储的bitfield结构支持原子的读,写和增加操作,使它们成为管理计数器和类似数值的好选择
使用场景
Bitfield的使用场景与
bitmap
类似,主要是一些需要用不同位长度的整数来表示状态或属性的场合,例如:
-
用一个16位的有符号整数来表示用户的等级,用一个16位的有符号整数来表示用户的经验值,可以方便地对这些数值进行设置,增加和获取
bitfield
和bitmap
都是基于string
类型的位操作,但是有一些区别:
-
bitmap
只能设置或获取指定偏移量上的位,而bitfield
可以对指定偏移量上的位进行增加或减少操作 -
bitmap
可以对多个字符串进行位运算,而bitfield
只能对单个字符串进行位操作 -
bitmap
的偏移量是从0开始的,而bitfield
的偏移量是从最高有效位开始的
bitmap
只能操作1位的无符号整数,而bitfield
可以操作任意位长度的有符号或无符号整数
bitfield存储用户的个人信息,
- 用一个8位的无符号整数来表示用户的性别,0表示男,1表示女
- 用一个8位的无符号整数来表示用户的年龄,范围是0-255
- 用一个16位的无符号整数来表示用户的身高,单位是厘米,范围是0-65535
- 用一个16位的无符号整数来表示用户的体重,单位是克,范围是0-65535
> BITFIELD user:1:info SET u8 #0 1 SET u8 #1 25 SET u16 #2 165 SET u16 #3 50000
0
0
0
0
然后,获取这个用户的信息,性别、年龄、身高、体重
> BITFIELD user:1:info GET u8 #0 GET u8 #1 GET u16 #2 GET u16 #3
1
25
165
50000