Redis Pub/Sub 监控任务状态

601 阅读5分钟

1. 前言

在现代应用中,任务执行过程的实时监控至关重要,特别是在处理大量任务或需要高效通知系统的场景下。无论是用户提交的后台处理任务,还是自动化处理流程,及时了解任务的当前状态都能够显著提升用户体验和系统响应能力。Redis的发布/订阅(Pub/Sub)机制,作为一种高效的消息传递工具,可以帮助我们在任务状态变化时即时通知相关方。本文将详细介绍如何使用 Redis Pub/Sub 来进行任务状态的实时监控。

2. 什么是 Redis 的发布/订阅?

Redis 的发布/订阅(Pub/Sub)是一种消息传递机制,它允许应用程序或服务通过频道(Channel)发送(发布)和接收(订阅)消息。发布者将消息发送到频道,而订阅者监听该频道,接收并处理消息。 发布者(Publisher):发布消息到指定的频道。 订阅者(Subscriber):监听一个或多个频道,接收来自这些频道的消息。

3. 环境

  • Linux
  • Python3.8
  • MySQL
  • Shell
  • Redis_version:6.2.11

关于以上环境搭建的细节,这里暂不做详细说明

4.如何使用 Redis Pub/Sub 监控任务状态

在任务执行过程中,我们希望能够实时跟踪每个任务的状态,如“进行中”、“已完成”或“失败”等。通过 Redis 的发布/订阅功能,我们可以轻松实现这一目标。

4.1 发布任务状态

当任务的状态发生变化时,我们可以通过 Redis 发布一个消息,告知所有相关系统或服务该任务的最新状态。例如,某个任务开始执行、完成或失败时,我们将这些变化信息发布到 Redis 频道。

#!/bin/bash

python_folder="/home/linlee/py/活动看板etl"
file_list=("shop_dishes_amount_package_goods.py" "shop_dishes_amount_package_event.py")


# 任务属性
script_id='**********'  # 任务ID
script_name='活动看板etl'  # 任务name
schedule_time=$(date +"%Y-%m-%d")" 10:15:00"  # 调度时间
schedule_platform="dolphinscheduler"  # 调度平台


publish_script="/home/linlee/py/redis订阅者/publish_status.py"


for file_name in "${file_list[@]}"
do
    
    file="$python_folder/$file_name"

    
    if [ -f "$file" ]; then
        # 发布开始状态
        /usr/local/bin/python3.8 "$publish_script" "$script_id" "$script_name" "$schedule_time" "$schedule_platform" "start" "start_time"

        
        echo "***************start*************************"
        echo "Executing $file"

        
        /usr/local/bin/python3.8 "$file"

        # 发布完成状态
        /usr/local/bin/python3.8 "$publish_script" "$script_id" "$script_name" "$schedule_time" "$schedule_platform" "complete" "finish_time"

        
        echo "Finished executing $file"
        echo "***************end*************************"
    else
        
        echo "File $file not found. Skipping..."
    fi
done
import json
import sys
from datetime import datetime
import redis

# 连接到 Redis
client = redis.StrictRedis(host='*******', port=6379, db=0, decode_responses=True)

def publish_status(script_id, script_name, schedule_time, schedule_platform, status, time_key):
    """发布脚本状态更新到 Redis"""
    timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    message = json.dumps({
        'script_name': script_name,
        'script_id': script_id,
        'schedule_time': schedule_time,
        'schedule_platform': schedule_platform,
        'status': status,
        time_key: timestamp
    })

    client.publish('script_status', message)
    print(f"Script {script_id} {status} at {timestamp}...")

if __name__ == "__main__":
    script_id = sys.argv[1]
    script_name = sys.argv[2]
    schedule_time = sys.argv[3]
    schedule_platform = sys.argv[4]
    status = sys.argv[5]
    time_key = sys.argv[6]

    # 发布脚本状态
    publish_status(script_id, script_name, schedule_time, schedule_platform, status, time_key)

由以上脚本可以看出:

在通过 shell 脚本在 Linux 服务器上执行文件夹中的 Python 脚本任务之前,会首先调用 publish_status.py 中的 publish_status 方法,并将相应的 ETL 任务基本属性和 start 状态作为参数传入。在 publish_status.py 中,client.publish 会将任务的状态变更信息发布到 Redis 的 script_status 频道; Python 脚本任务执行之后complete 状态属性也会及时更新到script_status 频道。

4.2 订阅任务状态

我们希望某些系统或前端应用能够实时接收到这些任务状态的更新。因此,订阅者通过 Redis 订阅任务状态频道,接收并处理发布者发送的消息。

import redis
import json
import pymysql
from datetime import datetime

# 连接到 Redis 服务器
client = redis.StrictRedis(host='********', port=6379, db=0)

# 连接到 MySQL 数据库
db_connection = pymysql.connect(
    host='='*****'', 
    user='*****', 
    password='='*****'', 
    database='*****'
)

cursor = db_connection.cursor()


# 定义一个回调函数来处理接收到的消息
def handle_message(message):
    data = json.loads(message['data'])

    script_name = data.get('script_name')
    script_id = data.get('script_id')
    schedule_time = data.get('schedule_time')
    schedule_platform = data.get('schedule_platform')
    status = data.get('status')
    exec_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

    print(f"Received message: Script {script_id} is {status}")

    # 删除同一 script_id 的旧记录
    try:
        delete_query = """
            DELETE FROM script_statuses_redis WHERE script_id = %s
        """
        cursor.execute(delete_query, (script_id,))
        db_connection.commit()
        print(f"Deleted old data for script_id {script_id}")
    except Exception as e:
        print(f"Error deleting old data from database: {e}")
        db_connection.rollback()

    # 插入新数据到 MySQL 数据库
    try:
        insert_query = """
            INSERT INTO script_statuses_redis (script_name,script_id,schedule_time,schedule_platform, status, exec_time)
            VALUES (%s,%s, %s, %s,%s,%s)
        """
        cursor.execute(insert_query, (script_name,script_id,schedule_time,schedule_platform, status, exec_time))
        db_connection.commit()
        print(f"Inserted new data for script_id {script_id}")
    except Exception as e:
        print(f"Error inserting data into database: {e}")
        db_connection.rollback()


# 创建 pubsub 客户端并订阅 `script_status` 频道
pubsub = client.pubsub()
pubsub.subscribe('script_status')

# 监听并处理消息
try:
    for message in pubsub.listen():
        if message['type'] == 'message':
            handle_message(message)
except KeyboardInterrupt:
    print("Subscription stopped.")
finally:
    # 关闭数据库连接
    cursor.close()
    db_connection.close()

[Unit]
Description=My Redis and MySQL Service
After=network.target

[Service]
ExecStart=/usr/local/bin/python3.8 /home/linlee/py/redis订阅者/subscriber_redis.py
WorkingDirectory=/home/linlee/py/redis订阅者
Restart=always
User=root
Group=root
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl start subscriber_redis.service
sudo systemctl stop subscriber_redis.service
sudo systemctl status subscriber_redis.service

image.png

注册为服务后,任何脚本只需订阅 script_status 频道,并持续监听该频道的消息。一旦有新的任务消息变更,订阅者将立即处理这些消息(例如更新前端界面或发送通知)。在本案例中,处理逻辑为将数据插入数据库。

5.小结

通过 Redis 的发布/订阅机制,我们能够非常方便地实现任务状态的实时监控。这种方式不仅解耦了系统之间的依赖关系,还能确保任务状态能够即时传递给所有相关方,提升了系统的响应能力和用户体验。 无论是在后台任务管理系统,还是在用户交互中,Redis Pub/Sub 都能为你带来高效、实时的任务状态监控解决方案。如果你还有更好的其方案,欢迎随时交流。