关于 Python 系列其他文章的传送门
-
《 Python 和 Redis 的那点事 》 您当前所在位置
关于 Redis 运维 系列其他文章的传送门

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 | import redis |
答案是:都可以。因为在 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。
适用场景:
- 多少秒过期,例如一个缓存数据失效;
PEXPIREAT key milliseconds-timestamp,比如现在开始缓存数据,到 0 点失效。
查看 Key 的剩余生存时间(Time To Live):
TTL key:秒;PTTL key:毫秒。
key 存在但没有设置 TTL,返回 -1;
key 存在,但还在生存期内,返回剩余的秒或者毫秒;
key 曾经存在,但已经消亡,返回 -2(2.8 版本之前返回 -1)。
1 | s1 20 秒过期 |
key 操作¶
keys pattern:pattern 可以取如下值:
*:任意长度字符;?:任意一个字符;[]:字符集合,表示一个字符。
1 | keys * |
TYPE key:key 类型;EXISTS key key:是否存在;RENAME key newkey、RENAMENX key newkey:键重命名;DEL key [key ...]:键删除。
字符串获取¶
GET key:获取值;MGET key [key ...]:获取多个给定的键的值;GETSET key value:返回旧值并设置新值,如果键不存在,就创建并赋值;STRLEN key:字符串长度。
1 | get s4 |
字符串操作¶
追加:
APPEND key value:追加字符串。
- 如果键存在就追加;如果不存在就等同于
SET key value;
获取:
GETRANGE key start end:获取子字符串。
- 索引值从
0开始,支持负索引,-1表示最后一个字符。范围是[start, end],前后都包。start必须在end的左边,否则返回空串。
覆盖:
SETRANGE key offset value:从指定索引处开始覆盖字符串,返回覆盖后字符串长度。如 key 不存在会创建新的。
1 | append s2 abc |
自增、自减¶
-
INCR key:步长 +1; -
DECR key:步长 -1。 -
INCRBY key increment:步长增increment; -
DECRBY key decrement:步长减decrement。
字符串值会被解释成 64 位的有符号的十进制整数来操作,结果依然转成字符串。
1 | set s3 3 |
库操作¶
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开始,注意官方说start、end是位的索引号,但经过测试后发现其实是字节,所以:BITCOUNT testkey 0 0:表示从索引为0个字节到索引为0个字节,就是第一个字节的统计。
- 从左向右从
BITCOUNT testkey 0 -1等同于BITCOUNT testkey;- 最常用的就是
BITCOUNT testkey。
1 | set test 7 # 请问在 redis 中如何理解存入了什么?以下的返回都是多少? |
位操作¶
对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。operation 可以是 AND、OR、NOT、XOR 这四种操作中的任意一种。
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 | set s2 a |

Bitmap 习题¶
1、网站用户的上线次数统计(活跃用户)¶
分析:用户 ID 为 key,天作为 offset,上线则置为 1;
- ID 为
500的用户,今年的第 1 天上线:SETBIT u500 1 1
- 第 30 天上线:
SETBIT u500 30 1
- 计算活跃天数:
BITCOUNT u500
KYES u*
1 | import redis |
2、按天统计网站活跃用户¶
天作为 key,用户 ID 为 offset,上线置为 1:
SETBIT 20160601 15 1:ID 作为索引,将索引15置为 1SETBIT 20160603 123 1:用户 ID123,一段时间内登录 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 | 阻塞式消息队列 |
List 习题 – 微博某贴最后评论的 50 条¶
微博某贴最后评论的 50 条。
1 | LPUSH u1234:forumid:comments "这是第 1 条评论" |
Hash 散列¶
值是由 field 和 value 组成的 map 键值对,field 和 value 都是字符串类型。
hash 可以看做是 python 中的字典。

命令¶
设置:
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 | HINCRBY numbers x 100 # 100 |
hash 用途¶
节约内存空间:
每创建一个键,它都会为这个键储存一些附加的管理信息(比如这个键的类型,这个键最后一次被访问的时间等等),
所以数据库里面的键越多,redis 数据库服务器在储存附加管理信息方面耗费的内存就越多,花在管理数据库键上 的 CPU 时间也会越多。
普通 ==> 使用 hash 节约

不适合 hash 的情况¶
-
使用二进制位操作命令:因为 Redis 目前仅支持对字符串键进行
SETBIT、GETBIT、BITOP等操作,如果你想使用这些操作,那么只能使用字符串键。虽然散列也能保存二进制数据。 -
使用过期键功能:Redis 的键过期功能目前只能对键(
key)进行过期操作,而不能对散列的字段(field)进行过期操作,因此如果你要对键值对数据使用过期功能的话,那么只能把键值对储存在字符串里面。
hash 习题¶
用户维度统计¶
用户维度统计,统计数包括:关注数、粉丝数、喜欢商品数、发帖数。用户为 Key,不同维度为 Field,Value 为统计数。
比如关注了 5 人
1 | HSET user:100000 follow 5 # 关注了 5 个用户 |
商品维度统计¶
统计值包括喜欢数,评论数,购买数,浏览数等。
1 | HSET item:58000 fav 500 |
缓存用户信息¶
登录后,反复需要读取用户的常用信息,最好的方式就是缓存起来。
1 | 这样不方便提取单个字段 |
Set 集合¶
集合的元素是无序的、去重的,元素是字符串类型。
增删:
SADD key member [member ...]:增加一个或多个元素,元素已存在将忽略;SREM key member [member ...]:移除一个或多个元素,元素不存在自动忽略;
元素相同的两个集合,未必有相同的顺序。如果需要去重且有序,可以使用 有序集合。
1 | SADD f1 "peter" "jack" "tom" "john" "may" "ben" |
获取:
SCARD key:返回集合中元素的个数。不需要遍历;SMEMBERS key:返回集合中的所有元素。注意:如果集合中元素过多,应当避免使用该方法;SISMEMBER key member:元素是否是在集合中。SRANDMEMBER key [count]:随机返回集合中指定个数的元素。- 如果
count为正数,且小于集合基数,那么命令返回一个包含count个元素的数组,数组中的元素各不相同; - 如果
count大于等于集合基数,那么返回整个集合; - 如果
count为负数,那么命令返回一个数组,数组中的元素可能会重复出现多次,而数组的长度为count的绝对值; - 如果
count为0,返回空; - 如果
count不指定,随机返回一个元素。
- 如果
随机移除:
SPOP key:从集合中随机移除一个元素并返回该元素;
移动:
SMOVE source destination member:把元素从源集合移动到目标集合。
Set 中的集合运算¶
差集¶
SDIFF key [key ...]:从第一个 key 的集合中去除其他集合和自己的交集部分;SDIFFSTORE destination key [key ...]:将差集结果存储在目标 key 中。
1 | SADD number1 123 456 789 |
交集¶
SINTER key [key ...]:取所有集合交集部分;SINTERSTORE destination key [key ...]:将交集结果存储在目标 key 中。
1 | SADD number1 123 456 789 |
并集¶
SUNION key [key ...]:取所有集合并集;SUNIONSTORE destination key [key ...]:将并集结果存储在目标 key 中。
1 | SADD number1 123 456 789 |
Set 习题 – 微博的共同关注¶
需求:当用户访问另一个用户的时候,会显示出两个用户共同关注哪些相同的用户。
设计:将每个用户关注的用户放在集合中,求交集即可。
1 | peter={'john','jack','may'} |
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 | ZADD employees 3500 jack 4000 peter 4000 john 4500 tom 2500 david |
ZRANGEBYSCORE key min max [WITHSCORES][LIMIT offset count]:返回指定分数区间的元素:- 返回 score 默认属于
[min,max]之间,元素按照 score 升序排列,score 相同则按字典序; LIMIT中offset代表跳过多少个元素,count是返回几个。类似于 Mysql 使用小括号,修改区间为开区间,例如(5(不包含 5)或者(10、5)(不包含 10 和 5);-inf和+inf表示负无穷和正无穷。
- 返回 score 默认属于
ZREVRANGEBYSCORE key max min [WITHSCORES][LIMIT offset count]:降序 返回指定分数区间的元素:- 返回 score 默认属于
[min,max]之间,元素按照 score 降序 排列,score 相同则按照字典降序。
- 返回 score 默认属于
1 | ZRANGEBYSCORE employees 3500 4000 |
移除:
ZREM key member [member ...]:移除一个或多个元素。元素不存在,自动忽略;ZREMRANGEBYRANK key start stop:移除指定排名范围的元素;ZREMRANGEBYSCORE key min max:移除指定分值范围的元素。
1 | ZREMRANGEBYRANK employees 0 1 |
集合运算¶
并集:
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 | ZADD scores1 70 tom 80 peter 60 john |
交集:
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 | import redis |
新浪微博翻页¶
新闻网站、博客、论坛、搜索引擎,页面列表条目多,都需要分页。
在 blog 这个 key 中使用时间戳作为 score。
ZADD blog 1407000000 '今天天气不错'ZADD blog 1450000000 '今天我们学习Redis'ZADD blog 1560000000 '几个Redis使用示例'
显示所有博客中的最后的指定的条目:
ZREVRANGE blog 10 20
京东图书畅销榜¶
统计单日榜,计算出周榜单、月榜单、年榜单,怎么做?
需求:每天统计一次排行榜:
1 | 每天的榜单累计数 |
因为上面的单日榜单是累计值,所以不能直接使用并集,要指定聚合运算为 MAX,取最大值。
1 | ZUNIONSTORE bk:it:01-03 3 bk:it:01 bk:it:02 bk:it:03 AGGREGATE MAX |
注意:如果参与并集元素的元素太多,会耗费大量内存和计算时间,可能会导致 Redis 服务阻塞,如果非要计算,选在空闲时间或备用服务器上计算。
另一种统计方法:
1 | 每天只记录当天的销售量 |