Redis入门 - 数据类型
# Redis入门 - 数据类型
首先对redis来说,所有的key(键)都是字符串。我们在谈基础数据结构时,讨论的是存储值的数据类型,主要包括常见的5种数据类型,分别是:String、List、Set、Zset、Hash。
结构类型 | 结构存储的值 | 结构的读写能力 |
---|---|---|
String字符串 | 可以是字符串、整数或浮点数 | 对整个字符串或字符串的一部分进行操作;对整数或浮点数进行自增或自减操作; |
List列表 | 一个链表,链表上的每个节点都包含一个字符串 | 对链表的两端进行push和pop操作,读取单个或多个元素;根据值查找或删除元素; |
Set集合 | 包含字符串的无序集合 | 字符串的集合,包含基础的方法有看是否存在添加、获取、删除;还包含计算交集、并集、差集等 |
Hash散列 | 包含键值对的无序散列表 | 包含方法有添加、获取、删除单个元素 |
Zset有序集合 | 和散列一样,用于存储键值对 | 字符串成员与浮点数分数之间的有序映射;元素的排列顺序由分数的大小决定;包含方法有添加、获取、删除单个元素以及根据分值范围或成员来获取元素 |
除了5中基本数据类型,Redis还提供了3中特殊类型,分别是HyperLogLogs(基数统计)、Bitmap(位存储)、geospatial(地理位置),详细见后文。
# String字符串
String类型是redis的最基础的数据结构,也是最经常使用到的类型,是二进制安全的,一个key对应一个value。 而且其他的四种类型多多少少都是在字符串类型的基础上构建的,所以String类型是redis的基础。
String 类型的值最大能存储 512MB,这里的String类型可以是简单字符串、 复杂的xml/json的字符串、二进制图像或者音频的字符串、以及可以是数字的字符串
下图是一个String类型的实例,其中键为hello,值为world
# 常用命令
命令 | 简述 |
---|---|
GET key | 获取指定 key 的值。 |
SET key value | 设置指定 key 的值 |
MGET key1 [key2..] | 获取所有(一个或多个)给定 key 的值。 |
SETEX key seconds value | 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。 |
SETNX key value | 只有在 key 不存在时设置 key 的值。 |
MSET key value [key value ...] | 同时设置一个或多个 key-value 对。 |
INCR key | 将 key 中储存的数字值增一。 |
INCRBY key increment | 将 key 所储存的值加上给定的增量值(increment) 。 |
DECR key | 将 key 中储存的数字值减一。 |
DECRBY key decrement | key 所储存的值减去给定的减量值(decrement) 。 |
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> del hello
(integer) 1
127.0.0.1:6379> get hello
(nil)
127.0.0.1:6379> set counter 2
OK
127.0.0.1:6379> get counter
"2"
127.0.0.1:6379> incr counter
(integer) 3
127.0.0.1:6379> get counter
"3"
127.0.0.1:6379> incrby counter 100
(integer) 103
127.0.0.1:6379> get counter
"103"
127.0.0.1:6379> decr counter
(integer) 102
127.0.0.1:6379> get counter
"102"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 应用场景
缓存功能:String字符串是最常用的数据类型,不仅仅是redis,各个语言都是最基本类型,因此,利用redis作为缓存,配合其它数据库作为存储层,利用redis支持高并发的特点,可以大大加快系统的读写速度、以及降低后端数据库的压力。
计数器:许多系统都会使用redis作为系统的实时计数器,可以快速实现计数和查询的功能。而且最终的数据结果可以按照特定的时间落地到数据库或者其它存储介质当中进行永久保存。
案例场景1:我们有一个web应用,我们想记录每个用户每天访问这个网站的次数
实现思路:web应用只需要通过拼接用户id和代表当天日期的字符串作为key,每次用户访问这个页面的时候对这个key执行一下incr命令。
其他扩展:通过结合使用INCR和EXPIRE命令,可以实现一个只记录用户在指定间隔时间内的访问次数的计数器 客户端可以通过GETSET命令获取当前计数器的值并且重置为0;通过类似于DECR或者INCRBY等原子递增/递减的命令,可以根据用户的操作来增加或者减少某些值,比如在线游戏,需要对用户的游戏分数进行实时控制,分数可能增加也可能减少。
案例场景2:限流,限制某个api每秒每个ip的请求次数不超过10次
案例场景3:幂等,MQ防止重复消费也可以利用INCR命令实现,如订单防重,订单5分钟之内只能被消费一次,订单号作为redis的key
案例场景4:监控应用程序,收集指标数据。
共享用户session:用户重新刷新一次界面,可能需要访问一下数据进行重新登录,或者访问页面缓存cookie,这两种方式做有一定弊端,1)每次都重新登录效率低下; 2)cookie保存在客户端,有安全隐患。这时可以利用redis将用户的session集中管理,在这种模式只需要保证redis的高可用,每次用户session的更新和获取都可以快速完成。大大提高效率。
# List列表
list类型是用来存储多个有序的字符串的,列表当中的每一个字符看做一个元素。一个列表当中可以存储有一个或者多个元素,redis的list支持存储2^32次方-1个元素。
Redis用双端链表实现List,可以从列表的两端进行插入(pubsh)和弹出(pop)元素,支持读取指定范围的元素集,或者读取指定下标的元素等操作。redis列表是一种比较灵活的链表数据结构,它可以充当队列或者栈的角色。
redis列表是链表型的数据结构,所以它的元素是有序的,而且列表内的元素是可以重复的。意味着它可以根据链表的下标获取指定的元素和某个范围内的元素集。
使用List结构,我们可以轻松地实现最新消息排队功能(比如新浪微博的TimeLine)。List的另一个应用就是消息队列,可以利用List的 PUSH 操作,将任务存放在List中,然后工作线程再用 POP 操作将任务取出进行执行。
# 常用命令
命令 | 简述 |
---|---|
RPUSH key value1 [value2] | 在列表中添加一个或多个值 |
LPUSH key value1 [value2] | 将一个或多个值插入到列表头部 |
BRPOP key1 [key2 ] timeout | 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
BLPOP key1 [key2 ] timeout | 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
RPOP key | 移除并获取列表最后一个元素 |
LPOP key | 移出并获取列表的第一个元素 |
LINDEX key index | 通过索引获取列表中的元素, -1 表示最后一个元素;-2 表示倒数第二个元素 |
LRANGE key start stop | 获取列表指定范围内的元素 |
LREM key count value | 移除列表元素 |
LSET key index value | 通过索引设置列表元素的值 |
LTRIM key start stop | 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 |
使用列表的技巧
- lpush+lpop=Stack(栈)
- lpush+rpop=Queue(队列)
- lpush+ltrim=Capped Collection(有限集合)
- lpush+brpop=Message Queue(消息队列)
127.0.0.1:6379> lpush mylist 1 2 ll ls mem
(integer) 5
127.0.0.1:6379> lrange mylist 0 -1
1) "mem"
2) "ls"
3) "ll"
4) "2"
5) "1"
127.0.0.1:6379> lindex mylist -1
"1"
127.0.0.1:6379> lindex mylist 10 # index不在 mylist 的区间范围内
(nil)
2
3
4
5
6
7
8
9
10
11
12
# 应用场景
- 消息队列:reids的链表结构,可以轻松实现阻塞队列,可以使用左进右出的命令组成来完成队列的设计。比如:数据的生产者可以通过Lpush命令从左边插入数据,而多个数据消费者,可以使用BRpop命令阻塞的“抢”列表尾部的数据。
- 文章列表或者数据分页展示的应用。比如,我们常用的博客网站的文章列表,当用户量越来越多时,而且每一个用户都有自己的文章列表,而且当文章多时,都需要分页展示,这时可以考虑使用redis的列表,列表不但有序同时还支持按照范围内获取元素,可以完美解决分页查询功能。大大提高查询效率。
- 微博TimeLine: 有人发布微博,用lpush加入时间轴,展示新的列表信息。
# Set无序集合
redis集合(set)类型和list列表类型类似,都可以用来存储多个字符串元素的集合。但是和list不同的是set集合当中不允许重复的元素。而且set集合当中元素是没有顺序的,不存在元素下标。
redis的set类型是使用哈希表构造的,因此复杂度是O(1),它支持集合内的增删改查,并且支持多个集合间的交集、并集、差集操作。可以利用这些集合操作,解决程序开发过程当中很多数据集合间的问题。
# 常用命令
命令 | 简述 |
---|---|
SADD key member1 [member2] | 向集合添加一个或多个成员 |
SCARD key | 获取集合的成员数 |
SDIFF key1 [key2] | 返回给定所有集合的差集 |
SINTER key1 [key2] | 返回给定所有集合的交集 |
SMEMBERS key | 返回集合中的所有成员 |
SISMEMBER key member | 判断 member 元素是否是集合 key 的成员 |
SREM key member1 [member2] | 移除集合中一个或多个成员 |
SUNION key1 [key2] | 返回所有给定集合的并集 |
127.0.0.1:6379> sadd myset hao hao1 xiaohao hao
(integer) 3
127.0.0.1:6379> smembers myset
1) "xiaohao"
2) "hao1"
3) "hao"
127.0.0.1:6379> sismember myset hao
(integer) 1
2
3
4
5
6
7
8
# 应用场景
- 标签:比如我们博客网站常常使用到的兴趣标签,把一个个有着相同爱好,关注类似内容的用户利用一个标签把他们进行归并,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。
- 共同好友功能,共同喜好,或者可以引申到二度好友之类的扩展应用。
- 统计网站的独立IP。利用set集合当中元素唯一性,可以快速实时统计访问网站的独立IP。
- 点赞,或点踩,收藏等,可以放到set中实现
# Zset有序集合
Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的, 但分数(score)却可以重复。有序集合是通过两种数据结构实现:
- 压缩列表(ziplist): ziplist是为了提高存储效率而设计的一种特殊编码的双向链表。它可以存储字符串或者整数,存储整数时是采用整数的二进制而不是字符串形式存储。它能在O(1)的时间复杂度下完成list两端的push和pop操作。但是因为每次操作都需要重新分配ziplist的内存,所以实际复杂度和ziplist的内存使用量相关
- 跳跃表(zSkiplist): 跳跃表的性能可以保证在查找,删除,添加等操作的时候在对数期望时间内完成,这个性能是可以和平衡树来相比较的,而且在实现方面比平衡树要优雅,这是采用跳跃表的主要原因。跳跃表的复杂度是O(log(n))。
# 常用命令
命令 | 简述 |
---|---|
ZADD key score1 member1 [score2 member2] | 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
ZCARD key | 获取有序集合的成员数 |
ZCOUNT key min max | 计算在有序集合中指定区间分数的成员数 |
ZINCRBY key increment member | 有序集合中对指定成员的分数加上增量 increment |
ZLEXCOUNT key min max | 在有序集合中计算指定字典区间内成员数量 |
ZRANGE key start stop [WITHSCORES] | 通过索引区间返回有序集合成指定区间内的成员 |
ZREVRANGE key start stop [WITHSCORES] | 返回有序集中指定区间内的成员,通过索引,分数从高到底 |
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] | 通过分数返回有序集合指定区间内的成员 |
ZSCORE key member | 返回有序集中,成员的分数值 |
ZREM key member [member ...] | 移除有序集合中的一个或多个成员 |
127.0.0.1:6379> zadd myscoreset 100 hao 90 xiaohao
(integer) 2
127.0.0.1:6379> ZRANGE myscoreset 0 -1
1) "xiaohao"
2) "hao"
127.0.0.1:6379> ZSCORE myscoreset hao
"100"
2
3
4
5
6
7
# 应用场景
- 排行榜:有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜,榜单维护可能是多方面:按照时间、按照播放量、按照获得的赞数等。
- 用Sorted Sets来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。
# Hash散列
Redis hash数据结构 是一个键值对(key-value)集合,它是一个 string 类型的 field 和 value 的映射表,redis本身就是一个key-value型数据库,因此hash数据结构相当于在value中又套了一层key-value型数据。所以redis中hash数据结构特别适合存储关系型对象
# 常用命令
命令 | 简述 |
---|---|
HSET key field value | 将哈希表 key 中的字段 field 的值设为 value 。 |
HGET key field | 获取存储在哈希表中指定字段的值 |
HGETALL key | 获取在哈希表中指定 key 的所有字段和值 |
HDEL key field2 [field2] | 删除一个或多个哈希表字段 |
HEXISTS key field | 查看哈希表 key 中,指定的字段是否存在。 |
HINCRBY key field increment | 为哈希表 key 中的指定字段的整数值加上增量 increment 。 |
HKEYS key | 获取所有哈希表中的字段 |
HMGET key field1 [field2] | 获取所有给定字段的值 |
HMSET key field1 value1 [field2 value2 ] | 同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
HSETNX key field value | 只有在字段 field 不存在时,设置哈希表字段的值。 |
127.0.0.1:6379> hset user name1 hao
(integer) 1
127.0.0.1:6379> hset user email1 hao@163.com
(integer) 1
127.0.0.1:6379> hgetall user
1) "name1"
2) "hao"
3) "email1"
4) "hao@163.com"
127.0.0.1:6379> hget user user
(nil)
127.0.0.1:6379> hget user name1
"hao"
127.0.0.1:6379> hset user name2 xiaohao
(integer) 1
127.0.0.1:6379> hset user email2 xiaohao@163.com
(integer) 1
127.0.0.1:6379> hgetall user
1) "name1"
2) "hao"
3) "email1"
4) "hao@163.com"
5) "name2"
6) "xiaohao"
7) "email2"
8) "xiaohao@163.com"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 应用场景
- 由于hash数据类型的key-value的特性,用来存储关系型数据库中表记录,是redis中哈希类型最常用的场景。一条记录作为一个key-value,把每列属性值对应成field-value存储在哈希表当中,然后通过key值来区分表当中的主键。
- 经常被用来存储用户相关信息。优化用户信息的获取,不需要重复从数据库当中读取,提高系统性能。
# HyperLogLogs(基数统计)
Redis 2.8.9 版本更新了 Hyperloglog 数据结构!
什么是基数?
举个例子,A = {1, 2, 3, 4, 5}, B = {3, 5, 6, 7, 9};那么基数(不重复的元素)= 1, 2, 4, 6, 7, 9; (允许容错,即可以接受一定误差)
HyperLogLogs 基数统计用来解决什么问题?
这个结构可以非常省内存的去统计各种计数,比如注册 IP 数、每日访问 IP 数、页面实时UV、在线用户数,共同好友数等。
它的优势体现在哪?
一个大型的网站,每天 IP 比如有 100 万,粗算一个 IP 消耗 15 字节,那么 100 万个 IP 就是 15M,如果更多呢?所以若使用set集合存储则会占用大量内存。而 HyperLogLog 在 Redis 中每个键占用的内容都是 12K,理论存储近似接近 2^64 个值,不管存储的内容是什么,它一个基于基数估算的算法,只能比较准确的估算出基数,可以使用少量固定的内存去存储并识别集合中的唯一元素。而且这个估算的基数并不一定准确,是一个带有 0.81% 标准错误的近似值(对于可以接受一定容错的业务场景,比如IP数统计,UV等,是可以忽略不计的)。
# 常用命令
127.0.0.1:6379> pfadd key1 a b c d e f g h i # 创建第一组元素
(integer) 1
127.0.0.1:6379> pfcount key1 # 统计元素的基数数量
(integer) 9
127.0.0.1:6379> pfadd key2 c j k l m e g a # 创建第二组元素
(integer) 1
127.0.0.1:6379> pfcount key2
(integer) 8
127.0.0.1:6379> pfmerge key3 key1 key2 # 合并两组:key1 key2 -> key3 并集
OK
127.0.0.1:6379> pfcount key3
(integer) 13
2
3
4
5
6
7
8
9
10
11
12
# 应用场景
- 网页统计UV (浏览用户数量,同一天同一个ip多次访问算一次访问,目的是计数,而不是保存用户),或注册 IP 数、每日访问 IP 数、页面实时UV、在线用户数,共同好友数等
# Bitmap (位存储)
Redis提供的Bitmaps这个“数据结构”可以实现对位的操作。Bitmaps本身不是一种数据结构,实际上就是字符串,但是它可以对字符串的位进行操作。可以把Bitmaps想象成一个以位为单位数组,数组中的每个单元只能存0或者1,数组的下标在bitmaps中叫做偏移量。单个bitmaps的最大长度是512MB,即2^32个比特位。
# 应用场景
比如:统计用户信息,活跃,不活跃! 登录,未登录! 打卡,不打卡! 两个状态的,都可以使用 Bitmaps!
如果存储一年的打卡状态需要多少内存呢? 365 天 = 365 bit 1字节 = 8bit 46 个字节左右!
# 常用命令
使用bitmap 来记录 周一到周日的打卡! 周一:1 周二:0 周三:0 周四:1 ......
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 0
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 1
(integer) 0
2
3
4
5
6
7
8
9
10
11
12
13
14
查看某一天是否有打卡!
127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 5
(integer) 0
2
3
4
统计操作,统计 打卡的天数!
127.0.0.1:6379> bitcount sign # 统计这周的打卡记录,就可以看到是否有全勤!
(integer) 3
2
# geospatial (地理位置)
Redis 的 Geo 在 Redis 3.2 版本就推出了! 这个功能可以推算地理位置的信息: 两地之间的距离, 方圆几里的人
geospatial将指定的地理空间位置(纬度、经度、名称)添加到指定的key中。这些数据将会存储到sorted set这样的目的是为了方便使用GEORADIUS或者GEORADIUSBYMEMBER命令对数据进行半径查询等操作。
sorted set使用一种称为Geohash的技术进行填充。经度和纬度的位是交错的,以形成一个独特的52位整数。sorted set的double score可以代表一个52位的整数,而不会失去精度。(有兴趣的同学可以学习一下Geohash技术,使用二分法构建唯一的二进制串)
# 常用命令
geoadd:添加地理位置
127.0.0.1:6379> geoadd china:city 118.76 32.04 manjing 112.55 37.86 taiyuan 123.43 41.80 shenyang (integer) 3 127.0.0.1:6379> geoadd china:city 144.05 22.52 shengzhen 120.16 30.24 hangzhou 108.96 34.26 xian (integer) 3
1
2
3
4规则:两级无法直接添加,我们一般会下载城市数据(这个网址可以查询 GEO: http://www.jsons.cn/lngcode)!
- 有效的经度从-180度到180度。
- 有效的纬度从-85.05112878度到85.05112878度。
# 当坐标位置超出上述指定范围时,该命令将会返回一个错误。 127.0.0.1:6379> geoadd china:city 39.90 116.40 beijin (error) ERR invalid longitude,latitude pair 39.900000,116.400000
1
2
3geopos:获取指定的成员的经度和纬度
127.0.0.1:6379> geopos china:city taiyuan manjing 1) 1) "112.54999905824661255" 1) "37.86000073876942196" 2) 1) "118.75999957323074341" 1) "32.03999960287850968"
1
2
3
4
5获得当前定位, 一定是一个坐标值!
geodist:如果不存在, 返回空。单位如:m、km、mi英里、ft英尺、
127.0.0.1:6379> geodist china:city taiyuan shenyang m "1026439.1070" 127.0.0.1:6379> geodist china:city taiyuan shenyang km "1026.4391"
1
2
3
4georadius:附近的人 ==> 获得所有附近的人的地址, 定位, 通过半径来查询
获得指定数量的人
127.0.0.1:6379> georadius china:city 110 30 1000 km // 以 100,30 这个坐标为中心, 寻找半径为1000km的城市 1) "xian" 2) "hangzhou" 3) "manjing" 4) "taiyuan" 127.0.0.1:6379> georadius china:city 110 30 500 km 1) "xian" 127.0.0.1:6379> georadius china:city 110 30 500 km withdist 1) 1) "xian" 2) "483.8340" 127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord withdist count 2 1) 1) "xian" 2) "483.8340" 3) 1) "108.96000176668167114" 2) "34.25999964418929977" 2) 1) "manjing" 2) "864.9816" 3) 1) "118.75999957323074341" 2) "32.03999960287850968"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19参数 key 经度 纬度 半径 单位 [显示结果的经度和纬度] [显示结果的距离] [显示的结果的数量]
georadiusbymember:显示与指定成员一定半径范围内的其他成员
127.0.0.1:6379> georadiusbymember china:city taiyuan 1000 km 1) "manjing" 2) "taiyuan" 3) "xian" 127.0.0.1:6379> georadiusbymember china:city taiyuan 1000 km withcoord withdist count 2 1) 1) "taiyuan" 2) "0.0000" 3) 1) "112.54999905824661255" 2) "37.86000073876942196" 2) 1) "xian" 2) "514.2264" 3) 1) "108.96000176668167114" 2) "34.25999964418929977"
1
2
3
4
5
6
7
8
9
10
11
12
13参数与 georadius 一样
geohash(较少使用):该命令返回11个字符的hash字符串
127.0.0.1:6379> geohash china:city taiyuan shenyang 1) "ww8p3hhqmp0" 2) "wxrvb9qyxk0"
1
2
3将二维的经纬度转换为一维的字符串, 如果两个字符串越接近, 则距离越近
# geo底层
geo底层的实现原理实际上就是Zset, 我们可以通过Zset命令来操作geo
127.0.0.1:6379> type china:city
zset
2
查看全部元素 删除指定的元素
127.0.0.1:6379> zrange china:city 0 -1 withscores
1) "xian"
2) "4040115445396757"
3) "hangzhou"
4) "4054133997236782"
5) "manjing"
6) "4066006694128997"
7) "taiyuan"
8) "4068216047500484"
9) "shenyang"
1) "4072519231994779"
2) "shengzhen"
3) "4154606886655324"
127.0.0.1:6379> zrem china:city manjing
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "xian"
2) "hangzhou"
3) "taiyuan"
4) "shenyang"
5) "shengzhen"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 应用场景
- 查看附近的人
- 微信位置共享
- 地图上直线距离的展示
# 参考文章
- https://pdai.tech/md/db/nosql-redis/db-redis-data-types.html
- https://pdai.tech/md/db/nosql-redis/db-redis-data-type-special.html
- https://mp.weixin.qq.com/s/7ct-mvSIaT3o4-tsMaKRWA
- https://www.w3cschool.cn/redis/redis-hashes.html