Redis --- 八种数据类型(基本命令)

五种基本数据类型

String、Hash、List、Set和Zset。

1、String

等同于java中的,Map<String,String>string 是redis里面的最基本的数据类型,一个key对应一个value。

  • string 是二进制安全的,可以把图片和视频文件保存在String中。
  • string的最大内存值 512M,即一个key或者value最大值是512M,官方说可以存2.5亿个key。

应用场景:String是最常用的一种数据类型,普通的key/value存储都可以归为此类,如用户信息,登录信息和配置信息等;

实现方式:String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr、decr等操作(自增自减等原子操作)时会转成数值型进行计算,此时redisObject的encoding字段为int。

Redis虽然是用C语言写的,但却没有直接用C语言的字符串,而是自己实现了一套字符串。目的就是为了提升速度,提升性能。Redis构建了一个叫做简单动态字符串(Simple Dynamic String),简称SDS。

struct sdshdr{    
    //  记录已使用长度    
    int len;    
    // 记录空闲未使用的长度    
    int free;    
    // 字符数组    
    char[] buf;    
};  

Redis的字符串也会遵守C语言的字符串的实现规则,即最后一个字符为空字符。然而这个空字符不会被计算在len里头。

  • 因为有了对字符串长度定义len, 所以在处理字符串时候不会以零值字节(\0)为字符串结尾标志.
  • 二进制安全就是输入任何字节都能正确处理, 即使包含零值字节.

Redis动态扩展步骤:

  • 计算出大小是否足够
  • 开辟空间至满足所需大小
  • 开辟与已使用大小len相长度同的空闲free空间(如果len < 1M),开辟1M长度的空闲free空间(如果len >= 1M)

Redis字符串的性能优势

  • 快速获取字符串长度:直接返回len
  • 避免缓冲区溢出:每次追加字符串时都会检查空间是否够用
  • 降低空间分配次数提升内存使用效率:(1)空间预分配;(2)惰性空间回收

常用命令:set/get/decr/incr/mget等,具体如下;

常用命令 命令
添加一对kv set key value
添加多对kv(可覆盖) mset key value key value….
添加多对kv(不可覆盖,只要有一个已存在,全部取消) msetnx key value key value….
获取 get value
获取多对kv mget key key…
删除 del key
在末尾追加 append key value
查询v的长度 strlen key
给数值类型的v加/减1 incr/decr key
给数值类型增加/减少指定大小的值 incrby/decrby key value
获取v的长度 getrange key
在指定位置添加指定值(中间默认用空格补全) setrange key offset value
添加指定生命周期的kv setex key seconds value
如果不存在则添加 setnx key value
获取旧值,设置新值 setget key value

ps:计数器(字符串的内容为整数的时候可以使用),如 set number 1。

补充:

127.0.0.1:6379> expire key  60 # 数据在 60s 后过期
(integer) 1
127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire)
OK
127.0.0.1:6379> ttl key # 查看数据还有多久过期
(integer) 56

2、Hash

等同于java中的:Map<String,Map<String,String>>,redis的hash是一个string类型的field和value的映射表,特别适合存储对象。在redis中,hash因为是一个集合,所以有两层。第一层是key:hash集合value,第二层是hashkey:string value。所以判断是否采用hash的时候可以参照有两层key的设计来做参考。并且注意的是,设置过期时间只能在第一层的key上面设置。

  • 使用hash,一般是有那种需要两层key的应用场景,也可以是‘删除一个key可以删除所有内容’的场景。例如一个商品有很多规格,规格里面有不同的值。
  • 或者查找所有商品的规格,查找商品id即可,具体的规格可以通过两个key查找。

应用场景:我们要存储一个用户信息对象数据,其中包括用户ID、用户姓名、年龄和生日,通过用户ID我们希望获取该用户的姓名或者年龄或者生日;

实现方式:Redis的Hash实际是内部存储的Value为一个HashMap,并提供了直接存取这个Map成员的接口。如,Key是用户ID, value是一个Map。这个Map的key是成员的属性名,value是属性值。这样对数据的修改和存取都可以直接通过其内部Map的Key(Redis里称内部Map的key为field), 也就是通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据。当前HashMap的实现有两种方式:当HashMap的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,这时对应的value的redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时redisObject的encoding字段为int。

常用命令:hget/hset/hgetall等,具体如下:

作用 命令
添加单个 hset key field value
获取单个 hget key field
一次性添加多个键值 hmset key field1 value1 field2 value2 …
一次性获取多个 hmget
获取所有键值 hgetall key
删除 hdel
获取键值对的个数 hlen
检查是否包含某个字段 hget key field
查看所有key hkeys
给某个数值类型(否则报错)的值增加指定整数值 hincrby key field increment
给某个数字类型值,增加指定浮点类型值 hincrbyfloat key field increment
如果不存在则添加 hsetnx

3、list

等同于java中的Map<String,List<String>>,list 底层是一个链表,在redis中,插入list中的值,只需要找到list的key即可,而不需要像hash一样插入两层的key。list是一种有序的、可重复的集合。

  • redis的list是一个双向链表(易于插入和删除元素)。
  • 它会按照我们插入的顺序排序,然后我们可以从他的头部添加/获取元素,也可以从它的尾部添加/获取元素,但带来的额外的空间开销

应用场景:Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现;

实现方式:Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。

常用命令:lpush/rpush/lpop/rpop/lrange等,具体如下:

常用命令 命令
左压栈 lpush key v1 v2 v3 v4…
右压栈 rpush key v1 v2 …
查看里面的元素 lrange key start offset
左弹栈 lpop key
右弹栈 rpop key
按照索引查找 lindex key index
查看长度 llen key
删除几个几 lrem key 数量 value
指定开始和结束的位置截取,再赋值给key ltrim key start offset
右出栈左压栈,把resoure的左后一个,压倒dest的第一个 rpoplpush resource destination
重置指定索引的值 lset key index value
在指定元素前/后插入指定元素 linsert key before/after 值1 值2

性能总结:

它是一个字符串链表,left、right都可以插入添加。

  • 如果键不存在,创建新的链表。
  • 如果键已经存在,新增内容。
  • 值全部移除,key消失。
  • 由于是链表,所以它对头和尾操作的效率都极高。但是假如是对中间元素的操作,效率就可怜了。

4、Set

等同于java中的Map<String,Set<String>>,Set 是一种无序的,不能重复的集合。并且在redis中,只有一个key它的底层由hashTable实现的,天生去重。

应用场景:Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动去重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的;如保存一些标签的名字。标签的名字不可以重复,顺序是可以无序的。

实现方式:set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。

常用命令:sadd/spop/smembers/sunion等,具体如下:

常用命令 命令
添加值 sadd key values
查看值 smembers key
检查集合是否有值 sismember key value
查看set集合里面的元素个数 scard key
删除集合中的指定元素 srem key value
随机弹出某个元素 srandmember key
随机出栈 spop key
把key1中的某个值赋值给key2 smove SourceSet destSet member
数学集合类 命令
差集 sdiff
交集 sinte
并集 sunion

5、Zset

ZSet(Sorted Set:有序集合) 每个元素都会关联一个double类型的分数score,分数允许重复,集合元素按照score排序(当score相同的时候,会按照被插入的键的字典顺序进行排序),还可以通过 score 的范围来获取元素的列表。

  • set的值是 k1 v1 k2 v2
  • zset的值 K1 score v1 k2 score v2

应用场景:Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构,比如twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。

底层实现zsetRedis提供的一个非常特别的数据结构,常用作排行榜等功能,以用户idvalue,关注时间或者分数作为score进行排序。实现机制分别是zipListskipList。规则如下:

zipList:满足以下两个条件

  • [score,value]键值对数量少于128个;
  • 每个元素的长度小于64字节;

skipList:不满足以上两个条件时使用跳表、组合了hash和skipList

  • hash用来存储valuescore的映射,这样就可以在O(1)时间内找到value对应的分数;
  • skipList按照从小到大的顺序存储分数
  • skipList每个元素的值都是[socre,value]

为什么用skiplist不用平衡树?

主要从内存占用、对范围查找的支持和实现难易程度这三方面总结的原因。

  • 内存占用:skiplist比平衡树更灵活一些。一般来说,平衡树每个节点至少包含2个指针,而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于参数p的大小。如果像Redis里的实现一样,取p=1/4,那么平均每个节点包含1.33个指针,比平衡树更有优势。
  • 范围查找的支持:在做范围查找的时候,平衡树比skiplist操作要复杂。在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对平衡树进行一定的改造,这里的中序遍历并不容易实现。而在skiplist上进行范围查找就非常简单,只需要在找到小值之后,对第1层链表进行若干步的遍历就可以实现。
  • 实现难易程度:skiplist更加简单,平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。

拓展:mysql为什么不用跳表?

常用命令:zadd/zrange/zrem/zcard等;

127.0.0.1:6379> zadd myZset 3.0 value1 # 添加元素到 sorted set 中 3.0 为权重
(integer) 1
127.0.0.1:6379> zadd myZset 2.0 value2 1.0 value3 # 一次添加多个元素
(integer) 2
127.0.0.1:6379> zcard myZset # 查看 sorted set 中的元素数量
(integer) 3
127.0.0.1:6379> zscore myZset value1 # 查看某个 value 的权重
"3"
127.0.0.1:6379> zrange  myZset 0 -1 # 顺序输出某个范围区间的元素,0 -1 表示输出所有元素
1) "value3"
2) "value2"
3) "value1"
127.0.0.1:6379> zrange  myZset 0 1 # 顺序输出某个范围区间的元素,0 为 start  1 为 stop
1) "value3"
2) "value2"
127.0.0.1:6379> zrevrange  myZset 0 1 # 逆序输出某个范围区间的元素,0 为 start  1 为 stop
1) "value1"
2) "value2"

三大特殊数据类型

1、geospatial

官网地址:https://redis.io/commands/geoadd

可以用来推算两地之间的距离,方圆半径内的人。

关于经度纬度的限制:https://www.redis.net.cn/order/3685.html

 1# 添加三个城市
 2127.0.0.1:16379[2]> geoadd china:city 116.40  39.99 beijing
 3(integer) 1
 4127.0.0.1:16379[2]> geoadd china:city 117.190 39.1255 tianjin
 5(integer) 1
 6127.0.0.1:16379[2]> geoadd china:city 120.36955 36.094 qingdao
 7(integer) 1
 8127.0.0.1:16379[2]>
 9
10# 获取指定key的经度和纬度
11127.0.0.1:16379[2]> geopos china:city beijing
121) 1) "116.39999896287918091"
13   2) "39.99000043587556519"
14127.0.0.1:16379[2]>
15
16# 获取两个给定位置的距离
17127.0.0.1:16379[2]> geodist china:city beijing qingdao # 默认单位为米
18"555465.2188"
19127.0.0.1:16379[2]> geodist china:city beijing qingdao km
20"555.4652"
21127.0.0.1:16379[2]> geodist china:city beijing qingdao m
22"555465.2188"
23127.0.0.1:16379[2]> geodist china:city beijing qingdao mi # 英里
24"345.1509"
25127.0.0.1:16379[2]> geodist china:city beijing qingdao ft # 英尺
26"1822392.4503"
27
28# 查找附近的人
29# 以给定的经纬度为中心,找出某一半径内的元素
30127.0.0.1:16379[2]> georadius china:city 117.190 39.1255 200 km # 半径为200km
311) "tianjin"
322) "beijing"
33127.0.0.1:16379[2]> georadius china:city 117.190 39.1255 200 km withdist # 指定显示距离
341) 1) "tianjin"
35   2) "0.0001"
362) 1) "beijing"
37   2) "117.6221"
38127.0.0.1:16379[2]> georadius china:city 117.190 39.1255 200 km count 2 # 指定显示2个结果
391) 1) "tianjin"
40   2) "0.0001"
412) 1) "beijing"
42   2) "117.6221"
43
44# 以指定的members为依据,找到它指定范围内的元素
45127.0.0.1:16379[2]> GEORADIUSBYMEMBER china:city beijing 120 km
461) "beijing"
472) "tianjin"
48
49# 返回一个或者多个位置的11位长度的hash串表示
50127.0.0.1:16379[2]> geohash china:city beijing
511) "wx4g2xzyss0"
52127.0.0.1:16379[2]> geohash china:city beijing tianjin
531) "wx4g2xzyss0"
542) "wwgqddx4sc0"
55
56# geo底层使用 zset 实现
57127.0.0.1:16379[2]> ZRANGE china:city 0 -1
581) "qingdao"
592) "tianjin"
603) "beijing"
61
62# 可以通过zrem删除 geo添加的key中的member
63127.0.0.1:16379[2]> ZREM china:city beijing
64(integer) 1
65127.0.0.1:16379[2]> ZRANGE china:city 0 -1
661) "qingdao"
672) "tianjin"

2、Hyperloglog

一般我们使用Hyperloglog做基数统计。

什么是基数?就是一个集合中不重复的数的个数。

集合A:{1,3,5,7,9,7}

集合B:{1,3,5,7,9}

AB集合的基数都是5

应用:统计网站的访问量(一个人访问网站很多次仍然算作一次)。

优点:占用的内存是固定的,找2^64次方个数的基数,只需要12KB内存。

缺点:有0.81%的错误率,可以忽略不计

 1# PFCOUNT 计算出来的数量就是Set的基数
 2127.0.0.1:16379[2]> PFADD key4 q w e r q
 3(integer) 1
 4127.0.0.1:16379[2]> PFCOUNT key4
 5(integer) 4
 6
 7# 添加key1和key对应的多个值
 8127.0.0.1:16379[2]> PFADD key1 q w e r
 9(integer) 1
10
11# 统计key下有多少个值
12127.0.0.1:16379[2]> PFCOUNT key1
13(integer) 4
14
15# 添加key2和key对应的多个值
16127.0.0.1:16379[2]> PFADD key2 a s d f
17(integer) 1
18
19# 合并多个key成为一个key
20127.0.0.1:16379[2]> PFMERGE key3 key1 key2
21OK
22127.0.0.1:16379[2]> PFCOUNT key3
23(integer) 8

3、Bitmap(*)

概述:bitmap 存储的是连续的二进制数字(0 和 1),通过 bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte,所以 bitmap 本身会极大的节省储存空间。

应用场景: 适合需要保存状态信息(比如是否签到、是否登录...)并需要进一步对这些信息进行分析的场景。比如用户签到情况、活跃用户情况、用户行为统计(比如是否点赞过某个视频)。

针对上面提到的一些场景,这里进行进一步说明。

使用场景一:用户行为分析 很多网站为了分析你的喜好,需要研究你点赞过的内容。

# 记录你喜欢过 001 号小姐姐
127.0.0.1:6379> setbit beauty_girl_001 uid 1
Copy to clipboardErrorCopied

使用场景二:统计活跃用户

使用时间作为 key,然后用户 ID 为 offset,如果当日活跃过就设置为 1

那么我该如果计算某几天/月/年的活跃用户呢(暂且约定,统计时间内只有有一天在线就称为活跃),有请下一个 redis 的命令

# 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
# BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数
BITOP operation destkey key [key ...]
Copy to clipboardErrorCopied

初始化数据:

127.0.0.1:6379> setbit 20210308 1 1
(integer) 0
127.0.0.1:6379> setbit 20210308 2 1
(integer) 0
127.0.0.1:6379> setbit 20210309 1 1
(integer) 0
Copy to clipboardErrorCopied

统计 20210308~20210309 总活跃用户数: 1

127.0.0.1:6379> bitop and desk1 20210308 20210309
(integer) 1
127.0.0.1:6379> bitcount desk1
(integer) 1
Copy to clipboardErrorCopied

统计 20210308~20210309 在线活跃用户数: 2

127.0.0.1:6379> bitop or desk2 20210308 20210309
(integer) 1
127.0.0.1:6379> bitcount desk2
(integer) 2
Copy to clipboardErrorCopied

使用场景三:用户在线状态

对于获取或者统计用户在线状态,使用 bitmap 是一个节约空间效率又高的一种方法。

只需要一个 key,然后用户 ID 为 offset,如果在线就设置为 1,不在线就设置为 0。

总结

  • 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
  • 支持丰富数据类型,支持string,list,set,sorted set(zset),hash

补充

  • 一个字符串类型的值能存储最大容量是512M
  • 其余类型元素最大存储量为2^32 - 1,注意hash是键值对的个数

巨人的肩膀:

https://www.cnblogs.com/Small-sunshine/p/11687809.html
https://mp.weixin.qq.com/s/CMu7oXVIKp2s-PXTdMlimA

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容