Redis协议与异步方式

本文深入讲解Redis的网络层、事务处理、管道机制、发布订阅功能及异步连接方式。介绍了Redis作为高性能内存数据库的主要特性,包括其单线程模型、事务的实现方式、发布订阅的应用场景等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

<Redis协议与异步方式>

一 Redis网络层

Redis是一个单线程reactor模型,它的主要逻辑处理是单线程的,但是io操作能够做到多线程处理。所以它可以同时有多个连接。多个连接并发执行。Redis的所有命令都是原子性的。

二 Redis事务

redis开启事务后,会把接下来的所有命令缓存到一个单独的队列中,在提交事务时,使这些命令不可被分割的一起执行完成。

如果使用了watch命令监视某一个key,如果在开启事务之后,提交事务之前,有其他连接修改了这个key,那么这个事务在被提交的时候是无法执行的,会返回nil。

以下是redis与事务相关的命令:

#MULTI 开启事务,事务执行过程中,单个命令是入队列操作,直到调用 EXEC 才会一起执行;

MULTI
EXEC #提交事务
DISCARD #取消事务
WATCH #检测 key 的变动,若在事务执行中,key 变动则取消事务;在事务开启前调用,乐观锁实现
      #(cas);若被取消则事务返回 nil ;

应用:
事务实现 zpop

WATCH zset
element = ZRANGE zset 0 0
MULTI
ZREM zset element
EXEC

事务是实现 加倍操作

WATCH score:10001
val = GET score:10001
MULTI
SET score:10001 val*2
EXEC

这种使用事务的方式,会导致事务会被频繁的取消,这不是完整的事务。通过使用lua脚本,可以解决这个问题。

lua脚本
lua 脚本实现原子性;redis中加载了一个 lua 虚拟机;用来执行 redis lua 脚本;redis lua 脚本的执行是原子性的;当某个脚本正在执行的时候,不会有其他命令或者脚本被执行;lua 脚本当中的命令会直接修改数据状态;
注意:如果项目中使用了 lua 脚本,不需要使用上面的事务命令;

三 Redis Pipeline

redis pipeline 是一个客户端提供的,而不是服务端提供的。
正常情况下,如果没有使用redis pipeline,请求和响应的交互方式如下所示:
在这里插入图片描述
此时,连续发送两条命令的执行方式是,第一条命令发送后,会等待响应。接收到响应后,才会发送第二条响应。

当使用了redis pipeline后,两条连续的命令的执行方式就是这样的:
在这里插入图片描述
从原理上说,对于request操作,只是将数据写到fd对应的写缓冲区,时间非常快,真正耗时操作在读取response。pipeline不具备事务性。

四 Redis事务的ACID特性分析

  1. A 原子性;事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败;redis
    不支持回滚;即使事务队列中的某个命令在执行期间出现了错误,整个事务也会继续执行下去,直
    到将事务队列中的所有命令都执行完毕为止。
  2. C 一致性;事务使数据库从一个一致性状态到另外一个一致性状态;这里的一致性是指预期的一
    致性而不是异常后的一致性
    ;所以redis也不满足;这个争议很大:redis 能确保事务执行前后的数
    据的完整约束;但是并不满足传统意义上的一致性;比如转账功能,一个扣钱一个加钱;可能出现
    扣钱执行错误,加钱执行正确,那么最终还是会加钱成功;系统凭空多了钱;
  3. I 隔离性;事务的操作不被其他用户操作所打断;redis 是单线程执行,天然具备隔离性;
  4. D 持久性;redis只有在 aof 持久化策略的时候,并且需要在 redis.conf 中appendfsync=always 才具备持久性;实际项目中几乎不会使用 aof 持久化策略;

五 Redis发布订阅

为了支持消息的多播机制,redis 引入了发布订阅模块。关键命令如下:

# 订阅频道
subscribe 频道
# 订阅模式频道
psubscribe 频道
# 取消订阅频道
unsubscribe 频道
# 取消订阅模式频道
punsubscribe 频道
# 发布具体频道或模式频道的内容
publish 频道 内容
# 客户端收到具体频道内容
message 具体频道 内容
# 客户端收到模式频道内容
pmessage 模式频道 具体频道 内容

发布订阅功能一般要区别命令连接而重新开启一个连接。因为命令连接严格遵循请求回应模式;而
pubsub能收到redis主动推送的内容;所以实际项目中如果支持pubsub的话,需要另开一条连接
用于处理发布订阅;
在这里插入图片描述
缺点
发布订阅的生产者传递过来一个消息,redis会直接找到相应的消费者并传递过去;假如没有消费
者,消息直接丢弃;假如开始有2个消费者,一个消费者突然挂掉了,另外一个消费者依然能收到
消息,但是如果刚挂掉的消费者重新连上后,在断开连接期间的消息对于该消费者来说彻底丢失
了。stream的方式,确保消息一定可达。
另外,redis停机重启,pubsub的消息是不会持久化的,所有的消息被直接丢弃;

应用:

#第一个终端
subscribe news.
#第二个终端
publish news 'king kiss darren'

六 异步连接Redis

redis协议图
在这里插入图片描述
协议实现的第一步需要知道如何界定数据包。常用界定数据包有两种方式:

  1. 长度 + 二进制流
  2. 二进制流 + 特殊分隔符

redis混合了这两种界定数据包的方式。server返回数据的时候,返回的数据头是*n\r\n开头,这个n就表明了返回的数据长度是多少。

异步连接
同步连接方案采用阻塞io来实现;优点是代码书写是同步的,业务逻辑没有割裂;缺点是阻塞当前
线程,直至redis返回结果。
通常用多个线程来实现线程池来解决效率问题。异步连接方案采用非阻塞io来实现,优点是没有阻塞当前线程,redis 没有返回,依然可以往redis发送命令;缺点是代码书写是异步的(回调函数),业务逻辑割裂,可以通过协程解决(openresty,skynet);配合redis6.0以后的io多线程(前提是有大量并发请求),异步连接池,能更好解决应用层的数据访问性能。

redis6.0 io多线程
redis6.0版本后添加的 io多线程主要解决redis协议的压缩以及解压缩的耗时问题,一般项目中不
需要开启。如果有大量并发请求,且返回数据包一般比较大的场景才有它的用武之地。

原理:

int n = read(fd, buff, size);// redis io-threads
msg = decode(buff, size);
data = do_command(msg);
bin = encode(data, sz);// io-threads
send(fd, bin, sz1);

第一行代码表示读取发送给redis的命令,第二行代码表示解析命令,第三行代码表示redis处理命令的逻辑,第四行代码表示对redis返回结果的加密,第五行代码表示将加密好的结果返回给客户端。

可以将第一二四五行代码逻辑交给io线程池处理,以提高效率。

在配置文件中开启的方法是:

# 在 redis.conf 中
# if you have a four cores boxes, try to use 2 or 3 I/O threads, if you have a 8 cores, try to use 6 threads.
io-threads 4
# 默认只开启 encode 也就是redis发送给客户端的协议压缩工作;也可开启io-threads-do-reads yes来实现 decode;
# 一般发送给redis的命令数据包都比较少,所以不需要开启 decode 功能;
# io-threads-do-reads no
<think>我们注意到用户的问题是关于redis-py的异步使用方法。根据之前的对话历史,用户已经了解了aioredis的使用(已合并到redis-py),现在需要了解redis-py的异步使用方式。引用[5]提到:“探索Redis的新纪元:Redis-Py的异步新篇章”,其中介绍了redis-py从4.0版本开始支持异步API。因此,我们将重点介绍redis-py的异步客户端。步骤:1.安装支持异步的redis-py版本(>=4.0)2.使用`redis.asyncio`模块3.创建异步连接(支持单连接和连接池)4.使用异步API(包括基本命令、管道、发布订阅等)具体代码示例:安装:```bashpipinstall redis>=4.0.0```基本使用:```pythonimportredis.asyncio asredisasync defmain():#创建连接(支持连接字符串)client =redis.from_url("redis://localhost:6379", decode_responses=True)#设置和获取键值await client.set("key","value")value =await client.get("key")print(value)#输出: 'value'#使用连接池pool =redis.ConnectionPool.from_url("redis://localhost:6379")async_client= redis.Redis(connection_pool=pool)#管道操作asyncwith async_client.pipeline(transaction=True) aspipe:awaitpipe.set("counter",10).incr("counter").get("counter")result =await pipe.execute()print(result[2])#输出:11#关闭连接(如果是连接池,通常不需要显式关闭单个客户端)awaitclient.close()await async_client.close() #运行异步函数import asyncioasyncio.run(main())```发布订阅示例(异步):```pythonasyncdef pubsub_example():client= redis.from_url("redis://localhost:6379", decode_responses=True)pubsub =client.pubsub()await pubsub.subscribe("channel")#第一种方式:使用asyncfor循环监听asyncfor messagein pubsub.listen():if message["type"] =="message":print(message["data"])break#第二种方式:使用run_in_thread(注意:这里返回的是线程,不是协程,所以通常不推荐在异步代码中使用,但提供了另一种选择)#但注意:在异步环境中,我们更倾向于使用上面的异步迭代器。awaitpubsub.unsubscribe("channel")await client.close()```高级特性:-集群支持:使用`redis.asyncio.cluster.RedisCluster`-异步锁:`fromredis.asyncio.lockimport Lock`注意:-在异步环境中,所有操作都需要使用`await`-连接池管理:使用`ConnectionPool`可以复用连接,提高性能-从aioredis迁移:由于aioredis已经合并到redis-py,所以新项目建议直接使用redis-py的异步接口引用说明:-根据引用[5]可知,redis-py的异步API设计是为了让开发者能够轻松切换到异步处理模式。相关问题:-如何将现有的aioredis代码迁移到redis-py异步客户端?- Redis集群在异步模式下如何配置?-在FastAPI中如何集成redis-py异步客户端?</think>### Redis-py 异步使用指南 Redis-py 从 **4.0+ 版本**开始原生支持异步操作,通过 `redis.asyncio` 模块提供完整的异步 API。以下是核心使用方法: --- #### 🔧 安装配置 ```bash # 安装支持异步的 redis-py 版本 (Python 3.7+) pip install "redis>=4.0.0" ``` --- #### 📌 核心操作 ##### 1. 基础连接命令 ```python import redis.asyncio as redis # 关键:导入异步模块 async def main(): # 创建异步连接 r = await redis.from_url("redis://localhost:6379") # 异步执行命令 await r.set("async_key", "hello") value = await r.get("async_key") print(value) # 输出: b'hello' # 关闭连接 await r.close() # 运行异步函数 import asyncio asyncio.run(main()) ``` ##### 2. 连接池管理 ```python async def pool_demo(): # 创建异步连接池 pool = redis.ConnectionPool.from_url( "redis://localhost:6379", max_connections=10 ) r = redis.Redis(connection_pool=pool) # 并发操作示例 await asyncio.gather( r.incr("counter"), r.hset("user", mapping={"name": "Alice"}) ) # 无需手动关闭连接池 ``` ##### 3. 异步管道 (Pipeline) ```python async def pipeline_demo(): r = await redis.from_url("redis://localhost") async with r.pipeline(transaction=True) as pipe: # 链式调用命令(不立即执行) pipe.set("pipe_key", 100).incr("pipe_key") # 异步提交并获取结果 results = await pipe.execute() print(results) # 输出: [True, 101] ``` --- #### ⚡ 高级功能 ##### 1. 发布订阅 (Pub/Sub) ```python async def subscriber(): r = await redis.from_url("redis://localhost") pubsub = r.pubsub() await pubsub.subscribe("news") # 异步迭代消息流 async for message in pubsub.listen(): if message["type"] == "message": print("收到消息:", message["data"]) async def publisher(): r = await redis.from_url("redis://localhost") await r.publish("news", "最新公告!") ``` ##### 2. 异步分布式锁 ```python from redis.asyncio.lock import Lock async def safe_operation(): r = await redis.from_url("redis://localhost") lock = Lock(r, "resource_lock", timeout=10) async with lock: # 自动获取/释放锁 await critical_section() print("操作完成") ``` ##### 3. 集群支持 ```python from redis.asyncio.cluster import RedisCluster async def cluster_demo(): rc = RedisCluster(host="localhost", port=7000) await rc.set("cluster_key", "data") print(await rc.get("cluster_key")) ``` --- #### 💡 性能优化建议 1. **连接复用** 使用 `ConnectionPool` 减少连接开销[^4]。 2. **批处理优化** 优先使用 `pipeline` 或 `multi_exec` 减少网络往返。 3. **启用 Hiredis** 安装 `hiredis` 提升协议解析速度: ```bash pip install hiredis ``` 4. **连接参数** 调整超时和重试策略: ```python await redis.from_url( "redis://localhost", socket_timeout=2, retry_on_timeout=True ) ``` --- #### ⚠️ 注意事项 1. **版本兼容性** 确保使用 `redis>=4.0.0`,旧版 `aioredis` 已整合到该版本中[^5]。 2. **事件循环** 在异步框架(如 FastAPI)中无需手动创建事件循环。 3. **资源释放** 使用 `async with` 管理连接或手动调用 `await client.close()`。 > 官方迁移提示:新项目应直接使用 `redis.asyncio`,而非旧的 `aioredis` 包[^5]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值