背景
Redis 5.0+版本后,Stream的支持可以使得Redis在轻度场景,作为消息队列使用。最近尝试借用该功能,实现一个基于消息调用的分布式任务框架的Demo,记录下学习内容。
主要功能
Agent+Redis-Stream消息队列,以Agent节点作为消息任务执行端,通过Redis消息传递调用:
- Agent节点上下线
- Agent的异步调用
- Agent的同步调用
- 调用消息(任务)的追踪和查询

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做消息队列,需要考虑如下建议:
- 开启Redis AOF持久化
- 已读取的消息,可在stream中的pending列表中查到
- 已处理的消息类型为HSET,并添加消息ID到索引 reply:queue:agent
- 可通过reply:queue 查看历史处理的消息
- 可根据stream pending列表遍历,查看堆积的消息,看是否重新投递、或者清理
- 对于多个redis操作,可尝试使用事务方式执行。
结束
不知道写哈了,结束吧