0%


Kubernetes (K8s) 运维笔记

目录

  1. Kubernetes 基础概念
  2. Pod 生命周期管理
  3. Kubernetes 常用命令
  4. K8s 资源管理
  5. 网络插件
  6. 调度与负载均衡
  7. Kubernetes 高可用性

1. Kubernetes 基础概念

K8s 核心组件

组件描述
Pod最小的部署单元,封装一个或多个容器
Node集群中的单个计算节点,运行 Pod 和代理服务
Cluster一组 Node,协调资源的调度和管理
Kube-apiserver集群的入口点,处理 REST 请求
etcd用于存储集群的状态数据
Kube-scheduler负责 Pod 的调度到 Node
Kube-controller-manager负责管理控制器,实现自动化控制
Kubelet运行在每个 Node 上,管理 Pod 和容器
Kube-proxy处理网络规则,负载均衡 Pod 请求

2. Pod 生命周期管理

Pod 生命周期阶段

  • Pending: Pod 被创建,但未绑定到 Node。
  • Running: Pod 已绑定到 Node 并启动了容器。
  • Succeeded: 所有容器成功完成并退出。
  • Failed: 某些容器异常终止并退出。
  • Unknown: 无法与 Node 通信。

Pod 生命周期管理组件

组件描述
Init Containers初始化容器,确保主容器启动前某些任务完成
Liveness Probe健康检查,确保容器存活
Readiness Probe就绪检查,确保容器可接受流量
Startup Probe启动检查,确保容器启动后运行稳定

3. Kubernetes 常用命令

集群管理命令

  • 查看集群状态

    1
    kubectl cluster-info
  • 查看所有 Node

    1
    kubectl get nodes
  • 获取 Pod 详情

    1
    kubectl describe pod <pod_name>

Pod 操作命令

  • 查看所有 Pod

    1
    kubectl get pods
  • 创建 Pod

    1
    kubectl run <pod_name> --image=<image_name>
  • 删除 Pod

    1
    kubectl delete pod <pod_name>

其他常用命令

  • 获取 Service 列表

    1
    kubectl get svc
  • 获取 Deployment 列表

    1
    kubectl get deployment

4. K8s 资源管理

资源种类

  • Pod: Kubernetes 中最小的部署单元。
  • Service: 用于暴露 Pod 服务,支持负载均衡。
  • ConfigMap: 存储配置信息,用于 Pod 内配置文件。
  • Secret: 安全存储敏感信息,例如密码和 API 密钥。
  • Volume: 用于持久化存储,Pod 重启后数据不会丢失。

命令示例

  • 创建 Deployment

    1
    kubectl create deployment <name> --image=<image>
  • 删除资源

    1
    kubectl delete <resource_type> <resource_name>

5. 网络插件

常见网络插件

  • Flannel: 最基础的网络插件,简单易用。
  • Calico: 支持网络策略,灵活且性能好。
  • Weave: 支持自动网络发现和加密。
  • Cilium: 基于 eBPF,支持细粒度的安全控制。

插件管理

  • 安装 Flannel

    1
    kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
  • 查看网络状态

    1
    kubectl get pods -n kube-system

6. 调度与负载均衡

K8s 调度算法

  • RR (Round Robin): 按顺序将请求调度到不同节点。
  • Least Connections: 将请求调度到连接数最少的节点。
  • Resource-Based: 根据节点资源使用情况进行调度。

负载均衡

  • Kubernetes 内置 Service 对 Pod 进行负载均衡。
  • 使用 Ingress 管理外部 HTTP 和 HTTPS 访问。

7. Kubernetes 高可用性

高可用架构

  • Master 高可用: 通过多 Master 节点保证控制平面高可用。
  • etcd 高可用: 使用 etcd 集群存储数据,保证高可用。
  • Node 高可用: 通过节点间冗余,Pod 可以在不同节点上运行,避免单点故障。

水平扩展

  • 使用 Horizontal Pod Autoscaler (HPA) 根据负载自动扩展 Pod。


数据库运维笔记

目录

  1. 数据库基本概念
  2. MySQL 基本操作
  3. 高可用解决方案
  4. 常见 SQL 语句
  5. 数据库备份与恢复
  6. 数据库性能优化

1. 数据库基本概念

ACID 特性

  • Atomicity(原子性): 事务的所有操作要么全部完成,要么全部不完成。
  • Consistency(一致性): 事务执行前后,数据库处于一致状态。
  • Isolation(隔离性): 并发事务互不干扰。
  • Durability(持久性): 事务一旦提交,修改将永久保存。

数据库引擎

  • InnoDB: 支持事务,支持外键,提供崩溃恢复功能。
  • MyISAM: 不支持事务,速度快,适用于读多写少的应用。

2. MySQL 基本操作

启动与停止 MySQL 服务

  • 启动服务

    1
    systemctl start mysqld
  • 停止服务

    1
    systemctl stop mysqld

登录 MySQL

1
mysql -u root -p

数据库管理

  • 创建数据库

    1
    CREATE DATABASE db_name;
  • 删除数据库

    1
    DROP DATABASE db_name;

用户管理

  • 创建用户

    1
    CREATE USER 'username'@'host' IDENTIFIED BY 'password';
  • 删除用户

    1
    DROP USER 'username'@'host';

3. 高可用解决方案

主从复制 (Master-Slave Replication)

  • 主服务器 (Master): 负责处理写操作,数据变化会传递给从服务器。
  • 从服务器 (Slave): 负责处理读操作,并同步主服务器的数据。

双主复制 (Master-Master Replication)

  • 两个服务器互为主服务器,支持双向数据同步。

PXC (Percona XtraDB Cluster)Galera Cluster

  • 提供多主复制,所有节点都能读写,实现真正的高可用。

Keepalived + MySQL: 通过虚拟 IP 实现 MySQL 的高可用性,保证数据库服务不间断。


4. 常见 SQL 语句

数据查询

  • 查询所有数据

    1
    SELECT * FROM table_name;
  • 查询指定条件的数据

    1
    SELECT * FROM table_name WHERE condition;

数据插入

  • 插入数据到表中
    1
    INSERT INTO table_name (column1, column2) VALUES (value1, value2);

数据更新

  • 更新表中的数据
    1
    UPDATE table_name SET column1 = value1 WHERE condition;

数据删除

  • 删除表中的数据
    1
    DELETE FROM table_name WHERE condition;

表结构管理

  • 创建表

    1
    2
    3
    4
    5
    CREATE TABLE table_name (
    column1 datatype,
    column2 datatype,
    ...
    );
  • 删除表

    1
    DROP TABLE table_name;
  • 修改表结构

    1
    ALTER TABLE table_name ADD column_name datatype;

5. 数据库备份与恢复

备份

  • 使用 mysqldump 进行数据库备份
    1
    mysqldump -u root -p database_name > backup.sql

恢复

  • 从备份文件恢复数据库

    1
    mysql -u root -p database_name < backup.sql
  • 恢复所有数据库

    1
    mysql -u root -p < alldatabases_backup.sql

增量备份

  • 使用 binlog 进行增量备份
    1
    mysqlbinlog binlog.000001 > incremental_backup.sql

6. 数据库性能优化

索引优化

  • 使用索引加速查询

    1
    CREATE INDEX idx_name ON table_name (column_name);
  • 查看表的索引

    1
    SHOW INDEX FROM table_name;
  • 删除索引

    1
    DROP INDEX idx_name ON table_name;

查询优化

  • 避免使用 SELECT *,只查询需要的字段。
  • 使用 EXPLAIN 语句分析查询语句的执行计划。
    1
    EXPLAIN SELECT * FROM table_name WHERE condition;

表优化

  • 对表进行碎片整理,提升性能
    1
    OPTIMIZE TABLE table_name;

缓存优化

  • 使用查询缓存减少重复查询,提升性能。
    • 启用查询缓存:
      在 MySQL 配置文件 my.cnf 中添加
      1
      2
      query_cache_type = 1
      query_cache_size = 64M


Redis 运维笔记

目录

  1. Redis 基本概念
  2. Redis 持久化机制
  3. Redis 高可用
  4. Redis 常用命令
  5. Redis 性能优化

1. Redis 基本概念

Redis 是什么?

  • Redis 是一个开源的内存数据结构存储系统,支持多种数据结构如:字符串、哈希、列表、集合、有序集合等。
  • Redis 提供持久化机制,可以将内存中的数据保存到磁盘中,重启时加载使用。

Redis 应用场景

  • 缓存: 提高访问速度,减少数据库压力。
  • 分布式锁: 利用 Redis 的原子操作实现高效分布式锁。
  • 消息队列: 利用列表结构实现高效队列。

2. Redis 持久化机制

Redis 支持两种主要的持久化方式:

1. RDB (Redis Database Backup)

  • 原理: 在指定时间间隔内生成数据快照并保存到磁盘。

  • 优点: RDB 文件紧凑,适合备份。

  • 缺点: 数据不是实时持久化,可能丢失部分数据。

  • 配置示例:

    1
    2
    3
    save 900 1
    save 300 10
    save 60 10000

2. AOF (Append-Only File)

  • 原理: 记录每次写操作,以日志的形式追加到文件中。

  • 优点: 数据持久化更实时,默认每秒保存一次。

  • 缺点: AOF 文件比 RDB 更大,恢复速度较慢。

  • 配置示例:

    1
    2
    appendonly yes
    appendfsync everysec

选择合适的持久化方式

  • RDB: 用于备份,适合在不要求实时性但需要快速恢复的场景。
  • AOF: 用于数据更实时的持久化,但需要更多的磁盘资源。

3. Redis 高可用

1. 主从复制 (Master-Slave Replication)

  • Redis 支持异步复制,一个 Master 可以有多个 Slave。
  • Slave 只读,接收 Master 数据的更新。
  • 命令:
    1
    SLAVEOF <master-ip> <master-port>

2. 哨兵模式 (Sentinel)

  • 实现高可用性,哨兵可以监控主从实例的运行状态,并在主节点故障时自动进行故障转移。
  • 优点: 自动故障切换,监控实例状态。
  • 配置文件:
    1
    sentinel monitor mymaster 127.0.0.1 6379 2

3. Redis 集群 (Redis Cluster)

  • 分布式: 将数据分布在多个节点上,支持水平扩展。
  • 哈希槽 (Hash Slot): Redis 集群通过哈希槽将数据分布在多个节点,集群默认有 16384 个槽。
  • 支持自动分片和故障转移。

4. Redis 常用命令

键管理

  • 查看所有键:

    1
    KEYS *
  • 删除键:

    1
    DEL <key>

字符串操作

  • 设置键值:

    1
    SET <key> <value>
  • 获取键值:

    1
    GET <key>

列表操作

  • 添加元素到列表:

    1
    LPUSH <key> <value>
  • 获取列表元素:

    1
    LRANGE <key> 0 -1

哈希操作

  • 设置哈希字段:

    1
    HSET <key> <field> <value>
  • 获取哈希字段:

    1
    HGET <key> <field>

集合操作

  • 添加元素到集合:

    1
    SADD <key> <value>
  • 获取集合所有元素:

    1
    SMEMBERS <key>

有序集合操作

  • 添加元素到有序集合:

    1
    ZADD <key> <score> <value>
  • 获取有序集合元素:

    1
    ZRANGE <key> 0 -1

5. Redis 性能优化

1. 使用缓存

  • 合理设置缓存过期时间,避免占用过多内存。
  • 配置示例:
    1
    2
    maxmemory 2gb
    maxmemory-policy allkeys-lru

2. 数据分区

  • 使用 Redis 集群,将数据分散到不同节点,减少单个节点的压力。

3. 禁用 AOF 或调整 AOF 策略

  • 如果对数据一致性要求不高,可以禁用 AOF 或调整为 everysec 以提高性能。

4. 优化 Redis 配置

  • 增加后台线程,调整 io-threads 来提升并发性能。


系统性能监控笔记

目录

  1. Linux 系统负载监控
  2. 网络监控
  3. 磁盘与 IO 监控
  4. 内存监控
  5. 系统性能分析工具

1. Linux 系统负载监控

查看系统负载

uptime

  • 显示系统的负载平均值。
    1
    uptime

top

  • 动态显示系统的进程和负载信息。
    1
    top

load average 的含义

  • Load average 是系统在 1 分钟、5 分钟、15 分钟内的平均负载。

2. 网络监控

查看网络连接状态

netstat

  • 显示网络连接、路由表、接口统计信息。
    1
    netstat -an

ss

  • 替代 netstat 的工具,更加快速。
    1
    ss -antp

查看带宽使用情况

iftop

  • 实时监控网络流量。
    1
    iftop

nload

  • 监控网络接口带宽使用情况。
    1
    nload

3. 磁盘与 IO 监控

查看磁盘空间

df

  • 显示磁盘使用情况。
    1
    df -h

du

  • 显示文件和目录的磁盘使用情况。
    1
    du -sh <directory>

监控 IO 性能

iostat

  • 监控 CPU 和设备的 IO 统计。
    1
    iostat

iotop

  • 实时查看磁盘 IO 使用情况。
    1
    iotop

4. 内存监控

查看内存使用情况

free

  • 显示系统的内存使用情况。
    1
    free -m

vmstat

  • 报告虚拟内存统计信息。
    1
    vmstat

top

  • top 命令中查看内存使用情况。

5. 系统性能分析工具

sar

  • 收集、报告和保存系统活动信息。
    1
    sar -u 1 3   # 每秒收集一次 CPU 使用率,连续三次

htop

  • 类似 top,但提供了更好的 UI 和更多功能。
    1
    htop

perf

  • Linux 性能分析工具,用于剖析 CPU 使用情况和应用性能。
    1
    perf top

在开发QQ机器人项目的过程中,我经历了一次颇具戏剧性的性能问题排查。这次乌龙事件不仅让我对Python的装饰器机制有了更深入的理解,也让我明白了细节对于程序性能的重要性。在此,我想分享整个排查过程,并深入探讨Python的装饰器。

问题的起因

为了提高代码的复用性和可维护性,我在项目中广泛使用了Python的装饰器,来实现鉴权、计算函数运行时间、捕获异常、限速、超时重试、屏蔽词过滤等功能。

某天,当我完成这些装饰器的更新后,重新运行机器人,意外地发现:原本仅需3到5秒就能回复消息的机器人,竟需要等待十几二十秒才能响应。这种明显的性能下降引起了我的注意。

初步排查

面对这个问题,我首先怀疑是程序内部逻辑出了问题。为了找到可能的异常,我在与消息处理相关的各个位置添加了日志。然而,日志显示程序运行正常,没有任何异常信息。

接下来,我怀疑是否是系统资源瓶颈导致的。于是,我检查了电脑的内存占用和硬盘I/O,结果它们都在正常范围内。而且,项目使用的是以高性能著称的MongoDB数据库,理论上应该不会成为瓶颈。

为了彻底排除数据库的问题,我尝试在消息数据和数据库之间增加一层缓存,直接使用内存来保存消息队列。然而,这并没有改善速度,机器人回复仍然需要十几秒。

灵光一闪

一系列的排查未能找到问题所在,让我一度陷入迷茫。突然,我灵光一闪,想到可以设计一个新的装饰器,用来计算函数的执行时间,从而精确定位问题。

于是,我编写了一个计算函数执行时间的装饰器,并将其应用在所有涉及网络通信、I/O操作、内存读写、进程间通信和线程切换的函数上,逐一排查性能瓶颈。

1
2
3
4
5
6
7
8
9
10
11
12
def async_timed():
"""计算异步函数执行时间的装饰器"""
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
start = asyncio.get_event_loop().time()
result = await func(*args, **kwargs)
end = asyncio.get_event_loop().time()
logger.debug(f"函数 {func.__name__} 执行时间: {end - start:.2f} 秒")
return result
return wrapper
return decorator

通过这个装饰器,我发现网络通信部分正常,耗时在3到5秒之间;其他与计算机相关的操作耗时也很短。真正的问题出现在消息的处理和发送部分。

问题的根源

原来,为了控制机器人发消息的速度,防止被服务器认为是刷屏,我在处理和发送消息的函数上添加了一个限速装饰器。最初的限速装饰器实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
def rate_limit(calls: int, period: float):
"""限速装饰器"""
semaphore = asyncio.Semaphore(calls)

def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
async with semaphore:
result = await func(*args, **kwargs)
await asyncio.sleep(period / calls)
return result
return wrapper
return decorator

这个实现存在一个严重问题:在每次函数执行后,都强制

await asyncio.sleep(period / calls)

,这会导致函数阻塞,从而严重影响了机器人的响应速度。

解决方案

意识到问题所在后,我决定重新设计限速装饰器,采用更为高效的令牌桶算法(Token Bucket Algorithm),以避免阻塞。

新的限速装饰器实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import time
import asyncio
from functools import wraps

def rate_limit(calls: int, period: float):
"""限速装饰器,使用令牌桶算法控制调用频率"""
max_tokens = calls # 令牌桶的容量
tokens = calls # 当前令牌数
refill_time = period / calls # 每个令牌的填充时间
last_check = time.time() # 上次检查的时间
lock = asyncio.Lock() # 异步锁,确保线程安全

def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
nonlocal tokens, last_check
async with lock:
current = time.time()
# 计算自上次检查后应添加的令牌数
elapsed = current - last_check
refill = elapsed / refill_time
if refill > 0:
tokens = min(tokens + refill, max_tokens)
last_check = current
if tokens >= 1:
tokens -= 1
else:
# 计算需要等待的时间
wait_time = refill_time - elapsed % refill_time
await asyncio.sleep(wait_time)
return await wrapper(*args, **kwargs)
return await func(*args, **kwargs)
return wrapper
return decorator

算法详解

  1. 变量初始化

    • max_tokens:令牌桶的最大容量。
    • tokens:当前令牌数,初始为满值。
    • refill_time:每个令牌的重新填充时间。
    • last_check:上次检查令牌的时间。
    • lock:异步锁,确保多协程环境下的线程安全。
  2. 令牌更新逻辑

    • 计算时间差 elapsed,根据时间差计算新增加的令牌数 refill
    • 更新当前令牌数 tokens,并确保不超过最大容量。
    • 更新 last_check 时间。
  3. 处理请求

    • 如果有足够的令牌(tokens >= 1),则扣除一个令牌,直接执行函数。
    • 如果令牌不足,则计算需要等待的时间 wait_time,异步等待后递归调用 wrapper

应用新的装饰器

将新的限速装饰器应用到消息发送函数上:

1
2
3
4
@rate_limit(calls=10, period=60)  # 限速,每分钟最多10次调用
async def send_msg(msg_type, number, msg, use_voice=False, is_error_message=False):
# 消息发送逻辑
# ...

这样,在不阻塞其他协程的情况下,成功地限制了消息发送的频率,机器人回复速度恢复正常。

进一步的装饰器应用

在项目中,我还使用了其他装饰器,如过滤敏感词、处理异常等。

过滤消息装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def filter_message(func):
"""过滤消息的装饰器"""
@wraps(func)
async def wrapper(message, *args, **kwargs):
if isinstance(message, dict):
content = message.get('text', '')
# 如果是系统消息,跳过检查
if message.get('role') == 'system':
return await func(message, *args, **kwargs)
else:
content = str(message)

blocked_word = word_filter.contains_blocked_word(content) # 你可以先把屏蔽词放进一个json或者yaml文件里,然后写一个读取的函数
if blocked_word:
logger.warning(f"检测到敏感词'{blocked_word}',消息已被过滤。")
return None # 或者返回特定的提示消息

return await func(message, *args, **kwargs)
return wrapper

该装饰器在消息处理函数执行前,检测消息内容是否包含敏感词,若包含则过滤掉,确保消息的合规性。

异常处理装饰器

1
2
3
4
5
6
7
8
9
10
11
def error_handler(func):
"""捕获并处理异常的装饰器"""
@wraps(func)
async def wrapper(*args, **kwargs):
try:
return await func(*args, **kwargs)
except Exception as e:
logger.error(f"执行函数 {func.__name__} 时发生异常:{e}", exc_info=True)
# 根据需要返回特定的错误提示
return "抱歉,处理您的请求时出现错误。"
return wrapper

这个装饰器捕获异步函数执行过程中可能出现的异常,防止程序崩溃,并提供统一的错误处理机制。

鉴权装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def admin_only(func):
"""管理员权限检查装饰器"""

@wraps(func)
async def wrapper(msg_type, user_info, *args, **kwargs):
user_id = user_info['user_id']
logger.info(f"Checking admin status for user: {user_id}")

if str(user_id) != str(config.ADMIN_ID):
logger.warning(f"Non-admin user {user_id} attempted to use admin command")
return "对不起,您没有执行此命令的权限。"
logger.info(f"Admin command executed by user: {user_id}")
return await func(msg_type, user_info, *args, **kwargs)
return wrapper

超时重试装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def retry(max_retries: int = 3, delay: float = 1.0):
"""重试装饰器"""

def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
for attempt in range(max_retries):
try:
return await func(*args, **kwargs)
except Exception as e:
if attempt == max_retries - 1:
logger.error(f"函数 {func.__name__}{max_retries} 次尝试后仍然失败: {str(e)}")
raise
await asyncio.sleep(delay * (2 ** attempt)) # 指数退避
return wrapper
return decorator

深入理解Python的装饰器

通过这次实践,我对Python的装饰器有了更深入的理解。

装饰器的本质

装饰器本质上是一个返回函数的函数,它可以在不修改原函数代码的情况下,增加新的功能。装饰器通过对原函数进行包装,控制其输入和输出。

使用@wraps保持元数据

在编写装饰器时,使用 @functools.wraps(func) 可以保留原函数的元数据,如函数名、注释等。这对于调试和文档生成非常有用。

装饰器的顺序

当一个函数被多个装饰器装饰时,装饰器的应用顺序是自下而上,即最内层的装饰器先被应用。例如:

1
2
3
4
@decorator_a
@decorator_b
def func():
pass

等同于

func = decorator_a(decorator_b(func))

装饰器在异步函数中的应用

在异步编程中,装饰器需要兼容 async/await 语法。例如,装饰器内部的函数需要使用 async def 定义,并在适当的位置使用 await

总结

这次乌龙事件让我深刻体会到细节对程序性能的影响。通过重新设计限速装饰器,采用更合理的算法,不仅解决了性能问题,还加深了我对Python装饰器的理解。

在开发过程中,使用装饰器可以提高代码的可读性和可维护性,但也需要谨慎,确保装饰器的实现不会引入新的问题。

最后,希望我的分享能对大家有所帮助。如果你也有类似的经验或想法,欢迎在评论区与我交流!