基于Redis的分布式任务调用框架实现

背景

Redis 5.0+版本后,Stream的支持可以使得Redis在轻度场景,作为消息队列使用。最近尝试借用该功能,实现一个基于消息调用的分布式任务框架的Demo,记录下学习内容。

主要功能

Agent+Redis-Stream消息队列,以Agent节点作为消息任务执行端,通过Redis消息传递调用:

  1. Agent节点上下线
  2. Agent的异步调用
  3. Agent的同步调用
  4. 调用消息(任务)的追踪和查询

Agent上线

简单Agent的上下线通过注册带有过期时间的节点HASH信息,实现Agent上报。通过不断地对HASH key续约,达到Agent节点持续在线的目的。基于该方式,Agent进行上报的同时,创建节点HASH Key的索引SET集合。创建索引集合的好处是无需进行全局Key扫描,即可查看所有Agent的在线离线状态。

如下图,本地测试使用SET集合作为节点的索引存储,

Agent异步调用

分析

我们先梳理异步调用的大致步骤:

1、向目标Agent端Stream发送异步调用消息

2、目标端读取Stream消息、并着手处理任务(消息)

3、目标端XACK消息,并将处理结果(HASH消息)回写,将HASH Key同步到索引列表

演示

1、发送 install_rpm的消息给节点 ali-master端,消息id: 7403288395704700928-1

2、Agent ali-master读取消息后,开始处理任务。消息此时进入stream中的pending队列

3、节点ali-master处理完消息并确认

4、查看所有的响应的任务列表

5、原生数据展示,queue:reply 为节点处理完成的消息的ID的索引列表

Agent同步调用

同步调用需要实时等待对方处理消息的结果。为防止无限制等待或者对端异常,至少需要添加超时时间。

高效的做法可通过监听 rpc:reply:message-id 的应答消息HASH Key事件。

演示效果(同步调用2s后返回调用结果):

代码对比如下:

python 复制代码
def cast(self, rpc_message: RPCMessage) -> str:
    """
    异步调用
    """
    redis_conn.xadd(
        self.req_stream, rpc_message.rpc_params, id=rpc_message.id
    )
    return f"{rpc_message.id}"


def call(self, rpc_message: RPCMessage, timeout: int = 30):
    """
    同步调用,基于键空间通知机制
    """
    reply_key = self.reply_key(str(rpc_message.id))
    # 订阅哈希键的事件
    pattern = f'__keyspace@0__:{reply_key}'
    pubsub = redis_conn.pubsub()
    pubsub.psubscribe(pattern)
    
    redis_conn.xadd(self.req_stream, rpc_message.rpc_params, id=rpc_message.id)

    start_at, elapsed_time = time.time(), 0
    try:
        while elapsed_time < timeout:
            msg = pubsub.get_message(timeout=10)
            elapsed_time = time.time() - start_at
            if msg is None:
                continue
                # 检查是否是 hset 相关事件
                if msg['type'] == 'pmessage' and msg['data'] in ['hset', 'hmset', 'set']:
                    reply_data = redis_conn.hgetall(reply_key)
                    return reply_data
    except Exception as e:
        ...
    finally:
        pubsub.unsubscribe(pattern)
    raise TimeoutError(f"{rpc_message.id} timeout after {timeout} seconds")

消息跟踪和查询

相对于grpc方式,Agent+消息队列的方式,能方便地实现消息跟踪、查询。

如果使用Redis做消息队列,需要考虑如下建议:

  1. 开启Redis AOF持久化
  2. 已读取的消息,可在stream中的pending列表中查到
  3. 已处理的消息类型为HSET,并添加消息ID到索引 reply:queue:agent
  4. 可通过reply:queue 查看历史处理的消息
  5. 可根据stream pending列表遍历,查看堆积的消息,看是否重新投递、或者清理
  6. 对于多个redis操作,可尝试使用事务方式执行。

结束

不知道写哈了,结束吧

相关推荐
小马爱打代码1 分钟前
ZooKeeper:入门实战
分布式·zookeeper·云原生
Elastic 中国社区官方博客6 分钟前
Elasticsearch:Workflows 介绍 - 9.3
大数据·数据库·人工智能·elasticsearch·ai·全文检索
仍然.10 分钟前
MYSQL--- 聚合查询,分组查询和联合查询
数据库
一 乐14 分钟前
校园二手交易|基于springboot + vue校园二手交易系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端
啦啦啦_999916 分钟前
Redis-0-业务逻辑
数据库·redis·缓存
自不量力的A同学1 小时前
Redisson 4.2.0 发布,官方推荐的 Redis 客户端
数据库·redis·缓存
Exquisite.1 小时前
Mysql
数据库·mysql
永远都不秃头的程序员(互关)1 小时前
CANN赋能AIGC分布式训练:硬核通信,加速大模型智能生成新纪元
分布式·aigc
fengxin_rou1 小时前
[Redis从零到精通|第四篇]:缓存穿透、雪崩、击穿
java·redis·缓存·mybatis·idea·多线程
全栈前端老曹1 小时前
【MongoDB】深入研究副本集与高可用性——Replica Set 架构、故障转移、读写分离
前端·javascript·数据库·mongodb·架构·nosql·副本集