ETCD 配额/空间告警模拟脚本

脚本说明

ETCD 配额/空间告警模拟方案

脚本代码

bash 复制代码
#!/bin/bash
################################################################################
# ETCD 配额模拟测试脚本
# 功能:模拟 ETCD 数据库高使用率和空间告警场景
# 用途:测试自愈脚本和碎片整理功能
# 优化:支持并发写入,显著提升写入速度
################################################################################
# Created by: johnny
################################################################################



set -uo pipefail

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# 配置参数
ETCD_CONTAINER_NAME="${ETCD_CONTAINER_NAME:-etcd}"
ETCDCTL_ENDPOINTS="${ETCDCTL_ENDPOINTS:-http://127.0.0.1:2379}"
ETCDCTL_USER="${ETCDCTL_USER:-root}"
ETCDCTL_PASSWORD="${ETCDCTL_PASSWORD:-johnny}"
LOG_FILE="/var/log/etcd-simulation.log"
BACKUP_DIR="/tmp/etcd-simulation-backup"
TEST_QUOTA_BYTES=104857600  # 100MB
TEST_DATA_SIZE_MB=60         # 测试数据大小 60MB
UPDATE_COUNT=1000            # 更新次数
TEST_KEY_PREFIX="/test-simulation/"
PARALLEL_JOBS="${PARALLEL_JOBS:-10}"  # 默认并发数
PID_FILE="/tmp/etcd-simulation.pid"

# 默认配置值
DEFAULT_QUOTA_BYTES=8589934592  # 8GB
DEFAULT_COMPACTION_MODE="periodic"
DEFAULT_COMPACTION_RETENTION="1h"

# 并发控制变量
SEMAPHORE_FD=6
RESULT_FILE=""
RUNNING_PIDS=()

################################################################################
# 信号处理
################################################################################
cleanup_on_exit() {
    echo -e "\n${YELLOW}[WARN]${NC} 收到中断信号,正在停止所有后台任务..." | tee -a "${LOG_FILE}"

    # 杀死当前脚本的所有子进程
    pkill -P $$ 2>/dev/null

    # 等待子进程结束
    wait 2>/dev/null

    # 关闭信号量文件描述符
    exec 6>&- 2>/dev/null

    # 清理临时文件
    [ -n "$RESULT_FILE" ] && rm -f "$RESULT_FILE" 2>/dev/null
    rm -f "$PID_FILE" 2>/dev/null

    echo -e "${GREEN}[INFO]${NC} 已停止所有任务" | tee -a "${LOG_FILE}"
    exit 1
}

# 捕获信号
trap cleanup_on_exit SIGINT SIGTERM SIGHUP

################################################################################
# 日志函数
################################################################################
log() {
    local level=$1
    shift
    local msg="$*"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo -e "${timestamp} [${level}] ${msg}" | tee -a "${LOG_FILE}"
}

log_info() {
    echo -e "${GREEN}[INFO]${NC} $*" | tee -a "${LOG_FILE}"
}

log_warn() {
    echo -e "${YELLOW}[WARN]${NC} $*" | tee -a "${LOG_FILE}"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $*" | tee -a "${LOG_FILE}"
}

log_step() {
    echo -e "\n${BLUE}=== $* ===${NC}" | tee -a "${LOG_FILE}"
}

################################################################################
# 并发控制函数
################################################################################

# 初始化信号量
init_semaphore() {
    local max_jobs=$1
    # 创建临时文件作为 FIFO
    local fifo=$(mktemp -u)
    mkfifo "$fifo"
    exec 6<>"$fifo"
    rm -f "$fifo"

    # 填充信号量
    for ((i=0; i<max_jobs; i++)); do
        echo >&6
    done
}

# 关闭信号量
close_semaphore() {
    exec 6>&- 2>/dev/null
}

# 获取信号量(阻塞)
acquire_semaphore() {
    read -u 6
}

# 释放信号量
release_semaphore() {
    echo >&6
}

################################################################################
# 初始化函数
################################################################################
init() {
    log_step "初始化测试环境"

    # 记录 PID
    echo $$ > "$PID_FILE"

    # 创建备份目录
    mkdir -p "${BACKUP_DIR}"

    # 检查容器是否存在
    if ! docker ps -a | grep "${ETCD_CONTAINER_NAME}"; then
        log_error "ETCD 容器 ${ETCD_CONTAINER_NAME} 不存在"
        exit 1
    fi

    # 检查容器是否运行
    if ! docker ps | grep "${ETCD_CONTAINER_NAME}"; then
        log_warn "ETCD 容器未运行,正在启动..."
        docker start "${ETCD_CONTAINER_NAME}"
        sleep 5
    fi

    # 检查 etcdctl 是否可用
    if ! docker exec "${ETCD_CONTAINER_NAME}" which etcdctl &>/dev/null; then
        log_error "容器中未找到 etcdctl 命令"
        exit 1
    fi

    log_info "初始化完成"
}

################################################################################
# 备份配置函数
################################################################################
backup_config() {
    log_step "备份当前配置"

    local timestamp=$(date +%Y%m%d-%H%M%S)
    local backup_file="${BACKUP_DIR}/etcd-config-backup-${timestamp}.json"

    # 备份容器配置
    docker inspect "${ETCD_CONTAINER_NAME}" > "${backup_file}"

    # 备份当前环境变量
    docker inspect "${ETCD_CONTAINER_NAME}" | jq -r '.[0].Config.Env[]' > "${BACKUP_DIR}/env-backup-${timestamp}.txt"

    # 记录当前数据库大小
    du -sh /data/etcd/member/snap/db > "${BACKUP_DIR}/db-size-backup-${timestamp}.txt" 2>/dev/null || true

    log_info "配置已备份到: ${backup_file}"
    log_info "环境变量已备份到: ${BACKUP_DIR}/env-backup-${timestamp}.txt"

    echo "${backup_file}"
}

################################################################################
# 获取当前配置
################################################################################
get_current_config() {
    log_step "获取当前配置"

    echo -e "\n${BLUE}=== 容器环境变量 ===${NC}"
    docker inspect "${ETCD_CONTAINER_NAME}" | jq -r '.[0].Config.Env[]' | grep ETCD | tee -a "${LOG_FILE}"

    echo -e "\n${BLUE}=== 数据库大小 ===${NC}"
    du -sh /data/etcd/member/snap/db 2>/dev/null || echo "无法获取数据库大小"

    echo -e "\n${BLUE}=== 当前配额 ===${NC}"
    local quota=$(docker inspect "${ETCD_CONTAINER_NAME}" | grep ETCD_QUOTA_BACKEND_BYTES | head -1 | sed 's/.*=\([0-9]*\).*/\1/')
    if [ -n "$quota" ]; then
        echo "ETCD_QUOTA_BACKEND_BYTES = ${quota} bytes ($(echo "scale=2; $quota / 1024 / 1024" | bc) MB)"
    else
        echo "未设置配额"
    fi
}

################################################################################
# 修改 ETCD 配置
################################################################################
modify_config() {
    local quota=${1:-${TEST_QUOTA_BYTES}}
    local compaction_mode=${2:-""}
    local compaction_retention=${3:-"0"}

    log_step "修改 ETCD 配置"
    log_info "新配额: ${quota} bytes ($(echo "scale=2; $quota / 1024 / 1024" | bc) MB)"
    log_info "压缩模式: ${compaction_mode:-禁用}"
    log_info "压缩保留: ${compaction_retention}"

    # 获取当前容器配置
    local current_image=$(docker inspect "${ETCD_CONTAINER_NAME}" | jq -r '.[0].Image')
    local current_env=$(docker inspect "${ETCD_CONTAINER_NAME}" | jq -r '.[0].Config.Env[]')

    # 停止并删除容器
    log_info "停止容器..."
    docker stop "${ETCD_CONTAINER_NAME}"
    docker rm "${ETCD_CONTAINER_NAME}"

    # 构建环境变量列表
    local env_args=""
    while IFS= read -r line; do
        # 跳过要修改的变量
        if [[ $line == ETCD_QUOTA_BACKEND_BYTES=* ]]; then
            continue
        fi
        if [[ $line == ETCD_AUTO_COMPACTION_MODE=* ]]; then
            continue
        fi
        if [[ $line == ETCD_AUTO_COMPACTION_RETENTION=* ]]; then
            continue
        fi
        env_args="${env_args} -e \"${line}\""
    done <<< "$current_env"

    # 添加新的环境变量
    env_args="${env_args} -e \"ETCD_QUOTA_BACKEND_BYTES=${quota}\""
    if [ -n "$compaction_mode" ]; then
        env_args="${env_args} -e \"ETCD_AUTO_COMPACTION_MODE=${compaction_mode}\""
    fi
    env_args="${env_args} -e \"ETCD_AUTO_COMPACTION_RETENTION=${compaction_retention}\""

    # 重新创建容器
    log_info "重新创建容器..."
    eval docker run -d \
        --name "${ETCD_CONTAINER_NAME}" \
        --network host \
        --restart always \
        -v /data/etcd:/data \
        ${env_args} \
        "${current_image}"

    # 等待容器启动
    log_info "等待容器启动..."
    sleep 10

    # 验证容器状态
    if ! docker ps | grep "${ETCD_CONTAINER_NAME}"; then
        log_error "容器启动失败"
        log_error "查看容器日志: docker logs ${ETCD_CONTAINER_NAME}"
        exit 1
    fi

    # 验证 etcd 健康状态
    log_info "验证 etcd 健康状态..."
    local max_attempts=30
    local attempt=0
    while [ $attempt -lt $max_attempts ]; do
        if docker exec -e ETCDCTL_API=3 "${ETCD_CONTAINER_NAME}" etcdctl endpoint health &>/dev/null; then
            log_info "ETCD 服务健康"
            break
        fi
        attempt=$((attempt + 1))
        echo -n "."
        sleep 2
    done
    echo

    if [ $attempt -eq $max_attempts ]; then
        log_error "ETCD 服务未能在预期时间内启动"
        exit 1
    fi

    log_info "配置修改完成"
}

################################################################################
# 单条写入函数(供并发调用)
################################################################################
write_single_key() {
    local key_index=$1
    local result_file=$2

    if dd if=/dev/urandom bs=1024 count=100 2>/dev/null | \
       docker exec -i -e ETCDCTL_API=3 "${ETCD_CONTAINER_NAME}" \
           etcdctl --endpoints="${ETCDCTL_ENDPOINTS}" \
           --user="${ETCDCTL_USER}:${ETCDCTL_PASSWORD}" \
           put "${TEST_KEY_PREFIX}key${key_index}" 2>/dev/null; then
        echo "1" >> "$result_file"
    else
        echo "0" >> "$result_file"
    fi
}

################################################################################
# 并发写入测试数据
################################################################################
write_test_data() {
    local size_mb=${1:-${TEST_DATA_SIZE_MB}}
    local parallel=${2:-${PARALLEL_JOBS}}
    local count=$((size_mb * 10))  # 每个文件约 100KB

    log_step "并发写入测试数据"
    log_info "目标大小: ${size_mb} MB"
    log_info "预计写入: ${count} 条记录"
    log_info "并发数: ${parallel}"

    # 创建结果文件
    RESULT_FILE=$(mktemp)

    # 初始化信号量
    init_semaphore "$parallel"

    local start_time=$(date +%s)
    local submitted=0

    for i in $(seq 1 $count); do
        # 获取信号量(阻塞直到有可用槽位)
        acquire_semaphore

        # 后台执行写入任务
        {
            write_single_key "$i" "$RESULT_FILE"
            release_semaphore
        } &

        submitted=$((submitted + 1))

        # 每 50 条显示进度
        if [ $((submitted % 50)) -eq 0 ]; then
            local current=$(wc -l < "$RESULT_FILE" 2>/dev/null || echo 0)
            local progress=$((current * 100 / count))
            echo -ne "\r进度: ${progress}% (完成: ${current}/${count}, 已提交: ${submitted}/${count})"
        fi
    done

    # 等待所有后台任务完成
    log_info "等待剩余任务完成..."
    wait

    # 关闭信号量
    close_semaphore

    local end_time=$(date +%s)
    local duration=$((end_time - start_time))

    echo  # 换行

    # 统计结果
    local success_count=$(grep -c "1" "$RESULT_FILE" 2>/dev/null || echo 0)
    local failed_count=$((count - success_count))

    # 清理结果文件
    rm -f "$RESULT_FILE"
    RESULT_FILE=""

    log_info "写入完成: 成功 ${success_count} 条, 失败 ${failed_count} 条"
    log_info "耗时: ${duration} 秒, 速度: $(echo "scale=2; $success_count / $duration" | bc) 条/秒"

    if [ $failed_count -gt 0 ]; then
        log_warn "部分数据写入失败,可能已达到配额限制"
    fi
}

################################################################################
# 单条更新函数(供并发调用)
################################################################################
update_single_key() {
    local update_index=$1
    local result_file=$2

    local value="value-${update_index}-$(date +%s%N)-$(head -c 16 /dev/urandom | xxd -p)"

    if docker exec -e ETCDCTL_API=3 "${ETCD_CONTAINER_NAME}" \
        etcdctl --endpoints="${ETCDCTL_ENDPOINTS}" \
        --user="${ETCDCTL_USER}:${ETCDCTL_PASSWORD}" \
        put "${TEST_KEY_PREFIX}key1" "${value}" 2>/dev/null; then
        echo "1" >> "$result_file"
    else
        echo "0" >> "$result_file"
    fi
}

################################################################################
# 并发更新触发版本增长
################################################################################
trigger_version_growth() {
    local updates=${1:-${UPDATE_COUNT}}
    local parallel=${2:-${PARALLEL_JOBS}}

    log_step "并发更新触发版本增长"
    log_info "更新次数: ${updates}"
    log_info "并发数: ${parallel}"

    # 创建结果文件
    RESULT_FILE=$(mktemp)

    # 初始化信号量
    init_semaphore "$parallel"

    local start_time=$(date +%s)
    local submitted=0

    for i in $(seq 1 $updates); do
        # 获取信号量(阻塞直到有可用槽位)
        acquire_semaphore

        # 后台执行更新任务
        {
            update_single_key "$i" "$RESULT_FILE"
            release_semaphore
        } &

        submitted=$((submitted + 1))

        # 每 100 条显示进度
        if [ $((submitted % 100)) -eq 0 ]; then
            local current=$(wc -l < "$RESULT_FILE" 2>/dev/null || echo 0)
            local progress=$((current * 100 / updates))
            echo -ne "\r进度: ${progress}% (完成: ${current}/${updates}, 已提交: ${submitted}/${updates})"
        fi
    done

    # 等待所有后台任务完成
    log_info "等待剩余任务完成..."
    wait

    # 关闭信号量
    close_semaphore

    local end_time=$(date +%s)
    local duration=$((end_time - start_time))
    [ $duration -eq 0 ] && duration=1  # 避免除零

    echo  # 换行

    # 统计结果
    local success_count=$(grep -c "1" "$RESULT_FILE" 2>/dev/null || echo 0)
    local failed_count=$((updates - success_count))

    # 清理结果文件
    rm -f "$RESULT_FILE"
    RESULT_FILE=""

    log_info "更新完成: 成功 ${success_count} 次, 失败 ${failed_count} 次"
    log_info "耗时: ${duration} 秒, 速度: $(echo "scale=2; $success_count / $duration" | bc) 次/秒"

    if [ $failed_count -gt 0 ]; then
        log_warn "部分更新失败,可能已触发空间告警"
    fi
}

################################################################################
# 检查告警状态
################################################################################
check_alarms() {
    log_step "检查告警状态"

    echo -e "\n${BLUE}=== 当前告警 ===${NC}"
    local cmd="docker exec -e ETCDCTL_API=3 ${ETCD_CONTAINER_NAME} etcdctl --endpoints=${ETCDCTL_ENDPOINTS} --user=${ETCDCTL_USER}:*** alarm list"
    log_info "执行命令: ${cmd}"

    local alarms=$(docker exec -e ETCDCTL_API=3 "${ETCD_CONTAINER_NAME}" etcdctl --endpoints="${ETCDCTL_ENDPOINTS}" --user="${ETCDCTL_USER}:${ETCDCTL_PASSWORD}" alarm list 2>/dev/null || echo "无法获取告警信息")
    echo "$alarms" | tee -a "${LOG_FILE}"

    if echo "$alarms" | grep "NOSPACE"; then
        log_warn "检测到 NOSPACE 告警!数据库已满"
    else
        log_info "未检测到空间告警"
    fi
}

################################################################################
# 检查数据库状态
################################################################################
check_db_status() {
    log_step "检查数据库状态"

    echo -e "\n${BLUE}=== Endpoint Status 表格 ===${NC}"
    local cmd="docker exec -e ETCDCTL_API=3 ${ETCD_CONTAINER_NAME} etcdctl --endpoints=${ETCDCTL_ENDPOINTS} --user=${ETCDCTL_USER}:*** endpoint status --write-out=table"
    log_info "执行命令: ${cmd}"
    docker exec -e ETCDCTL_API=3 "${ETCD_CONTAINER_NAME}" etcdctl --endpoints="${ETCDCTL_ENDPOINTS}" --user="${ETCDCTL_USER}:${ETCDCTL_PASSWORD}" endpoint status --write-out=table 2>/dev/null || echo "无法获取数据库状态"

    # 从 endpoint status 获取 DB SIZE(CSV格式)
    echo -e "\n${BLUE}=== Endpoint Status 原始数据 ===${NC}"
    local endpoint_status=$(docker exec -e ETCDCTL_API=3 "${ETCD_CONTAINER_NAME}" \
        etcdctl --endpoints="${ETCDCTL_ENDPOINTS}" \
        --user="${ETCDCTL_USER}:${ETCDCTL_PASSWORD}" \
        endpoint status 2>/dev/null)
    
    # 解析 endpoint status 输出(CSV格式)
    # 格式: endpoint, id, version, db_size, is_leader, raft_term, raft_index
    local db_size_str=$(echo "$endpoint_status" | cut -d',' -f4 | xargs)
    echo "DB Size (from endpoint): $db_size_str"

    # 从字符串提取数值(处理 GB/MB/KB)
    local db_size_bytes_endpoint=0
    if [[ "$db_size_str" == *"GB"* ]]; then
        local size_num=$(echo "$db_size_str" | sed 's/[^0-9.]//g')
        db_size_bytes_endpoint=$(echo "$size_num * 1024 * 1024 * 1024" | bc | cut -d'.' -f1)
    elif [[ "$db_size_str" == *"MB"* ]]; then
        local size_num=$(echo "$db_size_str" | sed 's/[^0-9.]//g')
        db_size_bytes_endpoint=$(echo "$size_num * 1024 * 1024" | bc | cut -d'.' -f1)
    elif [[ "$db_size_str" == *"KB"* ]]; then
        local size_num=$(echo "$db_size_str" | sed 's/[^0-9.]//g')
        db_size_bytes_endpoint=$(echo "$size_num * 1024" | bc | cut -d'.' -f1)
    fi

    # 宿主机直接读取文件(用户验证过的方案)
    echo -e "\n${BLUE}=== 数据库文件大小(宿主机检查) ===${NC}"
    local db_file="/data/etcd/member/snap/db"
    local db_size_host=0
    
    if [ -f "$db_file" ]; then
        db_size_host=$(ls -l "$db_file" 2>/dev/null | awk '{print $5}')
        local db_size_host_mb=$(echo "scale=2; $db_size_host / 1024 / 1024" | bc)
        local db_size_host_gb=$(echo "scale=2; $db_size_host / 1024 / 1024 / 1024" | bc)
        echo "文件路径: $db_file"
        echo "文件大小: $db_size_host bytes (${db_size_host_mb} MB / ${db_size_host_gb} GB)"
        log_info "执行命令: ls -l $db_file"
    else
        echo "宿主机上未找到数据库文件: $db_file"
    fi

    # 容器内 du 命令(原有方案)
    echo -e "\n${BLUE}=== 数据库文件大小(容器内检查) ===${NC}"
    local db_size_cmd="docker exec ${ETCD_CONTAINER_NAME} du -sb /data/etcd/member/snap/db"
    log_info "执行命令: ${db_size_cmd}"
    local db_size_container=$(docker exec "${ETCD_CONTAINER_NAME}" du -sb /data/etcd/member/snap/db 2>/dev/null | awk '{print $1}' || echo "0")
    
    if [ "$db_size_container" != "0" ]; then
        local db_size_container_mb=$(echo "scale=2; $db_size_container / 1024 / 1024" | bc)
        echo "容器内检查: ${db_size_container_mb} MB (${db_size_container} bytes)"
    else
        echo "容器内无法获取数据库大小 (du 命令返回 0)"
    fi

    # 配额设置
    echo -e "\n${BLUE}=== 配额设置 ===${NC}"
    local quota_cmd="docker inspect ${ETCD_CONTAINER_NAME} | jq -r '.[0].Config.Env[] | select(startswith(\"ETCD_QUOTA_BACKEND_BYTES=\")) | split(\"=\")[1]'"
    log_info "执行命令: ${quota_cmd}"
    local quota=$(docker inspect "${ETCD_CONTAINER_NAME}" | jq -r '.[0].Config.Env[] | select(startswith("ETCD_QUOTA_BACKEND_BYTES=")) | split("=")[1]' 2>/dev/null || echo "0")
    local quota_mb=$(echo "scale=2; $quota / 1024 / 1024" | bc)
    local quota_gb=$(echo "scale=2; $quota / 1024 / 1024 / 1024" | bc)
    echo "ETCD_QUOTA_BACKEND_BYTES = ${quota} bytes (${quota_mb} MB / ${quota_gb} GB)"

    # 计算使用率(优先使用宿主机数据,其次使用 endpoint 数据)
    echo -e "\n${BLUE}=== 使用率统计 ===${NC}"
    
    local usage_source=""
    local usage_db_size=0
    
    # 优先使用宿主机数据(最可靠)
    if [ "$db_size_host" != "0" ] && [ "$db_size_host" != "" ]; then
        usage_source="宿主机文件"
        usage_db_size=$db_size_host
    # 其次使用 endpoint 数据
    elif [ "$db_size_bytes_endpoint" != "0" ] && [ "$db_size_bytes_endpoint" != "" ]; then
        usage_source="Endpoint Status"
        usage_db_size=$db_size_bytes_endpoint
    # 最后使用容器内数据
    elif [ "$db_size_container" != "0" ] && [ "$db_size_container" != "" ]; then
        usage_source="容器内 du"
        usage_db_size=$db_size_container
    fi
    
    if [ "$usage_db_size" != "0" ] && [ "$quota" != "0" ] && [ "$quota" != "" ]; then
        local usage=$(echo "scale=2; $usage_db_size * 100 / $quota" | bc)
        local usage_db_size_gb=$(echo "scale=2; $usage_db_size / 1024 / 1024 / 1024" | bc)
        
        echo "数据来源: $usage_source"
        echo "数据库大小: ${usage_db_size_gb} GB (${usage_db_size} bytes)"
        echo "配额: ${quota_gb} GB (${quota} bytes)"
        echo "使用率: ${usage}%"

        if (( $(echo "$usage > 80" | bc -l) )); then
            log_warn "使用率超过 80%,接近配额限制"
        fi
    else
        log_warn "无法计算使用率:无法获取有效的数据库大小或配额"
    fi
}

################################################################################
# 清理测试数据
################################################################################
cleanup_test_data() {
    log_step "清理测试数据"

    log_info "删除测试数据: ${TEST_KEY_PREFIX}*"
    local deleted=$(docker exec -e ETCDCTL_API=3 "${ETCD_CONTAINER_NAME}" etcdctl --endpoints="${ETCDCTL_ENDPOINTS}" --user="${ETCDCTL_USER}:${ETCDCTL_PASSWORD}" del "${TEST_KEY_PREFIX}" --prefix 2>/dev/null || echo "0")
    log_info "已删除 ${deleted} 条测试数据"
}

################################################################################
# 恢复配置
################################################################################
restore_config() {
    log_step "恢复原始配置"

    log_info "已手动配置,跳过配置恢复"
    log_info "如需恢复配置,请手动执行或取消注释此函数"
}

################################################################################
# 执行碎片整理
################################################################################
run_defrag() {
    log_step "执行碎片整理"

    if [ -x "/data/health_check/etcd-defrag.sh" ]; then
        log_info "执行碎片整理脚本..."
        /bin/bash /data/health_check/etcd-defrag.sh
        log_info "碎片整理完成"
    else
        log_warn "碎片整理脚本不存在: /data/health_check/etcd-defrag.sh"
        log_info "尝试手动执行碎片整理..."

        # 手动执行碎片整理
        docker exec -e ETCDCTL_API=3 "${ETCD_CONTAINER_NAME}" etcdctl --endpoints="${ETCDCTL_ENDPOINTS}" --user="${ETCDCTL_USER}:${ETCDCTL_PASSWORD}" defrag
        log_info "手动碎片整理完成"
    fi
}

################################################################################
# 清除告警
################################################################################
clear_alarms() {
    log_step "清除告警"

    docker exec -e ETCDCTL_API=3 "${ETCD_CONTAINER_NAME}" etcdctl --endpoints="${ETCDCTL_ENDPOINTS}" --user="${ETCDCTL_USER}:${ETCDCTL_PASSWORD}" alarm disarm
    log_info "告警已清除"
}

################################################################################
# 停止正在运行的模拟
################################################################################
stop_simulation() {
    log_step "停止模拟任务"

    if [ -f "$PID_FILE" ]; then
        local pid=$(cat "$PID_FILE")
        if kill -0 "$pid" 2>/dev/null; then
            log_info "正在停止进程 $pid 及其子进程..."
            # 发送 TERM 信号
            kill -TERM "$pid" 2>/dev/null
            # 杀死子进程
            pkill -P "$pid" 2>/dev/null
            sleep 2
            # 如果还在运行,强制杀死
            if kill -0 "$pid" 2>/dev/null; then
                log_warn "进程未响应,强制终止..."
                kill -9 "$pid" 2>/dev/null
                pkill -9 -P "$pid" 2>/dev/null
            fi
            rm -f "$PID_FILE"
            log_info "模拟任务已停止"
        else
            log_warn "进程 $pid 不存在"
            rm -f "$PID_FILE"
        fi
    else
        log_warn "未找到运行中的模拟任务 (PID 文件不存在)"
        # 尝试查找相关进程
        local pids=$(pgrep -f "hw-etcd-quota-config" 2>/dev/null | grep -v $$)
        if [ -n "$pids" ]; then
            log_info "找到相关进程: $pids"
            echo "$pids" | xargs kill -TERM 2>/dev/null
            log_info "已发送停止信号"
        fi
    fi
}

################################################################################
# 显示帮助信息
################################################################################
show_help() {
    cat << EOF
ETCD 配额模拟测试脚本

用法: $0 <command> [options]

命令:
  init              初始化测试环境
  backup            备份当前配置
  config            显示当前配置
  modify            修改配置(降低配额、禁用压缩)
  write             写入测试数据(并发)
  update            频繁更新触发版本增长(并发)
  status            检查告警和数据库状态
  cleanup           清理测试数据
  restore           恢复原始配置
  defrag            执行碎片整理
  clear             清除告警
  simulate          执行完整模拟流程(不清理)
  full              执行完整测试流程(包含清理)
  stop              停止正在运行的模拟任务
  help              显示此帮助信息

选项:
  --quota <bytes>     设置测试配额(默认: 104857600 = 100MB)
  --size <mb>         设置测试数据大小(默认: 60MB)
  --updates <num>     设置更新次数(默认: 1000)
  --parallel <num>    设置并发数(默认: 10)

示例:
  # 执行完整测试(默认 10 并发)
  $0 full

  # 使用 20 并发执行模拟
  $0 simulate --parallel 20

  # 自定义全部参数
  $0 simulate --size 100 --parallel 30 --updates 2000

  # 单独执行写入测试
  $0 write --size 50 --parallel 15

  # 停止正在运行的任务
  $0 stop

性能参考:
  并发数 10:  约 50-80 条/秒
  并发数 20:  约 80-120 条/秒
  并发数 30:  约 100-150 条/秒
  (实际速度取决于 ETCD 性能和网络延迟)

EOF
}

################################################################################
# 完整模拟流程(不清理)
################################################################################
simulate() {
    log_step "开始 ETCD 配额模拟测试 (并发模式)"
    log_info "并发数: ${PARALLEL_JOBS}"

    init
    backup_config
    # modify_config "${TEST_QUOTA_BYTES}" "" "0"  # 已手动配置,跳过修改
    write_test_data "${TEST_DATA_SIZE_MB}" "${PARALLEL_JOBS}"
    trigger_version_growth "${UPDATE_COUNT}" "${PARALLEL_JOBS}"
    check_alarms
    check_db_status

    # 清理 PID 文件
    rm -f "$PID_FILE"

    log_step "模拟测试完成"
    log_info "模拟测试完成,配置未修改"
}

################################################################################
# 完整测试流程(包含清理)
################################################################################
full() {
    log_step "开始 ETCD 配额完整测试 (并发模式)"
    log_info "并发数: ${PARALLEL_JOBS}"

    # 模拟阶段
    init
    backup_config
    # modify_config "${TEST_QUOTA_BYTES}" "" "0"  # 已手动配置,跳过修改
    write_test_data "${TEST_DATA_SIZE_MB}" "${PARALLEL_JOBS}"
    trigger_version_growth "${UPDATE_COUNT}" "${PARALLEL_JOBS}"
    check_alarms
    check_db_status

    log_step "模拟阶段完成,开始清理和恢复"

    # 清理阶段
    cleanup_test_data
    # restore_config  # 已手动配置,跳过恢复
    run_defrag
    clear_alarms
    check_db_status

    # 清理 PID 文件
    rm -f "$PID_FILE"

    log_step "完整测试完成"
    log_info "测试数据已清理,环境已恢复"
}

################################################################################
# 主函数
################################################################################
main() {
    local command="${1:-help}"
    shift || true

    # 解析参数
    while [[ $# -gt 0 ]]; do
        case $1 in
            --quota)
                TEST_QUOTA_BYTES="$2"
                shift 2
                ;;
            --size)
                TEST_DATA_SIZE_MB="$2"
                shift 2
                ;;
            --updates)
                UPDATE_COUNT="$2"
                shift 2
                ;;
            --parallel)
                PARALLEL_JOBS="$2"
                shift 2
                ;;
            *)
                log_error "未知参数: $1"
                show_help
                exit 1
                ;;
        esac
    done

    # 执行命令
    case $command in
        init)
            init
            ;;
        backup)
            init
            backup_config
            ;;
        config)
            init
            get_current_config
            ;;
        modify)
            init
            backup_config
            modify_config "${TEST_QUOTA_BYTES}" "" "0"
            ;;
        write)
            init
            write_test_data "${TEST_DATA_SIZE_MB}" "${PARALLEL_JOBS}"
            ;;
        update)
            init
            trigger_version_growth "${UPDATE_COUNT}" "${PARALLEL_JOBS}"
            ;;
        status)
            init
            check_alarms
            check_db_status
            ;;
        cleanup)
            init
            cleanup_test_data
            ;;
        restore)
            init
            restore_config
            ;;
        defrag)
            init
            run_defrag
            ;;
        clear)
            init
            clear_alarms
            ;;
        simulate)
            simulate
            ;;
        full)
            full
            ;;
        stop)
            stop_simulation
            ;;
        help|--help|-h)
            show_help
            ;;
        *)
            log_error "未知命令: $command"
            show_help
            exit 1
            ;;
    esac
}

# 执行主函数
main "$@"
相关推荐
静听山水2 小时前
StarRocks查询加速
数据库
静听山水2 小时前
StarRocks高级特性
数据库
范纹杉想快点毕业2 小时前
从单片机基础到程序框架:全方位技术深度解析
数据库·mongodb
晚风_END2 小时前
Linux|操作系统|elasticdump的二进制方式部署
运维·服务器·开发语言·数据库·jenkins·数据库开发·数据库架构
devmoon2 小时前
Polkadot SDK 自定义 Pallet Benchmark 指南:生成并接入 Weight
开发语言·网络·数据库·web3·区块链·波卡
victory04312 小时前
服务器病毒处理记录
运维·服务器·chrome
数据知道2 小时前
PostgreSQL 故障排查:紧急排查与 SQL 熔断处理(CPU 占用 100% 等情况)
数据库·sql·postgresql
静听山水2 小时前
Redis的Pipeline (管道)
数据库·redis·php
数据知道2 小时前
PostgreSQL 性能优化: I/O 瓶颈分析,以及如何提高数据库的 I/O 性能?
数据库·postgresql·性能优化