redis简介
1.1 什么是 redis?
redis 是一个开源的、高性能的内存数据存储系统,可以作为数据库、缓存以及消息中间件使用。它支持丰富的数据结构,如字符串、哈希、列表、集合和有序集合等,因此被广泛用于需要高性能、高并发的应用场景。redis 采用单线程模型和基于内存的存储方式,保证了非常高的读写速度。
简而言之,redis 是一个键值数据库,它通过将数据存储在内存中,以实现快速的数据访问。redis 支持丰富的功能,如持久化(将数据保存在硬盘上以防丢失)、事务处理、发布/订阅模式、lua 脚本执行等。
redis 的最大特点是它的速度极快,而这一点正是它在 web 应用、高并发系统以及分布式环境中大受欢迎的原因之一。
1.2 redis 的应用场景
redis 因为其高速的读写能力和多种数据结构支持,应用场景非常广泛,常见的场景包括:
1.2.1 缓存系统
redis 最常见的应用场景就是作为缓存层。在应用中,我们常常会缓存一些查询频繁且不经常变动的数据。redis 作为缓存系统,可以极大提升数据读取速度,减轻数据库的负担。例如,网站首页的广告推荐信息、商品详情页的缓存等。
1.2.2 实时数据处理
redis 支持的数据结构非常适合实时数据处理,例如排行榜、计数器、实时消息推送等。通过 redis 的有序集合(zset),我们可以轻松实现排行榜等功能,并能实时更新排名数据。
1.2.3 消息队列
redis 提供的列表(list)和发布/订阅(pub/sub)功能,非常适合用作消息队列。在一个分布式系统中,生产者将消息推送到队列中,消费者异步处理这些消息。redis 提供的高效操作能确保消息队列处理的低延迟和高吞吐量。
1.2.4 会话管理
redis 的高效性和内存存储使它非常适合用作 会话管理(session store)。比如,在 web 应用中,用户登录时,常将用户会话信息存储在 redis 中,这样可以快速访问与更新会话信息。
1.2.5 分布式锁
redis 还可以用于实现分布式锁,通过它的 setnx 命令,可以实现保证分布式环境下任务的互斥访问。
1.3 redis 与传统数据库的区别
redis 作为 nosql (对非关系型数据库的统称)数据库,与传统的关系型数据库(如 mysql 和 sqlite3)相比,有许多显著的区别。我们从几个关键维度来对比 redis 和传统数据库:
1.3.1 数据存储方式
- redis:将数据存储在内存中,访问速度极快。redis 是一个内存数据库,支持内存持久化(rdb 快照和 aof 日志)以实现数据持久化。
- mysql/sqlite3:是磁盘数据库,所有数据都存储在磁盘中,虽然也有内存缓存机制,但相较于 redis,访问速度较慢。sqlite3 通常用于嵌入式应用和轻量级数据库,而 mysql 更适用于中大型应用和多用户环境。
1.3.2 数据结构支持
- redis:支持丰富的内存数据结构,除了基础的键值对,还支持哈希表、列表、集合、有序集合等数据结构,适合多种应用场景(如计数器、队列、集合去重等)。
- mysql/sqlite3:主要支持表格结构,通过表格的行和列来组织数据。它们基于关系模型进行数据存储,所有操作都是基于 sql 查询的。
1.3.3 性能与可扩展性
- redis:通过内存存储数据,redis 的读写速度非常快,适合高并发访问的场景。它采用单线程模型,避免了多线程的上下文切换和锁的竞争,能够在高并发场景中提供更高的性能。
- mysql/sqlite3:虽然 mysql 在大规模数据操作时表现出色,但在性能上受限于磁盘 i/o。sqlite3 通常用于单机环境,适合小型应用,不适合大规模的并发读写。
1.3.4 查询能力
- redis:没有复杂的查询语言,如 sql。所有操作都通过 redis 的命令完成,功能较为简单,适合简单的键值对存储以及对某些数据结构的操作。
- mysql/sqlite3:支持 sql 查询,能够进行复杂的多表联合查询、聚合、过滤等操作。适用于关系型数据,能够进行更复杂的数据分析和报表生成。
1.3.5 数据一致性
- redis:redis 对 acid 特性的支持是有限的。它支持命令级别的原子性,但不提供传统意义上的事务隔离与强一致性保障。在分布式环境中,redis 更加注重高性能和高可用性,允许在一定程度上牺牲数据一致性。
- mysql/sqlite3:mysql 提供了完善的事务支持,遵循 acid 原则,可以确保数据的一致性、可靠性和持久性,适合要求高一致性的场景。
补充:acid特性
| 缩写 | 全称 | 中文名 | 作用 |
|---|---|---|---|
| a | atomicity | 原子性 | 确保事务中的所有操作要么全部执行成功,要么全部不执行 |
| c | consistency | 一致性 | 确保事务前后,数据库始终处于一致的状态 |
| i | isolation | 隔离性 | 确保并发事务之间互不干扰 |
| d | durability | 持久性 | 一旦事务提交,其修改的数据应永久保存在数据库中 |
原子性(atomicity):
一个事务中的所有操作要么全部成功,要么全部失败,不可能只执行部分操作。
举例说明:
银行转账中,从账户 a 扣钱、给账户 b 加钱,这两个操作必须绑定执行。如果只扣了 a 的钱,但没给 b 加上,那事务就是不完整的 —— 原子性就被破坏了。
一致性(consistency):
事务执行前后,数据库必须处于一致的状态,即遵守所有的约束条件(如唯一性、外键、业务规则等)。
举例说明:
继续银行转账的例子,假设两个账户总额是 10000 元,无论转多少次,只要每次事务正确执行,总额都不能改变 —— 这就体现了一致性。
隔离性(isolation):
多个事务并发执行时,彼此之间互不干扰,各自的中间状态对其他事务不可见。
举例说明:
如果两个事务同时修改同一个库存,系统必须采取机制避免数据“被多次扣减”或出现“脏读”等问题。传统数据库通过锁机制或 mvcc(多版本并发控制)实现事务隔离,支持不同的隔离级别(如 read committed、repeatable read、serializable 等)。
持久性(durability):
一旦事务提交,其对数据库的更改必须永久保存,即使系统崩溃也不会丢失数据。
举例说明:
你转账后收到“转账成功”的提示,这笔交易即使服务器此时崩溃,系统重启后也必须能恢复该记录。数据库通常通过写 wal(write-ahead log)日志、刷盘等机制来保证持久性。
1.4 redis 的优势与局限性
1.4.1 优势
- 高性能:redis 的内存数据库模型使其读取和写入速度非常快,适用于高并发、高访问量的场景。
- 丰富的数据结构支持:除了基本的键值对,redis 还支持哈希、列表、集合、有序集合等多种数据结构,使得 redis 在解决特定问题时比传统数据库更具优势。
- 简单易用:redis 提供的命令简洁且直观,容易上手,适合快速开发。
- 高可用性与分布式支持:redis 提供了主从复制、redis sentinel 和 redis cluster 等功能,可以构建高可用、可扩展的分布式系统。
- 支持持久化:redis 可以将内存数据持久化到磁盘,通过 rdb 快照或 aof 日志来保证数据持久性,适应了大多数高性能应用场景。
1.4.2 局限性
- 内存限制:作为内存数据库,redis 的存储受限于服务器的内存大小。如果存储大量数据,可能会遇到内存不足的情况。
- 不支持复杂查询:redis 主要提供简单的 crud 操作,对于复杂的 sql 查询(如多表关联、聚合查询等)不支持,需要结合其他工具进行实现。
- 数据一致性问题:redis 牺牲了部分数据一致性和事务性,适用于非事务性和高可用性场景,但对于一些要求强一致性的应用,可能不合适。
redis 基础概念
2.1 redis 数据模型
2.1.1 键(key)
在 redis 中,每条数据都是一个键值对(key-value)。
key 就是数据的唯一标识符,相当于传统数据库中的主键(primary key)。它的特性如下:
- 类型:字符串(string),但内容可以是任何字节数组。
- 最大长度:512 mb。
- 通常作为查询的入口,不支持模糊检索(除了
keys命令)。 - 最佳实践:采用命名空间方式命名,如:
user:1001:name
2.1.2 值(value)
值的类型可以是多种结构,不仅仅是字符串。这是 redis 最有特色的设计。
2.1.3 数据类型
redis 支持五大基础数据结构(以及一些高级结构,如 hyperloglog、bitmap、geo):
| 类型 | 简介 | 使用场景 | 示例命令 |
|---|---|---|---|
| 字符串(string) | 最基本的数据类型,可存储文本或二进制 | 缓存、计数器、token 存储 | set key value, incr key |
| 哈希(hash) | 键值对集合,适用于对象表示 | 存储用户信息、配置项 | hset user:1001 name zhang |
| 列表(list) | 有序链表,支持左右两端插入弹出 | 消息队列、时间线 | lpush, rpop |
| 集合(set) | 无序不重复元素集合 | 标签、好友推荐、去重 | sadd, sinter |
| 有序集合(zset) | 每个元素有一个分数,用于排序 | 排行榜、带权集合 | zadd, zrange |
redis 的强大正是因为值(value)不仅是数据,也可以是结构化的数据集合。
2.2 redis 内存存储机制
redis 是内存数据库,数据读写全部在内存中完成,这也是其高性能的核心原因。
2.2.1 内存持久化(rdb 和 aof)
【rdb】(快照)
- 以时间间隔为单位,周期性生成数据快照并写入磁盘(
.rdb文件)。 - 优点:恢复速度快,占用空间小。
- 缺点:一旦崩溃,可能丢失上次快照之后的数据。
【aof】(append only file)
- 每次写操作都会以命令形式写入日志文件(
.aof)。 - 优点:数据几乎不丢失,恢复最完整。
- 缺点:文件大,恢复慢。
redis 支持 rdb 和 aof 混合持久化,也可以配置成只使用其中一种。
2.2.2 数据备份与恢复
- 备份方式:
- 复制 rdb 或 aof 文件。
- 使用
save或bgsave手动触发。
- 恢复方式:
- 直接将 rdb/aof 文件放置到数据目录,重启 redis。
[redis 内存数据]
|
|(定期快照)
v
rdb 文件 (或 aof 日志)
|
|
v
磁盘持久化文件2.3 redis 的高性能特点
redis 在高性能方面的设计非常值得称道,以下三点是核心:
2.3.1 单线程模型
redis 的命令处理采用单线程模型,不加锁、不阻塞,避免了线程切换开销。
- 所有命令串行执行,天然避免并发竞争问题。
- 主要瓶颈是 cpu,而不是锁。
客户端请求
|
v
+-----------+
| redis 主线程 |
+-----------+
|
v
处理命令(串行执行)2.3.2 异步 io(非阻塞网络模型)
- redis 使用 epoll(linux)/kqueue(bsd) 等多路复用技术进行 异步 io 处理。
- 虽然命令执行是单线程,但网络收发是非阻塞的,连接处理效率极高。
- 底层采用
ae_event_loop实现事件驱动模型。
+-------------+
| 客户端连接池 |
+-------------+
|
v
[ epoll 监听 socket 事件 ]
|
v
[ 有请求来了 ]
|
v
[ 主线程按序处理请求 ]
2.3.3 内存数据库
redis 完全基于内存工作,数据访问无需磁盘 i/o:
- 所有数据保存在内存中,读写速度为纳秒级(ns)。
- 后台定期将内存数据异步持久化到磁盘(非阻塞)。
这就是 redis 快如闪电的根本原因。
redis核心模型简化示意图
+----------------+ +----------------------+
| 客户端请求 | -----> | 网络层(非阻塞 io) |
+----------------+ +----------------------+
|
v
+------------------+
| 单线程命令处理 | <- redis 主线程(串行)
+------------------+
|
+----------------+----------------+
| |
+----------------+ +--------------------+
| 内存数据结构区 | | 持久化(rdb / aof) |
+----------------+ +--------------------+
redis安装与配置
3.1 linux安装 redis
3.1.1 使用包管理器安装
sudo apt update sudo apt install redis-server
安装完成后,可以使用以下命令启动和查看 redis 服务:
sudo systemctl start redis sudo systemctl enable redis # 开机启动 sudo systemctl status redis
默认配置文件路径为 /etc/redis/redis.conf,默认端口是 6379。
3.1.2 源码编译安装
# 1. 下载源码 wget https://download.redis.io/releases/redis-7.2.4.tar.gz tar -xzf redis-7.2.4.tar.gz cd redis-7.2.4 # 2. 编译 make -j4 # 3. 可选:执行测试(需要 tcl) make test # 4. 安装 sudo make install
安装后,会将 redis-server、redis-cli 等可执行文件放到 /usr/local/bin/。
你可以直接运行:
redis-server # 启动服务 redis-cli # 客户端命令行
配置文件可在源码目录中找到:redis.conf,你可以复制到合适位置如 /etc/redis/。
3.2 配置 redis
redis 的核心配置文件是:redis.conf,默认位置因安装方式而异。
你可以使用:
redis-server /redis.conf的path
来自定义启动 redis。
3.2.1 常见配置项(redis.conf)
| 配置项 | 作用 | 示例 |
|---|---|---|
port | 监听端口 | port 6379 |
bind | 限制绑定的 ip | bind 127.0.0.1 |
requirepass | 设置访问密码 | requirepass yourpassword |
dir | 持久化文件保存目录 | dir /var/lib/redis/ |
dbfilename | rdb 文件名 | dbfilename dump.rdb |
appendonly | 是否开启 aof | appendonly yes |
appendfilename | aof 文件名 | appendfilename "appendonly.aof" |
maxmemory | 限制最大内存 | maxmemory 256mb |
maxmemory-policy | 内存淘汰策略 | allkeys-lru、volatile-ttl |
3.2.2 安全配置(密码、ip 限制)
绑定指定 ip:
bind 127.0.0.1 # 限制本地访问
设置访问密码:要求所有客户端在执行任何命令前必须提供这个密码
requirepass mystrongpassword 客户端连接时输入上述步骤设置的密码: redis-cli -a mystrongpassword
关闭危险命令(如 flushall):
rename-command flushall "" rename-command flushdb ""
3.2.3 性能优化配置(内存、持久化等)
内存限制(适用于缓存场景):
# 设置 redis 使用的最大内存为 512mb,超出后会触发内存淘汰策略 maxmemory 512mb # 设置内存淘汰策略为 allkeys-lru(从所有键中挑选最近最少使用的键淘汰) maxmemory-policy allkeys-lru
持久化控制(视业务需求选择):
关闭持久化(仅做缓存):
save "" appendonly no
开启 aof 且设置为每秒同步:
appendonly yes appendfsync everysec
redis 常用命令
4.1 字符串(string)
redis 中最基本的数据类型,类似于传统数据库中的单个字段值。
redis 的字符串结构是:
key -> "value"
常用命令:
set key value:设置键值。get key:获取键值。del key:删除键。incr key/decr key:对数值型字符串进行自增/自减操作。incrby key amount/decrby key amount:按指定步长增减。append key value:向原字符串追加内容。strlen key:返回字符串长度。mset key1 val1 key2 val2 ...:批量设置多个键值对。mget key1 key2 ...:批量获取多个键值。setnx key value:仅当 key 不存在时设置。setex key seconds value:设置值并指定过期时间。
示例:
## 设置与获取 set name "alice" # 返回: ok # 当前键值对为: # name -> "alice" get name # 返回: "alice" # 说明: "name" 键的值为 "alice" ## 批量操作 mset age 25 city "shanghai" # 返回: ok # 当前键值对为: # name -> "alice" # age -> "25" # city -> "shanghai" mget name age city # 返回: # 1) "alice" # 2) "25" # 3) "shanghai" # 说明: 返回 "name", "age", "city" 键的值 ## 自增自减 set counter 10 # 返回: ok # 当前键值对为: # name -> "alice" # age -> "25" # city -> "shanghai" # counter -> "10" incr counter # 返回: 11 # 当前键值对为: # name -> "alice" # age -> "25" # city -> "shanghai" # counter -> "11" incrby counter 5 # 返回: 16 # 当前键值对为: # name -> "alice" # age -> "25" # city -> "shanghai" # counter -> "16" decr counter # 返回: 15 # 当前键值对为: # name -> "alice" # age -> "25" # city -> "shanghai" # counter -> "15" decrby counter 2 # 返回: 13 # 当前键值对为: # name -> "alice" # age -> "25" # city -> "shanghai" # counter -> "13" ## 字符串追加与长度 append name " smith" # 返回: 11 # 新字符串长度 # 当前键值对为: # name -> "alice smith" # age -> "25" # city -> "shanghai" # counter -> "13" get name # 返回: "alice smith" # 说明: "name" 键的值已更新为 "alice smith" strlen name # 返回: 11 # 说明: "name" 字段的长度为 11("alice smith") ## 设置过期键值对 setex token 60 "abc123" # 返回: ok # 当前键值对为: # name -> "alice smith" # age -> "25" # city -> "shanghai" # counter -> "13" # token -> "abc123"(将在60秒后自动过期) get token # 返回: "abc123" # 说明: "token" 键的值为 "abc123",且将在60秒后过期 ## 条件设置(键不存在时才设置) setnx city "beijing" # 返回: 0 # 因为 city 已存在,未设置成功 # 当前键值对为: # name -> "alice smith" # age -> "25" # city -> "shanghai" # counter -> "13" # token -> "abc123"(将在60秒后过期) get city # 返回: "shanghai" # 说明: "city" 键的值仍为 "shanghai" ## 最终键值状态(执行完上述命令后) name -> "alice smith" age -> "25" city -> "shanghai" counter -> "13" token -> "abc123" (将于60秒后自动失效)
4.2 哈希(hash)
哈希表用于存储对象,常用于表示用户信息、商品信息等。
redis 的哈希结构是:
key -> {
field1: value1,
field2: value2,
...
}常用命令:
hset key field value:设置字段。hget key field:获取字段值。hgetall key:获取所有字段和值。hmset key field1 val1 field2 val2 ...:一次设置多个字段(redis 4.0 后不推荐)。hmget key field1 field2 ...:一次获取多个字段。hdel key field [field ...]:删除字段。hexists key field:判断字段是否存在。hlen key:返回字段数量。hincrby key field increment:字段数值加减。hstrlen key field:字段值长度。
示例:
## 设置与获取
hset user:1001 name "bob" age "30" gender "male"
# 返回: 3 # 表示新增了3个字段
# 当前哈希表 user:1001 内容为:
# user:1001 -> {
# "name": "bob",
# "age": "30",
# "gender": "male"
# }
hget user:1001 name
# 返回: "bob"
# 说明: "name" 字段值为 "bob"
## 批量设置与获取
hmset user:1001 email "bob@example.com" phone "123456789"
# 返回: ok
# 当前哈希表 user:1001 内容为:
# user:1001 -> {
# "name": "bob",
# "age": "30",
# "gender": "male",
# "email": "bob@example.com",
# "phone": "123456789"
# }
hmget user:1001 name email phone
# 返回:
# 1) "bob"
# 2) "bob@example.com"
# 3) "123456789"
# 说明: 分别返回 "name", "email", "phone" 字段的值
## 删除字段与判断字段是否存在
hdel user:1001 gender
# 返回: 1 # 成功删除1个字段
# 当前哈希表 user:1001 内容为:
# user:1001 -> {
# "name": "bob",
# "age": "30",
# "email": "bob@example.com",
# "phone": "123456789"
# }
hexists user:1001 age
# 返回: 1 # 字段 age 存在
# 说明: "age" 字段仍然存在
## 哈希结构长度信息
hlen user:1001
# 返回: 4 # 当前哈希表中还有 4 个字段
# 当前哈希表 user:1001 内容为:
# user:1001 -> {
# "name": "bob",
# "age": "30",
# "email": "bob@example.com",
# "phone": "123456789"
# }
hstrlen user:1001 name
# 返回: 3 # name 字段的字符串长度为 3("bob")
# 说明: "name" 字段的值长度为 3
## 自增字段(用于数值字段)
hincrby user:1001 age 2
# 返回: 32 # 将 age 从 30 增加到 32
# 当前哈希表 user:1001 内容为:
# user:1001 -> {
# "name": "bob",
# "age": "32",
# "email": "bob@example.com",
# "phone": "123456789"
# }
## 获取全部字段和值
hgetall user:1001
# 返回:
# 1) "name"
# 2) "bob"
# 3) "age"
# 4) "32"
# 5) "email"
# 6) "bob@example.com"
# 7) "phone"
# 8) "123456789"
# 说明: 返回所有字段和值
## 最终哈希表 user:1001 的结构如下:
{
"name": "bob",
"age": "32",
"email": "bob@example.com",
"phone": "123456789"
}4.3 列表(list)
列表是一种双向链表结构,支持从两端插入和弹出元素。
redis 的列表结构是:
key -> [ value1, value2, value3, ... ]
常用命令:
lpush key value [value ...]/rpush key value [value ...]:从左/右插入。lpop key/rpop key:从左/右弹出。lrange key start stop:获取指定范围元素。llen key:获取列表长度。lrem key count value:移除指定值。lset key index value:设置指定索引的值。lindex key index:获取指定索引值。ltrim key start stop:保留指定区间,删除其余元素。blpop key [key ...] timeout/brpop key [key ...] timeout:阻塞式弹出。
示例:
# 插入元素 lpush tasks "task3" "task2" rpush tasks "task4" head <--> "task2" <--> "task3" <--> "task4" <--> tail # 查询 llen tasks #llen tasks 返回列表的长度,结果是 3。 lrange tasks 0 -1 #lrange tasks 0 -1 返回从索引 0 到 -1(即整个列表),结果是 ["task2", "task3", "task4"]。 lindex tasks 1 lindex tasks 1 返回索引 1 的元素,结果是 "task3"。 # 设置与移除 lset tasks 1 "task2_updated" head <--> "task2" <--> "task2_updated" <--> "task4" <--> tail lrem tasks 0 "task3" head <--> "task2" <--> "task2_updated" <--> "task4" <--> tail # 截取与弹出 ltrim tasks 0 1 head <--> "task2" <--> "task2_updated" <--> tail lpop tasks head <--> "task2_updated" <--> tail rpop tasks head <--> tail
4.4 集合(set)
集合用于存储不重复元素,支持集合运算。
redis 的集合结构是:
key -> {value1,value2,value3,...}常用命令:
sadd key member [member ...]:添加元素。smembers key:获取所有元素。srem key member [member ...]:删除元素。sismember key member:判断是否存在。scard key:集合元素数量。srandmember key [count]:随机返回一个或多个元素。spop key [count]:随机弹出元素。sunion key1 key2 ...:求并集。sinter key1 key2 ...:求交集。sdiff key1 key2 ...:求差集。
示例:
## 添加元素
sadd tags "redis" "database" "nosql"
# 返回: 3 # 成功添加了3个新元素
# 当前集合内容(顺序可能不同):
# tags -> {
# "redis",
# "database",
# "nosql"
# }
## 再次添加重复元素
sadd tags "redis" "backend"
# 返回: 1 # 只有 "backend" 是新元素
# 当前集合内容(顺序可能不同):
# tags -> {
# "redis",
# "database",
# "nosql",
# "backend"
# }
## 查看所有成员
smembers tags
# 返回:
# 1) "redis"
# 2) "nosql"
# 3) "database"
# 4) "backend"
# (集合是无序的,顺序可能不同)
## 判断元素是否存在
sismember tags "redis"
# 返回: 1 # 表示存在
sismember tags "mysql"
# 返回: 0 # 表示不存在
## 移除元素
srem tags "nosql"
# 返回: 1 # 成功移除1个元素
# 当前集合内容(顺序可能不同):
# tags -> {
# "redis",
# "database",
# "backend"
# }
## 集合大小
scard tags
# 返回: 3
## 随机弹出一个元素
spop tags
# 返回: (例如)"backend" # 每次随机,结果可能不同
# 当前集合内容(顺序可能不同):
# tags -> {
# "redis",
# "database"
# }
## 最终集合内容
smembers tags
# 返回:
# 1) "redis"
# 2) "database"
# (假设弹出了 "backend")4.5 有序集合(sorted set)
每个元素有一个 score,成员按照 score 自动从小到大排序。与集合(set)相比,多了一个“分数”维度,且结果是有序的。
redis 的有序集合结构是:
key -> {
member1: score1,
member2: score2,
...
}常用命令:
zadd key score member [score member ...]:添加元素。zrange key start stop [withscores]/zrevrange:按分数排序查询。zrem key member [member ...]:删除成员。zscore key member:获取某成员的分数。zrank key member/zrevrank:获取排名。zincrby key increment member:对成员分数自增。zcount key min max:统计分数在范围内的元素数量。zrangebyscore key min max:按分数范围查询。
示例:
# redis 有序集合操作演示(scoreboard)
# 1. 添加与查询
zadd scoreboard 100 "alice" 150 "bob" 130 "carol"
# 返回结果:
# (integer) 3 # 成功添加 3 个成员
# redis 中有序集合 scoreboard 内容为(按 score 排序):
# scoreboard -> {
# "alice": 100,
# "carol": 130,
# "bob": 150
# }
zrange scoreboard 0 -1 withscores
# 返回结果(按 score 从小到大):
# 1) "alice"
# 2) "100"
# 3) "carol"
# 4) "130"
# 5) "bob"
# 6) "150"
zrevrange scoreboard 0 1
# 返回结果(按 score 从大到小,前两个成员):
# 1) "bob"
# 2) "carol"
# 2. 分数与排名查询
zscore scoreboard alice
# 返回结果:
# "100"
# 说明: "alice" 的分数为 100
zrank scoreboard carol
# 返回结果:
# 1
# 说明: "carol" 排名第 2(从 0 开始)
zrevrank scoreboard bob
# 返回结果:
# 0
# 说明: "bob" 在倒序中排名第 1(最高分)
# 3. 分数修改与范围查询
zincrby scoreboard 10 alice
# 返回结果:
# "110"
# 说明: "alice" 的分数已增加 10,变为 110
zrange scoreboard 0 -1 withscores
# 返回结果(按 score 从小到大):
# 1) "alice"
# 2) "110"
# 3) "carol"
# 4) "130"
# 5) "bob"
# 6) "150"
zcount scoreboard 120 160
# 返回结果:
# (integer) 2
# 说明: "scoreboard" 中分数在 120 到 160 之间的成员有 2 个("carol" 和 "bob")
zrangebyscore scoreboard 120 200
# 返回结果:
# 1) "carol"
# 2) "bob"4.6 键操作(key operations)
用于管理所有键的通用命令。
常用命令:
expire key seconds:设置键过期时间。ttl key:查看剩余时间。persist key:取消过期时间。del key [key ...]:删除键。rename key newkey:重命名键。type key:查看键类型。keys pattern:通配符查询。exists key:判断键是否存在。move key db:将指定的键迁移到指定的数据库double object key:查看给定键的内部信息(包括内存占用等)
# 1. expire key seconds:设置键的过期时间 set mykey "hello" # 返回: ok expire mykey 60 # 返回: 1 # 设置成功,键 mykey 将在60秒后过期 ttl mykey # 返回: 60 # 返回剩余过期时间为 60 秒 # 2. ttl key:查看剩余时间 ttl mykey # 返回: 60 # mykey 剩余过期时间 60 秒 # 3. persist key:取消过期时间 persist mykey # 返回: 1 # 表示取消了过期时间 ttl mykey # 返回: -1 # 不再有过期时间 # 4. del key [key ...]:删除一个或多个键 del mykey # 返回: 1 # 删除成功 ttl mykey # 返回: (error) no such key # 键已删除,不存在 # 5. rename key newkey:重命名键 set mykey "hello" # 返回: ok rename mykey newkey # 返回: ok # 重命名成功 get newkey # 返回: "hello" # 可以通过新键名获取相同的值 # 6. type key:查看键的类型 set mykey "hello" # 返回: ok type mykey # 返回: string # 表示 mykey 的类型是 string # 7. keys pattern:通配符查询 set user1 "alice" set user2 "bob" set admin "charlie" # 返回: ok keys user* # 返回: # 1) "user1" # 2) "user2" # 8. exists key:判断键是否存在 exists mykey # 返回: 0 # 表示 mykey 不存在 exists newkey # 返回: 1 # 表示 newkey 存在 # 9. move key db:将键迁移到指定的数据库 set mykey "hello" # 返回: ok move mykey 1 # 返回: 1 # 表示成功将 mykey 从当前数据库迁移到数据库 1 select 1 # 返回: ok get mykey # 返回: "hello" # 在数据库 1 中可以找到 `mykey` # 10. object key:查看给定键的内部信息 set mykey "hello" # 返回: ok object encoding mykey # 返回: "raw" # 返回键的编码方式 object idletime mykey # 返回: 0 # 返回 mykey 的空闲时间(单位:秒)
4.7 redis常用数据库管理命令
select index
切换 redis 数据库。redis 默认有 16 个数据库,使用索引来切换。
select 2 # 返回: ok # 选择数据库 2
flushdb
删除当前数据库中的所有键。清空当前数据库,但不会影响其他数据库中的数据。
pflushdb
异步删除当前数据库中的所有键,redis 5.0+ 引入,减少阻塞。
flushall
删除所有数据库中的所有键。执行后会清空 redis 实例中的所有数据。此命令操作不可逆。
pflushall
异步删除所有数据库中的所有键,类似于 flushall,但执行时不会阻塞其他客户端请求。
dbsize
返回当前数据库中键的数量。用于获取当前数据库中的键数量。
info [section]
获取 redis 实例的各种信息,可以指定某一部分的详细信息(如服务器、内存、客户端、持久化等)。
info # 返回: # # server # redis_version:6.2.1 # # clients # connected_clients:10 # # memory # used_memory:100000
latency latest
查看 redis 实例的最新延迟信息。
client list
返回当前所有客户端的连接信息。包括客户端的 id、连接地址、空闲时间等。
client list # 返回: # 1) id=1 addr=127.0.0.1:6379 fd=8 name= age=10 idle=5 flags=n db=0 # 2) id=2 addr=127.0.0.1:6380 fd=9 name= age=20 idle=15 flags=n db=1
client kill id
关闭指定的客户端连接。
config get parameter
获取 redis 配置参数的当前值。
config get maxmemory # 返回: # 1) "maxmemory" # 2) "0"
config set parameter value
设置 redis 配置参数的值。注意某些配置只能在启动时设置。
config set maxmemory 1024mb # 返回: ok # 将最大内存限制设置为 1024mb
config rewrite
重写 redis 配置文件,将当前的配置更新到 redis 配置文件中。适用于持久化配置更改。
config rewrite # 返回: ok # 配置文件重写成功
shutdown
关闭 redis 实例。
lastsave
返回 redis 上次成功保存数据的时间戳
redis 持久化与备份
redis 提供了两种主要的持久化机制:rdb(快照)持久化和aof(追加文件)持久化。此外,redis 还支持 混合持久化,即同时启用 rdb 和 aof 持久化。
6.1 rdb 快照持久化
rdb 是 redis 的一种持久化方式,能够在指定时间间隔内创建 redis 数据库的快照,保存在磁盘上。rdb 文件存储了 redis 数据的完整快照,能够在 redis 重启时用于数据恢复。
6.1.1 配置与触发机制
redis 的 rdb 快照持久化通过配置文件中的 save 指令来控制触发条件。每当 redis 发生某些变化时,它会在满足特定条件后自动保存数据。
save 配置参数示例:
save 900 1 # 在 900 秒(15分钟)内,如果有至少 1 个键被修改,则触发 rdb 快照保存 save 300 10 # 在 300 秒(5分钟)内,如果至少有 10 个键被修改,则触发 rdb 快照保存 save 60 10000 # 在 60 秒内,如果至少有 10000 个键被修改,则触发 rdb 快照保存
配置说明:
- 每条
save规则由两个数字组成,<seconds>和<changes>,即在<seconds>秒内,如果发生了至少<changes>次写操作,redis 会触发 rdb 快照持久化。 - 例如,
save 900 1表示在 900 秒(15分钟)内,如果有至少 1 个键被修改,则触发快照保存。
手动触发 rdb 快照:
你也可以手动触发 rdb 快照:
bgsave
该命令会在后台创建 rdb 快照,并保存到磁盘中,允许 redis 继续响应客户端请求。
rdb 快照触发的场景:
- 定期自动保存:按照
save配置的规则触发。 - 手动保存:通过
bgsave或save命令手动触发。 - 主从同步:当 redis 实例作为主服务器时,rdb 快照也会用于从服务器同步数据。
6.1.2 恢复与恢复时的注意事项
rdb 恢复过程非常简单,redis 启动时会自动加载最新的 rdb 快照文件。恢复时需要注意以下几点:
- 文件路径:rdb 快照文件默认保存为
dump.rdb,位于 redis 配置文件中指定的dir目录。 - 启动时恢复:当 redis 启动时,它会自动检查当前目录中的
dump.rdb文件并加载它。如果找到了该文件,redis 会根据快照中的数据来恢复数据库。 - 数据丢失问题:rdb 快照的恢复时间受保存的时间间隔影响。如果发生 redis 重启,且上次保存快照时已有变化,恢复时会丢失在快照保存和重启期间的数据。
6.2 aof 日志持久化
aof(append only file)是 redis 的另一种持久化方式,它通过记录每个写命令到一个日志文件中,以此来实现持久化。
6.2.1 配置与触发机制
redis 默认情况下不启用 aof 持久化,若启用 aof,redis 会将所有写命令追加到 aof 文件中。你可以在 redis 配置文件中通过设置 appendonly 参数来启用 aof。
aof 配置参数示例:
appendonly yes # 启用 aof 持久化 appendfsync everysec # 每秒同步一次 aof 文件
aof 文件的三种同步策略:
- always:每次执行写命令后立即同步 aof 文件(会导致性能下降)。
- everysec:每秒同步一次 aof 文件(这是默认设置,性能与安全平衡)。
- no:不自动同步(只有通过后台线程在某个间隔内同步)。
6.2.2 数据恢复
aof 恢复过程如下:
- redis 启动时会读取 aof 文件,并按照文件中记录的写操作依次执行,从而恢复数据。
- aof 文件通常比 rdb 文件要大,因为它记录了每个写操作,因此恢复过程可能会比 rdb 恢复慢。
aof 恢复时的注意事项:
- aof 文件的位置与 rdb 文件类似,默认保存在 redis 的工作目录中,文件名为
appendonly.aof。 - 如果 aof 文件损坏,redis 会尝试修复(启用
aof-load-truncated配置项)。 - 为了避免 aof 文件膨胀过大,redis 提供了
aof 重写(aof rewrite)功能,它会根据当前数据库状态生成一个新的 aof 文件,去除重复操作。
6.3 混合持久化(rdb + aof)
redis 5.0 引入了混合持久化模式,它结合了 rdb 和 aof 的优势,既能提供数据的持久化,又能保证数据恢复时的快速性和完整性。
混合持久化工作原理
混合持久化在 rdb 快照的基础上,将 aof 文件和 rdb 文件结合。在持久化时,redis 会将数据库的快照存储在 rdb 文件中,同时将增量的写操作记录到 aof 文件中。这样做的好处是:
- rdb 提供了快速的恢复:通过 rdb 快照快速恢复数据。
- aof 提供了精确的恢复:aof 文件记录所有的写操作,可以精确地恢复数据。
混合持久化启用方式:
在配置文件中,可以通过设置 appendonly 和 appendfsync 参数来启用混合持久化。redis 默认启用了这种模式:
appendonly yes # 启用 aof 持久化 appendfsync everysec # 每秒同步一次 aof 文件
混合持久化的优势
- 恢复速度快:使用 rdb 文件作为基础快照,并通过 aof 增量更新数据,可以提高数据恢复速度。
- 数据完整性:aof 文件记录了所有写命令,确保数据不会丢失。
- 高效的空间使用:混合持久化减少了 aof 文件的写入操作,减少了 aof 文件的大小。
混合持久化恢复
结合了两者的优势,在每次生成 rdb 快照时,它不仅会写入 rdb 文件,还会嵌入到 aof 文件的开头,作为基准状态。随后,aof 文件会继续追加快照生成之后的写操作增量,以保证最新的数据不会丢失。再生成下一次快照时会将aof文件覆盖,并将新生成的快照继续嵌入到aof文件的开头,循环往复。这样,恢复数据时,redis 先加载嵌入的快照快速恢复大部分数据,再顺序执行快照之后的增量操作,就能完整恢复到最新状态,这样就同时兼顾恢复速度和数据安全性。
redis 高级特性
7.1 发布/订阅(pub/sub)
redis 提供了内建的发布/订阅消息系统(pub/sub),允许消息从发送者(发布者)广播给一个或多个接收者(订阅者),而无需两者之间直接通信。
它是一种典型的消息广播模型,适用于聊天室、实时通知、系统广播等场景。
核心命令:
| 命令 | 说明 |
|---|---|
subscribe channel [channel ...] | 订阅一个或多个频道 |
publish channel message | 向指定频道发布消息 |
unsubscribe [channel ...] | 取消订阅 |
psubscribe pattern [pattern ...] | 使用通配符订阅多个频道 |
punsubscribe [pattern ...] | 取消模式订阅 |
示例:
开启终端 a(作为订阅者):
subscribe news
此时 redis 会将该客户端状态切换为“订阅模式”,并阻塞式监听名为 news 的频道。redis 会自动推送任何发布到该频道的消息到这个客户端。
注意:客户端一旦 subscribe 或 psubscribe,进入阻塞状态,就不能再发送普通命令(比如 set、get),只能接收消息,直到:unsubscribe或连接断开。
开启终端 b(作为发布者):
publish news "redis 7.0 released!"
注意: redis 不会缓存消息,消息是实时广播的,若发布时没有任何订阅者,消息直接被丢弃,消息传递是同步推送到所有订阅者,大量订阅者可能拖慢发布速度。
终端 a 将自动接收到消息:
1) "message" 表示这条数据是一个消息类型的推送。 2) "news" 这是消息来自的频道名称。 3) "redis 7.0 released!" 这是发布者发送的具体消息内容。
redis 协议会把推送过来的内容打包成一个“数组”,所以你看到的是 1)、2)、3),这其实就是 redis 的 resp 协议(redis serialization protocol)中的数组结构形式。
+-------------------+ +-------------------+
| publisher | | subscriber a |
|-------------------| |-------------------|
| publish news "..."|-----> | subscribe news |
+-------------------+ +-------------------+
+-------------------+
| subscriber b |
|-------------------|
| subscribe news |
+-------------------+
-- 任何 publish news 的消息将同时被 a 和 b 接收到 --7.2 事务(multi / exec / watch)
redis 支持简单形式的事务机制,允许将多个命令打包为一个事务块执行,从而实现操作的原子性。
redis 事务主要由以下指令组成:
multi:标记事务开始。exec:执行所有事务命令。discard:放弃事务。watch:对一个或多个键设置监视,当其中任意键在事务执行前被修改,事务将被中断。
redis 事务的执行流程
watch(可选)对关键 key 进行乐观锁监控。multi开启事务队列。- 后续所有命令将被入队缓存。
exec触发事务,redis 依次执行队列中的命令。- 如果期间被监视的 key 被外部修改,事务失败,
exec返回 null。
示例操作 1:普通事务
multi set user:score 100 incrby user:score 50 get user:score exec
返回:
1) ok 2) (integer) 150 3) "150"
示例操作 2:使用 watch 实现乐观锁(模拟余额扣款)
客户端 a:
set balance 100
watch balance get balance # → "100" multi decrby balance 20 exec
如果此时客户端 b 修改了 balance:
客户端 b:
set balance 50
那么客户端 a 的 exec 将返回 nil,表示事务失败:
(nil)
这是因为监视的键 balance 在事务期间被其他客户端改动,redis 终止执行,以保证数据一致性。
注意:
| 特性 | 说明 |
|---|---|
| ✅ 原子性 | exec 中的命令会顺序执行,不会被打断 |
| ❌ 回滚能力 | redis 事务不支持回滚(没有 rollback) |
| ❌ 隔离级别 | 没有真正的隔离性,其他客户端仍可读写 |
| ✅ 并发控制 | 可使用 watch 模拟乐观锁 |
7.3 脚本与 lua 支持
为什么需要脚本支持?
redis 本身只支持原子执行的命令,但不支持复杂逻辑控制(如条件、循环)。lua 脚本支持带来了以下优势:
- 原子性执行:脚本中的所有命令作为一个整体执行,中间不会被其他命令打断。
- 减少网络开销:多个操作通过一次请求提交,节省 rt。
- 支持复杂逻辑:如 if/else、for 循环、函数等。
基本命令
eval script numkeys key [key ...] arg [arg ...]
- script:lua 脚本代码字符串
- numkeys:表示接下来有几个 key 参数
- key1...keyn:传递给脚本的 redis 键名
- arg1...argn:额外的参数,不作为键
示例 :只有在键不存在时才设置
eval "if redis.call('exists', keys[1]) == 0 then
redis.call('set', keys[1], argv[1])
return 'ok'
else
return 'exists'
end" 1 mykey myvalue解释:
keys[1]为"mykey",argv[1]为"myvalue"- 如果
mykey不存在,则设置为"myvalue"并返回"ok" - 否则返回
"exists"
示例 :批量删除匹配前缀的 key
eval "local keys = redis.call('keys', argv[1])
for i,k in ipairs(keys) do
redis.call('del', k)
end
return #keys" 0 "temp:*"说明:
- 删除所有匹配
"temp:*"的 key #keys返回删除的数量
lua脚本常用命令:
| 函数 | 参数 | 说明 | 使用示例 |
|---|---|---|---|
redis.call(command, ...) | command: redis 命令字符串;...args: 参数列表 | 用于执行 redis 命令,并返回结果 | lua<br>local result = redis.call('get', keys[1])<br>return result |
redis.pcall(command, ...) | command: redis 命令字符串;...args: 参数列表 | 类似 redis.call,但在错误时返回 nil | lua<br>local result = redis.pcall('get', keys[1])<br>if result then<br>return result<br>else<br>return "key not found"<br>end |
redis.sha1hex(string) | string: 需要计算 sha1 校验和的字符串 | 计算字符串的 sha1 校验和 | lua<br>local sha1 = redis.sha1hex('this is a test string')<br>return sha1 |
redis.evalsha(sha1, numkeys, ...) | sha1: 脚本的 sha1 校验和;numkeys: 键的数量;...: 键和参数列表 | 执行已加载的 lua 脚本(使用 sha1) | lua<br>local sha1 = redis.sha1hex('return redis.call("get", keys[1])')<br>redis.evalsha(sha1, 1, 'my_key') |
redis.log(level, message) | level: 日志级别("debug", "verbose", "notice", "warning" 等);message: 日志内容 | 记录日志消息 | lua<br>redis.log("notice", "this is a log message") |
redis.sleep(seconds) | seconds: 暂停的秒数(可以是小数) | 暂停脚本执行指定的时间 | lua<br>redis.sleep(2)<br>return "finished sleeping" |
redis.setex(key, seconds, value) | key: 键名;seconds: 过期时间(秒);value: 设置的值 | 设置一个带有过期时间的键 | lua<br>redis.setex("my_key", 300, "value")<br>return "key set with expiration" |
redis.bitcount(key) | key: 键名 | 计算指定键的位计数 | lua<br>redis.bitcount("my_bit_key") |
redis.getrange(key, start, end) | key: 键名;start: 起始位置;end: 结束位置 | 获取指定键的子字符串 | lua<br>redis.getrange("my_key", 0, 5) |
7.4 redis 集群与分片
为了突破单机 redis 在容量与并发性能上的限制,redis 官方提供了原生的 redis cluster 机制,它通过数据的分片(sharding)与节点间的协作,实现了高可用、可扩展的分布式部署方式。
7.4.1 分片(sharding)与哈希槽机制
分片(sharding) 是将数据水平切分为多个部分,每个部分存储在不同 redis 节点上,从而分担负载、提升容量。redis cluster 采用的是哈希槽分片策略。
注意:redis cluster 使用多个主节点(master nodes)来实现分片,每个主节点负责一部分哈希槽(0 ~ 16383 之间的一段)。
哈希槽(hash slot) 是 redis cluster 的核心分片单位。redis 将整个 key 空间划分为 16384 个槽(编号 0 到 16383),每个键通过哈希函数映射到某个槽位,再由特定节点负责这个槽。
哈希槽计算方法:
hash_slot = crc16(key) mod 16384
也就是说,redis 使用 crc16 哈希算法对 key 计算哈希值,并将其对 16384 取模,结果即为槽编号。
示例:计算某个键属于哪个哈希槽
> cluster keyslot mykey (integer) 10285
7.4.2 redis cluster 架构与节点角色
节点类型
redis cluster 中的节点分为两类:
- 主节点(master):负责持有实际的数据和哈希槽。
- 从节点(slave):用于主节点的备份,当主节点故障时进行故障转移。
高可用机制
当某个主节点宕机且多数节点(>半数)发现异常后,会自动触发 故障转移(failover),其从节点会被提升为主节点。
假设有 6 个主节点(m1 ~ m6),如果 m1 宕机,要至少有一半以上的其他主节点(也就是至少 ceil(6/2) = 4 个)同时认为 m1 不可达(pfail 或 fail),才会触发 failover 流程。这些判断是在 redis cluster 的 gossip 协议中完成的,主节点之间会周期性地互相 ping/ack 探测是否“可达”。
典型拓扑结构
- 至少 3 个主节点 + 3 个从节点
- 每个主节点管理若干个哈希槽
- 从节点分别跟随某个主节点做备份
node a (master) --> node a1 (slave) node b (master) --> node b1 (slave) node c (master) --> node c1 (slave)
7.4.3 redis 集群搭建流程(以 6 个节点为例)
step 1:准备配置文件
创建 6 个 redis 实例配置文件,以下为示例配置重点:
redis.conf port 7000 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes
其他端口(7001 - 7005)依次修改端口号。
step 2:启动所有 redis 实例
redis-server ./7000/redis.conf redis-server ./7001/redis.conf ... redis-server ./7005/redis.conf
step 3:创建集群
使用 redis-cli 的 --cluster create 命令:
redis-cli --cluster create \ 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \ 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \ --cluster-replicas 1 --cluster-replicas 1,意味着:每个主节点都配一个从节点 16384 个哈希槽均匀分配给3个主节点 这些节点的主从角色是 redis-cli 在执行时自动决定的,而不是你手动指定谁是主谁是从。 如果你想手动决定哪些是主哪些是从,**就不能使用这个命令,而是得用更底层的命令逐个 cluster meet、cluster addslots 等来构建集群。
此命令会自动分配槽位,并为每个主节点分配一个从节点。
step 4:验证集群状态
redis-cli -c -p 7000 cluster info redis-cli -c -p 7000 cluster nodes
7.4.4 集群管理相关命令
| 命令 | 说明 |
|---|---|
cluster info | 查看当前节点的集群信息 |
cluster nodes | 查看整个集群的所有节点 |
cluster slots | 查看槽分布情况 |
cluster keyslot <key> | 查询 key 属于哪个哈希槽 |
cluster forget <node-id> | 移除某个节点(强制) |
cluster meet <ip> <port> | 添加新节点到集群 |
cluster replicate <node-id> | 将当前节点设置为某主节点的从节点 |
cluster reset | 重置节点并清除其集群配置 |
示例:
查看集群信息
127.0.0.1:7000> cluster info
输出:
cluster_state:ok 表示集群正常。
cluster_slots_assigned:16384 表示总共分配了 16384 个槽位。
cluster_size:3 表示集群有 3 个主节点。
....
查看集群节点信息
127.0.0.1:7000> cluster nodes
07c37dfeb2352e56c38e8c801c0bb4a6b5fd64a6 127.0.0.1:7000 master - 0 1624567890 1 connected 0-5460
3c3f3f5c6b44c1a1beec77c57d56b983f56cb0a1 127.0.0.1:7003 slave 07c37dfeb2352e56c38e8c801c0bb4a6b5fd64a6 0 1624567891 2 connected
......
列出了集群中的所有节点。
每行展示一个节点的状态,包括节点 id、ip 地址、角色(master/slave)以及其负责的槽位范围。
查看哈希槽分配情况
127.0.0.1:7000> cluster slots
1) 1) (integer) 0
2) (integer) 5460
3) 1) "127.0.0.1"
2) (integer) 7000
3) "my-cluster-1"
2) 1) (integer) 5461
2) (integer) 10922
3) 1) "127.0.0.1"
2) (integer) 7001
3) "my-cluster-2"
3) 1) (integer) 10923
2) (integer) 16383
3) 1) "127.0.0.1"
2) (integer) 7002
3) "my-cluster-3"
显示了哈希槽(0-16383)的分配情况,列出了每个主节点及其负责的槽区间。
每个主节点对应的从节点会在后续的操作中自动关联。
查看某个键属于哪个哈希槽
127.0.0.1:7000> cluster keyslot mykey
(integer) 10285
mykey被分配到哈希槽10285中。
......到此这篇关于redis介绍与使用一文搞懂的文章就介绍到这了,更多相关redis介绍与使用内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论