前言
个人网站:https://linzyblog.netlify.app/
Redis官网:https://redis.io/docs/
Redis中文文档:http://www.redis.cn/topics/data-types-intro.html#strings
结合官网的学习一定是正确合理的,如果有命令或者理论我讲的不对,可以评论指出谢谢!
一、Redis数据类型介绍
Redis不仅只有key-value存储,实际上是一个数据结构服务器,支持不同的数据类型,可以解决从缓存到队列再到事件处理的各种问题。
- Strings:二进制安全的字符串
- Lists:按插入顺序排序的字符串元素的集合。Redis 列表是字符串值的链表。(用于实现栈和队列)
- Sets:不重复且无序的字符串元素的集合
- Sorted sets:类似Sets,不同的地方在于每个字符串元素都关联一个叫score的浮动数值(floating number value)。里面的元素是按score进行排序。它可以检索一系列的元素。(可以直接看前10个元素或后10个元素)
- Hashes:由field和关联的value组成的map。field和value都是字符串。实现类似于哈希表的概念。
- Bit arrays (或者说 simply bitmaps):通过特殊的命令,你可以将 String 值当作一系列 bits 处理:可以设置和清除单独的 bits,数出所有设为 1 的 bits 的数量,找到最前的被设为 1 或 0 的 bit,等等。
- HyperLogLog:是一种概率数据结构,这是被用于估计一个 set 中元素数量的概率性的数据结构。
二、Redis key
Redis key值是二进制安全的。可以用任意二进制序列作为key值,如“linzy”简单字符串到JPEG文件的内容都可以作为key值。空值也是有效的key值。
关于key的几条规则:
1.键值不要太长,例如:1024字节的键值不仅消耗大量内存,而且查询这个键值的成本也很高。
2.键值不要太短,就好是简洁表达含义的键值,例如:”u:1000:pwd”和”user:1000:password”,前者虽然简短,但是后者更容易阅读,一眼就知道键值的含义,并且后者增加的空间消耗相对较小。不过这个算个人习惯,没有人必须要求你该怎么写。
3.坚持使用一种模式,例如:”object-type : id : field"就是一个很不错的模式,像“user:1000:password"。对于多单词组合我习惯中间加_隔离,比如:”comment:123456:linzy_dashuaige“。
4.key允许最大为 512 MB。
三、Redis Strings
1、字符串简介
Redis 字符串存储字节序列,包括文本、序列化对象和二进制数组。因此,字符串是Redis最基本的数据类型。字符串用于缓存,通过提供的额外的功能也可以实现计算器以及按位运算。
2、字符串基本操作
1)通用命令
命令 | 描述 |
set key value [NX | XX] [GET] [EX seconds | PX milliseconds | |
添加 / 修改一个键值对。 EX(expire):设置过期秒数 PX:设置过期毫秒数 EXAT:设置过期的Unix秒数 PXAT:设置过期的Unix毫秒数 KEEPTTL:set时不重置ttl NX:只有当key不存在时添加一个新的键值对 XX:仅当key存在时覆盖当前value GET:修改一个键值并返回原值,如果原址不存在则返回nil |
get key | 获取key对应的value值。 注:key不存在会直接返回nil |
mset key1 value1 [key2 value2…] | 添加 / 修改一个或多个键值对。 |
mget key1 [key2…] | 获取key1 key2…获取对应的value1 value2 … |
注意:由于SET命令选项可以替换SETNX, SETEX, PSETEX, GETSET,因此在未来的 Redis 版本中,这些命令可能会被弃用并最终被删除。
#添加一个key为"name" 值为"linzy"的键值对 127.0.0.1:6379> set name linzy OK #查看key为name对应的值 127.0.0.1:6379> get name "linzy" #设置key为name的键值对10秒后过期 127.0.0.1:6379> set name linzydashuaige EX 10 OK #查看key剩余多少生存时间,key不存在则返回-2,永久键值对则返回-1 127.0.0.1:6379> ttl name (integer) 6 127.0.0.1:6379> ttl name (integer) 4 127.0.0.1:6379> ttl name (integer) 3 127.0.0.1:6379> ttl name (integer) 2 127.0.0.1:6379> ttl name (integer) -2 #清空当前库的所有键值对 127.0.0.1:6379> flushdb OK #添加多条键值对 127.0.0.1:6379> mset k1 v1 k2 v2 OK #查看当前库的所有键值对 127.0.0.1:6379> keys * 1) "k2" 2) "k1" #如果查不到则返回nil 127.0.0.1:6379> mget k1 k3 1) "v1" 2) (nil) #添加一个key为k1 值为v2的键值对,只有k1不存在才执行 127.0.0.1:6379> set k1 v2 NX (nil) 127.0.0.1:6379> get k1 "v1" #将key为k1的值修改为v2,只有k1存在才执行 127.0.0.1:6379> set k1 v2 XX OK 127.0.0.1:6379> get k1 "v2" 127.0.0.1:6379> set k1 v50 GET "v2"
2)value是字符串时操作
命令 | 描述 |
append key value | 在key对应原有的value后追加内容。 注:如果键值对不存在,则会创建新的键值对,类似于set的功能 |
strlen key | 对应value的字符串值的长度。 注:如果value非字符串类型则返回错误 |
getrange key start end | 获取[start,end]范围内的子字符串。 注:可以使用 负偏移量 来提供从字符串末尾开始的偏移量。所以 -1 表示最后一个字符,-2 表示倒数第二个字符,依此类推。 |
127.0.0.1:6379> set name linzy OK 127.0.0.1:6379> get name "linzy" #在key为name的内容后添加“dashuaibi” 127.0.0.1:6379> append name dashuaibi (integer) 14 127.0.0.1:6379> get name "linzydashuaibi" #获取key为name的字符串长度 127.0.0.1:6379> strlen name (integer) 14 #获取key为name的字符串在[5, 14]范围内的子串 127.0.0.1:6379> getrange name 5 14 "dashuaibi" 127.0.0.1:6379> getrange name 0 4 "linzy" 127.0.0.1:6379> getrange name -8 -1 "ashuaibi"
3)value是数值的操作
有关于增量/减量操作,如果键值对不存在,则会默认创建一个值为0的键值对。增量/减量操作是原子操作。
原子操作就是在多线程程序中“最小的且不可并行化的”操作,意味着多个线程访问同一个资源时,有且仅有一个线程能对资源进行操作。
命令 | 描述 |
incr key | 将key对应的value加一。 |
incrby key 整数 | 将key对应的value增加给定的数值。 |
incrbyfloat key 小数值 | 将key对应的value增加给定的数值。 |
decr key | 将key对应的value减一。 |
decrby key 整数 | 将key对应的value增加给定的数值。 |
127.0.0.1:6379> keys * (empty list or set) #将key为k1的值加一。k1不存在自动创建一个value为0的键值对 127.0.0.1:6379> incr k1 (integer) 1 127.0.0.1:6379> get k1 "1" #将key为k1的值加114514 127.0.0.1:6379> incrby k1 114514 (integer) 114515 #将key为k1的值加-1000.05 127.0.0.1:6379> incrbyfloat k1 -1000.05 "113514.95" #虽然value是数值,但是他的数据类型还是string,他依旧可以使用上面的指令 127.0.0.1:6379> type k1 string #将key为k1的值减一。k1不存在自动创建一个value为0的键值对 127.0.0.1:6379> decr k2 (integer) -1 #将key为k1的值减1000.02 127.0.0.1:6379> decrby k2 1000.02 (error) ERR value is not an integer or out of range 127.0.0.1:6379> decrby k2 1000 (integer) -1001
String 数据结构是简单的key-value 类型,value 其实不仅是String,也可以是数字。
4)临时键值对的操作
生存时间(time to live),简称为ttl,指键值对距离被删除的剩余时间(秒数或者毫秒数)
注:如果重新set设置生存时间,则会将之前的生存时间重置掉。
命令 | 描述 |
expire key 秒数 | 给键值对设置一个生存时间。 注:超时后只有对key执行DEL命令或者SET命令或者GETSET时才会清除。从概念上讲所有改变key的值的操作都会使他清除。 |
ttl key | 查看当前键值对剩余生存时间。 注:key不存在则返回-2,永久键值对返回-1 |
pexpire key 毫秒数 | 毫秒版expire |
pttl key | 毫秒版ttl |
persist key | 持久化(取消生存时间) |
127.0.0.1:6379> set name linzy OK #将key为name的键值对设置五秒后过期 127.0.0.1:6379> expire name 5 (integer) 1 127.0.0.1:6379> ttl name (integer) 3 127.0.0.1:6379> ttl name (integer) -2 127.0.0.1:6379> keys * (empty list or set) 127.0.0.1:6379> set name linzy OK #将key为name的键值对设置5000毫秒后过期 127.0.0.1:6379> pexpire name 5000 (integer) 1 127.0.0.1:6379> pttl name (integer) 2681 127.0.0.1:6379> pttl name (integer) -2 127.0.0.1:6379> set name linzy OK 127.0.0.1:6379> expire name 100 (integer) 1 127.0.0.1:6379> ttl name (integer) 98 127.0.0.1:6379> persist name (integer) 1 127.0.0.1:6379> ttl name (integer) -1
3、小结
Redis String 类型是可以与 Redis 键关联的最简单的值类型。Redis就像一个可以持久化的memcached服务器(注:memcache的数据仅保存在内存中,服务器重启后,数据将丢失)。
由于 Redis 的键是字符串,所以当我们也使用字符串类型作为值时,我们是在将一个字符串映射到另一个字符串。字符串数据类型可用于许多用例,例如缓存 HTML 片段或页面。
四、Redis Lists
1、列表简介
列表就是有序元素的序列,一般意义由两种数组(List)和链表(Linked List),例如 50,3,114514,20就是一个列表。
Redis lists基于链表实现。这意味着即使在一个list中有数百万个元素,在头部或尾部添加一个元素的操作,其时间复杂度也是常数O(1)级别的。用LPUSH 命令在十个元素的list头部添加新元素,和在千万元素list头部添加新元素的速度相同。
那么,坏消息是什么?在数组实现的list中利用索引访问元素的速度极快,而同样的操作在链表实现的list上没有那么快。
Redis Lists用链表实现的原因是:对于数据库系统来说,至关重要的特性是:能非常快的在很大的列表上添加元素。另一个重要因素是,正如你将要看到的:Redis lists能在常数时间取得常数长度。
如果需要快速访问集合元素,建议使用可排序集合(sorted sets)。
2、列表基本操作
1)通用命令
命令 | 描述 |
rpush key value0 [value1…] | 在列表尾部添加一个或多个value值。 |
lpush key value0 [value1…] | 在列表头部添加一个或多个value值。 |
rpushx key value0 [value1…] | 仅当列表存在时,在列表尾部添加一个或多个value值。 |
lpushx key value0 [value1…] | 仅当列表存在时,在列表头部添加一个或多个value值。 |
rpop key [整数] | 移除并返回存于 key 的 list 的尾部指定数值个元素。 注:与lpush组合可以实现队列,与rpush组合可以实现栈。 |
lpop key [整数] | 移除并返回存于 key 的 list 的头部指定数值个元素。 注:与rpush组合可以实现队列,与lpush组合可以实现栈。 |
lrange key start end | 查看[start,end]范围内的子序列。 注:lrange key 0 -1 查看列表所有值 |
#在key为nums的列表尾部添加[50, 3, 20, 114514]。如果键值对不存在,会自动创建一个键值对。 127.0.0.1:6379> rpush nums 50 3 20 114514 (integer) 4 #获取nums整个列表 127.0.0.1:6379> lrange nums 0 -1 1) "50" 2) "3" 3) "20" 4) "114514" # 在key为nums的列表头部添加[11451415, 666, 233] 127.0.0.1:6379> lpush nums 11451415 666 233 (integer) 7 127.0.0.1:6379> lrange nums 0 -1 1) "233" 2) "666" 3) "11451415" 4) "50" 5) "3" 6) "20" 7) "114514" # 移除并返回key为nums的列表尾部一个元素 127.0.0.1:6379> rpop nums "114514" 127.0.0.1:6379> rpop nums 2 1) "20" 2) "3" # 移除并返回key为nums的列表头部一个元素 127.0.0.1:6379> lpop nums "233" 127.0.0.1:6379> lpop nums 2 1) "666" 2) "11451415" 127.0.0.1:6379> lrange 0 -1 (error) ERR wrong number of arguments for 'lrange' command 127.0.0.1:6379> lrange nums 0 -1 1) "50" 127.0.0.1:6379> rpushx nums 1 2 3 4 (integer) 5 127.0.0.1:6379> lrange nums 0 -1 1) "50" 2) "1" 3) "2" 4) "3" 5) "4" 127.0.0.1:6379> lpushx nums 1145,14,45,14 (integer) 6 127.0.0.1:6379> lrange nums 0 -1 1) "1145,14,45,14" 2) "50" 3) "1" 4) "2" 5) "3" 6) "4"
2)数组操作
命令 | 描述 |
lset key index value | 将指定index位置上的元素修改为value。 |
linsert key <before / after> pivot value | 在基准值pivot的前 / 后插入一个值。 |
lindex key index | 按索引查看值。 |
llen key | 查看列表长度。 |
lrem key 整数 value | 删除整数个指定数值。 注:整数为正从左开始删,为负从尾部开始删 |
ltrim key start end | 只保留[start, end]范围的列表。 |
127.0.0.1:6379> rpush mylist linzy haoshuai (integer) 2 # 将key为mylist的列表索引为1的值改为dashuaige 127.0.0.1:6379> lset mylist 1 dashuaige OK 127.0.0.1:6379> lrange mylist 0 -1 1) "linzy" 2) "dashuaige" # 在key为mylist的列表的基准值"dashuaige"之前插入元素"shi"。(基准值就是从左往右找到的第一个元素就是基准值) 127.0.0.1:6379> linsert mylist before dashuaige shi (integer) 3 127.0.0.1:6379> lrange mylist 0 -1 1) "linzy" 2) "shi" 3) "dashuaige" # 获取key为mylist的列表索引为1的值 127.0.0.1:6379> lindex mylist 1 "shi" # 获取key为mylist的列表长度 127.0.0.1:6379> llen mylist (integer) 3 127.0.0.1:6379> rpush mylist 1 1 1 1 1 (integer) 8 127.0.0.1:6379> lrange mylist 0 -1 1) "linzy" 2) "shi" 3) "dashuaige" 4) "1" 5) "1" 6) "1" 7) "1" 8) "1" # 在key为mylist的列表从左往右删除3个"1"元素 127.0.0.1:6379> lrem mylist 3 1 (integer) 3 127.0.0.1:6379> lrange mylist 0 -1 1) "linzy" 2) "shi" 3) "dashuaige" 4) "1" 5) "1" 127.0.0.1:6379> lpush mylist 1 1 1 (integer) 8 127.0.0.1:6379> lrange mylist 0 -1 1) "1" 2) "1" 3) "1" 4) "linzy" 5) "shi" 6) "dashuaige" 7) "1" 8) "1" # 在key为mylist的列表从右往左删除3个"1"元素 127.0.0.1:6379> lrem mylist -3 1 (integer) 3 127.0.0.1:6379> lrange mylist 0 -1 1) "1" 2) "1" 3) "linzy" 4) "shi" 5) "dashuaige" # key为mylist的列表只保留[2, 5]的范围的子序列 127.0.0.1:6379> ltrim mylist 2 5 OK 127.0.0.1:6379> lrange mylist 0 -1 1) "linzy" 2) "shi" 3) "dashuaige"
列表索引从0开始,-n表示倒数第n个值。
3、小结
列表访问其头部或尾部的列表操作是 O(1),非常高效。但是,操作列表中元素的命令通常是 O(n)。这些示例包括LINDEX、LINSERT和LSET。运行这些命令时要小心,主要是在处理大型列表时。
列表可被用来实现聊天系统。还可以作为不同进程间传递消息的队列。关键是,你可以每次都以原先添加的顺序访问数据。这不需要任何SQL ORDER BY 操作,将会非常快,也会很容易扩展到百万级别元素的规模。
例如在评级系统中,比如社会化新闻网站 reddit.com,你可以把每个新提交的链接添加到一个list,用LRANGE可简单的对结果分页。
在博客引擎实现中,你可为每篇日志设置一个list,在该list中推入博客评论,等等。
五、Redis Hashes
1、哈希简介
Redis 哈希是字符串字段和字符串值之间的映射。可以使用哈希来表示基本对象并存储计数器分组等。
2、哈希基本操作
1)通用命令
命令 | 描述 |
hset key field1 value1 [field2 value2…] | 添加一个键与一或多个指定字段的值。 注:如果哈希表不存在,会自动创建一个哈希表 |
hget key field | 获取哈希表对应的field字段所关联的值。 |
hmget key field1 [field2…] | 获取哈希表对应的field1, field2…字段所关联的值。 |
hdel key field1 [field2…] | 删除哈希表对应的field1, field2…字段。 |
hsetnx key field value | 仅当field不存在时才添加field-value |
hkeys key | 查看哈希表所有的field |
hvals key | 查看哈希表所有的value |
hlen key | 获取哈希表有多少对field-value |
hexists key field | 查看field是否存在 |
hstrlen key field | 获取哈希表里field的值的长度 |
*哈希表: key对应的哈希表
# 创建一个key为myhash的哈希表,并在哈希表里面添加f1-v1 f2-v2 f3-v3的字段 127.0.0.1:6379> hset myhash f1 v1 f2 v2 f3 v3 (integer) 3 # 获取key为myhash的哈希表里f1对应的值 127.0.0.1:6379> hget myhash f1 "v1" 127.0.0.1:6379> hget myhash f4 (nil) # 获取key为myhash的哈希表里f3,f1,f5字段对应的值 127.0.0.1:6379> hmget myhash f3 f1 f5 1) "v3" 2) "v1" 3) (nil) # 删除key为myhash的哈希表里f2 f4的字段 127.0.0.1:6379> hdel myhash f2 f4 (integer) 1 # 在哈希表里面添加KFC-v50的字段 127.0.0.1:6379> hsetnx myhash KFC v50 (integer) 1 # 获取哈希表里所有的field字段 127.0.0.1:6379> hkeys myhash 1) "f1" 2) "f3" 3) "KFC" # 获取哈希表里所有的value值 127.0.0.1:6379> hvals myhash u 1) "v1" 2) "v3" 3) "v50" # 获取哈希表里有多少field字段 127.0.0.1:6379> hlen myhash (integer) 3 # 判断哈希表里是否存在KFC字段 127.0.0.1:6379> hexists myhash KFC (integer) 1 127.0.0.1:6379> hexists myhash fff (integer) 0 # 获取哈希表里KFC字段对应的值的长度 127.0.0.1:6379> hstrlen myhash KFC (integer) 3
2)value是数值的操作
命令 | 描述 |
hincrby key field 整数 | 将key对应的哈希表的field字段的值添加指定数值。 |
hincrbyfloat key field 小数值 | 将key对应的哈希表的field字段的值添加指定小数值。 |
127.0.0.1:6379> hset myhash f1 1 f2 2 KFC 50 (integer) 3 127.0.0.1:6379> hincrby myhash f1 1 (integer) 2 127.0.0.1:6379> hincrby myhash f2 10 (integer) 12 127.0.0.1:6379> hincrbyfloat myhash KFC 11.4514 "61.4514"
3、小结
一个拥有少量(100个左右)字段的hash需要 很少的空间来存储,所有你可以在一个小型的 Redis实例中存储上百万的对象。
尽管Hashes主要用来表示对象,但它们也能够存储许多元素,所以你也可以用Hashes来完成许多其他的任务。
一个hash最多可以包含232-1 个key-value键值对(超过40亿)。
六、Redis Sets
1、集合简介
Redis集合是一个无序的字符串合集。你可以以O(1) 的时间复杂度(无论集合中有多少元素时间复杂度都为常量)完成 添加,删除以及测试元素是否存在的操作。
集合的特性:集合内的元素都是唯一的,向集合多次添加同一个元素时,集合只会保留一个元素,添加元素的前就可以不用判断元素是否存在。
2、集合基本操作
1)通用命令
命令 | 描述 |
sadd key element [element …] | 添加一个或多个元素到集合内。 注:集合不存在时,会自动创建一个集合,并把元素添加进去 |
scard key | 获取集合内元素的数量。 |
smembers key | 获取集合内所有的元素。 |
srem key element [element…] | 在集合中删除一个或多个元素。 |
sismember key element | 查看集合中是否存在member元素。 |
smismember key element [element…] | 查看集合中是否存在一个或多个元素。 |
srandmember key [数量] | 随机查看指定数量的成员。 |
spop key [数量] | 随机删除指定数量的成员。 |
# 添加[a, 1, b, c, 5, 6, 7, 8]元素到myset集合内 127.0.0.1:6379> sadd myset a 1 b c 5 6 7 8 (integer) 8 # 获取myset集合元素数量 127.0.0.1:6379> scard myset (integer) 8 # 获取myset集合内所有元素,元素是无序的 127.0.0.1:6379> smembers myset 1) "a" 2) "8" 3) "6" 4) "5" 5) "1" 6) "b" 7) "7" 8) "c" # 删除myset集合内的元素,只会删除集合内存在的元素 127.0.0.1:6379> srem myset b d 1 8 7 (integer) 4 127.0.0.1:6379> smembers myset 1) "a" 2) "6" 3) "5" 4) "c" # 查看元素5是否存在 127.0.0.1:6379> sismember myset 5 (integer) 1 # 查看[1, b, a, 5, 8]元素是否存在,存在为1,不存在为0 127.0.0.1:6379> smismember myset 1 b a 5 8 1) (integer) 0 2) (integer) 0 3) (integer) 1 4) (integer) 1 5) (integer) 0 # 随机取出一个元素 127.0.0.1:6379> srandmember myset "5" 127.0.0.1:6379> srandmember myset 3 1) "a" 2) "6" 3) "c" # 随机删除两个元素 127.0.0.1:6379> spop myset 2 1) "a" 2) "c" 127.0.0.1:6379> smembers myset 1) "6" 2) "5"
2)交集、并集以及差集
命令 | 描述 |
smove key1 key2 element | 将集合1的指定元素移动到集合2。 |
sinter key1 [key2…] | 查看给定集合的交集。 注:多个集合交集,是两个集合先取交集,交集再跟后面的集合去交集。 |
sinterstore newkey key1 [key2…] | 将给定集合的交集创建为新的集合newkey。 |
sunion key1 [key2…] | 查看给定集合的并集。 |
sunionstore newkey key1 [key2…] | 将给定集合的并集创建为新的集合newkey。 |
sdiff key1 [key2…] | 查看给定集合的差集。 差集:集合1存在的,在集合2内不存在的元素。 |
sdiffstore newkey key1 [key2…] | 将给定集合的差集创建为新的集合newkey。 |
# 将set1集合内的元素3移到set2集合 127.0.0.1:6379> sadd set1 a b c d 1 2 3 (integer) 7 127.0.0.1:6379> sadd set2 c d e 1 5 0 (integer) 6 127.0.0.1:6379> smove set1 set2 3 (integer) 1 127.0.0.1:6379> smembers set2 1) "e" 2) "c" 3) "0" 4) "5" 5) "1" 6) "d" 7) "3" # 查看set1集合与set2集合的交集 127.0.0.1:6379> sinter set1 set2 1) "1" 2) "d" 3) "c" # 将set1集合与set2集合的交集存储到setinter集合内 127.0.0.1:6379> sinterstore setinter set1 set2 (integer) 3 127.0.0.1:6379> smembers setinter 1) "1" 2) "d" 3) "c" # 查看set1集合与set2集合的并集 127.0.0.1:6379> sunion set1 set2 1) "c" 2) "2" 3) "0" 4) "a" 5) "5" 6) "1" 7) "d" 8) "b" 9) "3" 10) "e" # 将set1集合与set2集合的并集存储到setunion集合内 127.0.0.1:6379> sunionstore setunion set1 set2 (integer) 10 127.0.0.1:6379> smembers setunion 1) "c" 2) "2" 3) "0" 4) "a" 5) "5" 6) "1" 7) "d" 8) "b" 9) "3" 10) "e" # 查看set1集合与set2集合的差集 127.0.0.1:6379> sdiff set1 set2 1) "a" 2) "2" 3) "b" # 将set1集合与set2集合的差集存储到setdiff集合内 127.0.0.1:6379> sdiffstore setdiff set1 set2 (integer) 3 127.0.0.1:6379> smembers setdiff 1) "a" 2) "2" 3) "b"
3、小结
Redis集合是一个无序的字符串合集。
集合可以高效的处理:
- 用集合跟踪一个独特的事。想要知道所有访问某个博客文章的独立IP?只要每次都用sadd来处理一个页面访问。那么你可以肯定重复的IP是不会插入的。
- Redis集合能很好的表示关系。你可以创建一个tagging系统,然后用集合来代表单个tag。接下来你可以用sadd添加命令把所有拥有tag的对象的所有ID添加进集合,这样来表示这个特定的tag。如果你想要同时有3个不同tag的所有对象的所有ID,那么你需要使用sinter交集命令。
- 使用spop或者srandmember命令随机地获取元素。
- 支持一些服务端的命令从现有的集合出发去进行集合运算。 所以你可以在很短的时间内完成合并(union),求交(intersection), 找出不同元素的操作。
七、Redis Sorted Sets
1、有序集合简介
有序集合与集合类似,不同的地方在于每个字符串元素都关联一个叫score的浮动数值(floating number value)。里面的元素是按score进行排序,当多个字符串具有相同的score时,这些字符串按字典序排列。
使用有序集合,你可以非常快地(O(log(N)))完成添加,删除和更新元素的操作。 因为元素是在插入时就排好序的,所以很快地通过分数(score)或者 位次(position)获得一个范围的元素。
访问有序集合的中间元素同样也是非常快的,因此你可以使用有序集合作为一个没有重复成员的优先队列(c++里面的优先队列的概念)。 在这个列表中, 你可以轻易地访问任何你需要的东西: 有序的元素,快速的存在性测试,快速访问集合中间元素!
2、有序集合基本操作
1) 通用命令
命令 | 描述 |
zadd key [NX | XX] [GT | LT] [CH] [INCR] score1 element [score2 element…] |
添加一个或多个元素,每个元素都有一个分数(score)用于排序。 NX:只有元素不存在的时候才添加 XX:只有元素存在时修改元素 LT(less than):只有在分数score低于原来元素的分数时才更新分数 GT(greater than):只有在分数score高于原来元素的分数时才更新分数 注:NX不可与GT\LT同时使用 CH(changed):返回变更的元素数量,默认返回新增的元素数量 INCR:累加分数 注:INCR只能操作一个分数-元素对 |
zrem key element [element…] | 删除有序集合内一个或多个元素。 |
zcount key left right | 获取在[left, right]区间内的元素数量。 注:默认闭区间,开区间在分数前加 ( 支持无穷大表示: inf 表示正无穷大,-inf 表示负无穷大 |
zscore key element | 查看有序集合内指定元素的分数。 注:元素不存在返回nil |
zmscore key element1 [element2…] | 查看有序集合内指定元素element1,element2…的分数。 |
zcard key | 查看有序集合内元素数量。 |
zincrby key 数值 element | 给有序集合内指定元素的分数增加指定数值。 |
# 添加元素100-linzy, 10-ww, 50-KFC, 66-hh到有序集合内mysset, NX只有元素不存在才添加 127.0.0.1:6379> zadd mysset NX 100 linzy 10 ww 50 KFC 66 hh (integer) 4 # 查看有序集合mysset内指定区间内的元素 byscore按照分数排序,withscores显示分数 127.0.0.1:6379> zrange mysset -inf inf byscore withscores 1) "ww" 2) "10" 3) "KFC" 4) "50" 5) "hh" 6) "66" 7) "linzy" 8) "100" # 删除有序集合内ww元素 127.0.0.1:6379> zrem mysset ww (integer) 1 127.0.0.1:6379> zrange mysset -inf inf byscore withscores 1) "KFC" 2) "50" 3) "hh" 4) "66" 5) "linzy" 6) "100" # 查看有序集合内KFC的分数 127.0.0.1:6379> zscore mysset KFC "50" # 批量查看分数 127.0.0.1:6379> zmscore mysset KFC linzy gggg 1) "50" 2) "100" 3) (nil) # 查看mysset有序集合内有多少元素 127.0.0.1:6379> zcard mysset (integer) 3 # 给mysset有序集合内指定元素hh的分数增加10 127.0.0.1:6379> zincrby mysset 10 hh "76" 127.0.0.1:6379> zrange mysset -inf inf byscore withscores 1) "KFC" 2) "50" 3) "hh" 4) "76" 5) "linzy" 6) "100"
2)区间操作
命令 | 描述 |
zrange key left right [BYSCORE | BYLEX] [REV] [LIMIT 偏移量 查看数量] [WITHSCORES] |
查看分数在[left, right]区间内元素。 BYSCORE:按分数升序排列。 注:BYSCORE支持开区间,需要在分数前加( 支持无穷大表示:inf表示正无穷大,-inf表示负无穷大 BYLEX:分数相同时,按照元素字典序排序 注:BYLEX指定字符串区间要指定开闭区间:“[string"表示闭区间,”(string"表示开区间 BYLEX支持选取从开始到结束:"-“表示开始,”+"表示结束 REV(reverse):有序集合排序后反转。 LIMIT:用于指定查看的范围 注:仅在BYSCORE/BYLEX时可用 WITHSCORES:输出时元素与分数一起输出 |
zrangestore newkey key left right | 将有序集合[left, right]区间内的元素存储到newkey有序集合内。 注:类似zrange key left right BYSCORE | BYLEX] [REV] [LIMIT 偏移量 查看数量] |
zlexcount key startString endString | 查看有序集合内指定字符串区间的元素数量。 注:类似zrange key startString endString BYLEX |
zremrangebyscore key minScore maxScore | 删除有序集合内指定分数区间的所有元素。 注:类似zrange key minScore maxScore BYSCORE |
zrank key element | 查看有序集合内指定元素的升序排名。 注:排名从第0位开始 |
zrevrank key element | |
zremrangebyrank key startRank endRank | 删除有序集合内指定排名区间的所有元素。 注:排名从第0位开始,-n表示倒数第几名 |
127.0.0.1:6379> zadd myzset 50 KFC 100 linzy 0 aa 0 bb 0 nn 0 zz (integer) 6 # 查看myzset有序集合内所有元素,升序且带分数显示 127.0.0.1:6379> zrange myzset -inf inf byscore withscores 1) "aa" 2) "0" 3) "bb" 4) "0" 5) "nn" 6) "0" 7) "zz" 8) "0" 9) "KFC" 10) "50" 11) "linzy" 12) "100" # 查看myzset有序集合(50, 100]分数区间内所有元素,rev反转,需要将maxScore写在左边,minScore写在右边 127.0.0.1:6379> zrange myzset 100 (50 byscore withscores rev 1) "linzy" 2) "100" # 查看myzset有序集合内所有元素,升序且带分数显示,limit限制从索引为2的元素开始只显示三个元素 127.0.0.1:6379> zrange myzset -inf inf byscore withscores limit 2 3 1) "nn" 2) "0" 3) "zz" 4) "0" 5) "KFC" 6) "50" 127.0.0.1:6379> zrange myzset [aa [ff bylex withscores (error) ERR syntax error, WITHSCORES not supported in combination with BYLEX # 查看有序集合分数相同时,按照字典序显示排名,不能用withscores,会报错 127.0.0.1:6379> zrange myzset [aa [ff bylex 1) "aa" 2) "bb" # 将myzset有序集合内[50,100]区间内所有元素存储到newzset有序集合内 127.0.0.1:6379> zrangestore newzset myzset 50 100 byscore (integer) 2 127.0.0.1:6379> zlexcount myzset ff nn (error) ERR min or max not valid string range item # 查看myzset有序集合内(ff,nn]区间内的所有元素 127.0.0.1:6379> zlexcount myzset (ff [nn (integer) 1 127.0.0.1:6379> zlexcount myzset (aa [nn (integer) 2 # 删除myzset有序集合内(ff,nn]区间内的所有元素 127.0.0.1:6379> zremrangebylex myzset (aa [nn (integer) 2 127.0.0.1:6379> zrange myzset -inf inf byscore withscores 1) "aa" 2) "0" 3) "zz" 4) "0" 5) "KFC" 6) "50" 7) "linzy" 8) "100" # 删除myzset有序集合内[0,49]分数区间内的所有元素 127.0.0.1:6379> zremrangebyscore myzset 0 49 (integer) 2 127.0.0.1:6379> zrange myzset -inf inf byscore withscores 1) "KFC" 2) "50" 3) "linzy" 4) "100" # 查看myzset有序集合内KFC升序的排名 127.0.0.1:6379> zrank myzset KFC (integer) 0 # 查看myzset有序集合内KFC降序的排名 127.0.0.1:6379> zrevrank myzset KFC (integer) 1 127.0.0.1:6379> zadd myzset 10 aa 20 bb 30 nn 0 zz (integer) 4 127.0.0.1:6379> zrevrank myzset aa (integer) 4 127.0.0.1:6379> zrank myzset KFC (integer) 4 127.0.0.1:6379> zrevrank myzset KFC (integer) 1 # 删除myzset有序集合升序排名内[3,5]名的元素 127.0.0.1:6379> zremrangebyrank myzset 3 5 (integer) 3 127.0.0.1:6379> zrange myzset -inf inf byscore withscores 1) "zz" 2) "0" 3) "aa" 4) "10" 5) "bb" 6) "20"
3)交集、并集以及差集
命令 | 描述 |
zinter key的数量 key1 [key2…] [weights 权重1 [权重2…] [aggregate sum | min | max] [withscores] |
查看给定的有序集合的交集。 werights:依次给每个集合的分数设置权重,默认权重为1 aggregate:新分数的计算方法,默认是sum 注:新分数=aggregate(集合1中该元素的分数 * 权重1+集合2中该元素的分数 * 权重2…) (符号是aggregate决定的可以是sum加,min取两者最小,max取两者最大) withscores:带分数显示 |
zinterstore newkey key的数量 key1 [key2…] [weights 权重1 [权重2…] [aggregate sum | min | max] |
将给定的有序集合的交集存储到新的有序集合newkey。 |
zunion key的数量 key1 [key2…] [weights 权重1 [权重2…] [aggregate sum | min | max] [withscores] |
查看给定的有序集合的并集。 |
zunionstore newkey key的数量 key1 [key2…] [weights 权重1 [权重2…] [aggregate sum | min | max] |
将给定的有序集合的并集存储到新的有序集合newkey。 |
zdiff key的数量 key1 [key2…] [withscores] | 查看给定的有序集合的差集。 |
zdiff newkey key的数量 key1 [key2…] | 将给定的有序集合的差集存储到新的有序集合newkey。 |
127.0.0.1:6379> zadd zset1 1 a 2 b 3 c 4 g (integer) 4 127.0.0.1:6379> zadd zset2 0 a 1 b 2 c 5 f (integer) 4 # 获取zset1与zset2的交集,显示的新分数 = zset1的元素分数 * 2 + zset2的元素分数 * 3 127.0.0.1:6379> zinter 2 zset1 zset2 weights 2 3 withscores 1) "a" 2) "2" 3) "b" 4) "7" 5) "c" 6) "12" # 获取zset1与zset2计算的新分数后的交集,显示的新分数 = min(zset1的元素分数 * 2, zset2的元素分数 * 3) 127.0.0.1:6379> zinter 2 zset1 zset2 weights 2 3 aggregate min withscores 1) "a" 2) "0" 3) "b" 4) "3" 5) "c" 6) "6" # 将zset1与zset2计算的新分数后的交集,存储到有序集合newzset1内 127.0.0.1:6379> zinterstore newzset1 2 zset1 zset2 weights 1 4 (integer) 3 127.0.0.1:6379> zrange newzset1 -inf inf byscore withscores 1) "a" 2) "1" 3) "b" 4) "6" 5) "c" 6) "11" # 获取zset1与zset2计算的新分数后的并集 127.0.0.1:6379> zunion 2 zset1 zset2 weights 2 3 withscores 1) "a" 2) "2" 3) "b" 4) "7" 5) "g" 6) "8" 7) "c" 8) "12" 9) "f" 10) "15" # 将zset1与zset2计算的新分数后的并集,存储到有序集合newzset2内 127.0.0.1:6379> zunionstore newzset2 2 zset1 zset2 weights 3 2 aggregate max (integer) 5 127.0.0.1:6379> zrange newzset2 -inf inf byscore withscores 1) "a" 2) "3" 3) "b" 4) "6" 5) "c" 6) "9" 7) "f" 8) "10" 9) "g" 10) "12" # 获取zset1与zset2的差集 127.0.0.1:6379> zdiff 2 zset1 zset2 withscores 1) "g" 2) "4" # 获取zset1与zset2的差集,存储到有序集合newzset3内 127.0.0.1:6379> zdiffstore newzset3 2 zset1 zset2 (integer) 1 127.0.0.1:6379> zrange newzset3 -inf inf byscore withscores 1) "g" 2) "4"
3、小结
使用有序集合你可以很好地完成很多在其他数据库中难以实现的任务。
有序集合可以:
- 在一个大型在线游戏中建立一个排行榜,每当有新的记录产生时,使用ZADD来更新它。你可以用ZRANGE轻松地获取排名靠前的用户, 你也可以提供一个用户名,然后用ZRANK获取他在排行榜中的名次。 同时使用ZRANK和ZRANGE你可以获得与指定用户有相同分数的用户名单。 所有这些操作都非常迅速。
- 有序集合通常用来索引存储在Redis中的数据。 例如:如果你有很多的hash来表示用户,那么你可以使用一个有序集合,这个集合的年龄字段用来当作评分,用户ID当作值。用·ZRANGEBYSCORE·可以简单快速地检索到给定年龄段的所有用户。
注意:
大多数有序集合操作是 O(log(n)),其中n是成员数。
ZRANGE在运行具有较大返回值(例如,数万条或更多)的命令时要小心。该命令的时间复杂度为 O(log(n) + m),其中m是返回的结果数。