21、Redis任务队列与Kubernetes部署实践

Redis任务队列与Kubernetes部署实践

1. Redis队列基础

Redis 提供了多种便捷的数据结构,其中队列是我们本次使用的重点。为实现先进先出(FIFO)的顺序,这在后台队列处理中很常见,我们将使用两个关键函数:
- RPUSH :用于将元素添加到队列的尾部。
- BLPOP :用于从队列的头部弹出元素,如果队列为空则会阻塞等待。

可以将队列想象成从右到左排列,最右边的元素在队列尾部,最左边的元素在队列头部。这样,“L”和“R”函数前缀就很好理解了,“R”表示右边(如 RPUSH 是从右边添加元素),“L”表示左边(如 BLPOP 是从左边弹出元素)。“B”前缀表示函数的阻塞形式,即当队列为空时,它会等待元素而不是立即返回 nil。

为了更直观地理解,我们以一个简单的任务为例:输入一个整数 n ,使用莱布尼茨级数公式计算圆周率 $\pi$。迭代次数越多,计算结果越精确。在实际应用中,任务可能会更复杂,例如创建 ZIP 文件或压缩图像等。

2. 创建工作容器

我们使用 Python 来创建工作容器,相关代码由三个 Python 文件组成:

2.1 pi.py :工作函数实现
from decimal import *

# Calculate pi using the Gregory-Leibniz infinity series
def leibniz_pi(iterations):
    precision = 20
    getcontext().prec = 20
    piDiv4 = Decimal(1)
    odd = Decimal(3)
    for i in range(0, iterations):
        piDiv4 = piDiv4 - 1/odd
        odd = odd + 2
        piDiv4 = piDiv4 + 1/odd
        odd = odd + 2
    return piDiv4 * 4

这个文件负责实际的计算,它不关心是否在队列中,只是单纯地执行计算任务。

2.2 pi_worker.py :工作者实现
import os
import redis
from pi import *

redis_host = os.environ.get('REDIS_HOST')
assert redis_host != None
r = redis.Redis(host=redis_host, port='6379', decode_responses=True)
print("starting")
while True:
    task = r.blpop('queue:task')  # A
    iterations = int(task[1])
    print("got task: " + str(iterations))
    pi = leibniz_pi(iterations)  # B
    print(pi)
# A Pop the next task (and block if there are none in the queue)
# B Perform the work

该文件从队列头部获取任务,并调用 leibniz_pi 函数执行任务。使用 Redis 的 BLPOP 命令从队列中获取任务,如果队列为空则会阻塞等待。

2.3 add_tasks.py :添加任务到队列
import os
import redis
import random

redis_host = os.environ.get('REDIS_HOST')
assert redis_host != None
r = redis.Redis(host=redis_host, port='6379', decode_responses=True)  # A
random.seed()
for i in range(0, 10):
    rand = random.randint(10, 100)
    iterations = rand * 100000
    r.rpush('queue:task', iterations)
    print("added task: " + str(iterations))  # B
print("queue depth", str(r.llen('queue:task')))
print("done")
# A Connect to our Redis service
# B Add the task with the “iterations” value to our ‘queue:task’ list

此文件用于向队列中添加任务,通过 RPUSH 命令将随机生成的任务添加到队列中。

将这些 Python 脚本打包成容器非常简单,使用官方 Python 基础镜像,并添加 Redis 依赖。以下是 Dockerfile:

FROM python:3
RUN pip install redis
COPY . /app
WORKDIR /app
CMD python3 pi_worker.py
3. 部署到 Kubernetes

Kubernetes 架构包含运行 Redis 的 StatefulSet 和运行工作者 Pod 的 Deployment。我们将手动添加任务进行演示。

3.1 部署 Redis

使用之前的 Redis 部署方案,例如 9.2.2_StatefulSet_Redis_Replicated 文件夹中的配置。从代码示例根目录运行以下命令:

$ kubectl create -f Chapter09/9.2.2_StatefulSet_Redis_Replicated
$ kubectl get pods
NAME      READY   STATUS     RESTARTS   AGE
redis-0   1/1     Running    0          20s
redis-1   1/1     Running    0          13s
redis-2   0/1     Init:0/1   0          7s
3.2 部署工作者

使用以下 deploy_worker.yaml 文件部署工作者:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pi-worker
spec:
  replicas: 2
  selector:
    matchLabels:
      app: pi
  template:
    metadata:
      labels:
        app: pi
    spec:
      containers:
      - name: pi-container
        image: docker.io/wdenniss/pi_worker:v1
        env:
        - name: REDIS_HOST  # A
          value: redis-0.redis-service  # A
        - name: PYTHONUNBUFFERED  # B
          value: "1"  # B
# A The Kubernetes service host name of the master Redis pod
# B Env variable to instruct Python to output all print statements immediately

运行以下命令进行部署:

kubectl create -f Chapter10/10.1.1_TaskQueue/deploy_worker.yaml
3.3 验证部署

最后,验证所有 Pod 是否正常运行:

$ kubectl get pods
NAME                         READY   STATUS    RESTARTS   AGE
pi-worker-55477bdf7b-7rmhp   1/1     Running   0          2m5s
pi-worker-55477bdf7b-ltcsd   1/1     Running   0          2m5s
redis-0                      1/1     Running   0          3m41s
redis-1                      1/1     Running   0          3m34s
redis-2                      1/1     Running   0          3m28s
4. 监控部署进度

可以使用以下两种方法监控部署进度:
- Kubernetes 内置监控 :在 kubectl 命令后添加 -w 参数,例如 kubectl get pods -w
- Linux watch 命令 :使用 watch -d kubectl get pods 命令,每 2 秒刷新一次状态,并突出显示变化。可以自定义刷新频率。

5. 查看日志

Kubernetes 没有内置的同时流式查看多个 Pod 日志的功能,但可以随机选择一个 Pod 并跟踪其日志:

$ kubectl logs -f deployment/pi-worker
Found 2 pods, using pod/pi-worker-55477bdf7b-7rmhp
Starting

如果想查看部署中所有 Pod 的日志(但不流式输出),可以通过标签引用:

$ kubectl logs --selector app=pi
starting
starting
6. 添加任务到队列

通常,Web 应用程序或其他进程会向队列添加任务,只需调用 redis.rpush('queue:task', object) 即可。对于本示例,我们可以在 Pod 中执行 add_tasks.py 脚本:

$ kubectl exec -it deploy/pi-worker -- python3 add_tasks.py
added task: 9500000
added task: 3800000
added task: 1900000
added task: 3600000
added task: 1200000
added task: 8600000
added task: 7800000
added task: 7100000
added task: 1400000
added task: 5600000
queue depth 8
done
7. 查看工作状态

添加任务后,查看工作者 Pod 的日志,观察任务执行情况:

$ kubectl logs -f deployment/pi-worker
Found 2 pods, using pod/pi-worker-54dd47b44c-bjccg
starting
got task: 9500000
3.1415927062213693620
got task: 8600000
3.1415927117293246813
got task: 7100000
3.1415927240123234505
8. 工作者 Pod 的信号处理

上述工作者实现没有处理 SIGTERM 信号,这意味着当 Pod 需要被替换时,它无法优雅地关闭。为了解决这个问题,我们可以添加信号处理程序:

import os
import signal
import redis
from pi import *

redis_host = os.environ.get('REDIS_HOST')
assert redis_host != None
r = redis.Redis(host=redis_host, port= '6379', decode_responses=True)
running = True  # A

def signal_handler(signum, frame):  # B
    print("got signal")  # B
    running = False  # B

signal.signal(signal.SIGTERM, signal_handler)  # B
print("starting")
while running:
    task = r.blpop('queue:task', 5)  # C
    if task != None:
        iterations = int(task[1])
        print("got task: " + str(iterations))
        pi = leibniz_pi(iterations)
        print(pi)
# A Add a running state variable instead of looping indefinitely
# B Register a signal handler to set the running state to false when SIGTERM received
# C Pop the next task, but only wait for 5 seconds so the loop can terminate. Returns “None” if there are no tasks in the queue after 5 seconds.

同时,在部署文件中添加 terminationGracePeriodSeconds 来指定处理 SIGTERM 信号的时间:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pi-worker
spec:
  replicas: 2
  selector:
    matchLabels:
      app: pi
  template:
    metadata:
      labels:
        app: pi
    spec:
      containers:
      - name: pi-container
        image: docker.io/wdenniss/pi_worker:v2
        imagePullPolicy: Always
        env:
        - name: REDIS_HOST
          value: redis-0.redis-service
        - name: PYTHONUNBUFFERED
          value: "1"
        resources:
            requests:
              cpu: 250m
              memory: 250Mi
      terminationGracePeriodSeconds: 120
9. 扩展工作者 Pod

扩展工作者 Pod 与其他 Deployment 的扩展方法相同,可以手动设置副本数量,也可以使用 Horizontal Pod Autoscaler (HPA)。由于示例工作负载是 CPU 密集型的,使用 CPU 指标进行扩展效果较好。以下是 HPA 的配置文件:

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: pi-worker-autoscaler
spec:
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 20
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: pi-worker

创建 HPA:

kubectl create -f kubectl create -f Chapter10/10.1.3_HPA

添加任务后,观察 HPA 的自动扩展效果:

$ kubectl exec -it deploy/pi-worker -- python3 add_tasks.py
$ kubectl get pods,hpa
NAME                             READY   STATUS    RESTARTS   AGE
pod/pi-worker-54dd47b44c-22x9b   1/1     Running   0          2m42s
pod/pi-worker-54dd47b44c-9wppc   1/1     Running   0          2m27s
pod/pi-worker-54dd47b44c-bjccg   1/1     Running   0          13m
pod/pi-worker-54dd47b44c-f79hx   1/1     Running   0          2m42s
pod/pi-worker-54dd47b44c-fptj9   1/1     Running   0          2m27s
pod/pi-worker-54dd47b44c-hgbqd   1/1     Running   0          2m27s
pod/pi-worker-54dd47b44c-lj2bk   1/1     Running   0          2m27s
pod/pi-worker-54dd47b44c-wc267   1/1     Running   0          2m10s
pod/pi-worker-54dd47b44c-wk4dg   1/1     Running   0          2m10s
pod/pi-worker-54dd47b44c-x2s4m   1/1     Running   0          13m
pod/redis-0                      1/1     Running   0          56m
pod/redis-1                      1/1     Running   0          56m
pod/redis-2                      1/1     Running   0          56m
NAME                                                       REFERENCE              TARGETS   
MINPODS   MAXPODS   REPLICAS   AGE
horizontalpodautoscaler.autoscaling/pi-worker-autoscaler   Deployment/pi-worker   66%/20%   
2         10        10         3m46s
10. 开源任务队列

除了自己构建任务队列,还可以选择使用开源任务队列。例如:
- Python - RQ :允许直接将函数调用及其参数入队,无需将函数包装在实现特定抽象方法的对象中。参考链接:https://python-rq.org/
- Ruby - Resque :由 GitHub 团队创建,任务是实现 perform 方法的 Ruby 类。Ruby on Rails 框架通过 Active Job 框架使 Resque 易于使用。参考链接:https://github.com/resque/resque

11. Kubernetes Jobs

Kubernetes 提供了 Job 构造来定义有限的工作集。Job 和 Deployment 都可用于处理“批处理作业”和后台处理,但关键区别在于:
- Job :设计用于处理有限的工作集,可能无需像 Redis 这样的队列数据结构。可用于运行一次性和周期性任务,如维护操作。
- Deployment :用于持续运行的后台队列,需要某种队列结构进行协调。

综上所述,通过 Redis 队列和 Kubernetes 的结合,我们可以构建高效、可扩展的后台任务处理系统。同时,要注意信号处理和任务的容错性,根据实际需求选择合适的任务队列解决方案。

Redis任务队列与Kubernetes部署实践

12. 总结与最佳实践

通过前面的步骤,我们完成了基于 Redis 队列的任务处理系统在 Kubernetes 上的部署,并对其进行了监控、扩展等操作。以下是一些实践过程中的总结和最佳实践:

  • 信号处理 :在工作者容器中添加 SIGTERM 信号处理是非常必要的,它能保证容器在接收到终止信号时,能够优雅地停止工作,避免任务丢失。
  • 日志查看 :虽然 Kubernetes 没有内置的多 Pod 日志流式查看功能,但可以通过标签引用查看所有 Pod 的日志,方便排查问题。
  • 扩展策略 :对于 CPU 密集型的工作负载,使用 HPA 基于 CPU 指标进行扩展是一个不错的选择。但要注意 HPA 的目标 CPU 利用率是绝对 CPU 单位,而不是相对请求的 CPU 百分比。
  • 开源选择 :在构建任务队列时,优先考虑使用开源的任务队列,如 Python 的 RQ 和 Ruby 的 Resque,它们可以节省开发时间和精力。
13. 操作流程总结

为了更清晰地展示整个系统的部署和操作流程,我们可以将其总结为以下步骤:

步骤 操作内容 命令示例
1 创建工作容器 使用 Python 脚本和 Dockerfile 构建容器
2 部署 Redis kubectl create -f Chapter09/9.2.2_StatefulSet_Redis_Replicated
3 部署工作者 kubectl create -f Chapter10/10.1.1_TaskQueue/deploy_worker.yaml
4 验证部署 kubectl get pods
5 监控部署进度 kubectl get pods -w watch -d kubectl get pods
6 查看日志 kubectl logs -f deployment/pi-worker kubectl logs --selector app=pi
7 添加任务到队列 kubectl exec -it deploy/pi-worker -- python3 add_tasks.py
8 查看工作状态 kubectl logs -f deployment/pi-worker
9 扩展工作者 Pod kubectl create -f Chapter10/10.1.3_HPA
14. 流程图展示

下面是一个 mermaid 格式的流程图,展示了整个系统的工作流程:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px

    A([开始]):::startend --> B(创建工作容器):::process
    B --> C(部署 Redis):::process
    C --> D(部署工作者):::process
    D --> E(验证部署):::process
    E --> F{部署是否成功?}:::decision
    F -->|是| G(监控部署进度):::process
    F -->|否| D
    G --> H(查看日志):::process
    H --> I(添加任务到队列):::process
    I --> J(查看工作状态):::process
    J --> K(扩展工作者 Pod):::process
    K --> L([结束]):::startend
15. 常见问题与解决方案

在实际操作过程中,可能会遇到一些常见问题,以下是一些问题及对应的解决方案:

  • 问题 1:工作者 Pod 无法连接到 Redis

    • 原因 :可能是 Redis 服务未正常启动,或者工作者容器中的 REDIS_HOST 环境变量配置错误。
    • 解决方案 :检查 Redis Pod 的状态,确保其正常运行。同时,检查 deploy_worker.yaml 文件中的 REDIS_HOST 配置是否正确。
  • 问题 2:添加任务到队列后,工作者 Pod 没有处理任务

    • 原因 :可能是工作者容器中的代码出现错误,或者队列中没有任务。
    • 解决方案 :查看工作者 Pod 的日志,检查是否有错误信息。同时,使用 kubectl exec 命令进入工作者容器,手动检查队列的状态。
  • 问题 3:HPA 没有按预期扩展 Pod

    • 原因 :可能是 HPA 的配置参数不合理,或者监控指标未正确采集。
    • 解决方案 :检查 HPA 的配置文件,确保 minReplicas maxReplicas targetCPUUtilizationPercentage 等参数设置正确。同时,检查 Kubernetes 的监控系统是否正常工作。
16. 未来展望

随着业务的发展,我们可以对现有的任务处理系统进行进一步的优化和扩展:

  • 引入分布式锁 :对于一些需要保证任务唯一性的场景,可以引入分布式锁,如 Redis 的 Redlock 算法,确保同一时间只有一个工作者处理某个任务。
  • 集成监控系统 :将 Prometheus 和 Grafana 等监控系统集成到 Kubernetes 中,实现对任务处理系统的实时监控和可视化展示。
  • 支持更多任务类型 :除了计算圆周率的任务,还可以支持更多类型的任务,如文件处理、数据清洗等。

通过不断地优化和扩展,我们可以构建一个更加健壮、高效的任务处理系统,满足不同业务场景的需求。

总之,Redis 队列和 Kubernetes 的结合为我们提供了一个强大的后台任务处理解决方案。通过合理的设计和实践,我们可以充分发挥它们的优势,构建出高可用、可扩展的应用系统。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值