0%

Python 和 Redis 的那点事

关于 Python 系列其他文章的传送门

  1. 《 Python 基础语法 》

  2. 《 Python 进阶:网络、多线程、异步、数据库、WEB 》

  3. 《 Python 和 RabbitMQ 的那点事 》

  4. 《 Python 和 Redis 的那点事 》      您当前所在位置

  5. 《 Python 爬虫基础和包 XPath、BeautifulSoup4、PhantomJS、Selenium 》

  6. 《 边爬豆瓣,边学习 Python 爬虫框架 Scrapy 》

  7. 《 用了 Scrapy-Redis 分布式爬虫后,一个能打的都没有! 》

  8. 《 全栈开发入门 JavaScript、React 和 Django 》

  9. 《 Django REST framework 的那点事 》


关于 Redis 运维 系列其他文章的传送门

  1. 《 我不要你觉得!我要我觉得!学习 Redis 中实用的命令和配置,就看这篇! 》

  2. 《 轻松搞定 Redis 的主从复制和高可用 》

  3. 《 手把手教你理解 Redis Cluster 》



logo

Python 和 Redis 的那点事

开源的(BSD协议),使用 ANSI C 编写,基于内存的且支持持久化,高性能的 Key-Value 的 NoSQL 数据库。

支持的数据结构类型丰富,有如字符串(strings),散列(hashes),列表(lists),集合(sets),有序集合(sorted sets)与范围查询,bitmaps,hyperloglogs 和 地理空间(geospatial)索引半径查询。

丰富的支持主流语言的客户端,C、C++、Python、Erlang、R、C#、Java、PHP、Objective-C、Perl、Ruby、Scala、Go、JavaScript。

常见用途:缓存(StackOverFlow)、数据库(微博)、消息中间件(微博)。

目前主要版本为 3.2,redis 在 2017 年也发布了 4.x 版本,目前是 4.0.x。

Windows 版本由微软提供 https://github.com/MicrosoftArchive/redis

可视化工具 RedisDesktopManager

安装和配置

大部分略,可以看之前的运维笔记,笔记详细

通过 docker 的启动命令,暴露 6379 端口:

1
docker run --name my-redis -p6379:6379 -d redis

Redis 在线命令大全

http://www.redis.cn/commands.html

Redis 数据模型

redis 支持的数据模型非常丰富。

键 Key

Redis key 值是二进制安全的,这意味着可以用任何二进制序列作为 key 值,从形如 foo 的简单字符串到一个 JPEG 文件的内容都可以。空字符串也是有效 key 值。

Key 取值原则:

  • 键值不需要太长,消耗内存,而且查找这类键值的计算成本较高;
  • 键值不宜过短,可读性较差;
  • 习惯上 key 采用 user:123:password 形式,表示用户 id 为 123 的用户的密码。

字符串

字符串是一种最基本简单的 Redis 值类型。Redis 字符串是二进制安全的,这意味着一个 Redis 字符串能包含任意类型的数据,例如:一张 JPEG 格式的图片或者一个序列化的 Ruby 对象。

一个字符串类型的值最多能存储 512M 字节的内容。

python 中 redis 编程

安装 redis 库:

1
pip install redis

创建字符串类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import redis

# 默认连接本地 6379 的 0 号库
db = redis.Redis()

# 测试 1
# 存一个二进制 value
db.set('testbin', 0b01100010) # 对应的 hex: 0x62 int: 98
# 返回的是 b'98',是个字符
print(db.get('testbin'))

# 测试 2
# 存个二进制的 key
db.set(0b11, 0x63) # 对应的 hex: 0x3 int: 3
# 下面哪种 get 可以获取到呢?
print(db.get(0b11))
print(db.get(0x3))
print(db.get(3))
print(db.get('3'))

答案是:都可以。因为在 Redis 中,字符串类型会把数值会先转换成 10 进制 64 位有符号数后,再转成字符串,存入 redis 中。

注意:上例中 0x62(1 字节)实际上发生了类型变化,因为返回的是 bytes 类型 98 ,实际上对应 ASCII 的 9 和 8,已经是 2 字节了。

字符串设置

设置字符串值SET key value [EX seconds][PX milliseconds] [NX|XX]

  • EX:设置过期时间,秒,等同于 SETEX key seconds value
  • PX:设置过期时间,毫秒,等同于 PSETEX key milliseconds value
  • NX:键不存在,才能设置,等同于 SETNX key value
  • XX:键存在时,才能设置。

一下设置多个键的字符串值:MSET key value [key value ...]

  • key 存在则覆盖,key 不存在则增加;
  • 原子操作。

一下设置多个键的字符串值:MSETNX key value [key value ...]

  • key 不存在则设置,key 存在则失败。NX 指不存在。
  • 原子操作。

过期操作和生存时间

Redis 中可以给每个 Key 设置一个生存时间(秒或毫秒),当达到这个时长后,这些键值将会被自动删除。

设置多少秒或者毫秒后过期

  • EXPIRE key seconds:秒;
  • PEXPIRE key milliseconds:毫秒。

设置在指定 Unix 时间戳过期:

  • EXPIREAT key timestamp:时间戳;
  • PEXPIREAT key milliseconds-timestamp:毫秒时间戳。

持久 key,即取消过期:

  • PERSIST key

适用场景

  1. 多少秒过期,例如一个缓存数据失效;
  2. PEXPIREAT key milliseconds-timestamp,比如现在开始缓存数据,到 0 点失效。

查看 Key 的剩余生存时间(Time To Live):

  • TTL key:秒;
  • PTTL key:毫秒。

key 存在但没有设置 TTL,返回 -1
key 存在,但还在生存期内,返回剩余的秒或者毫秒;
key 曾经存在,但已经消亡,返回 -2(2.8 版本之前返回 -1)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# s1 20 秒过期
set s6 abc ex 20
ttl s6

# 设置 s6
setnx s6 6
# 设置 s6 过期时间 60s
expire s6 60
pttl s6
# 设置 s6 永不过期
persist s6
ttl s6

# 设置 s6 在指定时间戳后过期
EXPIREAT s6 1355292000
PEXPIREAT s6 1555555555005

key 操作

keys patternpattern 可以取如下值:

  1. *:任意长度字符;
  2. ?:任意一个字符;
  3. []:字符集合,表示一个字符。
1
2
3
4
5
keys *
keys s?
keys s[13] # s1 或 s3
keys s*
keys ??
  • TYPE key:key 类型;
  • EXISTS key key:是否存在;
  • RENAME key newkeyRENAMENX key newkey:键重命名;
  • DEL key [key ...]:键删除。

字符串获取

  • GET key:获取值;
  • MGET key [key ...]:获取多个给定的键的值;
  • GETSET key value:返回旧值并设置新值,如果键不存在,就创建并赋值;
  • STRLEN key:字符串长度。
1
2
3
4
get s4
mget s1 s3 s5 s7
strlen s3
mgetset s5 100

字符串操作

追加
APPEND key value:追加字符串。

  • 如果键存在就追加;如果不存在就等同于 SET key value

获取
GETRANGE key start end:获取子字符串。

  • 索引值从 0 开始,支持负索引,-1 表示最后一个字符。范围是 [start, end],前后都包。start 必须在 end 的左边,否则返回空串。

覆盖

  • SETRANGE key offset value:从指定索引处开始覆盖字符串,返回覆盖后字符串长度。如 key 不存在会创建新的。
1
2
3
4
5
6
7
8
9
10
append s2 abc
get s2 # abc
getrange s2 1 3 # "bc"
getrange s2 0 -1 # "abc"

setrange s2 3 12 # "abc12"
setrange s2 7 12345 # "abc12\x00\x0012345"

get w1 # 没有 w1
setrange w1 3 abc # 前边没有的位会补上 ASCII 码的零。 "\x00\x00\x00abc"

自增、自减

  • INCR key:步长 +1;

  • DECR key:步长 -1。

  • INCRBY key increment:步长增 increment

  • DECRBY key decrement:步长减 decrement

字符串值会被解释成 64 位的有符号的十进制整数来操作,结果依然转成字符串。

1
2
3
4
set s3 3
incrby s3 4
decr s3
decrby s3 2

库操作

  • redis-cli -n 2:登录不同的库;
  • FLUSHDB:清除当前库数据;
  • FLUSHALL:清除所有库中的数据。

位图 bitmap

位图不是真正的数据类型,它是定义在字符串类型上,只不过把字符串按位操作

一个字符串类型的值最多能存储 512M 字节的内容,可以表示为 232 位。

位上限演算过程:

  • 512 = 29
  • 1M = 1024*1024 = 210+10
  • 1 Byte = 8 bit = 23
  • 29+10+10+3 = 232
  • b = 4294967296 b
  • 接近 43 亿个位。

设置

  • SETBIT key offset value:设置某一位上的值。
    • offset:偏移量,从 0 索引开始;
    • value 不写,默认是 0

获取

  • GETBIT key offset:获取某一位上的值;
  • BITPOS key bit [start][end]:返回指定值 0 或者 1 在指定区间上第一次出现的位置;
  • BITCOUNT key [start][end]:统计指定位区间上值为 1 的个数;
    • 从左向右从 0 开始,从右向左从 -1 开始,注意官方说 startend 是位的索引号,但经过测试后发现其实是字节,所以:
      • BITCOUNT testkey 0 0表示从索引为 0 个字节到索引为 0 个字节,就是第一个字节的统计
  • BITCOUNT testkey 0 -1 等同于 BITCOUNT testkey
  • 最常用的就是 BITCOUNT testkey
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
set test 7  # 请问在 redis 中如何理解存入了什么?以下的返回都是多少?
getbit test 0 # 0
getbit test 1 # 0
getbit test 2 # 1
getbit test 3 # 1
getbit test 4 # 0
getbit test 5 # 1
getbit test 6 # 1
getbit test 7 # 1

bitpos test 1 # 返回 1 第一次出现的索引 2
bitpos test 0 # 返回 0 第一次出现的索引 0
bitcount test 0 0 # 5

set str1 abc # 0x61 0x62 0x63
setbit str1 6 1 # 索引 6,即第一个字母的二进制第 7 位,变成 1
setbit str1 7 0 # 索引 7,即第一个字母的二进制第 8 位,变成 0
get str1 # 请问等于什么?
# "bbc"

位操作

对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。operation 可以是 ANDORNOTXOR 这四种操作中的任意一种。

  • BITOP AND destkey key [key ...]:对一个或多个 key 求逻辑 ,并将结果保存到 destkey
  • BITOP OR destkey key [key ...]:对一个或多个 key 求逻辑 ,并将结果保存到 destkey
  • BITOP XOR destkey key [key ...]:对一个或多个 key 求逻辑 异或,并将结果保存到 destkey
  • BITOP NOT destkey key:对给定 key 求逻辑 ,并将结果保存到 destkey

除了 NOT 操作之外,其他操作都可以接受一个或多个 key 作为输入。

BITOP 处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作 0

空的 key 也被看作是包含 0 的字符串序列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
set s2 a
set s3 b
bitop or s8 s2 s3 # 等于什么?

# 0110 0001
# or
# 0110 0010
# 等于
# 0110 0011 # c
get s8
"c"

set cn 中
get cn # "\xe4\xb8\xad" # redis 中默认使用 utf8
bitcount cn

redis-bitmap

Bitmap 习题

1、网站用户的上线次数统计(活跃用户)

分析:用户 ID 为 key,天作为 offset,上线则置为 1

  • ID 为 500 的用户,今年的第 1 天上线:
    • SETBIT u500 1 1
  • 第 30 天上线:
    • SETBIT u500 30 1
  • 计算活跃天数:
    • BITCOUNT u500
  • KYES u*
1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import redis

# 默认连接本地 6379 的 0 号库
r = redis.Redis(host='192.168.50.30', port=6379, db=2)

# u1
r.setbit('u1', 1, 1)
r.setbit('u1', 30, 1)

# u2
r.setbit('u2', 110, 1)
r.setbit('u2', 300, 1)

# u101
for i in range(3, 365, 3):
r.setbit('u101', i, 1)

# u105
for i in range(4, 365, 2):
r.setbit('u105', i, 1)

userList = r.keys('u*')
print(userList) # 所有用户 id 列表

Au = []
Nau = []
for u in userList:
loginCount = r.bitcount(u) # 提取登录天数
if loginCount > 100: # 大于 100 天就是活跃用户
Au.append((u, loginCount))
else:
Nau.append((u, loginCount))

for l in Au:
print(l[0].decode() + ' is a Active User, login day: ' + str(l[1]))

print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")

for l in Nau:
print(l[0].decode() + ' is not a Active User, login day: ' + str(l[1]))

2、按天统计网站活跃用户

天作为 key,用户 ID 为 offset,上线置为 1

  • SETBIT 20160601 15 1:ID 作为索引,将索引 15 置为 1
  • SETBIT 20160603 123 1:用户 ID 123,一段时间内登录 2 次
  • SETBIT 20160606 123 1

求 6 月 1 日到 6 月 10 日的活跃用户总数:

  • BITOP OR 20160601-10 20160601 20160603 20160610:做或运算,时间范围内只要有一天登录了就算,所以 20160606 可以省略。
  • BITCOUNT 20160601-10:结果为 2

List 列表

  • 其列表是基于 双向链表 实现,列表头尾增删快,中间增删慢;
  • 元素是字符串类型;
  • 元素可以重复出现;
  • 索引支持正索引和负索引,从左至右从 0 开始,从右至左从 -1 开始。

命令说明

字母 说明
B Block 阻塞
L Left 左起
R Right 右起
X exist 存在

压入

  • LPUSH key value [value ...]:从左边向队列中压入元素;
  • LPUSHX key value:从左边向队列加入元素,要求 key 必须存在;
  • RPUSH key value [value ...]:从右边向队列中压入数据;
  • RPUSHX key value:要求 key 存在。

弹出

  • LPOP key:从左边弹出列表中一个元素;
  • RPOP key:从右边弹出列表中一个元素。

转移

  • RPOPLPUSH source destination:从源列表中右边 pop 一个元素,从左边加入到目标列表。

获取

  • LLEN key:返回列表元素个数;
  • LRANGE key start stop:返回列表中指定访问的元素,例如 LRANGE user 0 -1
  • LINDEX key index:返回列表中指定索引的元素;

设置

  • LSET key index value:设置列表中指定索引位置的元素值,index 不能超界。

删除

  • LREM key count value:从左边删除列表中与 value 相等的元素:

    • count > 0:从左至右搜索,移除与 value 相等的元素,数量至多为 count 次;
    • count < 0:从右至左搜索,移除与 value 相等的元素,数量至多为 count 次;
    • count = 0:移除列表中所有 value 值。
  • LTRIM key start stop:去除指定 范围外 的元素;

    • RPUSH listkey c abc c ab 123 ab bj ab redis list
    • LTRIM listkey 0 -1:什么都没有去除,
    • LTRIM listkey 1 -1:去掉左边头,
    • LTRIM listkey 1 10000:去除 1-10000 之外的元素。

插入

  • LINSERT key BEFORE|AFTER pivot value:在列表中某个存在的值 pivot 前或后插入元素一次,key 或 pivot 不存在,不进行任何操作。
    • RPUSH lst 1 2 3 4 2 8
    • LINSERT lst AFTER 2 Python
    • LINSERT lst BEFORE 2 Ruby
    • lrange lst 0 -1 结果:"1" "Ruby" "2" "Python" "3" "4" "2" "8"

阻塞

如果弹出的列表不存在或者为空,就会阻塞;
超时时间设置为 0,就是永久阻塞,直到有数据可以弹出;
如果多个客户端阻塞在同一个列表上,使用 First In First Service 原则,先到先服务;

  • BLPOP key [key ...] timeout:列表左边阻塞弹出元素。timeout 是超时秒数,为 0 则永久阻塞;
  • BRPOP key [key ...] timeout:列表左边阻塞弹出元素;
  • BRPOPLPUSH source destination timeout:从一个列表尾部阻塞弹出元素压入到另一个列表的头部。
1
2
3
# 阻塞式消息队列
BLPOP MyQueue 0 # 没有数据,直接阻塞
RPUSH MyQueue hello # 直到有数据

List 习题 – 微博某贴最后评论的 50 条

微博某贴最后评论的 50 条。

1
2
3
4
LPUSH u1234:forumid:comments "这是第 1 条评论"
LPUSH u1234:forumid:comments "这是第 2 条评论"
LPUSH u1234:forumid:comments "这是第 3 条评论"
LTRIM u1234:forumid:comments 0 499 # 去除前 500 之外的所有元素

Hash 散列

值是由 fieldvalue 组成的 map 键值对,fieldvalue 都是字符串类型。

hash 可以看做是 python 中的字典。

redis-hash-1

命令

设置

  • HSET key field value:设置单个字段。field 不存在则创建,存在覆盖 value
  • HSETNX key field value:设置单个字段,要求 field 不存在。如果 key 不存在,相当于field 也不存在;
  • HMSET key field value [field value ...]:设置多个字段。

获取

  • HLEN key:返回字段个数;
  • HEXISTS key field:判断字段是否存在。key 或者 field 不存在,则返回 0
  • HGET key field:返回字段值;
  • HMGET key field [field ...]:返回多个字段值;
  • HGETALL key:返回所有的键值对;
  • HKEYS key:返回所有字段名;
  • HVALS key:返回所有值。

增量计算

  • HINCRBY key field increment:在字段对应的值上进行整数的增量计算
  • HINCRBYFLOAT key field increment:在字段对应的值上进行浮点数的增量计算

删除

  • HDEL key field [field ...]:删除指定的字段
1
2
3
4
5
HINCRBY numbers x 100   # 100
HINCRBY numbers x -30 # 70

HINCRBYFLOAT numbers x 3.14 # "73.14"
HDEL numbers x

hash 用途

节约内存空间:

每创建一个键,它都会为这个键储存一些附加的管理信息(比如这个键的类型,这个键最后一次被访问的时间等等),
所以数据库里面的键越多,redis 数据库服务器在储存附加管理信息方面耗费的内存就越多,花在管理数据库键上 的 CPU 时间也会越多。

普通 ==> 使用 hash 节约
redis-hash-2

不适合 hash 的情况

  1. 使用二进制位操作命令:因为 Redis 目前仅支持对字符串键进行 SETBITGETBITBITOP 等操作,如果你想使用这些操作,那么只能使用字符串键。虽然散列也能保存二进制数据。

  2. 使用过期键功能:Redis 的键过期功能目前只能对键(key)进行过期操作,而不能对散列的字段(field)进行过期操作,因此如果你要对键值对数据使用过期功能的话,那么只能把键值对储存在字符串里面。

hash 习题

用户维度统计

用户维度统计,统计数包括:关注数、粉丝数、喜欢商品数、发帖数。用户为 Key,不同维度为 FieldValue 为统计数。

比如关注了 5

1
2
HSET user:100000 follow 5   # 关注了 5 个用户
HINCRBY user:100000 follow 1 # 增加一个关于,自增 1

商品维度统计

统计值包括喜欢数,评论数,购买数,浏览数等。

1
2
HSET item:58000 fav 500
HINCRBY item:58000 fav 1

缓存用户信息

登录后,反复需要读取用户的常用信息,最好的方式就是缓存起来。

1
2
3
4
5
6
7
8
# 这样不方便提取单个字段
set user:001 "bob,18,20010101"

# 这样会有很多重复的 key,浪费内存
mset user:001:name "bob" user:001:age 18 user:001:birthday "20010101"

# 使用 hash,不仅节省空间,还方便提取单个字段。
hmset user:001 name "bob" age 18 birthday "20010101"

Set 集合

集合的元素是无序的、去重的,元素是字符串类型。

增删

  • SADD key member [member ...]:增加一个或多个元素,元素已存在将忽略;
  • SREM key member [member ...]:移除一个或多个元素,元素不存在自动忽略;

元素相同的两个集合,未必有相同的顺序。如果需要去重且有序,可以使用 有序集合

1
2
3
4
5
6
SADD f1 "peter" "jack" "tom" "john" "may" "ben"
SADD f2 "peter" "jack"
SADD f2 "tom" "john"
SADD f2 "may" "ben"
SMEMBERS f1 # 返回所有元素
SMEMBERS f2

获取

  • SCARD key:返回集合中元素的个数。不需要遍历;
  • SMEMBERS key:返回集合中的所有元素。注意:如果集合中元素过多,应当避免使用该方法;
  • SISMEMBER key member:元素是否是在集合中。
  • SRANDMEMBER key [count]:随机返回集合中指定个数的元素。
    • 如果 count 为正数,且小于集合基数,那么命令返回一个包含 count 个元素的数组,数组中的元素各不相同;
    • 如果 count 大于等于集合基数,那么返回整个集合;
    • 如果 count 为负数,那么命令返回一个数组,数组中的元素可能会重复出现多次,而数组的长度为 count 的绝对值;
    • 如果 count0,返回空;
    • 如果 count 不指定,随机返回一个元素。

随机移除

  • SPOP key:从集合中随机移除一个元素并返回该元素;

移动

  • SMOVE source destination member:把元素从源集合移动到目标集合。

Set 中的集合运算

差集

  • SDIFF key [key ...]:从第一个 key 的集合中去除其他集合和自己的交集部分;
  • SDIFFSTORE destination key [key ...]:将差集结果存储在目标 key 中。
1
2
3
SADD number1 123 456 789
SADD number2 123 456 999
SDIFF number1 number2 # "789"

交集

  • SINTER key [key ...]:取所有集合交集部分;
  • SINTERSTORE destination key [key ...]:将交集结果存储在目标 key 中。
1
2
3
SADD number1 123 456 789
SADD number2 123 456 999
SINTER number1 number2 # "123" "456"

并集

  • SUNION key [key ...]:取所有集合并集;
  • SUNIONSTORE destination key [key ...]:将并集结果存储在目标 key 中。
1
2
3
SADD number1 123 456 789
SADD number2 123 456 999
SUNION number1 number2 # "123" "456" "789" "999"

Set 习题 – 微博的共同关注

需求:当用户访问另一个用户的时候,会显示出两个用户共同关注哪些相同的用户。

设计:将每个用户关注的用户放在集合中,求交集即可。

1
2
3
4
5
6
peter={'john','jack','may'}
ben={'john','jack','tom'}

# 那么 peter 和 ben 的共同关注为:
SINTER peter ben
# 结果为 {'john','jack'}

SortedSet 有序集合

类似 Set 集合,有序的集合。

每一个元素都关联着一个浮点数分值(Score),并按照分值从小到大的顺序排列集合中的元素。分值可以相同。

创建

  • ZADD key score member [score member ...]:增加一个或多个元素。如果元素已经存在,则使用新的 score 覆盖旧的。

获取

  • ZCARD key:返回集合的元素个数;
  • ZCOUNT key min max:返回指定 score 范围元素的个数;
  • ZSCORE key member:显示分值;
  • ZRANGE key start stop [WITHSCORES]:返回指定索引区间元素,WITHSCORES 返回结果时带不带 scores。

默认按照 score 从小到大,如果需要 score 从大到小排列,使用 ZREVRANGE
如果 score 相同,则按照字典序(lexicographical order)排列。

  • ZREVRANGE key start stop [WITHSCORES]:返回指定索引区间元素。

默认按照 score 从大到小,如果需要 score 从小到大排列,使用 ZRANGE
如果 score 相同,则按照字典序(lexicographical order)的 逆序 排列;

  • ZRANK key member:返回元素的排名(索引);
  • ZREVRANK key member:返回元素的 逆序 排名(索引)。

更改

  • ZINCRBY key increment member:增加或减少分值。increment 为负数就是减少。
1
2
3
4
5
6
7
8
9
10
11
12
ZADD employees 3500 jack 4000 peter 4000 john 4500 tom 2500 david
ZCOUNT employees 3000 4000 # score 范围内的个数 3
ZADD employees 3.2 david
ZSCORE employees david # "3.2000000000000002"

ZINCRBY employees 1.5 jack # "3501.5"
ZINCRBY employees -500 tom # "4000"

ZRANGE employees 0 -1 WITHSCORES
ZRANK employees peter
ZREVRANGE employees 0 -1 WITHSCORES # 逆序后的索引,0 到 -1 即返回所有
ZREVRANK employees peter
  • ZRANGEBYSCORE key min max [WITHSCORES][LIMIT offset count]:返回指定分数区间的元素:
    • 返回 score 默认属于 [min,max] 之间,元素按照 score 升序排列,score 相同则按字典序;
    • LIMIToffset 代表跳过多少个元素,count 是返回几个。类似于 Mysql 使用小括号,修改区间为开区间,例如 (5 (不包含 5)或者 (10、5) (不包含 10 和 5);
    • -inf+inf 表示负无穷和正无穷。
  • ZREVRANGEBYSCORE key max min [WITHSCORES][LIMIT offset count]降序 返回指定分数区间的元素:
    • 返回 score 默认属于 [min,max] 之间,元素按照 score 降序 排列,score 相同则按照字典降序。
1
2
3
4
5
ZRANGEBYSCORE employees 3500 4000
ZRANGEBYSCORE employees (4000 5000 # 不包括 4000
ZRANGEBYSCORE employees 4000 5000 LIMIT 1 5 # 跳过一个,返回至多5个

ZREVRANGEBYSCORE employees +inf -inf

移除

  • ZREM key member [member ...]:移除一个或多个元素。元素不存在,自动忽略;
  • ZREMRANGEBYRANK key start stop:移除指定排名范围的元素;
  • ZREMRANGEBYSCORE key min max:移除指定分值范围的元素。
1
2
ZREMRANGEBYRANK employees 0 1
ZREMRANGEBYSCORE employees 4000 5000

集合运算

并集

  • ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]
    • numkeys:指定后边要做并集的 key 的数量,有几个 key 就写几。必填;
    • WEIGHTS:选项与前面设定的 key 对应,对应 key 中每一个 score 都要乘以这个权重;
    • AGGREGATE:指定并集结果的聚合方式;
    • SUM:将所有集合中某一个元素的 score 值之和作为结果集中该成员的 score 值,默认
    • MIN:将所有集合中某一个元素的 score 值中最小值作为结果集中该成员的 score 值;
    • MAX:将所有集合中某一个元素的 score 值中最大值作为结果集中该成员的 score 值。
1
2
3
4
5
6
ZADD scores1 70 tom 80 peter 60 john
ZADD scores2 90 peter 60 ben

ZUNIONSTORE scores-all 2 scores1 scores2
ZUNIONSTORE scores-all1 2 scores1 scores2 AGGREGATE SUM
ZUNIONSTORE scores-all2 2 scores1 scores2 WEIGHTS 1 0.5 AGGREGATE SUM

交集

  • ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]
    • numkeys:指定 key 的数量,必须;
    • WEIGHTS:与前面设定的 key 对应,对应 key 中每一个 score 都要乘以这个权重;
    • AGGREGATE:指定并集结果的聚合方式;
    • SUM:将所有集合中某一个元素的 score 值之和作为结果集中该成员的 score 值;
    • MIN:将所有集合中某一个元素的 score 值中最小值作为结果集中该成员的 score 值;
    • MAX:将所有集合中某一个元素的 score 值中最大值作为结果集中该成员的 score 值。

SortedSet 习题

音乐排行榜怎样实现?

  • 每首歌的歌名作为元素(先不考虑重复);
  • 每首歌的播放次数作为分值;
  • 通过 ZREVRANGE 来获取播放次数最多的歌曲(就是最多播放榜了,云音乐热歌榜,没有竞价,没有权重)
1
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
27
28
29
30
31
32
33
import redis

# 默认连接本地 6379 的 0 号库
r = redis.Redis(host='127.0.0.1', port=6379, db=2)

music_list1 = {'yellow': 1, 'rolling in the deep': 1, 'happy': 1, 'just the way you are': 1}
music_list2 = {'eye of the tiger': 1, 'billie jean': 1, 'say you say me': 1, 'payphone': 1}
music_list3 = {'my heart will go on': 1, 'when you believe': 1, 'hero': 1}

# 把歌曲列表创建为 有序 set,初始播放次数,分值为 1
r.zadd('mboard', music_list1)
r.zadd('mboard', music_list2)
r.zadd('mboard', music_list3)

# 为歌曲增加播放次数:分数
r.zincrby('mboard', 50, 'yellow')
r.zincrby('mboard', 60, 'rolling in the deep')
r.zincrby('mboard', 68.8, 'my heart will go on')
r.zincrby('mboard', 70, 'when you believe')

# 所有元素
allmusic = r.zrange('mboard', 0, -1, withscores=True)
print(type(allmusic))
for m in allmusic:
print(m)
print('-' * 30)

# 排行榜
musicboard = r.zrevrange('mboard', 0, 9, True)

print('欧美热曲榜')
for i, m in enumerate(musicboard):
print(i, *m)

新浪微博翻页

新闻网站、博客、论坛、搜索引擎,页面列表条目多,都需要分页。

blog 这个 key 中使用时间戳作为 score。

  • ZADD blog 1407000000 '今天天气不错'
  • ZADD blog 1450000000 '今天我们学习Redis'
  • ZADD blog 1560000000 '几个Redis使用示例'

显示所有博客中的最后的指定的条目:

  • ZREVRANGE blog 10 20

京东图书畅销榜

统计单日榜,计算出周榜单、月榜单、年榜单,怎么做?

需求:每天统计一次排行榜

1
2
3
4
5
6
7
# 每天的榜单累计数
ZADD bk:it:01 1000 'java' 1500 'Redis' 2000 'haoop' 100 'scala' 80 'python'
ZADD bk:it:02 1020 'java' 1500 'Redis' 2100 'haoop' 120 'python' 110 'scala'
ZADD bk:it:03 1620 'java' 1510 'Redis' 3000 'haoop' 150 'storm' 120 'python'

# 求 01-03 日内,销售前 10 名,这样合理吗?
ZUNIONSTORE bk:it:01-03 3 bk:it:01 bk:it:02 bk:it:03 # 并集

因为上面的单日榜单是累计值,所以不能直接使用并集,要指定聚合运算为 MAX,取最大值。

1
2
3
4
ZUNIONSTORE bk:it:01-03 3 bk:it:01 bk:it:02 bk:it:03 AGGREGATE MAX

# 查看结果
ZREVRANGE bk:it:01-03 0 9 WITHSCORES

注意:如果参与并集元素的元素太多,会耗费大量内存和计算时间,可能会导致 Redis 服务阻塞,如果非要计算,选在空闲时间或备用服务器上计算。

另一种统计方法:

1
2
3
4
5
6
7
8
9
# 每天只记录当天的销售量
ZADD bk:it:01 50 'java' 20 'Redis' 40 'haoop'
ZADD bk:it:02 70 'java' 30 'Redis' 20 'haoop'
ZADD bk:it:03 20 'java' 30 'Redis' 5 'haoop'

ZUNIONSTORE bk:it:01-03 3 bk:it:01 bk:it:02 bk:it:03 AGGREGATE SUM

# 查看结果
ZREVRANGE bk:it:01-03 0 9 WITHSCORES