1.Redis 基础知识

一、 引言

在 Web 应用发展的初期,关系型数据库受到了较为广泛的关注和应用,原因是那时候 Web 站点基本上访问和并发不高、交互也较少。而在后来,随着访问量的提升,使用关系型数据库的 Web 站点多多少少都开始在性能上出现了一些瓶颈,而瓶颈的源头一般是在磁盘的 I/O 上。随着互联网技术的进一步发展,各种类型的应用层出不穷,这导致在当今云计算、大数据盛行的时代,对性能有了更多的需求,主要体现在以下几个方面:

  1. 低延迟的读写速度:应用快速地反应能极大地提升用户的满意度。
  2. 支撑海量的数据和流量:对于搜索这样大型应用而言,需要利用 PB 级别的数据和能应对百万级的流量。
  3. 大规模集群的管理:系统管理员希望分布式应用能更简单的部署和管理。

为了克服这一问题,NoSQL 应运而生,它同时具备了高性能、可扩展性强、高可用等优点,受到广泛开发人员和数据库管理人员的青睐。

关系型数据库 (RDBMS) 与非关系型数据库 (NoSQL) 的对比

关系型数据库 (RDBMS) 数据库中表与表的数据之间存在某种关联的内在关系,因为这种关系,所以我们称这种数据库为关系型数据库。

  • 典型代表:MySQL/MariaDB、PostgreSQL、Oracle、SQL Server、DB2、Access、SQLite3。
  • 特点 :
    1. 全部使用 SQL(结构化查询语言)进行数据库操作。
    2. 都存在主外键关系,表等关系特征。
    3. 大部分都支持各种关系型的数据库的特性:事务、存储过程、触发器、视图、临时表、模式、函数。

非关系型数据库 (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 版本高仿的。

3.1 下载和安装

  • Windows 版本下载地址 :github.com/tporadowski...
  • 下载对应版本的安装包(如 Redis-x64-3.2.100.msi)后,直接运行安装程序即可。
  • Windows 安装过程中的重要配置 :
    1. 默认端口 :Redis 的默认运行端口是 6379。在安装时可以勾选将该端口添加到系统防火墙白名单,允许外部访问。
    2. 最大内存限制 (Max Memory) :在安装时可以勾选并设置 Redis 占用的最大内存(例如默认 100MB)。这用于防止 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"
}
  1. String 类型 (字符串): 字符串类型,是 Redis 中最为基础的数据存储类型。它在 Redis 中是二进制安全的,也就是 byte 类型。

    • 单个数据的最大容量是 512M。
    • 结构:key: 值
  2. Hash 类型 (哈希): 用于存储对象/字典,对象/字典的结构为键值对。key、域(属性)、值的类型都为 string。域在同一个 hash 中是唯一的。

    • 结构:

      json 复制代码
      key: {
          域 (属性): 值,
          域: 值,
          ...
      }
  3. List 类型 (列表/数组): 列表类型,它的子成员类型为 string。

    • 结构:key: [值1, 值2, 值3...]
  4. Set 类型 (集合) : 无序集合,它的子成员类型为 string 类型。元素唯一不重复,没有修改操作。

    • 结构:key: {值1, 值2, 值3...}

(注:还有第 5 种基本数据类型为 Zset 有序集合)


六、 Redis 常用数据操作 - String (字符串)

String 是 Redis 中最基本的数据类型,以下是常用的基础操作命令:

1. 设置和获取键值对 (set / get)

  • 设置键值 (set) :如果设置的键不存在,则为添加;如果已经存在,则直接覆盖。

    bash 复制代码
    127.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

  • 示例 :

    bash 复制代码
    127.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 已经支持直接设置多个字段。

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

    bash 复制代码
    127.0.0.1:6379> sadd authors zhangsan lisi wangwu
    (integer) 3

2. 获取集合的所有成员 (smembers)

  • 命令格式 :smembers key

  • 示例 :获取键 authors 的集合中所有元素

    bash 复制代码
    127.0.0.1:6379> smembers authors
    1) "lisi"
    2) "wangwu"
    3) "zhangsan"

3. 获取集合的长度 (scard)

  • 命令格式 :scard key

  • 示例 :

    bash 复制代码
    127.0.0.1:6379> scard authors
    (integer) 3

4. 随机抽取一个或多个元素 (spop)

抽取出来的成员会被删除掉。

  • 命令格式 :spop key [count=1]

  • 示例 :

    bash 复制代码
    127.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

    bash 复制代码
    127.0.0.1:6379> srem authors zhangsan

6. 交集、差集和并集 (sinter / sdiff / sunion)

常用于推荐系统(协同过滤,基于用户、基于物品)。

  • 命令格式 :

    • sinter key1 key2 key3 .... # 交集、比较多个集合中共同存在的成员
    • sdiff key1 key2 key3 .... # 差集、比较多个集合中不同的成员
    • sunion key1 key2 key3 .... # 并集、合并所有集合的成员,并去重
  • 示例 :

    bash 复制代码
    del 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 的成员

    bash 复制代码
    127.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 进行从高往低排序获取指定索引区间
  • 示例 :

    bash 复制代码
    zrange achievements 0 -1  # 从低到高全部成员

3. 获取集合长度 (zcard)

  • 命令格式 :zcard key

  • 示例 :获取 users 的长度 (示例中演示为 achievements)

    bash 复制代码
    zcard achievements

4. 获取指定成员的权重值 (zscore)

  • 命令格式 :zscore key member

  • 示例 :获取 users 中 xiaoming 的成绩

    bash 复制代码
    127.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 的分数排名,从大到小

    bash 复制代码
    127.0.0.1:6379> zrevrank achievements xiaohei
    (integer) 4

6. 获取 score 在指定区间的所有成员数量 (zcount)

  • 命令格式 :zcount key min max

  • 示例 :获取 achievements 从 0~60 分之间的人数闭区间

    bash 复制代码
    127.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 分

    bash 复制代码
    127.0.0.1:6379> ZINCRBY achievements 10 xiaobai
    "93"

8. 删除成员 (zrem)

  • 命令格式 :zrem key member1 member2 member3 ....

  • 示例 :从 achievements 中删除 xiaoming 的数据

    bash 复制代码
    127.0.0.1:6379> zrem achievements xiaoming

9. 删除指定数量的成员 (zpopmin / zpopmax)

  • 命令格式 :

    • zpopmin key [count] # 删除指定数量的成员,从最低score开始删除
    • zpopmax key [count] # 删除指定数量的成员,从最高score开始删除
  • 示例 :从 achievements 中提取并删除成绩最低的 2 个数据

    bash 复制代码
    127.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()
相关推荐
牛战士coding1 小时前
Java 对象写入 Redis 后类型变了?Jackson 多态序列化问题复盘
java·开发语言·redis
xiaoqiMikko1 小时前
Redis 实战(基础篇):把一个老 RedisUtil 搬到 Spring Boot 4,一路踩出来的坑(真机实测)
java·spring boot·redis
lv__pf2 小时前
redis【TL redis(6)】
数据库·redis·缓存
用户84913717547162 小时前
Redis 跳表用了"随机"的魔法,实现了比平衡树更快的查找!
数据结构·redis
hweiyu003 小时前
Redis命令:RPOPLPUSH
redis·缓存
Tsuki_tl3 小时前
Redis 单线程 + 多编码,到底怎么配合的?
数据库·redis·哈希表·redis数据类型·编码方式·单线程模型·redis底层
March.s4 小时前
Redis 核心知识与高可用架构实践
数据库·redis·架构
Knight_AL13 小时前
本地服务连接 SaaS 测试环境实践:Docker 部署 Redis 与 RocketMQ 完成接口联调
redis·docker·rocketmq
夕除15 小时前
redis--服务器优化
redis