Redis支持五种基本数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
Redis数据存储格式:
-
redis自身是一个Map,其中所有数据都是采用key:value的形式存储
-
数据类型指的是存储的数据类型,也就是value的类型,key部分永远都是字符串
以上的数据类型是 Redis 键值的数据类型,其实就是数据的保存形式,但是数据类型的底层实现是最重要的,底层的数据结构主要分为 6 种,分别是简单动态字符串、双向链表、压缩链表、哈希表、跳表和整数数组。各个数据类型和底层结构的对应关系如下:
数据类型和底层结构的对应关系
string | list | hash | set | sorted set |
---|---|---|---|---|
简单动态字符串 | 双向链表、压缩链表 | 压缩链表、哈希表 | 压缩链表、整数数组 | 压缩链表、跳表 |
底层实现的时间复杂度
跳表 | 双向链表 | 压缩链表 | 哈希表 | 整数数组 |
---|---|---|---|---|
O(logN) | O(N) | O(N) | O(1) | O(N) |
可以看出除了 string 类型的底层实现只有一种数据结构,其他四种均有两种底层实现,这四种类型为集合类型,其中一个键对应了一个集合的数据。
1、String字符串
String 是一组字节,是二进制安全的,可以包含任何数据。比如jpg图片或者序列化的对象。string 类型是 Redis 最基本的数据类型,String 类型的值最大能存储 512MB。
1.1、String命令
以下是一些用于在 Redis 中管理字符串的基本命令的列表(20个命令):
命令 | 描述 |
---|---|
SET | 设置指定 key 的值 |
GET | 获取指定 key 的值 |
GETRANGE | 返回 key 中字符串值的子字符 |
GETSET | 将给定 key 的值设为 value ,并返回 key 的旧值 ( old value ) |
GETBIT | 对 key 所储存的字符串值,获取指定偏移量上的位 ( bit ) |
MGET | 获取所有(一个或多个)给定 key 的值 |
SETBIT | 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit) |
SETEX | 设置 key 的值为 value 同时将过期时间设为 seconds |
SETNX | 只有在 key 不存在时设置 key 的值 |
SETRANGE | 从偏移量 offset 开始用 value 覆写给定 key 所储存的字符串值 |
STRLEN | 返回 key 所储存的字符串值的长度 |
MSET | 同时设置一个或多个 key-value 对 |
MSETNX | 同时设置一个或多个 key-value 对 |
PSETEX | 以毫秒为单位设置 key 的生存时间 |
INCR | 将 key 中储存的数字值增一 |
INCRBY | 将 key 所储存的值加上给定的增量值 ( increment ) |
INCRBYFLOAT | 将 key 所储存的值加上给定的浮点增量值 ( increment ) |
DECR | 将 key 中储存的数字值减一 |
DECRBY | 将 key 所储存的值减去给定的减量值 ( decrement ) |
APPEND | 将 value 追加到 key 原来的值的末尾 |
1、单个数据与多个数据操作
-
设置:set、setnx、mset、msetnx
-
获取:get、mget
#1、单个数据设置、获取
#对不存在的key进行set设置
127.0.0.1:6379> set key1 "one"
OK
127.0.0.1:6379> get key1
"one"
#对已存在的key进行set设置,key已存在就覆盖旧值
127.0.0.1:6379> set key1 "two"
OK
127.0.0.1:6379> get key1
"two"
#key不存在get返回特殊值 nil
127.0.0.1:6379> get key2
(nil)
#如果key不是字符串类型,那么返回一个错误。
127.0.0.1:6379> lpush db redis mongodb mysql
(integer) 3
127.0.0.1:6379> get db
(error) WRONGTYPE Operation against a key holding the wrong kind of value
#当key不存在时
127.0.0.1:6379> setnx key2 "three"
(integer) 1
127.0.0.1:6379> get key2
"three"
#当key存在时,setnx什么也不做。
127.0.0.1:6379> setnx key2 "four"
(integer) 0
127.0.0.1:6379> get key2
"three"
#2、多个数据设置、获取
#mset像set一样,会用新值替换旧值
127.0.0.1:6379> mset key1 "one" key2 "two"
OK
127.0.0.1:6379> mget key1 key2
1) "one"
2) "two"
#有某个key不存在或者值不是字符串,那么这个key返回特殊值 nil
127.0.0.1:6379> mget key1 key3
1) "one"
2) (nil)
#当key不存在时
127.0.0.1:6379> msetnx key1 "one" key2 "two"
(integer) 1
127.0.0.1:6379> mget key1 key2
1) "one"
2) "two"
#当key存在时,msetnx什么也不做。
127.0.0.1:6379> msetnx key1 "three" key2 "four"
(integer) 0
127.0.0.1:6379> mget key1 key2
1) "one"
2) "two"
#即使只有一个给定键已经存在, msetnx命令也会拒绝执行对所有键的设置操作。
127.0.0.1:6379> msetnx key1 "three" key3 "four"
(integer) 0
127.0.0.1:6379> mget key1 key2 key3
1) "one"
2) "two"
3) (nil)
注意:
-
setnx、msetnx命令与set、mset的主要区别在于setnx、msetnx只会在所有给定键都不存在的情况下对键进行设置,而不会像set、mset直接覆盖键已有值。
-
mset、msetnx是原子操作,所有 key 的值同时设置。若给定键当中有一个键已经有值,则setnx、msetnx放弃对所有给定键的设置操作。所有给定键要么就全部都被设置, 要么就全部都不设置。
-
一般情况下,多指令(mset)在操作多条数据的时候,比单指令(set)的耗时是要小点的。
-
set、setnx可以用来加锁
2、数值增减量操作
-
增量:incr、incrby、incrbyfloat
-
减量:decr、decrby
127.0.0.1:6379> set weight 60
OK
#incr命令将key中储存的数字值增一
127.0.0.1:6379> incr weight
(integer) 61
#decr命令将key储存的数字值减去一
127.0.0.1:6379> decr weight
(integer) 60
#incrby、decrby命令将key中储存的数字加上、减去指定的增量值。
127.0.0.1:6379> incrby weight 5
(integer) 65
127.0.0.1:6379> decrby weight 5
(integer) 60
#incrbyfloat命令将key中储存的值加上浮点数增量increment,key中的浮点数是以字符串形式存储的。
127.0.0.1:6379> incrbyfloat weight 1.5
"61.5"
#如果key不存在,那么key的值会先被初始化为0 ,然后再执行incr、decr、incrby、decrby、incrbyfloat操作。
127.0.0.1:6379> incr age
(integer) 1
127.0.0.1:6379> decr money
(integer) -1
127.0.0.1:6379> incrby age1 5
(integer) 5
127.0.0.1:6379> decrby age2 5
(integer) -5
127.0.0.1:6379> incrbyfloat age3 5.5
"5.5"
127.0.0.1:6379> mget age money age1 age2 age3
1) "1"
2) "-1"
3) "5"
4) "-5"
5) "5.5"
#如果值包含错误的类型,或字符串类型的值不能表示为数字,那么incr、decr、incrby、decrby、incrbyfloat返回一个错误
127.0.0.1:6379> lpush language php go c++ c python
(integer) 5
127.0.0.1:6379> incr language
(error) WRONGTYPE Operation against a key holding the wrong kind of value
注意:
-
string在redis中内部存储默认就是一个字符串,当遇到增减类操作incr,decr时会转成数值型进行计算。
-
redis所有的操作都是原子性,采用单线程处理所有业务,命令是一个一个执行,因此无需考虑并发带来的数据影响。
-
按数值进行操作的数据,如果原始数据不能转成数值,或超越了redis数值上限范围,将会报错。
9223372036854775807 (java中long型数据最大值,long.MAX_VALUE)
-
INCR 命令族主要用在复制和 AOF 文件中,用来当做 SET,减少源 SET 命令个数,浮点的实现差异不会是不一致的来源。
3、数据时效性设置
-
秒:setex
-
毫秒:psetex
#秒为单位
127.0.0.1:6379> setex tel 10 1
OK
127.0.0.1:6379> get tel
"1"
127.0.0.1:6379> get tel
(nil)
127.0.0.1:6379> keys *
(empty list or set)
#如果key已经存在,setex命令将会替换旧的值。
127.0.0.1:6379> setex tel 30 1
OK
127.0.0.1:6379> get tel
"1"
127.0.0.1:6379> setex tel 60 2
OK
127.0.0.1:6379> get tel
"2"
127.0.0.1:6379> get tel
"2"
127.0.0.1:6379> get tel
(nil)
127.0.0.1:6379> keys *
(empty list or set)
#毫秒为单位
127.0.0.1:6379> psetex tel 10 1
OK
127.0.0.1:6379> get tel
(nil)
127.0.0.1:6379> keys *
(empty list or set)
#当key已存在,psetex命令将会替换旧的值。
127.0.0.1:6379> psetex tel 3600 1
OK
127.0.0.1:6379> get tel
"1"
127.0.0.1:6379> psetex tel 72000 2
OK
127.0.0.1:6379> get tel
"2"
127.0.0.1:6379> get tel
"2"
127.0.0.1:6379> get tel
(nil)
127.0.0.1:6379> keys *
(empty list or set)
注意:
-
setex是一个原子(atomic)操作, 它可以在同一时间内完成设置值和设置过期时间这两个操作, 因此 setex命令在Redis用做缓存的时候非常实用。
-
当秒、毫秒参数不合法时, 命令将返回错误
-
key超过设置的有效期时,key将不存在
4、索引操作
每个字符串都是由一系列连续的字节组成,所以每个字节都有相对应索引。Redis为字符串键提供了索引操作命令,允许用户通过正数索引或者负数索引对值的某个字节或某部分进行处理。
-
设置:setrange
-
获取:getrange
#设置key
127.0.0.1:6379> set msg "hello world"
OK
127.0.0.1:6379> get msg
"hello world"
#指定key按偏移量设置、获取值,getrange由start和end偏移决定,-1表示最后一个字符, -2表示倒数第二个字符,以此类推。
127.0.0.1:6379> getrange msg 0 6
"hello w"
127.0.0.1:6379> getrange msg -5 -1
"world"
#setrange命令会返回被修改之后, 字符串值的长度。
127.0.0.1:6379> setrange msg 5 fff
(integer) 11
127.0.0.1:6379> getrange msg 0 -1
"hellofffrld"
#偏移量超出原子符长度,空字符处理
127.0.0.1:6379> setrange msg 13 aa
(integer) 15
127.0.0.1:6379> getrange msg 0 -1
"hellofffrldx00x00aa"
#不存在的key当作空白字符串处理。
127.0.0.1:6379> setrange message 5 aa
(integer) 7
127.0.0.1:6379> getrange message 0 -1
"x00x00x00x00x00aa"
注意:
-
setrange命令会确保字符串足够长以便将
value
设置到指定的偏移量上, 如果键key
原来储存的字符串长度比偏移量小(比如字符串只有5
个字符长,但你设置的offset
是10
), 那么原字符和偏移量之间的空白将用零字节(zerobytes,"x00"
)进行填充。
5、位操作
位操作只有两个值,0和1,8个位正好是1b,所以位操作是非常节省空间的一种操作。
-
设置:setbit
-
获取:getbit
#setbit设置或清除指定偏移量上的位(bit),根据值value是1或0来决定设置或清除位bit。当key不存在时会创建一个新的字符串。
#把第10086个位置设置为1
127.0.0.1:6379> setbit bit 10086 1
(integer) 0
#获取第10086个位置的值 看是0还是1
127.0.0.1:6379> getbit bit 10086
(integer) 1
#bit默认被初始化为0
127.0.0.1:6379> getbit bit 100
(integer) 0
其实就是把某个位标记为1或者0而已,但是它的好处在于非常节省空间。位的应用场景参考之前发的文章:Redis数据类型bitmap。
6、其他操作
-
获取字符串长度:strlen
-
追加内容:append
-
设置新值并返回旧值:getset
#设置key
127.0.0.1:6379> set key1 "one"
OK
127.0.0.1:6379> strlen key1
(integer) 3
#不存在的key,值长度为0
127.0.0.1:6379> strlen key2
(integer) 0
#append:如果原始信息存在就追加,否则新建
127.0.0.1:6379> append key1 "two"
(integer) 6
127.0.0.1:6379> get key1
"onetwo"
#返回旧值onetwo
127.0.0.1:6379> getset key1 "three"
"onetwo"
127.0.0.1:6379> get key1
"three"
#没有旧值,返回nil
127.0.0.1:6379> getset db redis
(nil)
注意:
-
append可以用来为一系列定长(fixed-size)数据(sample)提供一种紧凑的表示方式,通常称之为时间序列。这个模式的唯一缺陷是我们只能增长时间序列,而不能对时间序列进行缩短,因为 Redis 目前还没有对字符串进行修剪(tirm)的命令,但是,不管怎么说,这个模式的储存方式还是可以节省下大量的空间。
1.2、使用场景
1、缓存数据
我们将用户信息结构体使用JSON 序列化成字符串,然后将序列化后的字符串塞进Redis来缓存。同样,取用户信息会经过一次反序列化的过程。比如存储登录用户信息、电商中存储商品信息、主页高频信息显示控制,利用redis作为缓存,配合其它数据库作为存储层,利用redis支持高并发的特点,可以大大加快系统的读写速度、以及降低后端数据库的压力。
实例:新浪微博大V主页显示粉丝数与微博量
#为大V用户设定用户信息,以用户主键和属性值作为key,后台设定定时刷新策略即可。
127.0.0.1:6379> set user:id:00789:fans 123456789
OK
127.0.0.1:6379> set user:id:00789:blogs 739
OK
#粉丝数加1
127.0.0.1:6379> incr user:id:00789:fans
(integer) 123456790
#以json格式存储大V用户信息,定时刷新(也可以使用hash类型)
127.0.0.1:6379> set user:id:00789 (id:00789,blogs:789,fans:123456789)
OK
127.0.0.1:6379> get user:id:00789
"(id:00789,blogs:789,fans:123456789)"
数据库中的热点数据key命名惯例:
表名 | 主键名 | 主键值 | 字段名 | |
---|---|---|---|---|
eg1 | order | id | 29437595 | name |
eg2 | equiq | id | 390472345 | type |
eg3 | news | id | 202004150 | title |
注意:redis的数据主要还是来自于数据库。
2、计数器
想知道什么时候封锁一个IP地址(访问超过几次),短信限流,访问量统计,每次访问博客和文章的,都用incr命令加一。文章的阅读量,视频的播放量等等都会使用redis来计数,每播放一次,对应的播放量就会加1,同时将这些数据异步存储到数据库中达到持久化的目的。
3、共享用户Session
例如:一个分布式 Web 服务将用户的 Session 信息(例如用户登录信息)保存在各自服务器中,这样会造成一个问题,出于负载均衡的考虑,分布式服务会将用户的访问均衡到不同服务器上,用户刷新一次访问可能会发现需要重新登录,或者访问页面缓存cookie,这两种方式做有一定弊端,1)每次都重新登录效率低下 2)cookie保存在客户端,有安全隐患。为了解决这个问题,可以使用 Redis 将用户的 Session 进行集中管理,在这种模式下只要保证Redis是高可用和扩展性的,每次用户更新或者查询登录信息都直接从Redis 中集中获取,大大提高效率,如图:
4、控制数据库表主键ID
为数据库表主键提供生成策略,保障数据库表主键的唯一性。(此方案适用于所有数据库,且支持数据库集群。)
5、控制数据生命周期
通过数据是否失效控制业务行为,适用于所有具有时效性限定控制的操作。(限时投票类)
1.3、注意事项
string类型数据操作的注意事项。
1、数据操作不成功的反馈与数据正常操作之间的差异
#表示运行结果是否成功
(integer)O #false失败
(integer)1 #true成功
#表示运行结果值
(integer)3 #3 3个
(integer)1 #1 1个
2、数据未获取到
(nil) #等同于null
3、数据最大存储量
512MB
4、数值计算最大范围
9223372036854775807
2、Hash哈希
Redis hash 是一个 string 类型的 field 和 value 的映射表,redis本身就是一个key-value型数据库,因此hash数据结构相当于在value中又套了一层key-value型数据,所以hash 特别适合用于存储关系型对象。hash类型底层使用哈希表结构实现数据存储。每个 hash 可以存储 2^32 -1 键值对(40多亿)
2.1、Hash命令
以下是一些用于在 Redis 中管理哈希的基本命令的列表(15个命令):
命令 | 描述 |
---|---|
HDEL | 删除一个或多个哈希表字段 |
HEXISTS | 查看哈希表 key 中,指定的字段是否存在 |
HGET | 获取存储在哈希表中指定字段的值 |
HGETALL | 获取在哈希表中指定 key 的所有字段和值 |
HINCRBY | 为哈希表 key 中的指定字段的整数值加上增量 increment |
HINCRBYFLOAT | 为哈希表 key 中的指定字段的浮点数值加上增量 increment |
HKEYS | 获取所有哈希表中的字段 |
HLEN | 获取哈希表中字段的数量 |
HMGET | 获取所有给定字段的值 |
HMSET | 同时将多个 field-value (域-值)对设置到哈希表 key 中 |
HSET | 将哈希表 key 中的字段 field 的值设为 value |
HSETNX | 只有在字段 field 不存在时,设置哈希表字段的值 |
HVALS | 获取哈希表中所有值 |
HSCAN | 迭代哈希表中的键值对 |
HSTRLEN | 返回哈希表 key 中, 与给定域 field 相关联的值的字符串长度 |
1、单个数据与多个数据操作
-
设置:hset、hsetnx、hmset
-
获取:hget、hmget、hgetall
#单个数据设置、获取
127.0.0.1:6379> hset lin weight "65"
(integer) 1
127.0.0.1:6379> hget lin weight
"65"
#如果字段已经存在于哈希表中,旧值将被覆盖。
127.0.0.1:6379> hset lin weight 70
(integer) 0
127.0.0.1:6379> hget lin weight
"70"
#从Redis 4.0起,hset可以一次设置一个或多个 field/value 对
127.0.0.1:6379> hset lin address gz sex male
(integer) 2
127.0.0.1:6379> hget lin address
"gz"
127.0.0.1:6379> hget lin sex
"male"
#HSETNX命令用于为哈希表中不存在的字段赋值 。
127.0.0.1:6379> hsetnx chen weight "70"
(integer) 1
127.0.0.1:6379> hget chen weight
"70"
#如果字段已经存在于哈希表中,操作无效。
127.0.0.1:6379> hsetnx chen weight "75"
(integer) 0
127.0.0.1:6379> hget chen weight
"70"
#多个数据设置、获取
#hmset命令会覆盖哈希表中已存在的字段。
127.0.0.1:6379> hmset lin weight 65 height 175
OK
127.0.0.1:6379> hmget lin weight height
1) "65"
2) "175"
#如果指定的字段(field)不存在于哈希表或者 key 不存在,那么返回一个 nil 值。
127.0.0.1:6379> hget lin age
(nil)
127.0.0.1:6379> hgetall lin
1) "weight"
2) "65"
3) "height"
4) "175"
#若key不存在,hgetall返回空列表
127.0.0.1:6379> hgetall liu
(empty list or set)
2、数值增量操作
-
增量:hincrby、hincrbyfloat
127.0.0.1:6379> hset chen weight 65
(integer) 1
#Hincrby命令用于为哈希表中的字段值加上指定增量值
127.0.0.1:6379> hincrby chen weight 5
(integer) 70
#增量也可以为负数,相当于对指定字段进行减法操作。
127.0.0.1:6379> hincrby chen weight -5
(integer) 65
#Hincrbyfloat 命令用于为哈希表中的字段值加上指定浮点数增量值
127.0.0.1:6379> hincrbyfloat chen weight 5
"70"
#Hincrbyfloat也可以是负数
127.0.0.1:6379> hincrbyfloat chen weight -10
"60"
#如果指定的字段不存在,那么在执行命令前,字段的值被初始化为0
127.0.0.1:6379> hincrby chen age 20
(integer) 20
127.0.0.1:6379> hincrbyfloat chen foot 23.5
"23.5"
127.0.0.1:6379> hgetall chen
1) "weight"
2) "60"
3) "age"
4) "20"
5) "foot"
6) "23.5"
3、迭代键值对
hscan命令用于遍历哈希表中的键值对。
127.0.0.1:6379> hmset sites google "google.com" redis "redis.com.cn" baidu "baidu.com"
OK
127.0.0.1:6379> hkeys sites
1) "google"
2) "redis"
3) "baidu"
#返回的每个元素都是一个元组,每一个元组元素由一个字段(field) 和值(value)组成。
127.0.0.1:6379> hscan sites 0 match "red*"
1) "0"
2) 1) "redis"
2) "redis.com.cn"
4、其他操作
-
删除字段:hdel
-
字段检测:hexists
-
字段数量:hlen
-
获取所有字段:hkeys
-
获取所有字段值:hvals
-
获取字段值的字符串长度:hstrlen
127.0.0.1:6379> hmset lin weight 65 height 175 age 25
OK
#删除单个字段
127.0.0.1:6379> hdel lin age
(integer) 1
#删除多个字段,返回被成功删除字段的数量
127.0.0.1:6379> hdel lin weight height
(integer) 2
127.0.0.1:6379> hgetall lin
(empty list or set)
#Hexists命令用于查看哈希表的指定字段field 是否存在,1 哈希表含有给定字段field,0 哈希表不含有给定字段,或 key 不存在。
127.0.0.1:6379> hexists lin age
(integer) 0
127.0.0.1:6379> hset lin age 25
(integer) 1
127.0.0.1:6379> hexists lin age
(integer) 1
#HLEN命令用于获取哈希表中字段(fields)的数量,当 key 不存在时,返回 0
127.0.0.1:6379> hgetall lin
1) "age"
2) "25"
127.0.0.1:6379> hlen lin
(integer) 1
#HKEYS 返回存储在 key 中哈希表的所有字段,当 key 不存在时,返回空表
127.0.0.1:6379> hkeys lin
1) "age"
#HVALS 命令返回哈希表所有field的值
127.0.0.1:6379> hvals lin
1) "25"
#hstrlen返回给定field的值字符串长度
127.0.0.1:6379> hstrlen lin age
(integer) 2
2.2、使用场景
1、存储数据表记录
由于hash数据类型的key-value的特性,用来存储关系型数据库中表记录,是redis中哈希类型最常用的场景。一条记录作为一个key-value,把每列属性值对应成field-value存储在哈希表当中,然后通过key值来区分表当中的主键。
2、对象存储
Hash 也可以同于对象存储,比如存储用户信息,优化用户信息的获取,不需要重复从数据库当中读取,提高系统性能。与字符串不一样的是,字符串是需要将对象进行序列化(比如 json 序列化)之后才能保存,而 Hash 则可以讲用户对象的每个字段单独存储,这样就能节省序列化和反序列的时间。如下:
3、购物车
此外还可以保存用户的购买记录,比如 key 为用户 id,field 为商品 i d,value 为商品数量。同样还可以用于购物车数据的存储,比如 key 为用户 id,field 为商品 id,value为购买数量,购买时使用升降值来控制产品数量等等:
解决方案:
-
以用户id作为key,为每位客户创建一个hash存储结构存储对
-
将商品编号作为field,购买数量作为value进行存储
-
添加商品:hset追加全新的field和value
-
浏览:遍历hash
-
更改数量:hincrby自增/自减,设置value值
-
商品总数:hlen商品field
-
删除商品:hdel删除field
-
全选商品:hgetall
-
清空:del删除key
购物车实现:
#创建g:1001、g:1002商品
127.0.0.1:6379> hmset user:001 g:1001 100 g:1002 200
OK
127.0.0.1:6379> hmget user:001 g:1001 g:1002
1) "100"
2) "200"
127.0.0.1:6379> hincrby user:001 g:1001 10
(integer) 110
127.0.0.1:6379> hlen user:001
(integer) 2
127.0.0.1:6379> hset user:001 g:1003 300
(integer) 1
#所有购买商品记录
127.0.0.1:6379> hgetall user:001
1) "g:1001"
2) "110"
3) "g:1002"
4) "200"
5) "g:1003"
6) "300"
#删除商品
127.0.0.1:6379> hdel user:001 g:1003
(integer) 1
当前仅仅是将数据存储到了redis中,并没有起到加速作用,商品信息还需要存到redis中。
加速购物车呈现:
-
每条购物车中的商品记录保存成两条field
-
field1用于保存购买数量:
命名格式:商品id:nums
保存数据:数值
-
field2用于保存购物车中的显示信息,包含文字描述,图片地址,所属商家信息等等
命名格式:商品id:info
保存数据:json
-
使用hsetnx判断重复信息
实例:创建g01商品信息info
#重复信息
127.0.0.1:6379> hmset 003 g01:nums 100 g01:info {....}
OK
127.0.0.1:6379> hgetall 003
1) "g01:nums"
2) "100"
3) "g01:info"
4) "{....}"
127.0.0.1:6379> hset 003 g01:nums 200
(integer) 0
127.0.0.1:6379> hgetall 003
1) "g01:nums"
2) "200"
3) "g01:info"
4) "{....}"
#hsetnx去重 hsetnx只有在字段 field 不存在时,设置哈希表字段的值。
127.0.0.1:6379> hsetnx 003 g01:nums 400
(integer) 0
127.0.0.1:6379> hsetnx 003 g05:nums 1
(integer) 1
127.0.0.1:6379> hgetall 003
1) "g01:nums"
2) "200"
3) "g01:info"
4) "{....}"
5) "g05:nums"
6) "1"
4、抢购、限购活动
应用于抢购、限购类、限量发送优惠券、激活码等业务的数据库存储设计。
解决方案:
-
以商家id为key
-
将参与抢购的商品id作为field
-
将参与抢购的商品数量作为对应的value
-
抢购时使用降值的方式控制产品数量
-
实际业务中还有超卖等实际问题,这里不做讨论
实例:商家售卖30、50、100块商品抢购活动
#实现抢购
127.0.0.1:6379> hmset p01 c30 1000 c50 1000 c100 1000
OK
127.0.0.1:6379> hincrby p01 c30 -2
(integer) 998
127.0.0.1:6379> hincrby p01 c100 -20
(integer) 980
127.0.0.1:6379> hgetall p01
1) "c30"
2) "998"
3) "c50"
4) "1000"
5) "c100"
6) "980"
注意:redis原则上只做数据的提供和保存,尽量不要把业务放到redis上。
2.3、注意事项
hash类型数据操作的注意事项:
1、hash类型下的value只能存储字符串,不允许存储其他数据类型,不存在嵌套现象。如果数据未获取到,对应的值为(nil)
2、每个hash可以存储2^32-1个键值对
3、hash类型十分贴近对象的数据存储形式,并且可以灵活添加别除对象属性。但hash设计初衷不是为了存储大量对象而设计的,切记不可滥用,更不可以将hash作为对象列表使用
4、hgetall操作可以获取全部属性,如果内部field过多,遍历整体数据效率就很会低,有可能成为数据访问瓶颈
3、List列表
Redis 列表定义为字符串列表,列表当中的每一个字符看做一个元素,按插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。当向列表中添加元素值时,首先需要给这个列表指定一个 key 键,然后使用相应的命令,从列表的左侧(头部)或者右侧(尾部)来添加元素,这些元素会以添加时的顺序排列。列表最多可存储 2^32 – 1 元素 (4294967295, 每个列表可存储40多亿)。列表底层使用双向链表结构实现。
3.1、List命令
下表列出了列表相关命令(17个命令):
命令 | 描述 |
---|---|
BLPOP | 移出并获取列表的第一个元素 |
BRPOP | 移出并获取列表的最后一个元素 |
BRPOPLPUSH | 从列表中弹出一个值,并将该值插入到另外一个列表中并返回它 |
LINDEX | 通过索引获取列表中的元素 |
LINSERT | 在列表的元素前或者后插入元素 |
LLEN | 获取列表长度 |
LPOP | 移出并获取列表的第一个元素 |
LPUSH | 将一个或多个值插入到列表头部 |
LPUSHX | 将一个值插入到已存在的列表头部 |
LRANGE | 获取列表指定范围内的元素 |
LREM | 移除列表元素 |
LSET | 通过索引设置列表元素的值 |
LTRIM | 对一个列表进行修剪(trim) |
RPOP | 移除并获取列表最后一个元素 |
RPOPLPUSH | 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 |
RPUSH | 在列表中添加一个或多个值 |
RPUSHX | 为已存在的列表添加值 |
1、插入、移出元素
-
插入头部:lpush、lpushx
-
插入尾部:rpush、rpushx
-
元素前或后插入:linsert
#lpush从队列左边头部插入元素 ->这样插入
127.0.0.1:6379> lpush lang php go
(integer) 2
127.0.0.1:6379> lrange lang 0 -1
1) "go"
2) "php"
#lpushx将一个值插入到已存在的列表头部,如果列表不存在时操作无效。
127.0.0.1:6379> lpushx db mysql
(integer) 0
127.0.0.1:6379> lrange db 0 -1
(empty list or set)
127.0.0.1:6379> lpush db mysql
(integer) 1
127.0.0.1:6379> lpushx db redis
(integer) 2
127.0.0.1:6379> lrange db 0 -1
1) "redis"
2) "mysql"
#rpush从队列右边尾部插入元素 <-这样插入
127.0.0.1:6379> rpush web html css js
(integer) 3
127.0.0.1:6379> lrange lang 0 -1
(empty list or set)
127.0.0.1:6379> lrange web 0 -1
1) "html"
2) "css"
3) "js"
#rpushx将一个值插入到已存在的列表尾部, 如果列表不存在,操作无效。
127.0.0.1:6379> rpushx mq rocket rabbit kafka
(integer) 0
127.0.0.1:6379> lrange mq 0 -1
(empty list or set)
127.0.0.1:6379> rpush mq rocket
(integer) 1
127.0.0.1:6379> rpushx mq rabbit
(integer) 2
127.0.0.1:6379> lrange mq 0 -1
1) "rocket"
2) "rabbit"
#linsert用于在列表的元素前或者后插入元素。当指定元素不存在于列表中时,不执行任何操作。
127.0.0.1:6379> lpush lang php go
(integer) 2
127.0.0.1:6379> lrange lang 0 -1
1) "go"
2) "php"
127.0.0.1:6379> linsert lang before go java
(integer) 3
127.0.0.1:6379> lrange lang 0 -1
1) "java"
2) "go"
3) "php"
#元素不存在情况
127.0.0.1:6379> linsert key1 before num1 10
(integer) 0
127.0.0.1:6379> lrange key1 0 -1
(empty list or set)
-
移出第一个:blpop、lpop
-
移出最后一个:brpop、rpop
#Blpop 命令移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
#返回一个含有两个元素的列表,第一个元素是被弹出元素所属的 key ,第二个元素是被弹出元素的值。
127.0.0.1:6379> lpush lang go php rust
(integer) 3
127.0.0.1:6379> blpop lang 0
1) "lang"
2) "rust"
127.0.0.1:6379> lrange lang 0 -1
1) "php"
2) "go"
#阻塞情况
127.0.0.1:6379> blpop list1 100
#另一客户端插入数据
127.0.0.1:6379> lpush list1 test1
(integer) 1
#返回数据
127.0.0.1:6379> blpop list1 100
1) "list1"
2) "test1"
(64.86s)
#Lpop 命令用于移除并返回列表的第一个元素。
127.0.0.1:6379> lpop lang
"php"
127.0.0.1:6379> lrange lang 0 -1
1) "go"
#Brpop 命令移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
#假如在指定时间内没有任何元素被弹出,则返回一个 nil 和等待时长。反之,返回一个含有两个元素的列表,第一个元素是被弹出元素所属的 key ,第二个元素是被弹出元素的值。
127.0.0.1:6379> rpush list1 a b c
(integer) 3
127.0.0.1:6379> brpop list1 0
1) "list1"
2) "c"
#阻塞情况
127.0.0.1:6379> brpop list2 100
(nil)
(100.07s)
#Rpop 命令用于移除列表的最后一个元素,返回值为移除的元素。
127.0.0.1:6379> rpop list1
"b"
127.0.0.1:6379> lrange list1 0 -1
1) "a"
-
移出最后一个元素并插入:brpoplpush、rpoplpush
#Brpoplpush 命令从列表中取出最后一个元素,并插入到另外一个列表的头部;如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
#假如在指定时间内没有任何元素被弹出,则返回一个 nil 和等待时长。反之,返回一个含有两个元素的列表,第一个元素是被弹出元素的值,第二个元素是等待时长。
127.0.0.1:6379> rpush list1 a b c
(integer) 3
127.0.0.1:6379> rpush list2 d e f g
(integer) 4
127.0.0.1:6379> brpoplpush list1 list2 100
"c"
127.0.0.1:6379> lrange list2 0 -1
1) "c"
2) "d"
3) "e"
4) "f"
5) "g"
#空列表
127.0.0.1:6379> brpoplpush list3 list1 1
(nil)
(1.04s)
#阻塞情况,等待可弹出元素
127.0.0.1:6379> brpoplpush list3 list1 100
127.0.0.1:6379> rpush list3 j
(integer) 1
127.0.0.1:6379> brpoplpush list3 list1 100
"j"
(16.40s)
127.0.0.1:6379> lrange list1 0 -1
1) "j"
2) "a"
3) "b"
#rpoplpush移除列表的最后一个元素,并将该元素添加到另一个列表并返回。
127.0.0.1:6379> rpush list1 a b c
(integer) 3
127.0.0.1:6379> rpush list2 d e
(integer) 2
127.0.0.1:6379> rpoplpush list1 list2
"c"
127.0.0.1:6379> lrange list2 0 -1
1) "c"
2) "d"
3) "e"
-
移除元素:lrem
#删除指定元素,lrem 命令会从列表中找到等于 value 的元素进行删除,根据 count 的不同 分为三种情况:
#count>0,从左到右,删除最多 count 个元素。
#count<0,从右到左,删除最多 count 绝对值个元素。
#count=0,删除所有。
127.0.0.1:6379> rpush book c java php java python java
(integer) 6
127.0.0.1:6379> lrange book 0 -1
1) "c"
2) "java"
3) "php"
4) "java"
5) "python"
6) "java"
127.0.0.1:6379> lrem book 1 java
(integer) 1
127.0.0.1:6379> lrem book -1 java
(integer) 1
127.0.0.1:6379> lrem book 0 java
(integer) 1
127.0.0.1:6379> lrange book 0 -1
1) "c"
2) "php"
3) "python"
#不存在key会被当作空list处理,所以当 key 不存在的时候,这个命令会返回 0。
127.0.0.1:6379> lrem list3 0 a
(integer) 0
2、索引操作
-
设置:lindex
-
修剪:ltrim
-
设置:lset
#Lindex 命令用于通过索引获取列表中的元素。你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
127.0.0.1:6379> rpush list1 a b c d e
(integer) 5
127.0.0.1:6379> lrange list1 0 -1
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
127.0.0.1:6379> lindex list1 1
"b"
127.0.0.1:6379> lindex list1 -1
"e"
# 如果指定索引值不在列表的区间范围内,返回 nil 。
127.0.0.1:6379> lindex list1 10
(nil)
#Ltrim 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
#下标 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
127.0.0.1:6379> rpush list2 a b c d
(integer) 4
127.0.0.1:6379> lrange list2 0 -1
1) "a"
2) "b"
3) "c"
4) "d"
127.0.0.1:6379> ltrim list2 1 -1
OK
127.0.0.1:6379> lrange list2 0 -1
1) "b"
2) "c"
3) "d"
#Lset 通过索引来设置元素的值,当索引参数超出范围,或对一个空列表进行 LSET 时,返回一个错误。
127.0.0.1:6379> rpush list1 a b c
(integer) 3
127.0.0.1:6379> lrange list1 0 -1
1) "a"
2) "b"
3) "c"
127.0.0.1:6379> lset list1 1 d
OK
127.0.0.1:6379> lrange list1 0 -1
1) "a"
2) "d"
3) "c"
3、其他操作
-
获取列表指定元素:lrange
-
获取列表长度:llen
#Lrange 返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
127.0.0.1:6379> rpush language python php go
(integer) 3
127.0.0.1:6379> lrange language 0 -1
1) "python"
2) "php"
3) "go"
#不存在的区间
127.0.0.1:6379> lrange language 5 10
(empty list or set)
#Llen 命令用于返回列表的长度。如果列表 key 不存在,则 key 被解释为一个空列表,返回 0 。如果 key 不是列表类型,返回一个错误。
127.0.0.1:6379> llen language
(integer) 3
3.2、使用场景
1、消息队列
Redis 列表可以被当做栈、队列来使用,如果列表的元素是“左进右出”那就是队列模型;如果元素是“右进右出”那就是栈模型。实现工作队列(利用 lists 的 push 操作,将任务存在 lists 中,然后工作线程再用 pop 操作将任务取出进行执行 ),例如消息队列
-
右进左出
127.0.0.1:6379> rpush book c python java
(integer) 3
127.0.0.1:6379> lpop book
"c"
127.0.0.1:6379> lpop book
"python"
127.0.0.1:6379> lpop book
"java"
127.0.0.1:6379> lpop book
(nil)
-
右进右出
127.0.0.1:6379> rpush book c python java
(integer) 3
127.0.0.1:6379> rpop book
"java"
127.0.0.1:6379> rpop book
"python"
127.0.0.1:6379> rpop book
"c"
127.0.0.1:6379> rpop book
(nil)
-
实现日志消息队列
企业运营中,系统将产生大量的运营数据,通过消息队列保障多台服务器操作日志统一顺序输出
127.0.0.1:6379> rpush logs a1..
(integer) 1
127.0.0.1:6379> rpush logs a1...
(integer) 2
127.0.0.1:6379> rpush logs b1..
(integer) 3
127.0.0.1:6379> rpush logs c1..
(integer) 4
127.0.0.1:6379> rpush logs b1...
(integer) 5
127.0.0.1:6379> rpush logs c1...
(integer) 6
127.0.0.1:6379> lrange logs 0 -1
1) "a1.."
2) "a1..."
3) "b1.."
4) "c1.."
5) "b1..."
6) "c1..."
除上述模型外,Redis 的列表也常被用作异步队列。使用流程大致如下:一个线程将需要延时处理的任务序列化成字符串,并“塞”进 Redis 列表中,而另外一个线程则以轮询的方式从该列表中读取“任务”。
使用参考:
-
lpush+lpop=Stack(栈)
-
lpush+rpop=Queue(队列)
-
lpush+ltrim=Capped Collection(有限集合)
-
lpush+brpop=Message Queue(消息队列)
数据的生产者可以通过Lpush命令从左边插入数据,多个数据消费者,可以使用BRpop命令阻塞的“抢”列表尾部的数据。
2、最新消息展示
应用于具有操作先后顺序的数据控制。比如最新列表,最新评论。List 类型的 lpush 命令和 lrange 命令能实现最新列表的功能,每次通过 lpush 命令往列表里插入新的元素,然后通过 lrange 命令读取最新的元素列表,如朋友圈的点赞列表、评论列表:
1)、A关注了B,C等大V
2)、B发微博了,消息ID为1001:LPUSH msg:{A的ID} 1001
3)、C发微博了,消息ID为1002:LPUSH msg:{A的ID} 1002
4)、A查看最新的5条微博消息:LRANGE msg:{A的ID} 0 5
按照时间排序的这些朋友圈信息等。
3、热销榜、文章列表
热销榜,文章列表或者数据分页展示的应用。比如,我们常用的博客网站的文章列表,当用户量越来越多时,而且每一个用户都有自己的文章列表,而且当文章多时,都需要分页展示,这时可以考虑使用redis的列表,列表不但有序同时还支持按照范围内获取元素,可以使用命令lrange key 0 n 分页获取文章列表,完美解决分页查询功能。大大提高查询效率。
3.3、注意事项
list类型数据操作注意事项如下:
1、list中保存的数据都是string类型的,数据总容量是有限的,最多232-1个元素(4294967295)。
2、list具有索引的概念,但是操作数据时通常以队列的形式进行入队出队操作,或以栈的形式进行入栈出栈操作。
3、获取全部数据操作结束索引设置为-1。
4、list可以对数据进行分页操作,通常第一页的信息来自于list,第2页及更多的信息通过数据库的形式加载。
4、Set集合
redis集合(set)类型和list列表类型类似,都可以用来存储多个字符串元素的集合,但是和list不同的是set集合当中不允许重复的元素。而且set集合当中元素是没有顺序的,不存在元素下标。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1),它支持集合内的增删改查, 并且支持多个集合间的交集、并集、差集操作。可以利用这些集合操作,解决程序开发过程当中很多数据集合间的问题。集合中最大的成员数为 2^32 – 1(4294967295, 每个集合可存储40多亿个成员)。
4.1、Set命令
下表列出了 Redis 集合相关命令(15个命令):
命令 | 描述 |
---|---|
SADD | 向集合添加一个或多个成员 |
SCARD | 获取集合的成员数 |
SDIFF | 返回给定所有集合的差集 |
SDIFFSTORE | 返回给定所有集合的差集并存储在 destination 中 |
SINTER | 返回给定所有集合的交集 |
SINTERSTORE | 返回给定所有集合的交集并存储在 destination 中 |
SISMEMBER | 判断 member 元素是否是集合 key 的成员 |
SMEMBERS | 返回集合中的所有成员 |
SMOVE | 将 member 元素从 source 集合移动到 destination 集合 |
SPOP | 移除并返回集合中的一个随机元素 |
SRANDMEMBER | 返回集合中一个或多个随机数 |
SREM | 移除集合中一个或多个成员 |
SUNION | 返回所有给定集合的并集 |
SUNIONSTORE | 所有给定集合的并集存储在 destination 集合中 |
SSCAN | 迭代集合中的元素 |
1、添加、获取元素
-
添加:sadd
-
获取:smembers
#Sadd 命令将一个或多个成员元素加入到集合中,已经存在于集合的成员元素将被忽略。
127.0.0.1:6379> sadd db redis mysql mongodb
(integer) 3
#Smembers 命令返回集合中的所有的成员。不存在的集合 key 被视为空集合。
127.0.0.1:6379> smembers db
1) "mongodb"
2) "redis"
3) "mysql"
#空集合
127.0.0.1:6379> smembers set1
(empty list or set)
2、随机操作元素
-
获取:srandmember
-
移出:spop
#Srandmember 命令随机从集合返回指定个数元素,[count]是可选参数,如果不写默认为 1,如果 count 为正数,且小于集合基数,那么命令返回一个包含 count 个元素的数组,数组中的元素各不相同。如果 count 大于等于集合基数,那么返回整个集合。如果 count 为负数,那么命令返回一个数组,数组中的元素可能会重复出现多次,而数组的长度为 count 的绝对值。
127.0.0.1:6379> sadd list1 a b c d
(integer) 4
127.0.0.1:6379> srandmember list1 2
1) "b"
2) "d"
# Spop 命令用于移除集合中的指定 key 的一个或多个随机元素,移除后会返回移除的元素。
127.0.0.1:6379> sadd db mysql redis mongodb memcache
(integer) 4
127.0.0.1:6379> spop db 2
1) "memcache"
2) "redis"
127.0.0.1:6379> smembers db
1) "mongodb"
2) "mysql"
Srandmember和 Spop相似,但 Spop 将随机元素从集合中移除并返回,而 Srandmember 则仅仅返回随机元素,而不对集合进行任何改动。
3、集合的交并差集
-
交集:sinter、sinterstore(交集取两个集合共有部分)
-
并集:sunion、sunionstore(并集取两个集合全部子集)
-
差集:sdiff、sdiffstore(差集取左边集合独有子集)
#求多个集合的交集
127.0.0.1:6379> sadd book redis mysql mongodb
(integer) 3
127.0.0.1:6379> sadd database memcache redis
(integer) 2
127.0.0.1:6379> sinter book database
1) "redis"
127.0.0.1:6379> sinterstore db book database
(integer) 1
#两个集合共有部分
127.0.0.1:6379> smembers db
1) "redis"
#求多个集合的并集
127.0.0.1:6379> sadd book redis mysql mongodb
(integer) 3
127.0.0.1:6379> sadd database memcache redis
(integer) 2
127.0.0.1:6379> sunion book database
1) "mysql"
2) "mongodb"
3) "memcache"
4) "redis"
127.0.0.1:6379> sunionstore union book database
(integer) 4
#合并全部子集
127.0.0.1:6379> smembers union
1) "redis"
2) "memcache"
3) "mysql"
4) "mongodb"
#求多个集合的差集
#返回第一个集合与其他集合之间的差异,也可以认为说第一个集合中独有的元素。不存在的集合 key 将视为空集。
127.0.0.1:6379> sadd book redis mysql mongodb
(integer) 3
127.0.0.1:6379> sadd database memcache redis
(integer) 2
127.0.0.1:6379> sdiff book databse
1) "mysql"
2) "mongodb"
127.0.0.1:6379> sdiffstore diff book database
(integer) 2
127.0.0.1:6379> smembers diff
1) "mysql"
2) "mongodb"
4、其他操作
-
迭代元素:sscan
-
删除元素:srem
-
检测元素:sismember
-
计算元素个数:scard
-
移动元素:smove
#Sscan 命令用于迭代集合中键的元素,Sscan 继承自 Scan。
127.0.0.1:6379> sadd key1 one two
(integer) 2
127.0.0.1:6379> sadd key2 three four
(integer) 2
127.0.0.1:6379> sscan key1 0 match t*
1) "0"
2) 1) "two"
#Srem 删除元素,返回结果为成功删除元素个数
127.0.0.1:6379> smembers book
1) "redis"
2) "mysql"
3) "mongodb"
127.0.0.1:6379> srem book redis mysql mongodb
(integer) 0
127.0.0.1:6379> smembers book
(empty list or set)
#Sismember 判断元素是否在集合中,如果给定元素 element 在集合内返回 1,反之返回 0
127.0.0.1:6379> smembers book
1) "redis"
2) "mysql"
3) "mongodb"
127.0.0.1:6379> sismember book memcache
(integer) 0
127.0.0.1:6379> sismember book redis
(integer) 1
#Scard 计算元素个数,scard 的时间复杂度为 O(1),它不会遍历集合所有元素
127.0.0.1:6379> sadd book mysql redis mongodb
(integer) 3
127.0.0.1:6379> scard book
(integer) 3
#Smove 用于从集合source 中移动成员member 到集合 destination
127.0.0.1:6379> sadd key1 one two three
(integer) 3
127.0.0.1:6379> sadd key2 four
(integer) 1
127.0.0.1:6379> smove key1 key2 two
(integer) 1
127.0.0.1:6379> smembers key2
1) "four"
2) "two"
4.2、使用场景
1、标签、共同好友
应用于同类信息的关联搜索,二度关联搜索,深度关联搜索。例如:共同好友 ,给用户添加标签,一个人对应多个不同的标签。
同类信息解决方案:
-
显示共同关注(一度)
-
显示共同好友(一度)
-
由用户A出发,获取到好友用户B的好友信息列表(一度)
-
由用户A出发,获取到好友用户B的购物清单列表(二度)
-
由用户A出发,获取到好友用户B的游戏充值列表(二度)
#给用户打标签
127.0.0.1:6379> sadd user:1:tags tag1 tag2
(integer) 2
#给标签添加用户
127.0.0.1:6379> sadd tag1:users user:1
(integer) 1
127.0.0.1:6379> sadd tag2:users user:1
(integer) 1
#使用交集(sinter)求两个user的共同标签
127.0.0.1:6379> sinter user:1:tags user:2:tags
(empty list or set)
好友推荐时,根据tag求交集,大于某个阈值就可以推荐。好友/关注/粉丝/感兴趣的人集合,可以使用上面的取交集、并集相关的命令。
2、统计网站的独立IP
利用set集合当中元素不唯一性,用于同类型数据的快速去重,统计访问网站的所有独立ip。
解决方案:
-
利用set集合的数据去重特征,记录各种访问数据
-
建立string类型数据,利用incr统计每日访问量(PV)
-
建立set模型,记录不同cookies数量(UV)
-
建立set模型,记录不同IP数量(IP)
#网站访问量统计
127.0.0.1:6379> sadd ips 1.2.3.4
(integer) 1
127.0.0.1:6379> sadd ips 2.3.4.5
(integer) 1
127.0.0.1:6379> sadd ips 2.3.4.5
(integer) 0
127.0.0.1:6379> scard ips
(integer) 2
3、权限校验
解决方案:
-
依赖set集合数据不重复特点,依赖set集合hash存储结构特征完成数据过滤与快速查询
-
根据用户id获取用户所有角色
-
根据用户所有角色获取用户所有操作权限放入set集合
-
根据用户所有角色获取用户所有数据全选放入set集合
127.0.0.1:6379> sadd rid:001 getall
(integer) 1
127.0.0.1:6379> sadd rid:001 getById
(integer) 1
127.0.0.1:6379> sadd rid:002 getCount
(integer) 1
127.0.0.1:6379> sadd rid:002 getall
(integer) 1
127.0.0.1:6379> sadd rid:002 insert
(integer) 1
127.0.0.1:6379> sunionstore uid:007 rid:001 rid:002
(integer) 4
127.0.0.1:6379> smembers uid:007
1) "insert"
2) "getById"
3) "getCount"
4) "getall"
127.0.0.1:6379> sismember uid:007 insert
(integer) 1
校验:redis提供基础数据
4、随机数据展示
1)、随机推荐类信息
通过 srandmember 随机返回对应的内容,像一些首页获取动态内容可以这么玩。还有热点歌单推荐,应用APP推荐,大V推荐等等。
2)、微信抽奖小程序
-
点击参与抽奖加入集合:SADD item:1001 {userID}
-
查看参与抽奖的所有用户:SMEMBERS item:1001
-
抽取3名中奖者:SRANDMEMBER/SPOP item:1001 3 (SRANDMEMBER后,中奖的用户不会从集合中移除,SPOP 后,中奖的用户会从集合中移除)
5、黑白名单控制
基于白名单和黑名单设置的服务控制,有业务出于安全性方面的考虑,需要设置用户黑名单、ip 黑名单、设备黑名单等,set 类型适合存储这些黑名单数据(反爬虫),sismember 命令可用于判断用户、ip、设备是否处于黑名单之中。
4.3、注意事项
1、set类型不允许数据重复,如果添加的数据在set中已经存在,将只保留一份。
127.0.0.1:6379> sadd a a
(integer) 1
127.0.0.1:6379> sadd a a
(integer) 0
127.0.0.1:6379> smembers a
1) "a"
2、set虽然与hash存储结构相同,但是无法启用hash中存储的空间。
5、Zset有序集合
Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。但是不同的是,有序集合给每个元素多设置了一个分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数(score)却可以重复。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。集合中最大的成员数为 2^32 – 1 (4294967295, 每个集合可存储40多亿个成员)。
5.1、Zset命令
下表列出了 Redis 有序集合的基本命令(20个命令)
命令 | 描述 |
---|---|
ZADD | 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
ZCARD | 获取有序集合的成员数 |
ZCOUNT | 计算在有序集合中指定区间分数的成员数 |
ZINCRBY | 有序集合中对指定成员的分数加上增量 increment |
ZINTERSTORE | 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中 |
ZLEXCOUNT | 在有序集合中计算指定字典区间内成员数量 |
ZRANGE | 通过索引区间返回有序集合成指定区间内的成员 |
ZRANGEBYLEX | 通过字典区间返回有序集合的成员 |
ZRANGEBYSCORE | 通过分数返回有序集合指定区间内的成员 |
ZRANK | 返回有序集合中指定成员的索引 |
ZREM | 移除有序集合中的一个或多个成员 |
ZREMRANGEBYLEX | 移除有序集合中给定的字典区间的所有成员 |
ZREMRANGEBYRANK | 移除有序集合中给定的排名区间的所有成员 |
ZREMRANGEBYSCORE | 移除有序集合中给定的分数区间的所有成员 |
ZREVRANGE | 返回有序集中指定区间内的成员,通过索引,分数从高到底 |
ZREVRANGEBYSCORE | 返回有序集中指定分数区间内的成员,分数从高到低排序 |
ZREVRANK | 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序 |
ZSCORE | 返回有序集中,成员的分数值 |
ZUNIONSTORE | 计算一个或多个有序集的并集,并存储在新的 key 中 |
ZSCAN | 迭代有序集合中的元素(包括元素成员和元素分值) |
1、添加、获取元素
-
添加:zadd
-
获取:zrange、zrevrange、zrank、zrevrank(通过索引);zrangebylex(通过字典);zrangebyscore、zrevrangebyscore(通过分数)
#Zadd 命令用于将一个或多个成员元素及其分数值加入到有序集当中。
#如果某个成员已经是有序集的成员,那么更新这个成员的分数值,并通过重新插入这个成员元素,来保证该成员在正确的位置上。分数值可以是整数值或双精度浮点数。
127.0.0.1:6379> zadd db 1 mysql 2 redis 3 mongodb
(integer) 3
#Zrange 返回有序集中,指定排名范围的成员,zrange 是从低到高返回,zrevrange 反之。具有相同分数值的成员按字典序(lexicographical order )来排列。
#下标参数 start 和 stop 都以 0 为底,也就是说,以 0 表示有序集第一个成员,以 1 表示有序集第二个成员,以此类推。你也可以使用负数下标,以 -1 表示最后一个成员, -2 表示倒数第二个成员,以此类推。
127.0.0.1:6379> zrange db 0 -1 withscores # 显示整个有序集成员
1) "mysql"
2) "1"
3) "redis"
4) "2"
5) "mongodb"
6) "3"
127.0.0.1:6379> zrange db 1 2 withscores # 显示有序集下标区间 1 至 2 的成员
1) "redis"
2) "2"
3) "mongodb"
4) "3"
127.0.0.1:6379> zrange db 10 20 withscores # 测试当给定区间不存在于有序集时的情况
(empty list or set)
#Zrevrange 命令返回有序集中,指定区间内的成员。除了成员按分数值递减的次序排列这一点外, ZREVRANGE 命令的其他方面和 ZRANGE 命令一样。
127.0.0.1:6379> zrevrange db 0 -1 withscores
1) "mongodb"
2) "3"
3) "redis"
4) "2"
5) "mysql"
6) "1"
#Zrank 返回有序集中指定成员的排名,zrank 是从分数从低到高返回排名,zrevrank 反之
127.0.0.1:6379> zadd salary 3500 peter 4000 tom 5000 jack
(integer) 3
127.0.0.1:6379> zrange salary 0 -1 withscores
1) "peter"
2) "3500"
3) "tom"
4) "4000"
5) "jack"
6) "5000"
127.0.0.1:6379> zrank salary tom # 显示 tom 的薪水排名,第二
(integer) 1
127.0.0.1:6379> zrevrank salary peter # 显示 peter 的薪水排名,第三
(integer) 2
#Zrangebylex 通过字典区间返回有序集合的成员。
127.0.0.1:6379> zadd myzset 0 a 0 b 0 c 0 d 0 e 0 f 0 g
(integer) 7
127.0.0.1:6379> zrangebylex myzset - [c
1) "a"
2) "b"
3) "c"
127.0.0.1:6379> zrangebylex myzset - (c
1) "a"
2) "b"
127.0.0.1:6379> zrangebylex myzset [aaa (g
1) "b"
2) "c"
3) "d"
4) "e"
5) "f"
#Zrangebyscore 返回有序集合中指定分数区间的成员列表。其中 zrangebyscore 按照分数从低到高返回,zrevrangebyscore 反之
#具有相同分数值的成员按字典序来排列(该属性是有序集提供的,不需要额外的计算)。
#默认情况下,区间的取值使用闭区间 (小于等于或大于等于),你也可以通过给参数前增加 ( 符号来使用可选的开区间 (小于或大于)。
127.0.0.1:6379> zadd salary 2500 jack 5000 tom 12000 peter
(integer) 3
127.0.0.1:6379> zrangebyscore salary -inf +inf # 显示整个有序集
1) "jack"
2) "tom"
3) "peter"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores # 显示整个有序集及成员的 score 值
1) "jack"
2) "2500"
3) "tom"
4) "5000"
5) "peter"
6) "12000"
127.0.0.1:6379> zrangebyscore salary -inf 5000 withscores # 显示工资 <=5000 的所有成员
1) "jack"
2) "2500"
3) "tom"
4) "5000"
127.0.0.1:6379> zrangebyscore salary (5000 400000 # 显示工资大于 5000 小于等于 400000 的成员
1) "peter"
#Zrevrangebyscore 返回有序集中指定分数区间内的所有的成员。除了成员按分数值递减的次序排列这一点外, ZREVRANGEBYSCORE 命令的其他方面和 ZRANGEBYSCORE 命令一样。
127.0.0.1:6379> zrevrangebyscore salary +inf -inf # 逆序排列所有成员
1) "peter"
2) "tom"
3) "jack"
127.0.0.1:6379> zrevrangebyscore salary 10000 2000 # 逆序排列薪水介于 10000 和 2000 之间的成员
1) "tom"
2) "jack"
2、增量操作
-
增量:zincrby
#Zincrby 命令对有序集合中指定成员的分数加上增量 increment,可以通过传递一个负数值 increment ,让分数减去相应的值,比如 ZINCRBY key -5 member ,就是让 #member 的 score 值减去 5 。当 key 不存在,或分数不是 key 的成员时, ZINCRBY key increment member 等同于 ZADD key increment member 。
127.0.0.1:6379> zrange db 0 -1 withscores
1) "mysql"
2) "1"
3) "redis"
4) "2"
5) "mongodb"
6) "3"
127.0.0.1:6379> zincrby db 5 redis
"7"
127.0.0.1:6379> zrange db 0 -1 withscores
1) "mysql"
2) "1"
3) "mongodb"
4) "3"
5) "redis"
6) "7"
3、删除元素
-
不带条件删除:zrem
-
带条件删除:zremrangebylex、zremrangebyrank、zremrangebyscore
#Zrem 命令用于移除有序集中的一个或多个成员,不存在的成员将被忽略。
127.0.0.1:6379> zadd web 1 baidu 2 alibaba 3 tencent
(integer) 3
127.0.0.1:6379> zrange web 0 -1 withscores
1) "baidu"
2) "1"
3) "alibaba"
4) "2"
5) "tencent"
6) "3"
127.0.0.1:6379> zrem web alibaba # 移除单个元素
(integer) 1
127.0.0.1:6379> zrange web 0 -1 withscores
1) "baidu"
2) "1"
3) "tencent"
4) "3"
127.0.0.1:6379> zrem web tencent baidu # 移除多个元素
(integer) 2
127.0.0.1:6379> zrange web 0 -1 withscores
(empty list or set)
#Zremrangebylex 命令用于移除有序集合中给定的字典区间的所有成员。
127.0.0.1:6379> zadd myzset 0 aaaa 0 b 0 c 0 d 0 e
(integer) 5
127.0.0.1:6379> zadd myzset 0 foo 0 zap 0 zip 0 ALPHA 0 alpha
(integer) 5
127.0.0.1:6379> zrange myzset 0 -1
1) "ALPHA"
2) "aaaa"
3) "alpha"
4) "b"
5) "c"
6) "d"
7) "e"
8) "foo"
9) "zap"
10) "zip"
127.0.0.1:6379> zremrangebylex myzset [alpha [omega
(integer) 6
127.0.0.1:6379> zrange myzset 0 -1
1) "ALPHA"
2) "aaaa"
3) "zap"
4) "zip"
#Zremrangebyrank 命令用于移除有序集中,指定排名(rank)区间内的所有成员。
127.0.0.1:6379> zadd salary 2000 jack 5000 tom 3500 peter
(integer) 3
127.0.0.1:6379> zremrangebyrank salary 0 -1 #移除全部元素
(integer) 3
127.0.0.1:6379> zrange salary 0 -1 withscores
(empty list or set)
#Zremrangebyscore 命令用于移除有序集中,指定分数(score)区间内的所有成员。
127.0.0.1:6379> zadd salary 2000 tom 3500 peter 5000 jack
(integer) 3
127.0.0.1:6379> zrange salary 0 -1 WITHSCORES # 显示有序集内所有成员及其 score 值
1) "tom"
2) "2000"
3) "peter"
4) "3500"
5) "jack"
6) "5000"
127.0.0.1:6379> zremrangebyscore salary 1500 3500 # 移除所有薪水在 1500 到 3500 内的员工
(integer) 2
127.0.0.1:6379> zrange salary 0 -1 withscores # 剩下的有序集成员
1) "jack"
2) "5000"
4、元素交并集
-
交集:zinterstore
-
并集:zunionstore
#命令求集合的交集和并集,可用参数比较多,可用到再查文档
127.0.0.1:6379> zadd key1 1 one 2 two
(integer) 2
127.0.0.1:6379> zadd key2 1 one 2 two 3 three
(integer) 3
#交集
127.0.0.1:6379> zinterstore key3 2 key1 key2
(integer) 2
127.0.0.1:6379> zrange key3 0 -1 withscores
1) "one"
2) "2"
3) "two"
4) "4"
#并集
127.0.0.1:6379> zunionstore key4 2 key1 key2
(integer) 3
127.0.0.1:6379> zrange key4 0 -1
1) "one"
2) "three"
3) "two"
127.0.0.1:6379> zrange key4 0 -1 withscores
1) "one"
2) "2"
3) "three"
4) "3"
5) "two"
6) "4"
5、统计元素数据
-
计算成员个数:zcard、zcount、zlexcount
-
计算成员分数:zscore
127.0.0.1:6379> zadd database 1 redis 2 mysql 3 mongodb
(integer) 3
#计算成员个数
127.0.0.1:6379> zcard database
(integer) 3
#返回指定分数范围成员个数
127.0.0.1:6379> zcount database 2 3
(integer) 2
#返回指定字典范围成员个数
127.0.0.1:6379> zadd key1 0 a 0 b 1 c 1 d 0 e 0 f 2 g
(integer) 7
127.0.0.1:6379> zlexcount key1 - +
(integer) 7
#计算某个成员的分数
127.0.0.1:6379> zscore database redis
"1"
6、迭代元素
-
迭代元素:zscan
127.0.0.1:6379> zadd key1 1 one 2 two
(integer) 2
127.0.0.1:6379> zadd key2 1 one 2 two 3 three
(integer) 3
127.0.0.1:6379> zscan key1 0 match t*
1) "0"
2) 1) "two"
2) "2"
5.2、使用场景
1、排行榜
有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜,榜单维护可能是多方面:按照时间、按照播放量、按照获得的赞数等。
#电影排行榜
127.0.0.1:6379> zadd movies 143 aa 97 bb 201 cc
(integer) 3
127.0.0.1:6379> zrank movies bb
(integer) 0
127.0.0.1:6379> zrevrank movies bb
(integer) 2
127.0.0.1:6379> zscore movies aa
"143"
127.0.0.1:6379> zincrby movies 1 aa
"144"
127.0.0.1:6379> zscore movies aa
"144"
2、标签
比如我们博客网站常常使用到的兴趣标签,把一个个有着相同爱好,关注类似内容的用户利用一个标签把他们进行归并。
3、带权重的消息队列
用Sorted Sets来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。
解决方案:
-
对于带有权重的任务,优先处理权重高的任务,采用score记录权重即可。
多条件任务权重设定:如果权重条件过多时,需要对排序score值进行处理,保障score值能够兼容2条件或者多条件,例如外贸订单优先国内订单,总裁订单优先员工订单,经理订单优先员工订单。
-
因score长度有限,需要对数据进行截断处理,尤其是时间设置为小时或分钟级即可(折算后)
-
先设定订单类型,后设定订单角色类别,整体score长度必须是统一的,不足位补0,第一排序规则首位不得是0
-
例如外贸101,国内102,经理004,员工008
-
员工下的外贸单score值为101008(优先)
-
经理下的外贸单score值为102004
127.0.0.1:6379> zadd tasks 4 order:id:005
(integer) 1
127.0.0.1:6379> zadd tasks 1 order:id:425
(integer) 1
127.0.0.1:6379> zadd tasks 9 order:id:345
(integer) 1
127.0.0.1:6379> zrevrange tasks 0 -1 withscores
1) "order:id:345"
2) "9"
3) "order:id:005"
4) "4"
5) "order:id:425"
6) "1"
127.0.0.1:6379> zrevrange tasks 0 0
1) "order:id:345"
127.0.0.1:6379> zrem tasks order:id:345
(integer) 1
127.0.0.1:6379> zrevrange tasks 0 -1 withscores
1) "order:id:005"
2) "4"
3) "order:id:425"
4) "1"
#多条件任务权重设定
127.0.0.1:6379> zadd tt 102004 order:id:1
(integer) 1
127.0.0.1:6379> zadd tt 101008 order:id:2
(integer) 1
127.0.0.1:6379> zrevrange tt 0 -1 withscores
1) "order:id:1"
2) "102004"
3) "order:id:2"
4) "101008"
127.0.0.1:6379> zrange tt 0 -1
1) "order:id:2"
2) "order:id:1"
127.0.0.1:6379> zadd ts 14 oeder:id:3
(integer) 1
127.0.0.1:6379> zadd ts 1332 order:id:4
(integer) 1
4、时效性任务管理
基础服务+增值服务类网站会设定各位会员的试用,让用户充分体验会员优势。
解决方案:
-
对于基于时间线限定的任务处理,将处理时间记录为score值,利用排序功能区分处理的先后顺序
-
记录下一个要处理的时间,当到期后处理对应任务,移除redis中的记录,并记录下一个要处理的时间
-
当新任务要加入时,判定并更新当前下一个要处理的任务时间
-
为提升sort_set的性能,通常将任务根据特征存储成若干个sort_set。例如1小时内,1天内,周内,月内等等,操作时逐级提升,将即张操作的若干任务,纳入到1小时内处理的队列中
-
获取当前系统时间:time
127.0.0.1:6379> zadd ts 1509802345 uid:001
(integer) 1
127.0.0.1:6379> zadd ts 1509800654 uid:007
(integer) 1
127.0.0.1:6379> zadd ts 1509899254 uid:008
(integer) 1
127.0.0.1:6379> zrange ts 0 -1 withscores
1) "uid:007"
2) "1509800654"
3) "uid:001"
4) "1509802345"
5) "uid:008"
6) "1509899254"
127.0.0.1:6379> time
1) "1656324407"
2) "842782"
127.0.0.1:6379> time
1) "1656324412"
2) "246923"
还有,延迟消息队列。下单系统,下单后需要在15分钟内进行支付,如果15分钟未支付则自动取消订单。将下单后的十五分钟后时间作为score,订单作为value存入redis,消费者轮询去消费,如果消费的大于等于这笔记录的score,则将这笔记录移除队列,取消订单。
5、统计用户数据
用户发布了n篇文章,其他人看到文章后给喜欢的文章点赞,使用score来记录点赞数,有序集合会根据score排行。流程如下:
#用户发布一篇文章,初始点赞数为0,即score为0
127.0.0.1:6379> zadd user:article 0 a
(integer) 1
#有人给文章a点赞,递增1
127.0.0.1:6379> zincrby user:article 1 a
"1"
#查询点赞前三篇文章
127.0.0.1:6379> zrevrangebyscore user:article 0 2
#查询点赞后三篇文章
127.0.0.1:6379> zrangebyscore user:article 0 2
5.3、注意事项
sorted set数据类型操作注意事项如下:
1、score保存的数据存储空间是64位,如果是整数范围是-9007199254740992-9007199254740992
2、score保存的数据也可以是一个双精度的double值,基于双精度浮点数的特征,可能会丢失精度,使用时候要慎重。
3、score底层存储还是基于set结构的,因此数据不能重复,如果重复添加相同数据,score将被反复覆盖,保留最后一次修改结果。
6、总结
1、string存储对象(json)与hash存储对象
数据类型 |
特点 |
---|---|
string | 整体操作,读为主 |
hash | 更新操作灵活 |
2、列表、集合和有序集合异同
3、数据类型简单导图
本文介绍了redis五种基本数据类型的基础知识,希望能够对读者有些许帮助。对于基本数据类型的实际开发上的使用,日后还需多多使用和深入学习。
参考资料:
redis教程:https://www.bilibili.com/video/BV1XV411o7xP/?spm_id_from=333.999.0.0&vd_source=0edcff893efab75b96f0b85251f409a9
一篇详文带你入门 Redis:https://mp.weixin.qq.com/s/-3fcK4WspGk6SEsaVrdx8A
一文搞懂redis:https://mp.weixin.qq.com/s/7ct-mvSIaT3o4-tsMaKRWA
redis支持的数据类型:https://www.redis.com.cn/redis-data-types.html
带你走进redis:https://mp.weixin.qq.com/s/4bAPVdUr_XbIw9xFCtWhfw
redis数据类型:https://www.runoob.com/redis/redis-data-types.html
原文始发于微信公众号(面试技术):Redis数据类型总结
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之家整理,本文链接:https://www.bmabk.com/index.php/post/187023.html