Redis 超越缓存,使用 Python 配合
mhr18 2024-12-14 11:04 16 浏览 0 评论
作为一名Python 开发者, 肯定使用过 Redis , 并且认为它是一个很棒的缓存。 虽然你的印象没有错, Redis 的确是一个很棒的缓存, 但使用 Redis 能够解决的问题并不仅限于缓存。
我们将探索 Redis 和 Redis Enterprise 的一些其他用途。 为了找点乐子, 我将使用之前《 使用 Redis 储存地理位置数据 》一文中的大脚怪(Bigfoot)数据。 此外, 由于这篇文章的读者都是 Python 开发者, 所以我将使用 Python 来编写本文的所有代码!
我在接下来展示的代码中使用了 aioredis 客户端库, 因为它对 async/await 提供了非常棒的支持。 如果你对 async/await 不熟悉的话, 那么可以去看看 这篇文章 , 里面提到了 async/await 对提升性能的帮助。
使用 Redis 构建队列
Redis 提供了字符串、哈希、集合和列表等多种数据结构可供使用。 这些数据结构都是储存数据的好帮手, 其中列表就可以用作一个非常棒的队列(queue)。
为了将列表用作队列, 我们需要使用 RPUSH 将新项目推送至列表末尾, 然后使用 LPOP 或者 BLPOP 将它们从列表的前面弹出。 由于 Redis 对数据库的所有修改都是在单个线程里面完成的, 所以这些操作都是原子的。
作为例子, 下面这段在队列里面添加了一些大脚怪的踪迹。
import asyncio
import aioredis
async def main():
redis = await aioredis.create_redis('redis://:foobared@localhost:6379/0', encoding='utf-8')
await asyncio.gather(
add_to_queue(redis, 'Possible vocalizations east of Makanda'),
add_to_queue(redis, 'Sighting near the Columbia River'),
add_to_queue(redis, 'Chased by a tall hairy creature')
)
redis.close()
await redis.wait_closed()
def add_to_queue(redis, message):
return redis.rpush('bigfoot:sightings:received', message)
asyncio.run(main())
这个程序非常直接。 我们只需要在第 18 行调用 redis.rpush , 就能够将指定的元素推入到队列。 接下来是从队列另一端读取元素的代码, 同样非常简单。
import asyncio
import aioredis
from pprint import pp
async def main():
redis = await aioredis.create_redis('redis://:foobared@localhost:6379/0', encoding='utf-8')
while True:
sighting = await redis.blpop('bigfoot:sightings:received')
pp(sighting)
asyncio.run(main())
第 11 行和第 12 行的无限循环将等待并且打印被推入至队列中的大脚怪踪迹。 这里使用了 redis.blpop 而不是 redis.lpop , 因为前者可以阻塞客户端并等待列表中的元素返回。 比起让 Redis 和 Python 代码之间的网络无休止地轮询并做无用功, 让客户端阻塞并等待元素出现的做法会高效得多。
Redis 还有 一些同样很酷的命令 , 它们不仅可以将列表用作队列甚至堆栈。 我最喜欢的是 BRPOPLPUSH , 它可以从列表的右侧阻塞并弹出一些元素, 然后将被弹出的元素推入到另一个列表。 你可以使用这个命令来将一个队列中的元素传递至另一个队列, 这是非常棒的一个命令。
使用 Redis 订阅和发送事件
Redis 提供的东西中有些并不是数据结构, 比如订阅与发布(Pub/Sub)特性就是其中之一。 这个特性就像它的名字一样, 是一个内置于 Redis 中的发布与订阅机制。 得益于这个特性, 我们只需要 使用一些命令 就可以在自己的 Python 应用里面添加强大的订阅与发布机制。
通过执行订阅操作可以让我们发现事件, 以下是代码:
import asyncio
import aioredis
from pprint import pp
async def main():
redis = await aioredis.create_redis('redis://:foobared@localhost:6379/0', encoding='utf-8')
[channel] = await redis.psubscribe('bigfoot:broadcast:channel:*')
while True:
message = await channel.get()
pp(message)
asyncio.run(main())
因为我想要接收所有跟大脚兽有关的消息, 所以我在这段代码的第 10 行使用 redis.psubscribe 订阅了一个 Glob 风格的模式, 通过使用 bigfoot:broadcast:channel:* 作为模式, 客户端将接收到所有以 bigfoot:broadcast:channel: 开头的事件。
用于匹配模式的 redis.psubscribe 函数和非模式匹配的 redis.subscribe 函数都返回 Python 列表, 以便包含不定数量的元素。 程序将解构这个列表(Python 的术语是解包)以获得我想要的通道, 并在之后使用 .get 进行阻塞调用以等待下一条消息。
发布事件非常简单, 下面是代码:
import asyncio
import aioredis
async def main():
redis = await aioredis.create_redis('redis://:foobared@localhost:6379/0', encoding='utf-8')
await asyncio.gather(
publish(redis, 1, 'Possible vocalizations east of Makanda'),
publish(redis, 2, 'Sighting near the Columbia River'),
publish(redis, 2, 'Chased by a tall hairy creature')
)
redis.close()
await redis.wait_closed()
def publish(redis, channel, message):
return redis.publish(f'bigfoot:broadcast:channel:{channel}', message)
asyncio.run(main())
这段代码的重点是第 18 行, 它使用了名字非常直接的 redis.publish 来将消息发布至所需的通道。
值得注意的是, 发布与订阅是一个发送即遗忘机制(fire-and-forget)。 如果代码发布了一个事件但是却没有人监听, 那么该事件就会消失。 如果你想让自己的事件持续存在, 那么可以考虑使用前面提到的队列, 又或者接下来将要介绍的 Redis 流。
使用 Redis 储存数据流
除了发布与订阅之外, Redis 还可以使用流来发布和订阅事件。 Redis 流 是一个非常大的话题, 但使用它只需要 掌握少量命令 。 从 Python 来看, 这些命令的用法都是非常简单的, 我将一一向你说明。
下面的代码将把三次大脚兽的目击事件添加到流里面。
import asyncio
import aioredis
async def main():
redis = await aioredis.create_redis('redis://:foobared@localhost:6379/0', encoding='utf-8')
await asyncio.gather(
add_to_stream(redis, 1, 'Possible vocalizations east of Makanda', 'Class B'),
add_to_stream(redis, 2, 'Sighting near the Columbia River', 'Class A'),
add_to_stream(redis, 3, 'Chased by a tall hairy creature', 'Class A'))
redis.close()
await redis.wait_closed()
def add_to_stream(redis, id, title, classification):
return redis.xadd('bigfoot:sightings:stream', {
'id': id, 'title': title, 'classification': classification })
asyncio.run(main())
这段代码中最重要的就是第 17 行和第 18 行, 它使用了 redis.xadd 函数将一次目击事件的字段添加到流里面。
每个新添加的流事件都有一个唯一标识符, 其中包含自 1970 年开始的时间戳(毫秒)和一个用破折号连接的序列号。 例如, 当我写这篇文章的时候, 1970 年 1 月 1 日(Unix纪元)午夜已经过去了 1,593,120,357,193 毫秒(1.59千兆秒)。 因此当我运行上面这段代码的时候, 命令将创建出 ID 为 1593120357193-0 的事件。
我们在添加事件的时候可以使用 * 来代替具体的 ID , 这样 Redis 就会根据当前时间来自动生成事件的 ID , 这也是 redis.xadd 函数的默认行为。
正如接下来的代码所示, 在读取流元素的时候, 我们需要设置一个起始 ID 。 你可以看到, 在第 10 行, 程序将变量 last_id 设置成了 0-0 , 这个 ID 代表流的起始位置。
import asyncio
import aioredis
from pprint import pp
async def main():
redis = await aioredis.create_redis('redis://:foobared@localhost:6379/0', encoding='utf8')
last_id = '0-0'
while True:
events = await redis.xread(['bigfoot:sightings:stream'], timeout=0, count=5, latest_ids=[last_id])
for key, id, fields in events:
pp(fields)
last_id = id
asyncio.run(main())
程序的第 12 行使用 redis.xread 函数从流中请求最多 5 个 0-0 之后的事件。 该调用将返回一个列表, 然后程序将对其进行循环和解构, 以获得事件的字段和标识符。 事件的标识符会被储存起来, 以便将来调用 redis.xread 时可以获得新的事件并在有需要时重新读取之前读取过的旧事件。
将 Redis 用作搜索引擎
Redis 可以通过模块(Module)扩展来增加新的命令和功能。 有 大量的模块 可以用于 AI 模型服务、图形数据库、时间序列数据库以及本例中的搜索引擎。
RedisSearch 是一个强大的搜索引擎, 它摄取数据的速度快得惊人。 有些人喜欢用它来进行 瞬时搜索 , 但除此之外它也可以用来进行其他搜索。 下面是使用该模块的一个例子:
import asyncio
import aioredis
from pprint import pp
async def main():
redis = await aioredis.create_redis('redis://:foobared@localhost:6379/0', encoding='utf-8')
await redis.execute('FT.DROP', 'bigfoot:sightings:search')
await redis.execute('FT.CREATE', 'bigfoot:sightings:search',
'SCHEMA', 'title', 'TEXT', 'classification', 'TEXT')
await asyncio.gather(
add_document(redis, 1, 'Possible vocalizations east of Makanda', 'Class B'),
add_document(redis, 2, 'Sighting near the Columbia River', 'Class A'),
add_document(redis, 3, 'Chased by a tall hairy creature', 'Class A'))
results = await search(redis, 'chase|east')
pp(results)
redis.close()
await redis.wait_closed()
def add_document(redis, id, title, classification):
return redis.execute('FT.ADD', 'bigfoot:sightings:search', id, '1.0',
'FIELDS', 'title', title, 'classification', classification)
def search(redis, query):
return redis.execute('FT.SEARCH', 'bigfoot:sightings:search', query)
asyncio.run(main())
在第 12 和第 13 行, 程序使用 FT.CREATE 创建了一个索引。 索引需要描述程序将要添加的每个文档中的字段的模式。 在这个例子中, 程序需要添加大脚兽的目击事件, 该文档包含一个标题和一个分类, 并且它们都是文本字段。
在拥有了索引之后, 程序就可以向里面添加文档了, 这一操作发生在程序的第 27 行和第 28 行, 通过 FT.ADD 命令来完成。 每个文档偶读需要一个唯一 ID 、一个介于 0.0 和 1.0 之间的权重(rank)以及相应的字段。
正如程序的第 31 行所示, 在索引加载文档之后, 程序就可以使用 FT.SEARCH 命令和具体的查询语句来执行查询操作。 第 20 行的特定查询指示 RedisSearch 在索引中查找包含这些术语之一的文档。 在这个例子中, 该查询将返回两个文档。
使用 Redis 作为主数据库
Redis 可以作为一个速度奇快的内存存储数据库来使用。 下面的代码使用了哈希来演示这种用法。 哈希是一种非常棒的数据结构, 它可以建模你想要储存的记录类型, 并且能够将数据的主键用作键名的其中一部分。
import asyncio
import aioredis
from pprint import pp
async def main():
redis = await aioredis.create_redis('redis://:foobared@localhost:6379/0', encoding='utf-8')
await asyncio.gather(
add_sighting(redis, 1, 'Possible vocalizations east of Makanda', 'Class B'),
add_sighting(redis, 2, 'Sighting near the Columbia River', 'Class A'),
add_sighting(redis, 3, 'Chased by a tall hairy creature', 'Class A'))
sightings = await asyncio.gather(
read_sighting(redis, 1),
read_sighting(redis, 2),
read_sighting(redis, 3))
pp(sightings)
redis.close()
await redis.wait_closed()
def add_sighting(redis, id, title, classification):
return redis.hmset(f'bigfoot:sighting:{id}',
'id', id, 'title', title, 'classification', classification)
def read_sighting(redis, id):
return redis.hgetall(f'bigfoot:sighting:{id}')
asyncio.run(main())
你可能会这样想”如果我把服务器关掉了怎么办?如果它崩溃了怎么办?那我就什么数据都没有了!“ No,不会的! 你可以修改你的 redis.conf 文件, 用几种不同的方式来持久化内存中的数据 。 此外, 如果你使用的是 Redis Enterprise , 我们也有为你提供 相应的解决方案 , 使得你可以直接使用 Redis 而不必担心持久化的问题。
为了方便你亲手尝试这些例子, 我把文中涉及的 所有代码都放到了 GitHub 上面 , 你可以克隆并开始使用它们。 如果你是 Docker 用户, 项目里面也有一个名为 start-redis.sh 的 shell 脚本, 它可以拉取一个镜像, 然后启动一个能够运行这些例子的 Redis 版本。
如果你在玩耍完毕之后想要认真地构建一些软件, 那么可以注册并尝试 Redis Cloud Essentials 。 它和你所熟悉和喜欢的 Redis 一样, 唯一的区别就是这种 Redis 由云端进行管理, 所以你只需要专注于构建你的软件即可。
大家平时学习Python的时候肯定会遇到很多问题,小编我为大家准备了Python学习资料,将这些免费分享给大家!如果想要的可以找我领取
领取方式:
如果想获取这些学习资料,先关注我然后私信小编“01”即可免费领取!(私信方法:点击我头像进我主页右上面有个私信按钮)
如果这篇文章对你有帮助,请记得给我来个评论+转发
- 上一篇:Redis原理—1.Redis数据结构
- 下一篇:237.C# Redis 缓存
相关推荐
- IT界10倍高效学习法!用这种方式,一年学完清华大学四年的课程
-
有没有在某一个瞬间,让你放弃学编程刚开始学python时,我找了几十本国内外的python编程书籍学习后,我还是似懂非懂,那些书里面到处都是抽象的概念,复杂的逻辑,这样的书,对于专业开发者来说,在平常...
- 数据库设计原则:构建高效可靠的数据存储
-
简介数据库设计是软件开发中至关重要的一环,它决定了数据在系统中的存储方式和组织结构。良好的数据库设计可以提高系统的性能、可靠性和可维护性。本文将介绍一些常见的数据库设计原则,包括范式化、冗余与一致性、...
- Deno 操作 Oracle 数据库全解析:从入门到精通的实战指南
-
在企业级应用开发中,Oracle数据库凭借其强大的功能、高可靠性和安全性,一直是大型系统的首选数据存储方案。Deno作为新一代JavaScript/TypeScript运行时,以安全、高效和现...
- APIJSON快速入门-零后端代码,接口所见即所得
-
1.流程说明一个接口的开发,比如Java用SpringBoot,Mybatis来开发一般来说就像下面这个流程部署上这个项目后,流程变成了这样如果使用apijson-framework,还可进一步简化...
- 每天从外包系统同步百万数据,用什么方案?Java实战讲解
-
用户问的是每天从外包系统同步百万数据,用什么方案,还要Java实战讲解。首先,我需要明确用户的需求。他们每天需要处理的数据量是百万级别,这个量级不算特别大,但也要考虑效率和稳定性。可能用户是企业的开发...
- SpringBoot条件化配置(@Conditional)全面解析与实战指南
-
一、条件化配置基础概念1.1什么是条件化配置条件化配置是Spring框架提供的一种基于特定条件来决定是否注册Bean或加载配置的机制。在SpringBoot中,这一机制通过@Conditional...
- 禁止join,我该怎么办(禁止后不再提示是什么意思)
-
join是什么SQL中JOIN子句用于把来自两个或多个表的行结合起来。在实际的数据库应用中,经常需要从多个数据表中读取数据,这时就可以使用SQL语句中的连接(JOIN)子句,在两个或多个数据...
- Spring IoC Container 原理解析(spring的ioc底层实现原理)
-
IoC、DI基础概念关于IoC和DI大家都不陌生,我们直接上martinfowler的原文,里面已经有DI的例子和spring的使用示例《InversionofControlContainer...
- SUSE再次宣布推出自己的 RHEL 兼容发行版
-
SUSE再次宣布推出自己的RHEL兼容发行版变色龙牧场主选择了一条新的岩石路线周三12七月2023SUSE正在重新考虑去年初所做的改变:它正在推出自己的RHEL兼容发行版,或者用它的话说...
- Seatunnel介绍与datax对比及使用教程
-
什么是SeatunnelSeaTunnel是一款极简易用、性能卓越的分布式数据集成平台。它以轻量高效的架构设计,为用户提供了一站式数据集成解决方案,可轻松实现跨系统、跨格式的数据实时/批量同步与...
- Spring Boot 大屏展示,私活项目,已开源,接私活必备,真香
-
一款基于代码生成器的低代码平台!前后端分离架构SpringBoot2.x,SpringCloud,AntDesign&Vue,Mybatis-plus,Shiro,JWT,支持微服务。强大的代码生...
- 互联网大厂后端必看Spring Boot3与Oracle数据库的高效连接全攻略
-
你是不是在使用SpringBoot3开发项目时,正为连接Oracle数据库而头疼?明明按照常规方法配置,却总是出现各种错误,耗费大量时间却毫无进展?别担心,这篇文章就是为你量身定制的!开发现...
- Oracle 和 Microsoft 推出 Oracle Exadata 数据库服务
-
据oracle网3月20日报道,Oracle宣布在OracleDatabase@Azure的百万兆次级基础设施上全面推出OracleExadata数据库服务。百万兆次级部署选项无需供应...
- Oracle数据库中查询最前面的100条数据,三种方法对比
-
在Oracle数据库中查询最前面的100条数据,可通过多种方法实现,不同方法适用于不同场景和版本。以下是基于当前技术文档的三种核心方案及其适用性分析:一、使用ROWNUM伪列(通用方案,全版本适用...
- Oracle数据库中判断字段不为空?(oracle判断字段是否包含数字)
-
Oracle数据库中如何判断字段不为空在Oracle数据库中,判断字段(列)不为空通常涉及到几种不同的场景和需求。下面是一些常见的方法来检查字段是否不为空:1.使用NVL函数NVL函数可以用来将NU...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- IT界10倍高效学习法!用这种方式,一年学完清华大学四年的课程
- 数据库设计原则:构建高效可靠的数据存储
- Deno 操作 Oracle 数据库全解析:从入门到精通的实战指南
- APIJSON快速入门-零后端代码,接口所见即所得
- 每天从外包系统同步百万数据,用什么方案?Java实战讲解
- SpringBoot条件化配置(@Conditional)全面解析与实战指南
- 禁止join,我该怎么办(禁止后不再提示是什么意思)
- Spring IoC Container 原理解析(spring的ioc底层实现原理)
- SUSE再次宣布推出自己的 RHEL 兼容发行版
- Seatunnel介绍与datax对比及使用教程
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)