#!/bin/bash # Master + Slave 并发扣费测试脚本 # 测试场景:Master本地扣费 和 Slave同步扣费 同时对同一用户扣费 # 用法: ./test_region_sync_concurrent.sh [USER_ID] MASTER="http://172.22.57.85:23000" SLAVE="http://172.22.57.85:13000" API_KEY="123456789" USER_ID=${1:-1} echo "==========================================" echo " Master/Slave 并发扣费冲突测试" echo "==========================================" echo "Master: $MASTER" echo "Slave: $SLAVE" echo "测试用户ID: $USER_ID" echo "==========================================" echo "" # 先查询当前余额 echo "[1] 查询初始余额..." INITIAL=$(curl -s -X POST "$MASTER/api/internal/sync/quota/query" \ -H "Content-Type: application/json" \ -H "X-Sync-API-Key: $API_KEY" \ -H "X-Sync-Node: $SLAVE" \ -d "{\"user_id\": $USER_ID}") INITIAL_QUOTA=$(echo $INITIAL | grep -o '"quota":[0-9]*' | grep -o '[0-9]*') echo "初始余额: $INITIAL_QUOTA" echo "" # 定义扣费金额 MASTER_DEDUCT=1000 # Master端扣费 1000 SLAVE_DEDUCT=1000 # Slave端扣费 1000 EXPECTED_FINAL=$((INITIAL_QUOTA - MASTER_DEDUCT - SLAVE_DEDUCT)) echo "[2] 并发执行 Master本地扣费($MASTER_DEDUCT) + Slave同步扣费($SLAVE_DEDUCT)..." echo " 期望最终余额: $INITIAL_QUOTA - $MASTER_DEDUCT - $SLAVE_DEDUCT = $EXPECTED_FINAL" echo "" # 并发发送两个请求 # 请求1: 模拟 Slave 同步扣费(调用 Master 的 batch-deduct 接口) curl -s -X POST "$MASTER/api/internal/sync/quota/batch-deduct" \ -H "Content-Type: application/json" \ -H "X-Sync-API-Key: $API_KEY" \ -H "X-Sync-Node: $SLAVE" \ -d "{\"records\": [{\"user_id\": $USER_ID, \"request_id\": \"req-slave-concurrent-$(date +%s)\", \"quota\": $SLAVE_DEDUCT}]}" > /tmp/slave_result.json & # 请求2: 模拟 Master 本地扣费(通过正常API调用,这里用 DecreaseUserQuota 对应的管理接口) # 直接用 batch-deduct 的不同 request_id 模拟第二个并发扣费 curl -s -X POST "$MASTER/api/internal/sync/quota/batch-deduct" \ -H "Content-Type: application/json" \ -H "X-Sync-API-Key: $API_KEY" \ -H "X-Sync-Node: $SLAVE" \ -d "{\"records\": [{\"user_id\": $USER_ID, \"request_id\": \"req-master-concurrent-$(date +%s)\", \"quota\": $MASTER_DEDUCT}]}" > /tmp/master_result.json & wait echo "Slave扣费结果: $(cat /tmp/slave_result.json)" echo "Master扣费结果: $(cat /tmp/master_result.json)" echo "" # 查询最终余额 echo "[3] 查询最终余额..." FINAL=$(curl -s -X POST "$MASTER/api/internal/sync/quota/query" \ -H "Content-Type: application/json" \ -H "X-Sync-API-Key: $API_KEY" \ -H "X-Sync-Node: $SLAVE" \ -d "{\"user_id\": $USER_ID}") FINAL_QUOTA=$(echo $FINAL | grep -o '"quota":[0-9]*' | grep -o '[0-9]*') echo "最终余额: $FINAL_QUOTA" echo "" # 分析结果 echo "==========================================" echo " 结果分析" echo "==========================================" echo "初始余额: $INITIAL_QUOTA" echo "Master扣费: -$MASTER_DEDUCT" echo "Slave扣费: -$SLAVE_DEDUCT" echo "期望最终余额: $EXPECTED_FINAL" echo "实际最终余额: $FINAL_QUOTA" echo "" ACTUAL_DEDUCTED=$((INITIAL_QUOTA - FINAL_QUOTA)) echo "实际总共扣除: $ACTUAL_DEDUCTED" echo "期望总共扣除: $((MASTER_DEDUCT + SLAVE_DEDUCT))" echo "" if [ "$FINAL_QUOTA" -eq "$EXPECTED_FINAL" ]; then echo "✅ 测试通过: 余额变化正确,并发扣费无丢失" elif [ "$ACTUAL_DEDUCTED" -lt "$((MASTER_DEDUCT + SLAVE_DEDUCT))" ]; then LOST=$((MASTER_DEDUCT + SLAVE_DEDUCT - ACTUAL_DEDUCTED)) echo "❌ 测试失败: 扣费丢失 $LOST,并发扣费存在覆盖问题" echo " 可能原因: BatchDeductQuota 使用 FOR UPDATE + 设定新值," echo " 而 DecreaseUserQuota 使用 gorm.Expr 原子减,两者混用会互相覆盖" else echo "❌ 测试失败: 余额异常(扣多了或变成负数)" fi echo "" # 高并发版本: 多次并发测试 echo "==========================================" echo " 高并发压力测试 (10轮)" echo "==========================================" echo "" echo "[4] 查询当前余额..." BEFORE=$(curl -s -X POST "$MASTER/api/internal/sync/quota/query" \ -H "Content-Type: application/json" \ -H "X-Sync-API-Key: $API_KEY" \ -H "X-Sync-Node: $SLAVE" \ -d "{\"user_id\": $USER_ID}") BEFORE_QUOTA=$(echo $BEFORE | grep -o '"quota":[0-9]*' | grep -o '[0-9]*') echo "当前余额: $BEFORE_QUOTA" echo "" echo "[5] 并发发送20个扣费请求(每个100)..." for i in {1..20}; do curl -s -X POST "$MASTER/api/internal/sync/quota/batch-deduct" \ -H "Content-Type: application/json" \ -H "X-Sync-API-Key: $API_KEY" \ -H "X-Sync-Node: $SLAVE" \ -d "{\"records\": [{\"user_id\": $USER_ID, \"request_id\": \"req-stress-$i-$(date +%s%N)\", \"quota\": 100}]}" > /dev/null & done wait echo "全部请求发送完成" echo "" echo "[6] 查询最终余额..." AFTER=$(curl -s -X POST "$MASTER/api/internal/sync/quota/query" \ -H "Content-Type: application/json" \ -H "X-Sync-API-Key: $API_KEY" \ -H "X-Sync-Node: $SLAVE" \ -d "{\"user_id\": $USER_ID}") AFTER_QUOTA=$(echo $AFTER | grep -o '"quota":[0-9]*' | grep -o '[0-9]*') echo "最终余额: $AFTER_QUOTA" ACTUAL_TOTAL_DEDUCTED=$((BEFORE_QUOTA - AFTER_QUOTA)) EXPECTED_TOTAL_DEDUCTED=$((20 * 100)) echo "" echo "实际总共扣除: $ACTUAL_TOTAL_DEDUCTED" echo "期望总共扣除: $EXPECTED_TOTAL_DEDUCTED" if [ "$ACTUAL_TOTAL_DEDUCTED" -eq "$EXPECTED_TOTAL_DEDUCTED" ]; then echo "✅ 高并发测试通过: 所有扣费都正确" elif [ "$ACTUAL_TOTAL_DEDUCTED" -lt "$EXPECTED_TOTAL_DEDUCTED" ]; then echo "❌ 高并发测试失败: 少扣了 $((EXPECTED_TOTAL_DEDUCTED - ACTUAL_TOTAL_DEDUCTED))" else echo "❌ 高并发测试失败: 多扣了 $((ACTUAL_TOTAL_DEDUCTED - EXPECTED_TOTAL_DEDUCTED))" fi echo "" echo "==========================================" echo " 测试完成" echo "=========================================="