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
注册为服务后,任何脚本只需订阅
script_status频道,并持续监听该频道的消息。一旦有新的任务消息变更,订阅者将立即处理这些消息(例如更新前端界面或发送通知)。在本案例中,处理逻辑为将数据插入数据库。
5.小结
通过 Redis 的发布/订阅机制,我们能够非常方便地实现任务状态的实时监控。这种方式不仅解耦了系统之间的依赖关系,还能确保任务状态能够即时传递给所有相关方,提升了系统的响应能力和用户体验。 无论是在后台任务管理系统,还是在用户交互中,Redis Pub/Sub 都能为你带来高效、实时的任务状态监控解决方案。如果你还有更好的其方案,欢迎随时交流。