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配置是否正确。
-
原因
:可能是 Redis 服务未正常启动,或者工作者容器中的
-
问题 2:添加任务到队列后,工作者 Pod 没有处理任务
- 原因 :可能是工作者容器中的代码出现错误,或者队列中没有任务。
-
解决方案
:查看工作者 Pod 的日志,检查是否有错误信息。同时,使用
kubectl exec命令进入工作者容器,手动检查队列的状态。
-
问题 3:HPA 没有按预期扩展 Pod
- 原因 :可能是 HPA 的配置参数不合理,或者监控指标未正确采集。
-
解决方案
:检查 HPA 的配置文件,确保
minReplicas、maxReplicas和targetCPUUtilizationPercentage等参数设置正确。同时,检查 Kubernetes 的监控系统是否正常工作。
16. 未来展望
随着业务的发展,我们可以对现有的任务处理系统进行进一步的优化和扩展:
- 引入分布式锁 :对于一些需要保证任务唯一性的场景,可以引入分布式锁,如 Redis 的 Redlock 算法,确保同一时间只有一个工作者处理某个任务。
- 集成监控系统 :将 Prometheus 和 Grafana 等监控系统集成到 Kubernetes 中,实现对任务处理系统的实时监控和可视化展示。
- 支持更多任务类型 :除了计算圆周率的任务,还可以支持更多类型的任务,如文件处理、数据清洗等。
通过不断地优化和扩展,我们可以构建一个更加健壮、高效的任务处理系统,满足不同业务场景的需求。
总之,Redis 队列和 Kubernetes 的结合为我们提供了一个强大的后台任务处理解决方案。通过合理的设计和实践,我们可以充分发挥它们的优势,构建出高可用、可扩展的应用系统。
超级会员免费看
535

被折叠的 条评论
为什么被折叠?



