一、业务中定时任务在集群里的调度执行

1.1 什么是定时任务和集群调度

在业务里,定时任务就好比有个小闹钟,会在特定时间干某些事儿。比如每天凌晨 2 点备份数据库,或者每小时统计一下网站的访问量。而集群呢,就是好多台机器一起干活,就像一个团队。那集群调度就是安排这些机器在什么时间、怎么执行定时任务。

1.2 常见的调度方法

1.2.1 中心化调度

这种方法就像有个“老大”,所有的定时任务安排都由它说了算。“老大”会告诉每台机器什么时候该干啥。

# Python 实现简单的中心化调度示例
import schedule
import time

def job():
    print("This is a scheduled job")

# 每天早上 8 点执行任务
schedule.every().day.at("08:00").do(job)

while True:
    schedule.run_pending()
    time.sleep(1)

这个示例里,Python 的 schedule 库就相当于“老大”,它负责安排任务在每天早上 8 点执行。

优点:好管理,所有任务安排都在一个地方,方便修改和查看。 缺点:“老大”要是出问题了,整个调度就乱套了。

1.2.2 分布式调度

分布式调度就是每台机器自己有点“小主见”,它们会根据一定规则自己决定什么时候执行任务。比如用 Redis 的分布式锁,每台机器去抢锁,抢到了就执行任务。

import redis
import time

# 连接 Redis
r = redis.Redis(host='localhost', port=6379, db=0)

def job():
    # 尝试获取锁
    lock = r.lock('job_lock', timeout=60)
    if lock.acquire():
        try:
            print("Executing the job")
            time.sleep(10)  # 模拟任务执行时间
        finally:
            # 释放锁
            lock.release()

while True:
    job()
    time.sleep(60)

这个示例里,通过 Redis 的锁机制,保证同一时间只有一台机器能执行任务。

优点:可靠性高,一台机器出问题不影响其他机器执行任务。 缺点:管理起来比较复杂,要处理好锁的竞争和释放问题。

1.3 应用场景

1.3.1 数据备份

很多公司会在每天业务低谷期进行数据备份,通过集群调度可以让多台机器同时备份不同的数据,提高效率。

1.3.2 报表生成

每个月、每个季度都要生成业务报表,定时任务可以在特定时间自动从数据库取数据生成报表,集群调度能让这个过程更快速。

1.4 注意事项

  • 任务依赖:有些任务之间是有先后顺序的,调度时要考虑这种依赖关系。
  • 资源分配:要合理分配每台机器的任务,避免有的机器忙死,有的机器闲着。

二、Docker Swarm 运行周期性批处理作业的问题与解决办法

2.1 Docker Swarm 是啥

Docker Swarm 就像是一个“指挥官”,能把好多 Docker 容器组织起来,让它们像一个团队一样工作。周期性批处理作业就是每隔一段时间就执行一次的任务,比如每天晚上清理日志。

2.2 运行周期性批处理作业的坑

2.2.1 重复执行问题

有时候,因为网络波动或者机器故障,同一个任务可能会被执行多次,这就会浪费资源。

2.2.2 任务调度不准确

可能因为 Swarm 集群里机器的负载不均衡,导致任务不能按时执行。

2.3 解决方案

2.3.1 避免重复执行

可以用分布式锁来解决重复执行的问题。比如用 Redis 作为分布式锁的存储。

# 在 Dockerfile 中安装 Redis 客户端
FROM alpine
RUN apk add --no-cache redis

# 编写一个执行任务的脚本,使用 Redis 锁
COPY job.sh /job.sh
RUN chmod +x /job.sh
CMD ["/job.sh"]

#!/bin/sh
# job.sh
REDIS_HOST=redis_server
REDIS_PORT=6379

# 尝试获取锁
if redis-cli -h $REDIS_HOST -p $REDIS_PORT setnx job_lock 1; then
    # 设置锁的过期时间,避免死锁
    redis-cli -h $REDIS_HOST -p $REDIS_PORT expire job_lock 60

    # 执行批处理作业
    echo "Running batch job"

    # 释放锁
    redis-cli -h $REDIS_HOST -p $REDIS_PORT del job_lock
else
    echo "Job is already running. Skipping."
fi

这个示例里,通过 Redis 的 setnx 命令尝试获取锁,获取到了才执行任务,避免了重复执行。

2.3.2 提高调度准确性

可以使用一些专门的调度工具,比如 Cronitor。它可以和 Docker Swarm 集成,更准确地调度任务。

2.4 应用场景

2.4.1 日志清理

每天晚上定时清理 Docker 容器产生的日志,避免占用过多磁盘空间。

2.4.2 数据同步

每隔一段时间从一个数据源同步数据到另一个数据源。

2.5 技术优缺点

优点:

  • 可以利用 Docker 的隔离性,让不同的批处理作业互不影响。
  • Docker Swarm 能自动管理容器的生命周期,方便维护。

缺点:

  • 配置和管理相对复杂,需要对 Docker 和 Swarm 有一定了解。
  • 网络和机器故障可能会影响任务的执行。

2.6 注意事项

  • 容器资源限制:要给每个容器分配合适的资源,避免资源竞争。
  • 日志监控:及时监控任务执行的日志,发现问题及时处理。

三、完整梳理避免重复执行

3.1 重复执行的危害

重复执行会浪费系统资源,比如 CPU、内存和网络带宽。还可能导致数据不一致,比如重复插入数据到数据库。

3.2 通用的避免方法

3.2.1 唯一标识

给每个任务分配一个唯一的标识,执行任务前先检查这个标识是否已经存在。

import redis

r = redis.Redis(host='localhost', port=6379, db=0)

def execute_task(task_id):
    # 检查任务是否已经执行
    if r.sadd('executed_tasks', task_id):
        print(f"Executing task {task_id}")
        # 执行任务的具体代码
    else:
        print(f"Task {task_id} has already been executed. Skipping.")

# 模拟执行任务
execute_task('task_123')

这个示例里,通过 Redis 的 sadd 命令检查任务标识是否已经存在,不存在就执行任务,存在就跳过。

3.2.2 时间窗口

设置一个时间窗口,在这个时间窗口内,同一个任务只执行一次。

import time
import redis

r = redis.Redis(host='localhost', port=6379, db=0)
TIME_WINDOW = 3600  # 时间窗口为 1 小时

def execute_task(task_id):
    last_execution = r.get(task_id)
    if last_execution is None or (time.time() - float(last_execution)) > TIME_WINDOW:
        print(f"Executing task {task_id}")
        r.set(task_id, time.time())
    else:
        print(f"Task {task_id} was executed recently. Skipping.")

# 模拟执行任务
execute_task('task_456')

这个示例里,通过记录任务的最后执行时间,和当前时间比较,判断是否在时间窗口内,从而决定是否执行任务。

3.3 应用场景

3.3.1 数据导入

从外部数据源导入数据到系统时,避免重复导入相同的数据。

3.3.2 消息处理

处理消息队列里的消息时,避免重复处理相同的消息。

3.4 注意事项

  • 唯一标识的设计:要保证唯一标识的唯一性,不能出现重复。
  • 时间窗口的设置:时间窗口要根据实际业务情况合理设置,不能太短也不能太长。

四、总结

在业务里,定时任务的调度执行很重要,尤其是在集群环境中。中心化调度和分布式调度各有优缺点,要根据实际情况选择。Docker Swarm 能很好地运行周期性批处理作业,但要注意避免重复执行和提高调度准确性的问题。通过一些通用的方法,比如使用唯一标识和时间窗口,可以有效避免任务的重复执行。

在实际应用中,要根据不同的业务场景,合理选择技术和方法,同时注意资源分配、任务依赖、容器资源限制和日志监控等问题,这样才能让业务稳定、高效地运行。