1. Redis 基础入门


文档摘要

Redis 基础入门 Redis 基础入门详解:从零到实战 Redis (Remote Dictionary Server) 是一款高性能的键值对(key-value)数据库。它以其卓越的速度、丰富的数据结构和强大的功能,在缓存、会话管理、消息队列、实时分析等领域得到了广泛应用。对于希望提升系统性能和构建现代应用的开发者来说,Redis 已经成为一项必备技能。 本文将带您从零开始,深入浅出地了解 Redis 的基础知识,并通过丰富的代码实践,让您快速掌握 Redis 的核心概念和常用操作。 初识 Redis:为何选择 Redis? 在深入代码之前,我们首先需要了解 Redis 的基本概念以及它为何如此受欢迎。 1.1 Redis 是什么?

1. Redis 基础入门

Redis 基础入门详解:从零到实战

Redis (Remote Dictionary Server) 是一款高性能的键值对(key-value)数据库。它以其卓越的速度、丰富的数据结构和强大的功能,在缓存、会话管理、消息队列、实时分析等领域得到了广泛应用。对于希望提升系统性能和构建现代应用的开发者来说,Redis 已经成为一项必备技能。

本文将带您从零开始,深入浅出地了解 Redis 的基础知识,并通过丰富的代码实践,让您快速掌握 Redis 的核心概念和常用操作。

1. 初识 Redis:为何选择 Redis?

在深入代码之前,我们首先需要了解 Redis 的基本概念以及它为何如此受欢迎。

1.1 Redis 是什么?

Redis 是一种开源的、内存中的数据结构存储系统,通常用作数据库、缓存和消息代理。它与传统的关系型数据库(如 MySQL)不同,Redis 将数据存储在内存中,这使得它拥有极快的读写速度。为了保证数据的持久性,Redis 也提供了将数据持久化到磁盘的机制。

1.2 Redis 的主要特点:

  • 速度极快: 数据存储在内存中,读写速度非常快,单线程架构避免了线程切换的开销。

  • 丰富的数据结构: 除了基本的键值对外,Redis 还支持列表(List)、哈希表(Hash)、集合(Set)、有序集合(Sorted Set)等多种数据结构,满足各种应用场景的需求。

  • 持久化支持: Redis 提供了 RDB(快照)和 AOF(追加文件)两种持久化机制,保证数据在服务器重启后不会丢失。

  • 功能丰富: 支持事务、发布/订阅、Lua 脚本、过期策略、地理空间索引等高级功能。

  • 简单易用: Redis 的命令简洁明了,客户端支持多种编程语言,学习曲线平缓。

  • 高可用和可扩展: 通过 Sentinel 和 Cluster 机制,可以实现 Redis 的高可用和水平扩展。

1.3 Redis 的应用场景:

  • 缓存: 将热点数据缓存到 Redis 中,减轻数据库压力,提升应用响应速度。这是 Redis 最常见的应用场景。

  • 会话管理: 存储用户会话信息,实现分布式会话共享。

  • 排行榜/计数器: 利用 Redis 的有序集合和原子操作,实现实时排行榜和计数器功能。

  • 消息队列: 利用 Redis 的发布/订阅功能或列表数据结构,构建简单的消息队列系统。

  • 社交网络: 存储用户关系、时间线、点赞评论等数据。

  • 地理空间索引: 存储地理位置信息,实现附近的人、附近商家等功能。

  • 限流: 利用 Redis 的原子操作和过期策略,实现接口限流。

2. Redis 安装与连接:准备开发环境

在开始代码实践之前,我们需要先安装 Redis 并连接到 Redis 服务器。

2.1 Redis 安装 (以 Ubuntu 为例):

在 Linux 系统(如 Ubuntu)上,可以使用包管理器轻松安装 Redis:

sudo apt update sudo apt install redis-server

安装完成后,Redis 服务器会自动启动。您可以使用以下命令检查 Redis 服务器状态:

redis-cli ping

如果返回 PONG,则表示 Redis 服务器运行正常。

其他操作系统安装:

  • macOS: 可以使用 Homebrew 安装:brew install redis

  • Windows: 可以从 Redis 官网下载 Windows 版本,或者使用 Docker 镜像。

2.2 连接 Redis 服务器 (使用 Python redis-py 客户端):

为了方便代码实践,我们选择使用 Python 的 redis-py 客户端库。首先需要安装 redis-py

pip install redis

然后,在 Python 代码中连接 Redis 服务器:

import redis # 连接 Redis 服务器 (默认连接本地 6379 端口) r = redis.Redis(host='localhost', port=6379, db=0) # 测试连接 try: r.ping() print("Redis 连接成功!") except redis.exceptions.ConnectionError as e: print(f"Redis 连接失败: {e}")

这段代码创建了一个 Redis 连接对象 r,并使用 r.ping() 命令测试连接是否成功。hostportdb 参数分别指定 Redis 服务器的主机地址、端口号和数据库编号(默认为 0)。

3. Redis 数据结构与常用命令:代码实践详解

Redis 提供了多种数据结构,每种数据结构都有其特定的应用场景和操作命令。接下来,我们将逐一介绍 Redis 的核心数据结构,并通过 Python 代码示例演示常用命令的使用方法。

3.1 String (字符串)

String 是 Redis 最基本的数据结构,可以存储文本、数字或二进制数据,最大可以存储 512MB 的数据。

常用命令:

  • SET key value: 设置键值对。

  • GET key: 获取键对应的值。

  • MSET key1 value1 key2 value2 ...: 批量设置多个键值对。

  • MGET key1 key2 ...: 批量获取多个键的值。

  • DEL key: 删除键值对。

  • EXISTS key: 判断键是否存在。

  • INCR key: 将键对应的值自增 1(值必须是整数)。

  • DECR key: 将键对应的值自减 1(值必须是整数)。

  • APPEND key value: 将 value 追加到键对应的值的末尾。

  • STRLEN key: 获取键对应的值的长度。

Python 代码示例:

import redis r = redis.Redis() # SET 和 GET r.set('name', 'Redis') name = r.get('name') print(f"name: {name.decode()}") # 输出: name: Redis # MSET 和 MGET r.mset({'city': 'Shanghai', 'language': 'Python'}) cities_langs = r.mget('city', 'language') print(f"cities_langs: {[item.decode() for item in cities_langs]}") # 输出: cities_langs: ['Shanghai', 'Python'] # INCR 和 DECR r.set('count', 10) r.incr('count') print(f"count after incr: {r.get('count').decode()}") # 输出: count after incr: 11 r.decr('count') print(f"count after decr: {r.get('count').decode()}") # 输出: count after decr: 10 # APPEND r.append('name', '-Server') print(f"appended name: {r.get('name').decode()}") # 输出: appended name: Redis-Server # DEL 和 EXISTS r.delete('city') exists_city = r.exists('city') print(f"city exists: {exists_city}") # 输出: city exists: 0 (False)

应用场景:

  • 缓存: 存储网页内容、API 响应等。

  • 计数器: 网站访问量、点赞数、浏览数等。

  • session 存储: 存储用户 session 信息。

  • 分布式锁: 利用 SETNX (SET if Not eXists) 命令实现分布式锁。

3.2 List (列表)

List 是有序的字符串列表,元素可以重复。Redis List 底层是双向链表,所以插入和删除操作非常快,时间复杂度为 O(1)。

常用命令:

  • LPUSH key value1 value2 ...: 从列表左侧(头部)添加一个或多个元素。

  • RPUSH key value1 value2 ...: 从列表右侧(尾部)添加一个或多个元素。

  • LPOP key: 移除并返回列表左侧的第一个元素。

  • RPOP key: 移除并返回列表右侧的第一个元素。

  • LRANGE key start stop: 返回列表中指定范围的元素(从 startstop,包含 startstop)。

  • LINDEX key index: 返回列表中指定索引位置的元素。

  • LLEN key: 返回列表的长度。

  • LREM key count value: 根据 value 移除列表中前 count 个匹配的元素(count > 0 从左往右移除,count < 0 从右往左移除,count = 0 移除所有匹配的元素)。

  • LTRIM key start stop: 修剪列表,只保留指定范围的元素。

Python 代码示例:

import redis r = redis.Redis() # LPUSH 和 RPUSH r.lpush('tasks', 'task1', 'task2') # 左侧添加 r.rpush('tasks', 'task3', 'task4') # 右侧添加 print(f"tasks list: {[item.decode() for item in r.lrange('tasks', 0, -1)]}") # 输出: tasks list: ['task2', 'task1', 'task3', 'task4'] # LPOP 和 RPOP left_task = r.lpop('tasks') right_task = r.rpop('tasks') print(f"left_task: {left_task.decode()}, right_task: {right_task.decode()}") # 输出: left_task: task2, right_task: task4 print(f"tasks list after pop: {[item.decode() for item in r.lrange('tasks', 0, -1)]}") # 输出: tasks list after pop: ['task1', 'task3'] # LRANGE 和 LINDEX tasks_range = r.lrange('tasks', 0, 0) # 获取第一个元素 first_task = r.lindex('tasks', 0) # 获取第一个元素 print(f"tasks_range: {[item.decode() for item in tasks_range]}, first_task: {first_task.decode()}") # 输出: tasks_range: ['task1'], first_task: task1 # LLEN tasks_len = r.llen('tasks') print(f"tasks list length: {tasks_len}") # 输出: tasks list length: 2 # LREM 和 LTRIM r.lpush('mylist', 'a', 'b', 'c', 'b', 'a') r.lrem('mylist', 2, 'b') # 从左往右移除 2 个 'b' print(f"mylist after lrem: {[item.decode() for item in r.lrange('mylist', 0, -1)]}") # 输出: mylist after lrem: ['a', 'c', 'a'] r.ltrim('mylist', 0, 1) # 只保留索引 0 到 1 的元素 print(f"mylist after ltrim: {[item.decode() for item in r.lrange('mylist', 0, -1)]}") # 输出: mylist after ltrim: ['a', 'c']

应用场景:

  • 消息队列: 使用 LPUSHRPOPLPUSHBRPOP (阻塞式弹出) 实现简单的消息队列。

  • 最新列表: 例如,最新的文章列表、最新的评论列表等。

  • 时间轴: 例如,用户的朋友圈时间轴。

3.3 Hash (哈希表)

Hash 是一种键值对集合,适合存储对象类型的数据。Redis Hash 类似于 Python 的字典或 Java 的 HashMap。

常用命令:

  • HSET key field value: 设置哈希表中字段 field 的值为 value

  • HGET key field: 获取哈希表中字段 field 的值。

  • HMSET key field1 value1 field2 value2 ...: 批量设置哈希表中多个字段的值。

  • HMGET key field1 field2 ...: 批量获取哈希表中多个字段的值。

  • HGETALL key: 获取哈希表中所有字段和值。

  • HDEL key field1 field2 ...: 删除哈希表中一个或多个字段。

  • HEXISTS key field: 判断哈希表中字段 field 是否存在。

  • HKEYS key: 获取哈希表中所有字段名。

  • HVALS key: 获取哈希表中所有字段值。

  • HLEN key: 获取哈希表中字段的数量。

Python 代码示例:

import redis r = redis.Redis() # HSET 和 HGET r.hset('user:1001', 'name', 'Alice') r.hset('user:1001', 'age', 30) name = r.hget('user:1001', 'name') age = r.hget('user:1001', 'age') print(f"user:1001 - name: {name.decode()}, age: {age.decode()}") # 输出: user:1001 - name: Alice, age: 30 # HMSET 和 HMGET r.hmset('user:1002', {'name': 'Bob', 'city': 'Beijing'}) user_info = r.hmget('user:1002', 'name', 'city') print(f"user:1002 - info: {[item.decode() for item in user_info]}") # 输出: user:1002 - info: ['Bob', 'Beijing'] # HGETALL user_all = r.hgetall('user:1001') print(f"user:1001 - all: { {k.decode(): v.decode() for k, v in user_all.items()} }") # 输出: user:1001 - all: {'name': 'Alice', 'age': '30'} # HDEL 和 HEXISTS r.hdel('user:1001', 'age') exists_age = r.hexists('user:1001', 'age') print(f"user:1001 - age exists: {exists_age}") # 输出: user:1001 - age exists: 0 (False) # HKEYS 和 HVALS user_keys = r.hkeys('user:1002') user_vals = r.hvals('user:1002') print(f"user:1002 - keys: {[k.decode() for k in user_keys]}, vals: {[v.decode() for v in user_vals]}") # 输出: user:1002 - keys: ['name', 'city'], vals: ['Bob', 'Beijing'] # HLEN user_len = r.hlen('user:1002') print(f"user:1002 - field count: {user_len}") # 输出: user:1002 - field count: 2

应用场景:

  • 存储对象: 例如,用户信息、商品信息等。

  • 购物车: 以用户 ID 为 key,商品 ID 和数量为 field-value 存储购物车信息。

3.4 Set (集合)

Set 是无序的、唯一的字符串集合。Redis Set 提供了集合操作,例如交集、并集、差集等。

常用命令:

  • SADD key member1 member2 ...: 向集合中添加一个或多个成员。

  • SMEMBERS key: 返回集合中的所有成员。

  • SREM key member1 member2 ...: 移除集合中一个或多个成员。

  • SISMEMBER key member: 判断成员 member 是否是集合的成员。

  • SCARD key: 返回集合的成员数量。

  • SINTER key1 key2 ...: 返回多个集合的交集。

  • SUNION key1 key2 ...: 返回多个集合的并集。

  • SDIFF key1 key2 ...: 返回集合 key1 与其他集合的差集。

  • SPOP key [count]: 随机移除并返回集合中一个或多个成员。

  • SRANDMEMBER key [count]: 随机返回集合中一个或多个成员,但不移除。

Python 代码示例:

import redis r = redis.Redis() # SADD 和 SMEMBERS r.sadd('tags', 'python', 'redis', 'database') tags = r.smembers('tags') print(f"tags set: {[item.decode() for item in tags]}") # 输出: tags set: ['database', 'python', 'redis'] (顺序可能不同) # SREM 和 SISMEMBER r.srem('tags', 'database') is_member = r.sismember('tags', 'database') print(f"is 'database' in tags: {is_member}") # 输出: is 'database' in tags: 0 (False) # SCARD tags_count = r.scard('tags') print(f"tags set count: {tags_count}") # 输出: tags set count: 2 # SINTER, SUNION, SDIFF r.sadd('tags1', 'redis', 'java', 'mysql') r.sadd('tags2', 'redis', 'python', 'mongodb') intersection = r.sinter('tags1', 'tags2') union = r.sunion('tags1', 'tags2') difference = r.sdiff('tags1', 'tags2') print(f"intersection: {[item.decode() for item in intersection]}") # 输出: intersection: ['redis'] print(f"union: {[item.decode() for item in union]}") # 输出: union: ['java', 'mysql', 'python', 'redis', 'mongodb'] (顺序可能不同) print(f"difference: {[item.decode() for item in difference]}") # 输出: difference: ['java', 'mysql'] (顺序可能不同) # SPOP 和 SRANDMEMBER random_member_pop = r.spop('tags') random_member_no_pop = r.srandmember('tags') print(f"random_member_pop: {random_member_pop.decode()}, tags after spop: {[item.decode() for item in r.smembers('tags')]}") # 输出: 随机成员, tags 数量减少 print(f"random_member_no_pop: {random_member_no_pop.decode()}, tags after srandmember: {[item.decode() for item in r.smembers('tags')]}") # 输出: 随机成员, tags 数量不变

应用场景:

  • 标签系统: 例如,文章标签、用户标签等。

  • 共同好友/粉丝: 利用交集计算共同好友,并集计算所有好友。

  • 抽奖: 使用 SPOPSRANDMEMBER 实现抽奖功能。

  • 去重: 利用 Set 的唯一性去重数据。

3.5 Sorted Set (有序集合)

Sorted Set 是有序的、唯一的字符串集合。每个成员都关联一个分数(score),Redis 根据分数对集合成员进行排序。Sorted Set 非常适合实现排行榜功能。

常用命令:

  • ZADD key score1 member1 score2 member2 ...: 向有序集合中添加一个或多个成员,并指定分数。

  • ZRANGE key start stop [WITHSCORES]: 返回有序集合中指定范围的成员(按分数从小到大排序)。WITHSCORES 可选,表示同时返回分数。

  • ZREVRANGE key start stop [WITHSCORES]: 返回有序集合中指定范围的成员(按分数从大到小排序)。WITHSCORES 可选。

  • ZSCORE key member: 返回成员 member 的分数。

  • ZREM key member1 member2 ...: 移除有序集合中一个或多个成员。

  • ZCARD key: 返回有序集合的成员数量。

  • ZINCRBY key increment member: 将成员 member 的分数增加 increment

  • ZRANK key member: 返回成员 member 在有序集合中的排名(按分数从小到大排序,排名从 0 开始)。

  • ZREVRANK key member: 返回成员 member 在有序集合中的排名(按分数从大到小排序,排名从 0 开始)。

  • ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]: 返回分数在指定范围内的成员。

  • ZCOUNT key min max: 统计分数在指定范围内的成员数量。

Python 代码示例:

import redis r = redis.Redis() # ZADD r.zadd('leaderboard', {'player1': 100, 'player2': 150, 'player3': 120}) # ZRANGE 和 ZREVRANGE top_players_asc = r.zrange('leaderboard', 0, -1, withscores=True) # 升序 top_players_desc = r.zrevrange('leaderboard', 0, -1, withscores=True) # 降序 print(f"top_players_asc: {[(player.decode(), score) for player, score in top_players_asc]}") # 输出: top_players_asc: [('player1', 100.0), ('player3', 120.0), ('player2', 150.0)] print(f"top_players_desc: {[(player.decode(), score) for player, score in top_players_desc]}") # 输出: top_players_desc: [('player2', 150.0), ('player3', 120.0), ('player1', 100.0)] # ZSCORE player2_score = r.zscore('leaderboard', 'player2') print(f"player2 score: {player2_score}") # 输出: player2 score: 150.0 # ZREM r.zrem('leaderboard', 'player1') print(f"leaderboard after zrem: {[(player.decode(), score) for player, score in r.zrevrange('leaderboard', 0, -1, withscores=True)]}") # 输出: leaderboard after zrem: [('player2', 150.0), ('player3', 120.0)] # ZCARD leaderboard_count = r.zcard('leaderboard') print(f"leaderboard count: {leaderboard_count}") # 输出: leaderboard count: 2 # ZINCRBY r.zincrby('leaderboard', 20, 'player3') # player3 加 20 分 print(f"leaderboard after zincrby: {[(player.decode(), score) for player, score in r.zrevrange('leaderboard', 0, -1, withscores=True)]}") # 输出: leaderboard after zincrby: [('player2', 150.0), ('player3', 140.0)] # ZRANK 和 ZREVRANK player3_rank_asc = r.zrank('leaderboard', 'player3') # 升序排名 player3_rank_desc = r.zrevrank('leaderboard', 'player3') # 降序排名 print(f"player3 rank asc: {player3_rank_asc}, rank desc: {player3_rank_desc}") # 输出: player3 rank asc: 1, rank desc: 0

应用场景:

  • 排行榜: 游戏排行榜、销售额排行榜、积分排行榜等。

  • 延迟队列: 利用分数作为时间戳,实现延迟任务队列。

  • 限流: 结合时间窗口和分数,实现滑动窗口限流。

4. Redis 持久化:数据安全保障

虽然 Redis 是内存数据库,但它提供了两种持久化机制,确保数据在服务器重启后不会丢失。

4.1 RDB (快照) 持久化:

RDB 持久化是指 Redis 会定期将内存中的数据快照保存到磁盘上的 .rdb 文件中。当 Redis 重启时,会加载 RDB 文件恢复数据。

优点:

  • 性能高: RDB 是通过 fork 子进程来执行持久化操作,主进程不需要进行任何 IO 操作,性能影响较小。

  • 恢复速度快: RDB 文件是紧凑的二进制文件,恢复数据速度比 AOF 快。

缺点:

  • 数据丢失风险: 如果在两次快照之间 Redis 服务器发生故障,则会丢失这段时间内的数据。

配置 RDB 持久化:

可以在 redis.conf 文件中配置 RDB 持久化策略,例如:

save 900 1 # 900 秒内,如果至少有 1 个 key 被修改,则触发快照 save 300 10 # 300 秒内,如果至少有 10 个 key 被修改,则触发快照 save 60 10000 # 60 秒内,如果至少有 10000 个 key 被修改,则触发快照

4.2 AOF (追加文件) 持久化:

AOF 持久化是指 Redis 会将每个写命令追加到磁盘上的 .aof 文件中。当 Redis 重启时,会重新执行 AOF 文件中的命令恢复数据。

优点:

  • 数据安全性高: 可以配置不同的 fsync 策略,例如每秒 fsync 或每次写命令都 fsync,最大限度地保证数据安全。

  • 文件可读性好: AOF 文件是文本文件,可以查看和分析。

缺点:

  • 性能略低: AOF 持久化需要执行 IO 操作,性能比 RDB 略低。

  • 文件体积较大: AOF 文件通常比 RDB 文件大。

配置 AOF 持久化:

可以在 redis.conf 文件中启用 AOF 持久化:

appendonly yes appendfsync everysec # 每秒 fsync,兼顾性能和数据安全 # appendfsync always # 每次写命令都 fsync,数据安全最高,性能最低 # appendfsync no # 不进行 fsync,性能最高,数据安全最低

选择 RDB 还是 AOF?

  • 对数据安全性要求不高,追求高性能: 选择 RDB。

  • 对数据安全性要求高: 选择 AOF。

  • 同时使用 RDB 和 AOF: Redis 允许同时启用 RDB 和 AOF 持久化,AOF 优先级更高,重启时会优先加载 AOF 文件恢复数据。

5. Redis 事务:保证操作原子性

Redis 事务允许将多个命令打包成一个原子操作序列。事务中的所有命令要么全部执行成功,要么全部执行失败。

Redis 事务命令:

  • MULTI: 开启事务。

  • EXEC: 执行事务中的所有命令。

  • DISCARD: 取消事务,放弃执行事务中的所有命令。

  • WATCH key1 key2 ...: 监视一个或多个 key,如果在事务执行之前被监视的 key 被修改,则事务会被取消。

  • UNWATCH: 取消所有 key 的监视。

Python 代码示例:

import redis r = redis.Redis() # 开启事务 pipe = r.pipeline() try: # 开启 WATCH 监视 'balance' 键 pipe.watch('balance') # 获取当前余额 balance = int(pipe.get('balance') or 0) # 检查余额是否足够 if balance >= 100: # 开启 MULTI 事务 pipe.multi() pipe.decrby('balance', 100) # 扣款 100 pipe.incrby('points', 10) # 积分增加 10 result = pipe.execute() # 执行事务 print(f"事务执行结果: {result}") # 输出: 事务执行结果: [True, 10] (表示扣款和积分增加都成功) else: print("余额不足,事务取消!") pipe.unwatch() # 取消 WATCH 监视 except redis.exceptions.WatchError as e: print(f"WATCH 冲突,事务取消: {e}") finally: pipe.reset() # 重置 pipeline 状态

事务的 ACID 特性:

  • 原子性 (Atomicity): Redis 事务是原子性的,要么全部执行,要么全部不执行。

  • 一致性 (Consistency): 事务执行前后,数据始终保持一致性状态。

  • 隔离性 (Isolation): Redis 事务不支持隔离级别,但 Redis 是单线程的,命令串行执行,可以保证事务的隔离性。

  • 持久性 (Durability): 事务的持久性取决于 Redis 的持久化配置 (RDB 或 AOF)。

注意:

  • Redis 事务不支持回滚操作。如果在事务执行过程中出现错误,Redis 不会回滚已执行的命令,而是会继续执行事务中的剩余命令。

发布者: 作者: 转发
评论区 (0)
U