一、 引言
在 Web 应用发展的初期,关系型数据库受到了较为广泛的关注和应用,原因是那时候 Web 站点基本上访问和并发不高、交互也较少。而在后来,随着访问量的提升,使用关系型数据库的 Web 站点多多少少都开始在性能上出现了一些瓶颈,而瓶颈的源头一般是在磁盘的 I/O 上。随着互联网技术的进一步发展,各种类型的应用层出不穷,这导致在当今云计算、大数据盛行的时代,对性能有了更多的需求,主要体现在以下几个方面:
- 低延迟的读写速度:应用快速地反应能极大地提升用户的满意度。
- 支撑海量的数据和流量:对于搜索这样大型应用而言,需要利用 PB 级别的数据和能应对百万级的流量。
- 大规模集群的管理:系统管理员希望分布式应用能更简单的部署和管理。
为了克服这一问题,NoSQL 应运而生,它同时具备了高性能、可扩展性强、高可用等优点,受到广泛开发人员和数据库管理人员的青睐。
关系型数据库 (RDBMS) 与非关系型数据库 (NoSQL) 的对比
关系型数据库 (RDBMS) 数据库中表与表的数据之间存在某种关联的内在关系,因为这种关系,所以我们称这种数据库为关系型数据库。
- 典型代表:MySQL/MariaDB、PostgreSQL、Oracle、SQL Server、DB2、Access、SQLite3。
- 特点 :
- 全部使用 SQL(结构化查询语言)进行数据库操作。
- 都存在主外键关系,表等关系特征。
- 大部分都支持各种关系型的数据库的特性:事务、存储过程、触发器、视图、临时表、模式、函数。
非关系型数据库 (NoSQL) NOSQL = Not Only SQL,泛指非关系型数据库。泛指那些不使用 SQL 语句进行数据操作的数据库,所有数据库中只要不使用 SQL 语句的都是非关系型数据库。
- 典型代表:Redis、MongoDB、HBase、Hadoop、Elasticsearch、图数据库 (Neo4j) 等。
二、 Redis 概述
2.1 定义
Redis (Remote Dictionary Server,远程字典服务) 是一个使用 ANSI C 编写的开源、支持网络、基于内存、可选持久性的键值对 (Key-Value) 存储数据库,属于 NoSQL 数据库。
Redis 的出现主要是为了替代早期的 Memcache 缓存系统。它是一个 map 内存型 (数据存放在内存中) 的非关系型 (NoSQL) key-value (键值存储) 数据库。
核心特点:
- 支持数据持久化:基于 RDB 和 AOF。数据持久化时将数据存放到文件中,每次启动 Redis 之后会先将文件中数据加载到内存。
- 常见用途:经常用来做缓存、数据共享、购物车、消息队列、计数器、限流等。(最基本的就是缓存一些经常用到的数据,提高读写速度)。
2.2 Redis 特性
- 速度快
- 持久化
- 多种数据结构
- 支持多种编程语言
- 主从复制
- 高可用、分布式
2.3 Redis 的应用场景有哪些?
- 缓存系统("热点"数据:高频读、低频写):缓存用户信息、优惠券过期时间、验证码过期时间、Session、Token 等。
- 计数器:帖子的浏览数、视频播放次数、评论次数、点赞次数等。
- 消息队列、秒杀系统。
- 社交网络:粉丝、共同好友(可能认识的人)、兴趣爱好(推荐商品)。
- 排行榜(有序集合)。
- 发布订阅:粉丝关注、消息通知。
三、 Redis 环境安装
Redis 官方只提供了 Linux 版本的 Redis,Windows 系统的 Redis 是微软团队根据官方的 Linux 版本高仿的。
- 官方原版 :redis.io/
- 中文官网 :www.redis.cn
3.1 下载和安装
- Windows 版本下载地址 :github.com/tporadowski...
- 下载对应版本的安装包(如
Redis-x64-3.2.100.msi)后,直接运行安装程序即可。 - Windows 安装过程中的重要配置 :
- 默认端口 :Redis 的默认运行端口是
6379。在安装时可以勾选将该端口添加到系统防火墙白名单,允许外部访问。 - 最大内存限制 (Max Memory) :在安装时可以勾选并设置 Redis 占用的最大内存(例如默认
100MB)。这用于防止 Redis 无限制地消耗系统内存。
- 默认端口 :Redis 的默认运行端口是
四、 Redis 常用运维命令 (Linux)
在 Linux/Mac 环境下,我们经常需要打开客户端、查看 Redis 进程状态或停止 Redis 服务,以下是常用的几个命令:
1. 打开客户端 (redis-cli)
bash
redis-cli
- 作用:在终端启动 Redis 的命令行客户端,用于直接连接到本地的 Redis Server 进行各种数据操作和交互。
2. 查找 Redis 进程
bash
ps -ef | grep redis-server
- 作用 :主要用来确认 Redis 服务是否在后台运行,并获取 Redis 进程的 PID(进程号)。
3. 强制终止 (关闭) Redis 进程
bash
kill -9 8557
- 作用:强制杀掉 PID 为 8557 的 Redis 进程。通常在 Redis 无法正常停止,或者需要强制重启时使用。
五、 Redis 数据类型概述
Redis 可以理解成一个全局的大字典,key 就是数据的唯一标识符。根据 key 对应的值不同,可以划分成 5 个基本数据类型。
javascript
redis = {
"name": "yuan"
}
-
String 类型 (字符串): 字符串类型,是 Redis 中最为基础的数据存储类型。它在 Redis 中是二进制安全的,也就是 byte 类型。
- 单个数据的最大容量是 512M。
- 结构:
key: 值
-
Hash 类型 (哈希): 用于存储对象/字典,对象/字典的结构为键值对。key、域(属性)、值的类型都为 string。域在同一个 hash 中是唯一的。
-
结构:
jsonkey: { 域 (属性): 值, 域: 值, ... }
-
-
List 类型 (列表/数组): 列表类型,它的子成员类型为 string。
- 结构:
key: [值1, 值2, 值3...]
- 结构:
-
Set 类型 (集合) : 无序集合,它的子成员类型为 string 类型。元素唯一不重复,没有修改操作。
- 结构:
key: {值1, 值2, 值3...}
- 结构:
(注:还有第 5 种基本数据类型为 Zset 有序集合)
六、 Redis 常用数据操作 - String (字符串)
String 是 Redis 中最基本的数据类型,以下是常用的基础操作命令:
1. 设置和获取键值对 (set / get)
-
设置键值 (
set) :如果设置的键不存在,则为添加;如果已经存在,则直接覆盖。bash127.0.0.1:6379> set name yuan OK -
获取键值 (
get) :根据键获取值,如果不存在此键则返回nil。
2. 仅当键不存在时设置 (setnx)
用于一个变量只能被设置一次的情况。一般用于给数据加锁(分布式锁)。
bash
127.0.0.1:6379> setnx goods_1 101
(integer) 1 # 第一次设置,键不存在,设置成功返回 1
127.0.0.1:6379> setnx goods_1 102
(integer) 0 # 键已存在,设置失败返回 0
3. 设置键值的过期时间 (setex)
Redis 中可以对一切的数据设置有效期,以秒为单位。
bash
127.0.0.1:6379> setex name 5 rain
OK
# 等待 5 秒钟后再次获取将返回 (nil)
4. 设置和获取多个键值 (mset / mget)
bash
127.0.0.1:6379> mset a1 goland a2 java a3 c
OK
127.0.0.1:6379> mget a1 a2
5. 字符串拼接值 (append)
常见于大文件上传等场景。向已有的字符串末尾追加内容。
bash
127.0.0.1:6379> set title "我的"
127.0.0.1:6379> append title "redis"
6. 自增自减 (incr / decr)
应用场景:电商抢购、秒杀;游戏里面的投票、攻击计数;计算当前在线人数等。
bash
127.0.0.1:6379> set id 1
127.0.0.1:6379> incr id # 相当于 id+1
(integer) 2
127.0.0.1:6379> decr id # 相当于 id-1
(integer) 1
7. 获取字符串的长度 (strlen)
bash
127.0.0.1:6379> set name xiaoming
127.0.0.1:6379> strlen name
(integer) 8
8. 比特流操作 (Bitmaps)
常用于签到记录等场景,非常节省内存。
SETBIT key offset value:按从左到右的偏移量设置一个 bit 数据的值(只能是 0 或 1)。GETBIT key offset:获取一个 bit 数据的值。BITCOUNT key:统计字符串被设置为 1 的 bit 数量。
七、 Redis 通用 Key 操作
Redis 中所有的数据都是通过 key(键)来进行操作,以下是任何数据类型都通用的命令:
1. 查找键 (keys)
参数支持简单的正则表达式。
bash
127.0.0.1:6379> keys * # 查看所有键
127.0.0.1:6379> keys *a* # 查看名称中包含 `a` 的键
127.0.0.1:6379> keys a* # 查看以 a 开头的键
127.0.0.1:6379> keys *_1 # 查看以 _1 结尾的键
2. 判断键是否存在 (exists)
如果存在返回 1,不存在返回 0。
bash
127.0.0.1:6379> exists key1
127.0.0.1:6379> exists title
3. 查看键的值的数据类型 (type)
bash
127.0.0.1:6379> type a1
string # 返回结果可能为 string、hash、list、set、zset
4. 删除键以及键对应的值 (del)
可以同时删除一个或多个键。
bash
127.0.0.1:6379> del key1 key2 ...
5. 查看键的有效期 (ttl)
bash
127.0.0.1:6379> ttl key
- 正整数:以秒为单位的剩余有效时间。
-1:表示永不过期。-2:表示当前数据已经过期(或该键根本不存在)。
6. 设置 key 的有效期 (expire)
Redis 中所有的数据都可以通过 expire 重新设置有效期,到期后自动删除。
bash
127.0.0.1:6379> expire key seconds
7. 清空所有 key (flushall)
⚠️ 慎用! 一旦执行,则 Redis 所有数据库(0~15 号库)的全部 key 都会被清除。
bash
127.0.0.1:6379> flushall
8. key 重命名 (rename)
bash
# 把 name 重命名为 username
127.0.0.1:6379> rename name username
9. 切换数据库 (select)
Redis 配置文件中默认有 0~15 共 16 个数据库,默认操作的是 0 号数据库。
bash
# 切换到 1 号数据库,终端前缀会变成 [1]
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> set name xiaoming
OK
八、 Redis 常用数据操作 - List (列表/数组)
List(列表)相当于一个队列或数组,列表的子成员类型为 string。
1. 基础命令概览
lpush key value:左侧(前)插入rpush key value:右侧(后)插入linsert key after|before 指定元素 value:指定元素前后插入lindex key index:根据索引获取元素lrange key start stop:获取指定范围内的元素lset key index value:修改指定索引的值lrem key count value:删除指定数量的特定元素lpop key:移除并获取列表的第一个成员rpop key:移除并获取列表的最后一个成员
2. 添加子成员
可以一次性添加一条或多条数据。
-
左侧 (前) 添加 (
lpush) :bash# 例:从键为 brother 的列表左侧依次添加 127.0.0.1:6379> lpush brother liubei # 当前列表:[liubei] 127.0.0.1:6379> lpush brother guanyu zhangfei xiaoming # 当前列表:[xiaoming, zhangfei, guanyu, liubei] # 注意:连续添加时,后添加的元素会被顶到更左侧 -
右侧 (后) 添加 (
rpush) :bash# 例:从键为 brother 的列表右侧依次添加 127.0.0.1:6379> rpush brother xiaohong # 当前列表:[xiaoming, zhangfei, guanyu, liubei, xiaohong] 127.0.0.1:6379> rpush brother xiaobai xiaohui # 当前列表:[xiaoming, zhangfei, guanyu, liubei, xiaohong, xiaobai, xiaohui] -
在指定元素旁插入 (
linsert) :bash# 在指定元素的左边(前) / 右边(后) 插入数据 127.0.0.1:6379> linsert key before 指定元素 value 127.0.0.1:6379> linsert key after 指定元素 value
3. 基于索引获取列表成员 (lindex)
根据指定的索引(下标)获取成员的值。负数下标从右边 -1 开始,逐个递减。
bash
# 示例:获取 brother 下标为 2 以及 -2 的成员
127.0.0.1:6379> del brother
127.0.0.1:6379> lpush brother guanyu zhangfei xiaoming
# 此时列表为:[xiaoming, zhangfei, guanyu]
127.0.0.1:6379> lindex brother 2
"guanyu"
127.0.0.1:6379> lindex brother -2
"zhangfei"
4. 移除并获取列表成员 (lpop / rpop)
lpop key:第一个成员出列(移除并获取左侧第一个元素)。rpop key:最后一个成员出列(移除并获取右侧最后一个元素)。
bash
# 获取并移除 brother 中的第一个成员
127.0.0.1:6379> lpop brother
"xiaoming"
💡 核心开发技巧 : 在开发中,我们往往使用
rpush和lpop这两个命令组合,来实现队列 (Queue) 的数据结构,从而完美实现数据的"入列"和"出列"(先进先出)。
5. 获取列表的切片 (lrange)
获取列表中指定范围的成员,起始和结束索引都是基于 0 的。
bash
127.0.0.1:6379> lrange key start stop
# 示例:获取 brother 列表中所有的元素(0 到 -1 表示获取所有)
127.0.0.1:6379> lrange brother 0 -1
1) "xiaoming"
2) "zhangfei"
3) "guanyu"
# 获取索引 0 到 1 的元素
127.0.0.1:6379> lrange brother 0 1
1) "xiaoming"
2) "zhangfei"
6. 获取列表的长度 (llen)
获取列表中包含的成员数量。
bash
127.0.0.1:6379> llen key
# 示例:获取 brother 列表的长度
127.0.0.1:6379> llen brother
(integer) 3
7. 按索引设置值 (lset)
修改指定索引位置上的成员的值。
bash
127.0.0.1:6379> lset key index value
# 示例:将 brother 列表中索引为 1 的成员修改为 "zhaoyun"
127.0.0.1:6379> lset brother 1 "zhaoyun"
OK
127.0.0.1:6379> lrange brother 0 -1
1) "xiaoming"
2) "zhaoyun"
3) "guanyu"
8. 删除指定成员 (lrem)
根据参数 count 的值,移除列表中与参数 value 相等的元素。
count > 0: 从左往右(表头向表尾),移除与 value 相等的元素,数量为 count。count < 0: 从右往左(表尾向表头),移除与 value 相等的元素,数量为 count 的绝对值。count = 0: 移除表中所有与 value 相等的值。
bash
127.0.0.1:6379> lrem key count value
# 示例:假设 num 列表里面为 [1, 2, 3, 1, 2, 3, 1, 2, 3]
# 从左向右,删除 2 个 "1"
127.0.0.1:6379> lrem num 2 1
(integer) 2
# 从右向左,删除 1 个 "3"
127.0.0.1:6379> lrem num -1 3
(integer) 1
# 删除所有的 "2"
127.0.0.1:6379> lrem num 0 2
(integer) 3
九、 Redis 常用数据操作 - Hash (哈希)
专门用于结构化的数据信息。对应的就是 map/结构体。
结构:
css
键key: {
域field: 值value,
域field: 值value,
域field: 值value,
}
1. 设置指定键的属性 / 域 (hset)
设置指定键的单个或多个属性。
-
命令格式 :
hset key field value -
示例 :
bash127.0.0.1:6379> hset user_1 name xiaoming # user_1没有会自动创建 (integer) 1 127.0.0.1:6379> hset user_1 name xiaohei # user_1中重复的属性会被修改 (integer) 0 127.0.0.1:6379> hset user_1 age 16 # user_1中不存在的属性会被新增 (integer) 1 127.0.0.1:6379> hset user:1 name xiaohui # user:1会在redis界面操作中以:作为目录分隔符 (integer) 1 127.0.0.1:6379> hset user:1 age 15 (integer) 1 127.0.0.1:6379> hset user:2 name xiaohong age 16 # 一次性添加或修改多个属性 -
注意事项 :
- 老版本的 Redis 批量设置需要用
hmset,在较新版本中hset已经支持直接设置多个字段。
- 老版本的 Redis 批量设置需要用
2. 获取指定键的域 / 属性的值 (hget / hmget)
-
命令格式 :
- 获取单个:
hget key field - 获取多个:
hmget key field1 field2 ...
- 获取单个:
-
示例 :
bash# 获取键 user:3 属性 name 的值 127.0.0.1:6379> hget user:3 name "xiaohong" # 获取键 user:2 属性 name、age 的值 127.0.0.1:6379> hmget user:2 name age 1) "xiaohong" 2) "16" -
注意事项 :如果请求的字段(field)不存在,或者指定的 key 本身不存在,会返回
nil。
3. 获取 Hash 的所有域值对 (hgetall / hkeys / hvals)
-
命令格式 :
- 获取所有值:
hvals key - 获取所有域 (属性名):
hkeys key - 获取所有域值对:
hgetall key
- 获取所有值:
-
示例 :
bash# 获取指定键的所有域值对 127.0.0.1:6379> hset user:1 name xiaoming age 16 sex 1 (integer) 3 127.0.0.1:6379> hgetall user:1 1) "name" 2) "xiaoming" 3) "age" 4) "16" 5) "sex" 6) "1" # 获取指定键的所有域 (属性名) 127.0.0.1:6379> hkeys info 1) "name" 2) "age" # 获取指定键的所有值 127.0.0.1:6379> hvals info 1) "rain" 2) "32" -
注意事项 :
hgetall返回的结果是一个列表:先是字段名,紧跟着是该字段的值,交替出现。- 慎用 :如果 Hash 内部的数据量非常大(成千上万个字段),直接使用
hgetall可能会导致阻塞,此时推荐使用hscan。
4. 删除指定键的域 / 属性 (hdel)
-
命令格式 :
hdel key field1 field2 ... -
示例 :
bash# 删除键 user:3 的属性 sex/age/name,当键中的hash数据没有任何属性,则当前键会被redis删除 127.0.0.1:6379> hdel user:3 sex age name -
注意事项:如果删除的字段不存在,则直接忽略。返回值是被成功删除的字段数量。如果哈希表中的所有字段都被删除了,这个键(key)本身也会被 Redis 自动删除。
5. 判断指定属性 / 域是否存在于当前键对应的hash中 (hexists)
-
命令格式 :
hexists key field -
示例 :
bash# 判断user:2中是否存在age属性 127.0.0.1:6379> hexists user:3 age (integer) 0 127.0.0.1:6379> hexists user:2 age (integer) 1 -
注意事项:只能判断单个字段是否存在,不能一次性判断多个。
6. 属性值自增自减 (hincrby)
-
命令格式 :
hincrby key field number -
示例 :给 user:2 的 age 属性在原值基础上+/-10,然后在 age 现有值的基础上-2
bash# 按指定数值自增 127.0.0.1:6379> hincrby user:2 age 10 (integer) 77 127.0.0.1:6379> hincrby user:2 age 10 (integer) 87 # 按指定数值自减 127.0.0.1:6379> hincrby user:2 age -10 (integer) 77 127.0.0.1:6379> hincrby user:2 age -10 (integer) 67 -
注意事项 :
- 如果目标字段不存在,会先将该字段的值自动设为 0,然后再执行加法操作。
- 目标字段的值必须是能够被解析为数字的字符串,否则会报错。
十、 Redis 常用数据操作 - Set (集合)
无序集合,重点就是去重 和无序。
1. 添加元素 (sadd)
-
命令格式 :
sadd key member1 member2 ... -
示例 :向键 authors 的集合中添加元素 zhangsan、lisi、wangwu
bash127.0.0.1:6379> sadd authors zhangsan lisi wangwu (integer) 3
2. 获取集合的所有成员 (smembers)
-
命令格式 :
smembers key -
示例 :获取键 authors 的集合中所有元素
bash127.0.0.1:6379> smembers authors 1) "lisi" 2) "wangwu" 3) "zhangsan"
3. 获取集合的长度 (scard)
-
命令格式 :
scard key -
示例 :
bash127.0.0.1:6379> scard authors (integer) 3
4. 随机抽取一个或多个元素 (spop)
抽取出来的成员会被删除掉。
-
命令格式 :
spop key [count=1] -
示例 :
bash127.0.0.1:6379> spop authors "wangwu" 127.0.0.1:6379> smembers authors 1) "lisi" 2) "zhangsan" -
注意事项 :
count为可选参数,不填则默认一个。被提取的成员会从集合中被删除掉。
5. 删除指定元素 (srem)
-
命令格式 :
srem key value -
示例 :删除键 authors 的集合中元素 zhangsan
bash127.0.0.1:6379> srem authors zhangsan
6. 交集、差集和并集 (sinter / sdiff / sunion)
常用于推荐系统(协同过滤,基于用户、基于物品)。
-
命令格式 :
sinter key1 key2 key3 ....# 交集、比较多个集合中共同存在的成员sdiff key1 key2 key3 ....# 差集、比较多个集合中不同的成员sunion key1 key2 key3 ....# 并集、合并所有集合的成员,并去重
-
示例 :
bashdel user:1 user:2 user:3 user:4 sadd user:1 1 2 3 4 # user:1 = {1,2,3,4} sadd user:2 1 3 4 5 # user:2 = {1,3,4,5} sadd user:3 1 3 5 6 # user:3 = {1,3,5,6} sadd user:4 2 3 4 # user:4 = {2,3,4} # 交集 127.0.0.1:6379> sinter user:1 user:2 1) "1" 2) "3" 3) "4" 127.0.0.1:6379> sinter user:1 user:3 1) "1" 2) "3" 127.0.0.1:6379> sinter user:1 user:4 1) "2" 2) "3" 3) "4" 127.0.0.1:6379> sinter user:2 user:4 1) "3" 2) "4" # 并集 127.0.0.1:6379> sunion user:1 user:2 user:4 1) "1" 2) "2" 3) "3" 4) "4" 5) "5" # 差集 127.0.0.1:6379> sdiff user:2 user:3 1) "4" # 此时可以给user:3推荐4 127.0.0.1:6379> sdiff user:3 user:2 1) "6" # 此时可以给user:2推荐6 127.0.0.1:6379> sdiff user:1 user:3 1) "2" 2) "4"
十一、 Redis 常用数据操作 - zset (有序集合)
有序集合 (score/value) ,去重并且根据 score 权重值来进行排序的。score 从小到大排列。
1. 添加成员 (zadd)
-
命令格式 :
zadd key score1 member1 score2 member2 score3 member3 .... -
示例 :设置榜单 achievements,设置成绩和用户名作为 achievements 的成员
bash127.0.0.1:6379> zadd achievements 61 xiaoming 62 xiaohong 83 xiaobai 78 xiaohei 87 xiaohui 99 xiaolan (integer) 6 127.0.0.1:6379> zadd achievements 85 xiaohuang (integer) 1 127.0.0.1:6379> zadd achievements 54 xiaoqing
2. 获取 score 在指定区间的所有成员 (zrangebyscore / zrange 等)
-
命令格式 :
zrangebyscore key min max# 按 score 进行从低往高排序获取指定 score 区间zrevrangebyscore key min max# 按 score 进行从高往低排序获取指定 score 区间zrange key start stop# 按 score 进行从低往高排序获取指定索引区间zrevrange key start stop# 按 score 进行从高往低排序获取指定索引区间
-
示例 :
bashzrange achievements 0 -1 # 从低到高全部成员
3. 获取集合长度 (zcard)
-
命令格式 :
zcard key -
示例 :获取 users 的长度 (示例中演示为 achievements)
bashzcard achievements
4. 获取指定成员的权重值 (zscore)
-
命令格式 :
zscore key member -
示例 :获取 users 中 xiaoming 的成绩
bash127.0.0.1:6379> zscore achievements xiaobai "93" 127.0.0.1:6379> zscore achievements xiaohong "62" 127.0.0.1:6379> zscore achievements xiaoming "61"
5. 获取指定成员在集合中的排名 (zrank / zrevrank)
排名从 0 开始计算。
-
命令格式 :
zrank key member# score 从小到大的排名zrevrank key member# score 从大到小的排名
-
示例 :获取 achievements 中 xiaohei 的分数排名,从大到小
bash127.0.0.1:6379> zrevrank achievements xiaohei (integer) 4
6. 获取 score 在指定区间的所有成员数量 (zcount)
-
命令格式 :
zcount key min max -
示例 :获取 achievements 从 0~60 分之间的人数闭区间
bash127.0.0.1:6379> zcount achievements 0 60 (integer) 2 127.0.0.1:6379> zcount achievements 54 60 (integer) 2
7. 给指定成员增加增加权重值 (zincrby)
-
命令格式 :
zincrby key score member -
示例 :给 achievements 中 xiaobai 增加 10 分
bash127.0.0.1:6379> ZINCRBY achievements 10 xiaobai "93"
8. 删除成员 (zrem)
-
命令格式 :
zrem key member1 member2 member3 .... -
示例 :从 achievements 中删除 xiaoming 的数据
bash127.0.0.1:6379> zrem achievements xiaoming
9. 删除指定数量的成员 (zpopmin / zpopmax)
-
命令格式 :
zpopmin key [count]# 删除指定数量的成员,从最低score开始删除zpopmax key [count]# 删除指定数量的成员,从最高score开始删除
-
示例 :从 achievements 中提取并删除成绩最低的 2 个数据
bash127.0.0.1:6379> zpopmin achievements 2
十二、 Python操作redis
一、 python对redis基本操作
(1) 连接redis
方式1:
python
import redis
# 方式1
r = redis.Redis(host='127.0.0.1', port=6379)
r.set("foo", "Bar")
方式2 (连接池):
python
import redis
pool = redis.ConnectionPool(host='127.0.0.1', port=6379)
r = redis.Redis(connection_pool=pool)
r.set('bar', 'Foo')
print(r.get('bar'))
连接池原理与优势说明: 通常情况下,当我们需要做redis操作时,会创建一个连接,并基于这个连接进行redis操作,操作完成后,释放连接,一般情况下,这是没问题的,但当并发量比较高的时候,频繁的连接创建和释放对性能会有较高的影响。于是,连接池就发挥作用了。连接池的原理是,通过预先创建多个连接,当进行redis操作时,直接获取已经创建的连接进行操作,而且操作完成后,不会释放,用于后续的其他redis操作。这样就达到了避免频繁的redis连接创建和释放的目的,从而提高性能。
(2) 数据类型操作
python
import redis
pool = redis.ConnectionPool(host='127.0.0.1', port=6379, db=0, decode_responses=True)
r = redis.Redis(connection_pool=pool)
# 一 字符串操作
r.set('bar', 'Foo')
print(r.get('bar'))
# 不允许对已经存在的键设置值
ret = r.setnx("name", "yuan")
print(ret) # False
# 设置键有效期
r.setex("good_1001", 10, "2")
# 自增自减
r.set("age", 22)
r.incr("age", 10)
print(r.get("age"))
# 二、hash操作
# r.hset("info", "name", "rain")
# print(r.hget("info", "name"))
# r.hmset("info", {"gedner": "male", "age": 22})
# print(r.hgetall("info")) # {b'name': b'rain', b'gedner': b'male', b'age': b'22'}
# 三、list操作:设置list
r.rpush("scores", "100", "90", "80") # "scores" :["120","100", "90", "80","70"]
r.rpush("scores", "70")
r.lpush("scores", "120")
print(r.lrange("scores", 0, -1)) # ['120', '100', '90', '80', '70']
r.linsert("scores", "AFTER", "100", 95)
print(r.lrange("scores", 0, -1)) # ['120', '100', '95', '90', '80', '70']
print(r.lpop("scores")) # 120
print(r.rpop("scores")) # 70
print(r.lrange("scores", 0, -1))
print(r.lindex("scores", 1)) # '95'
# (6) 集合操作
# key对应的集合中添加元素
r.delete("name_set")
r.sadd("name_set", "zhangsan", "lisi", "wangwu")
# 获取key对应的集合的所有成员
print(r.smembers("name_set")) # {'lisi', 'zhangsan', 'wangwu'}
# 从key对应的集合中随机获取 numbers 个元素
print(r.srandmember("name_set", 2))
print(r.smembers("name_set"))
r.srem("name_set", "lisi")
print(r.smembers("name_set")) # {'wangwu', 'zhangsan'}
# (7) 有序集合操作
# 在key对应的有序集合中添加元素
r.delete("jifenbang")
r.zadd("jifenbang", {"yuan": 78, "rain": 20, "alvin": 89, "eric": 45})
# 按照索引范围获取key对应的有序集合的元素
# zrange( name, start, end, desc=False, withscores=False, score_cast_func=float)
print(r.zrange("jifenbang", 0, -1)) # ['rain', 'eric', 'yuan', 'alvin']
print(r.zrange("jifenbang", 0, -1,
withscores=True)) # [(b'rain', 20.0), (b'eric', 45.0), (b'yuan', 78.0), (b'alvin', 89.0)]
print(r.zrevrange("jifenbang", 0, -1, withscores=True)) # [(b'alvin', 89.0), (b'yuan', 78.0), (b'eric', 45.0), (b'rain', 20.0)]
print(r.zrangebyscore("jifenbang", 60, 100)) # [b'yuan', b'alvin']
print(r.zrangebyscore("jifenbang", 60, 100, start=0, num=1)) # [b'yuan']
# 删除key对应的有序集合中值是values的成员
print(r.zrem("jifenbang", "yuan")) # 删除成功返回1
print(r.zrange("jifenbang", 0, -1)) # ['rain', 'eric', 'alvin']
# (8) 键操作
r.delete("scores")
print(r.exists("scores"))
print(r.keys("*"))
r.expire("name", 10)
二、 关于redis的实战案例
(1) 案例1:KV缓存
第1个是最基础也是最常用的就是KV功能,我们可以用Redis来缓存用户信息、会话信息、商品信息等等。下面这段代码就是通过缓存读取逻辑。
python
import redis
pool = redis.ConnectionPool(host='127.0.0.1', port=6379, db=6, decode_responses=True)
r = redis.Redis(connection_pool=pool)
def get_user(user_id):
user = r.get(user_id)
if not user:
user = UserInfo.objects.get(pk=user_id)
r.setex(user_id, 3600, user)
return user
(2) 案例2:分布式锁
什么是分布式锁
分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。
提到Redis的分布式锁,很多小伙伴马上就会想到 setnx + expire 命令。即先用 setnx 来抢锁,如果抢到之后,再用 expire 给锁设置一个过期时间,防止锁忘记了释放。
SETNX是SET IF NOT EXISTS的简写.日常命令格式是SETNX key value,如果 key不存在,则SETNX成功返回1,如果这个key已经存在了,则返回0。
假设某电商网站的某商品做秒杀活动,key可以设置为key_resource_id,value设置任意值,伪代码如下:
方案1
python
import redis
pool = redis.ConnectionPool(host='127.0.0.1')
r = redis.Redis(connection_pool=pool)
ret = r.setnx("key_resource_id", "ok")
if ret:
r.expire("key_resource_id", 5) # 设置过期时间
print("抢购成功!")
r.delete("key_resource_id") # 释放资源
else:
print("抢购失败!")
但是这个方案中,setnx 和 expire 两个命令分开了,「不是原子操作 」。如果执行完 setnx 加锁,正要执行 expire 设置过期时间时,进程crash或者要重启维护了,那么这个锁就"长生不老"了,「别的线程永远获取不到锁啦」。
方案2:SETNX + value值是(系统时间+过期时间)
为了解决方案一,「发生异常锁得不到释放的场景」,可以把过期时间放到 setnx 的value值里面。如果加锁失败,再拿出value值校验一下即可。加锁代码如下:
python
import time
def foo():
expiresTime = time.time() + 10
ret = r.setnx("key_resource_id", expiresTime)
if ret:
print("当前锁不存在,加锁成功")
return True
oldExpiresTime = r.get("key_resource_id")
if float(oldExpiresTime) < time.time(): # 如果获取到的过期时间,小于系统当前时间,表示已经过期
# 锁已过期,获取上一个锁的过期时间,并设置现在锁的过期时间
newExpiresTime = r.getset("key_resource_id", expiresTime)
if oldExpiresTime == newExpiresTime:
# 考虑多线程并发的情况,只有一个线程的设置值和当前值相同,它才可以加锁
return True # 加锁成功
return False # 其余情况加锁皆失败
foo()
方案3
实际上,我们还可以使用Py的redis模块中的set函数来保证原子性(包含setnx和expire两条指令) 代码如下:
python
r.set("key_resource_id", "1", nx=True, ex=10)
(3) 案例4:延迟队列
延时队列可以通过Redis的zset(有序列表)来实现。我们将消息序列化为一个字符串作为zset的值。这个消息的到期时间处理时间作为score,然后用多个线程轮询zset获取到期的任务进行处理,多线程时为了保障可用性,万一挂了一个线程还有其他线程可以继续处理。因为有多个线程,所有需要考虑并发争抢任务,确保任务不能被多次执行。
(4) 案例5:发布订阅
bash
subscribe channel # 订阅
publish channel mes # 发布消息
python
import threading
import redis
r = redis.Redis(host='127.0.0.1')
def recv_msg():
pub = r.pubsub()
pub.subscribe("fm104.5")
pub.parse_response()
while 1:
msg = pub.parse_response()
print(msg)
def send_msg():
msg = input(">>>")
r.publish("fm104.5", msg)
t = threading.Thread(target=send_msg)
t.start()
recv_msg()