文档结构(7 大章节,约 1200 行)
| 层级 | 章节 | 核心内容 |
|---|---|---|
| 最外层 | 二、边缘拦截与防御 | CDN 缓存策略(Cache-Control头)、Nginx 令牌桶限流(limit_req/limit_conn)、WAF 防注入防爬、IP 自动封禁、请求签名/PoW 挑战 |
| 中层 | 三、缓冲隔离与降级 | 微服务核心/非核心隔离、自适应协程池 (信号量+扩缩容)、三级缓存(L1本地+L2 Redis+L3 DB)、singleflight 热点保护 、缓存三问题防护、MQ 异步削峰、熔断器(Closed→Open→HalfOpen) |
| 底层 | 四、一致性与保底 | DB 限流令牌桶、3种幂等方案 (唯一约束/Token/参数哈希)、乐观锁+重试 、Redis 分布式锁+DB行锁、本地消息表+定时补偿 (指数退避)、Saga 编排(正向+逆向补偿) |
额外覆盖
- 全链路监控:Prometheus 指标定义、各层流量上报
- 压测规划:并发压测代码示例、单 Pod 容量公式
- 落地 CheckList:40+ 条生产环境检查项
设计哲学
scss
边缘 (CDN/Nginx/WAF):10000 QPS → 3000 QPS
缓冲 (Redis/MQ/熔断):3000 QPS → 300 QPS
保底 (幂等/乐观锁/补偿):300 QPS → 安全落地 DB
所有 Go 代码示例均可直接参考落地,包含完整的中间件、连接池、协程池、熔断器、幂等管理器等实现。
Go 后端高并发架构:从外到内的立体防御体系
核心思路:高并发不是某一个组件能解决的,需要从外到内、层层设防,形成纵深防御体系。每一层让流量更平滑、更少、更安全地落到数据库。
目录
- 一、立体防御全景图
- 二、最外层:边缘拦截与防御
- [2.1 CDN 静态资源缓存](#2.1 CDN 静态资源缓存 "#21-cdn-%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90%E7%BC%93%E5%AD%98")
- [2.2 Nginx 网关限流](#2.2 Nginx 网关限流 "#22-nginx-%E7%BD%91%E5%85%B3%E9%99%90%E6%B5%81")
- [2.3 WAF 与防爬虫](#2.3 WAF 与防爬虫 "#23-waf-%E4%B8%8E%E9%98%B2%E7%88%AC%E8%99%AB")
- [2.4 黑名单与 IP 封禁](#2.4 黑名单与 IP 封禁 "#24-%E9%BB%91%E5%90%8D%E5%8D%95%E4%B8%8E-ip-%E5%B0%81%E7%A6%81")
- 三、中层:缓冲、隔离与降级
- [3.1 微服务架构拆分](#3.1 微服务架构拆分 "#31-%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84%E6%8B%86%E5%88%86")
- [3.2 核心与非核心链路隔离](#3.2 核心与非核心链路隔离 "#32-%E6%A0%B8%E5%BF%83%E4%B8%8E%E9%9D%9E%E6%A0%B8%E5%BF%83%E9%93%BE%E8%B7%AF%E9%9A%94%E7%A6%BB")
- [3.3 协程池隔离与并发控制](#3.3 协程池隔离与并发控制 "#33-%E5%8D%8F%E7%A8%8B%E6%B1%A0%E9%9A%94%E7%A6%BB%E4%B8%8E%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6")
- [3.4 Redis 抗读并发](#3.4 Redis 抗读并发 "#34-redis-%E6%8A%97%E8%AF%BB%E5%B9%B6%E5%8F%91")
- [3.5 消息队列抗写并发](#3.5 消息队列抗写并发 "#35-%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%E6%8A%97%E5%86%99%E5%B9%B6%E5%8F%91")
- [3.6 服务熔断与降级](#3.6 服务熔断与降级 "#36-%E6%9C%8D%E5%8A%A1%E7%86%94%E6%96%AD%E4%B8%8E%E9%99%8D%E7%BA%A7")
- 四、底层:数据一致性与保底
- [4.1 数据库接入前的流量平滑](#4.1 数据库接入前的流量平滑 "#41-%E6%95%B0%E6%8D%AE%E5%BA%93%E6%8E%A5%E5%85%A5%E5%89%8D%E7%9A%84%E6%B5%81%E9%87%8F%E5%B9%B3%E6%BB%91")
- [4.2 幂等性设计](#4.2 幂等性设计 "#42-%E5%B9%82%E7%AD%89%E6%80%A7%E8%AE%BE%E8%AE%A1")
- [4.3 乐观锁与分布式锁](#4.3 乐观锁与分布式锁 "#43-%E4%B9%90%E8%A7%82%E9%94%81%E4%B8%8E%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81")
- [4.4 补偿机制与最终一致性](#4.4 补偿机制与最终一致性 "#44-%E8%A1%A5%E5%81%BF%E6%9C%BA%E5%88%B6%E4%B8%8E%E6%9C%80%E7%BB%88%E4%B8%80%E8%87%B4%E6%80%A7")
- [4.5 分布式事务(Saga/TCC)](#4.5 分布式事务(Saga/TCC) "#45-%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1sagatcc")
- 五、全链路监控与可观测性
- 六、压测与容量规划
- [七、完整架构落地 CheckList](#七、完整架构落地 CheckList "#%E4%B8%83%E5%AE%8C%E6%95%B4%E6%9E%B6%E6%9E%84%E8%90%BD%E5%9C%B0-checklist")
一、立体防御全景图
yaml
┌──────────────────────────────────────────────┐
│ 用户请求 (QPS: 10000+) │
└─────────────────────┬────────────────────────┘
│
╔═════════════════════════════════════════════╪═════════════════════════╗
║ 第一层:最外层拦截 (Edge Defense) │ ║
║ ▼ ║
║ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ║
║ │ CDN │ │ WAF │ │ Nginx │ │ 网关层 │ ║
║ │ 静态资源 │ │ 防爬/CC │ │ 限流/黑名单│ │ 认证/鉴权 │ ║
║ └──────────┘ └──────────┘ └────┬─────┘ └──────────┘ ║
╚═══════════════════════════════════╪══════════════════════════════════╝
│ 过滤后 QPS: 3000
╔═══════════════════════════════════╪══════════════════════════════════╗
║ 第二层:中层缓冲 (Buffer) │ ║
║ ▼ ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ API Gateway / BFF │ ║
║ │ - 统一入口 - 协议转换 - 聚合裁剪 - 限流降级 │ ║
║ └───────┬──────────────────┬──────────────────┬─────────────────┘ ║
║ │ │ │ ║
║ ▼ ▼ ▼ ║
║ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ║
║ │ 订单服务 │ │ 商品服务 │ │ 用户服务 │ ║
║ │ (核心链路) │ │ (核心链路) │ │ (核心链路) │ ║
║ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ ║
║ │ │ │ ║
║ ┌──────┴─────────────────┴─────────────────┴──────┐ ║
║ │ Redis Cluster (缓存层) │ ║
║ │ - 热点数据缓存 - 分布式锁 │ ║
║ │ - 限流计数器 - 排行榜/计数器 │ ║
║ └──────────────────────────────────────────────────┘ ║
║ │ │ │ ║
║ ┌──────┴─────────────────┴─────────────────┴──────┐ ║
║ │ 消息队列 (MQ - 异步削峰) │ ║
║ │ - RocketMQ / Kafka / RabbitMQ │ ║
║ │ - 异步写入 - 流量削峰 - 解耦服务 │ ║
║ └──────────────────────────────────────────────────┘ ║
║ │ ║
║ │ 非核心链路 ║
║ ▼ ║
║ ┌──────────────┐ ┌──────────────┐ ║
║ │ 日志服务 │ │ 通知服务 │ ← 独立协程池隔离 ║
║ │ (非核心) │ │ (非核心) │ ║
║ └──────────────┘ └──────────────┘ ║
╚══════════════════════════════════════════════════════════════════════╝
│ 写入 QPS: 300
╔═══════════════════════════════════╪══════════════════════════════════╗
║ 第三层:底层保底 (Consistency)│ ║
║ ▼ ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ 数据库层 (MySQL / TiDB) │ ║
║ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ ║
║ │ │ 读写分离 │ │ 分库分表 │ │ 乐观锁 │ │ 幂等设计 │ │ ║
║ │ │ (主从) │ │ (Sharding)│ │ (CAS) │ │ (防重) │ │ ║
║ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ ║
║ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ ║
║ │ │ 连接池 │ │ 补偿任务 │ │ 死信队列 │ │ 对账脚本 │ │ ║
║ │ │ (限流) │ │ (重试) │ │ (兜底) │ │ (修复) │ │ ║
║ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ ║
║ └──────────────────────────────────────────────────────────────┘ ║
╚══════════════════════════════════════════════════════════════════════╝
设计哲学
yaml
最外层 (减少打到服务层的流量)
├─ CDN: 缓存静态内容,80%流量在此终结
├─ WAF: 拦截恶意请求
└─ Nginx: 限流、黑名单、协议校验
中层 (保护数据库不被冲垮)
├─ Redis: 扛住读流量,热点数据不进DB
├─ MQ: 扛住写流量,削峰填谷
└─ 熔断降级: 核心链路保活
底层 (守护数据最终正确)
├─ 幂等: 确保不会重复处理
├─ 乐观锁: 确保并发不冲突
└─ 补偿: 确保最终一致性
二、最外层:边缘拦截与防御
目标:在流量到达应用服务之前,尽可能多地拦截和缓存,将打到后端的请求量降到最低。
2.1 CDN 静态资源缓存
原理
scss
用户 → CDN 边缘节点 (命中 → 直接返回,不经过源站)
(未命中 → 回源 → 缓存到边缘)
Go 后端配合 CDN 的关键配置
go
// middleware/cache_control.go
package middleware
import (
"net/http"
"time"
)
// CDNCacheMiddleware 为不同类型的资源设置缓存策略
func CDNCacheMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
// 静态资源:长期缓存(带 hash 的文件名)
if isStaticAsset(path) {
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
w.Header().Set("Expires", time.Now().Add(365*24*time.Hour).Format(http.TimeFormat))
next.ServeHTTP(w, r)
return
}
// API 响应:根据不同接口设置不同缓存策略
switch {
case isPublicAPI(path):
// 公共数据(如商品列表):短时 CDN 缓存
w.Header().Set("Cache-Control", "public, max-age=60, s-maxage=300")
w.Header().Set("Surrogate-Control", "max-age=300") // CDN 专用头
case isUserSpecific(path):
// 用户相关数据:不缓存
w.Header().Set("Cache-Control", "private, no-cache, no-store, must-revalidate")
default:
w.Header().Set("Cache-Control", "no-cache")
}
// ETag 支持条件请求
w.Header().Set("ETag", generateETag(r))
next.ServeHTTP(w, r)
})
}
func isStaticAsset(path string) bool {
return strings.HasPrefix(path, "/static/") ||
strings.HasPrefix(path, "/assets/") ||
strings.HasSuffix(path, ".js") ||
strings.HasSuffix(path, ".css") ||
strings.HasSuffix(path, ".png")
}
func isPublicAPI(path string) bool {
// 商品列表、公开配置等
return strings.HasPrefix(path, "/api/v1/products") ||
strings.HasPrefix(path, "/api/v1/public/")
}
func isUserSpecific(path string) bool {
// 订单、个人中心等
return strings.HasPrefix(path, "/api/v1/orders") ||
strings.HasPrefix(path, "/api/v1/user/")
}
CDN 缓存策略总结
| 资源类型 | Cache-Control | CDN 缓存时长 | 示例 |
|---|---|---|---|
| 静态资源 (带hash) | max-age=31536000, immutable |
永久 | app.a3f2.js |
| 公共 API 数据 | public, s-maxage=300 |
5 分钟 | 商品列表 |
| 半动态数据 | public, s-maxage=10 |
10 秒 | 首页推荐 |
| 用户私有数据 | private, no-cache |
不缓存 | 订单详情 |
2.2 Nginx 网关限流
2.2.1 连接数限制
nginx
# nginx.conf
# 定义限流区域
# 单个 IP 最多 10 个并发连接
limit_conn_zone $binary_remote_addr zone=per_ip:10m;
# 整个 server 最多 10000 个并发连接
limit_conn_zone $server_name zone=per_server:10m;
server {
listen 80;
# 应用连接限制
limit_conn per_ip 10;
limit_conn per_server 10000;
# 超过限制返回 503
limit_conn_status 503;
location / {
proxy_pass http://backend;
proxy_connect_timeout 3s;
proxy_read_timeout 5s;
}
}
2.2.2 请求速率限制(令牌桶)
nginx
# 请求速率限制
# 单个 IP 每秒最多 50 个请求,突发 20
limit_req_zone $binary_remote_addr zone=req_per_ip:10m rate=50r/s;
# 针对敏感接口更严格限制 (登录/注册/秒杀)
limit_req_zone $binary_remote_addr zone=req_sensitive:10m rate=5r/s;
server {
location /api/ {
limit_req zone=req_per_ip burst=20 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
location /api/v1/login {
limit_req zone=req_sensitive burst=3 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
location /api/v1/seckill/ {
limit_req zone=req_sensitive burst=10 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
}
2.2.3 Go 应用层限流(配合 Nginx)
go
// middleware/rate_limiter.go
package middleware
import (
"context"
"net/http"
"sync"
"time"
"golang.org/x/time/rate"
)
// IPRateLimiter 基于 IP 的应用层令牌桶限流器
type IPRateLimiter struct {
mu sync.RWMutex
limiters map[string]*rateLimiterEntry
rate rate.Limit
burst int
ttl time.Duration
}
type rateLimiterEntry struct {
limiter *rate.Limiter
lastSeen time.Time
}
func NewIPRateLimiter(r rate.Limit, burst int) *IPRateLimiter {
l := &IPRateLimiter{
limiters: make(map[string]*rateLimiterEntry),
rate: r,
burst: burst,
ttl: 10 * time.Minute,
}
// 定期清理过期 IP
go l.cleanup()
return l
}
func (l *IPRateLimiter) Allow(ip string) bool {
l.mu.Lock()
entry, exists := l.limiters[ip]
if !exists {
entry = &rateLimiterEntry{
limiter: rate.NewLimiter(l.rate, l.burst),
lastSeen: time.Now(),
}
l.limiters[ip] = entry
}
entry.lastSeen = time.Now()
l.mu.Unlock()
return entry.limiter.Allow()
}
func (l *IPRateLimiter) cleanup() {
for {
time.Sleep(time.Minute)
l.mu.Lock()
for ip, entry := range l.limiters {
if time.Since(entry.lastSeen) > l.ttl {
delete(l.limiters, ip)
}
}
l.mu.Unlock()
}
}
// RateLimiterMiddleware 限流中间件
func RateLimiterMiddleware(limiter *IPRateLimiter) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := getClientIP(r)
if !limiter.Allow(ip) {
w.Header().Set("Retry-After", "1")
w.Header().Set("X-RateLimit-Limit", "50")
http.Error(w, `{"code":429,"msg":"请求过于频繁,请稍后重试"}`, http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
}
func getClientIP(r *http.Request) string {
// 优先取 X-Forwarded-For(经过 Nginx 代理)
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
parts := strings.Split(xff, ",")
return strings.TrimSpace(parts[0])
}
if xri := r.Header.Get("X-Real-IP"); xri != "" {
return xri
}
// fallback
host, _, _ := net.SplitHostPort(r.RemoteAddr)
return host
}
2.3 WAF 与防爬虫
Nginx + Lua 简易 WAF 规则
nginx
# 基于 lua-resty-waf 或自定义规则
location / {
access_by_lua_block {
local uri = ngx.var.uri
local ua = ngx.var.http_user_agent or ""
local ip = ngx.var.remote_addr
-- 规则1: 拦截 SQL 注入尝试
local args = ngx.var.query_string or ""
if args:match("(%d+)%s*=%s*%d+%s+or%s+") then
ngx.log(ngx.WARN, "SQL injection attempt from ", ip)
ngx.exit(403)
end
-- 规则2: 拦截无 UA 的请求(爬虫特征)
if ua == "" or ua == "-" then
ngx.log(ngx.WARN, "Empty UA from ", ip)
-- 对 API 路径放宽,对页面路径拦截
if not uri:find("^/api/") then
ngx.exit(403)
end
end
-- 规则3: 单 IP 短时间高频访问(CC 攻击特征)
local count = ngx.shared.limit:incr("cc_" .. ip, 1, 10)
if count and count > 500 then
ngx.log(ngx.ERR, "CC attack detected from ", ip)
-- 自动加入黑名单
ngx.shared.blacklist:set(ip, true, 3600)
ngx.exit(403)
end
}
proxy_pass http://backend;
}
Go 应用层防爬虫
go
// middleware/anti_crawler.go
package middleware
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"net/http"
"time"
)
// AntiCrawler 防爬虫中间件
type AntiCrawler struct {
// 合法请求的频率统计
frequency map[string]*SlidingWindow // IP -> 滑动窗口
// 签名密钥
signSecret string
}
type SlidingWindow struct {
timestamps []int64
mu sync.Mutex
}
func NewAntiCrawler(secret string) *AntiCrawler {
return &AntiCrawler{
frequency: make(map[string]*SlidingWindow),
signSecret: secret,
}
}
// ★ 方式一:请求签名校验(防接口被脚本调用)
func (a *AntiCrawler) ValidateSignature(r *http.Request) bool {
timestamp := r.Header.Get("X-Timestamp")
sign := r.Header.Get("X-Sign")
nonce := r.Header.Get("X-Nonce")
// 时间戳过期检查(5分钟有效期)
ts, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil || time.Now().Unix()-ts > 300 {
return false
}
// 重新计算签名
payload := timestamp + nonce + r.URL.Path + a.signSecret
mac := hmac.New(sha256.New, []byte(a.signSecret))
mac.Write([]byte(payload))
expectedSign := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(sign), []byte(expectedSign))
}
// ★ 方式二:PoW (Proof of Work) 挑战-应答
func (a *AntiCrawler) IssueChallenge(w http.ResponseWriter, r *http.Request) string {
// 生成随机 challenge
challenge := fmt.Sprintf("%d_%s", time.Now().Unix(), randomString(16))
// 存入 Redis,5 分钟有效
rdb.Set(ctx, "challenge:"+challenge, "pending", 5*time.Minute)
return challenge
}
func (a *AntiCrawler) VerifyChallenge(challenge, answer string) bool {
val, _ := rdb.Get(ctx, "challenge:"+challenge).Result()
if val != "pending" {
return false
}
// 验证 PoW(例如 hash 前 N 位为 0)
hash := sha256.Sum256([]byte(challenge + answer))
return hex.EncodeToString(hash[:])[:4] == "0000"
}
// ★ 方式三:行为分析(滑动窗口计数)
func (a *AntiCrawler) IsAbnormalBehavior(ip string) bool {
window, exists := a.frequency[ip]
if !exists {
window = &SlidingWindow{}
a.frequency[ip] = window
}
window.mu.Lock()
defer window.mu.Unlock()
now := time.Now().UnixMilli()
window.timestamps = append(window.timestamps, now)
// 只保留最近 10 秒的记录
threshold := now - 10000
var valid []int64
for _, ts := range window.timestamps {
if ts > threshold {
valid = append(valid, ts)
}
}
window.timestamps = valid
// 10 秒内超过 200 次请求 = 异常
return len(valid) > 200
}
2.4 黑名单与 IP 封禁
go
// middleware/blacklist.go
package middleware
// IPBlacklist IP 黑名单(Redis 实现,支持自动过期)
type IPBlacklist struct {
rdb *redis.Client
maxViolations int // 触发多少次违规才封禁
banDuration time.Duration // 封禁时长
}
func NewIPBlacklist(rdb *redis.Client) *IPBlacklist {
return &IPBlacklist{
rdb: rdb,
maxViolations: 5,
banDuration: time.Hour,
}
}
// RecordViolation 记录一次违规
func (b *IPBlacklist) RecordViolation(ctx context.Context, ip, reason string) error {
key := fmt.Sprintf("blacklist:violation:%s", ip)
pipe := b.rdb.Pipeline()
// 增加违规计数
incr := pipe.Incr(ctx, key)
// 设置过期时间(计数窗口)
pipe.Expire(ctx, key, 10*time.Minute)
_, err := pipe.Exec(ctx)
if err != nil {
return err
}
count := incr.Val()
// 超过阈值,自动封禁
if count >= int64(b.maxViolations) {
return b.BanIP(ctx, ip, reason)
}
return nil
}
// BanIP 封禁 IP
func (b *IPBlacklist) BanIP(ctx context.Context, ip, reason string) error {
key := fmt.Sprintf("blacklist:banned:%s", ip)
data := map[string]interface{}{
"reason": reason,
"banned_at": time.Now().Unix(),
"expires_at": time.Now().Add(b.banDuration).Unix(),
}
pipe := b.rdb.Pipeline()
pipe.HMSet(ctx, key, data)
pipe.Expire(ctx, key, b.banDuration)
// 同时清理违规计数
pipe.Del(ctx, fmt.Sprintf("blacklist:violation:%s", ip))
_, err := pipe.Exec(ctx)
return err
}
// IsBanned 检查 IP 是否被封禁
func (b *IPBlacklist) IsBanned(ctx context.Context, ip string) bool {
key := fmt.Sprintf("blacklist:banned:%s", ip)
exists, _ := b.rdb.Exists(ctx, key).Result()
return exists > 0
}
// BlacklistMiddleware 黑名单中间件
func (b *IPBlacklist) BlacklistMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := getClientIP(r)
if b.IsBanned(r.Context(), ip) {
http.Error(w, `{"code":403,"msg":"访问被拒绝"}`, http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
三、中层:缓冲、隔离与降级
目标:保护核心服务不被冲垮,用 Redis 扛读、用 MQ 抗写,非核心链路隔离开。
3.1 微服务架构拆分
scss
┌─────────────┐
│ API Gateway │ (BFF / 网关)
└──────┬──────┘
│
┌─────────┬─────────┼─────────┬─────────┐
▼ ▼ ▼ ▼ ▼
┌─────────┐┌─────────┐┌─────────┐┌─────────┐┌─────────┐
│ 用户服务 ││ 商品服务 ││ 订单服务 ││ 支付服务 ││ 库存服务 │
│(核心) ││(核心) ││(核心) ││(核心) ││(核心) │
└─────────┘└─────────┘└─────────┘└─────────┘└─────────┘
│ │
┌─────────┐┌─────────┐ ┌─────────┐
│ 通知服务 ││ 日志服务 │ │ 营销服务 │
│(非核心) ││(非核心) │ │(非核心) │
└─────────┘└─────────┘ └─────────┘
拆分原则
| 原则 | 说明 |
|---|---|
| 高内聚低耦合 | 一个服务只做一件事,通过 API 通信 |
| 按业务域拆分 | 用户、商品、订单、支付各自独立 |
| 核心/非核心隔离 | 订单、支付是核心;通知、日志是非核心 |
| 独立部署 | 每个服务独立发布、独立扩缩容 |
| 独立数据库 | 每个服务有自己的数据库,不共享 |
3.2 核心与非核心链路隔离
go
// service/service_context.go
package service
import (
"context"
"sync"
)
// ServicePool 按优先级隔离协程池
type ServicePool struct {
// 核心业务:大池子,高并发
corePool chan struct{} // 信号量,控制并发数
// 非核心业务:小池子,限并发
nonCorePool chan struct{}
// 通知/日志:极小池子,不影响核心
bgPool chan struct{}
}
func NewServicePool(coreMax, nonCoreMax, bgMax int) *ServicePool {
return &ServicePool{
corePool: make(chan struct{}, coreMax),
nonCorePool: make(chan struct{}, nonCoreMax),
bgPool: make(chan struct{}, bgMax),
}
}
// ExecuteCore 执行核心业务(高优先级)
func (p *ServicePool) ExecuteCore(ctx context.Context, fn func() error) error {
// 尽力获取信号量,如果池满了就等待
p.corePool <- struct{}{}
defer func() { <-p.corePool }()
return fn()
}
// ExecuteNonCore 执行非核心业务(低优先级,池满直接丢弃)
func (p *ServicePool) ExecuteNonCore(ctx context.Context, fn func()) {
select {
case p.nonCorePool <- struct{}{}:
go func() {
defer func() { <-p.nonCorePool }()
fn()
}()
case <-ctx.Done():
return
default:
// ★ 非核心链路池满 → 直接丢弃,不影响核心链路
log.Println("[warn] non-core pool full, task discarded")
}
}
// ExecuteBackground 执行后台任务(最低优先级)
func (p *ServicePool) ExecuteBackground(fn func()) {
select {
case p.bgPool <- struct{}{}:
go func() {
defer func() { <-p.bgPool }()
fn()
}()
default:
// 静默丢弃
}
}
// --- 使用示例 ---
var svcPool = NewServicePool(1000, 100, 20)
// 下单(核心链路)
func CreateOrder(ctx context.Context, req CreateOrderReq) error {
return svcPool.ExecuteCore(ctx, func() error {
// 1. 扣库存
// 2. 创建订单
// 3. 发起支付
return nil
})
}
// 发短信通知(非核心链路)
func SendSMSNotification(phone, msg string) {
svcPool.ExecuteNonCore(context.Background(), func() {
// 调用短信 API ...
})
// 池满了直接丢弃通知,不影响下单
}
// 打日志(后台链路,丢弃也无妨)
func LogUserBehavior(userID int64, action string) {
svcPool.ExecuteBackground(func() {
// 写入 ES / ClickHouse ...
})
}
3.3 协程池隔离与并发控制
go
// pool/adaptive_worker_pool.go
package pool
import (
"context"
"sync"
"sync/atomic"
"time"
)
// AdaptiveWorkerPool 自适应协程池
// 根据系统负载动态调整并发度
type AdaptiveWorkerPool struct {
name string
// 配置
maxWorkers int32
minWorkers int32
// 运行时状态
activeCount int32 // 当前活跃协程数
pendingTasks chan func() // 待处理任务队列
stopCh chan struct{}
// 统计
totalSubmitted uint64
totalCompleted uint64
totalDropped uint64
totalTimeout uint64
mu sync.RWMutex
}
func NewAdaptiveWorkerPool(name string, minWorkers, maxWorkers int, queueSize int) *AdaptiveWorkerPool {
p := &AdaptiveWorkerPool{
name: name,
maxWorkers: int32(maxWorkers),
minWorkers: int32(minWorkers),
pendingTasks: make(chan func(), queueSize),
stopCh: make(chan struct{}),
}
// 启动最小数量 worker
for i := 0; i < minWorkers; i++ {
go p.worker()
}
// 启动自适应扩缩容控制器
go p.autoScaler()
return p
}
func (p *AdaptiveWorkerPool) worker() {
for {
select {
case task := <-p.pendingTasks:
atomic.AddInt32(&p.activeCount, 1)
task()
atomic.AddUint64(&p.totalCompleted, 1)
atomic.AddInt32(&p.activeCount, -1)
case <-p.stopCh:
return
}
}
}
// Submit 提交任务
// timeout: 等待超时时间,超过则丢弃
func (p *AdaptiveWorkerPool) Submit(task func(), timeout time.Duration) error {
atomic.AddUint64(&p.totalSubmitted, 1)
select {
case p.pendingTasks <- task:
return nil
case <-time.After(timeout):
atomic.AddUint64(&p.totalDropped, 1)
return fmt.Errorf("pool [%s] is full, task dropped", p.name)
}
}
// autoScaler 自适应扩缩容
func (p *AdaptiveWorkerPool) autoScaler() {
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
queueLen := len(p.pendingTasks)
capacity := cap(p.pendingTasks)
// 队列利用率 > 80%:扩容
if float64(queueLen)/float64(capacity) > 0.8 {
p.scaleUp()
}
// 队列利用率 < 20%:缩容
if float64(queueLen)/float64(capacity) < 0.2 {
p.scaleDown()
}
case <-p.stopCh:
return
}
}
}
func (p *AdaptiveWorkerPool) scaleUp() {
p.mu.Lock()
defer p.mu.Unlock()
active := atomic.LoadInt32(&p.activeCount)
if active < p.maxWorkers {
toAdd := (p.maxWorkers - active) / 2
if toAdd < 1 {
toAdd = 1
}
for i := int32(0); i < toAdd; i++ {
go p.worker()
}
log.Printf("[pool:%s] scaled up: +%d workers", p.name, toAdd)
}
}
func (p *AdaptiveWorkerPool) scaleDown() {
// 缩容可由 worker 自行退出(通过 stopCh 信号)
// 此处简化,实际可用 context.WithCancel 控制
}
// Stats 获取统计数据
func (p *AdaptiveWorkerPool) Stats() PoolStats {
return PoolStats{
Name: p.name,
Submitted: atomic.LoadUint64(&p.totalSubmitted),
Completed: atomic.LoadUint64(&p.totalCompleted),
Dropped: atomic.LoadUint64(&p.totalDropped),
ActiveWorkers: atomic.LoadInt32(&p.activeCount),
QueueLen: len(p.pendingTasks),
QueueCap: cap(p.pendingTasks),
}
}
3.4 Redis 抗读并发
3.4.1 多级缓存架构
dart
请求 → 本地缓存 (sync.Map/ristretto) → Redis → 数据库
命中率 ~60%, TTL 短 命中率 ~30% 命中率 ~10%
go
// cache/multi_level_cache.go
package cache
import (
"context"
"encoding/json"
"sync"
"time"
"github.com/redis/go-redis/v9"
)
// MultiLevelCache 三级缓存
// L1: 本地内存(进程内,超快)
// L2: Redis(分布式,跨实例共享)
// L3: 数据库(权威数据源)
type MultiLevelCache struct {
rdb *redis.Client
// L1 本地缓存
local sync.Map
localTTL time.Duration
// 统计
hitsL1 uint64
hitsL2 uint64
misses uint64
}
type cacheEntry struct {
data []byte
expiredAt time.Time
}
func NewMultiLevelCache(rdb *redis.Client, localTTL time.Duration) *MultiLevelCache {
c := &MultiLevelCache{
rdb: rdb,
localTTL: localTTL,
}
// 定期清理过期本地缓存
go c.localCleaner()
return c
}
// Get 三级缓存读取
func (c *MultiLevelCache) Get(ctx context.Context, key string, dest interface{},
dbLoader func() (interface{}, error), redisTTL time.Duration,
) error {
// L1: 本地缓存
if val, ok := c.local.Load(key); ok {
entry := val.(*cacheEntry)
if time.Now().Before(entry.expiredAt) {
atomic.AddUint64(&c.hitsL1, 1)
return json.Unmarshal(entry.data, dest)
}
c.local.Delete(key)
}
// L2: Redis
data, err := c.rdb.Get(ctx, key).Bytes()
if err == nil {
atomic.AddUint64(&c.hitsL2, 1)
// 回填 L1
c.setLocal(key, data)
return json.Unmarshal(data, dest)
}
// L3: 数据库 + 回填 L1/L2
atomic.AddUint64(&c.misses, 1)
result, err := dbLoader()
if err != nil {
return err
}
// ★ 使用 singleflight 防止缓存击穿
// 见下面的 HotKeyProtector
// 序列化并回填缓存
jsonData, _ := json.Marshal(result)
// 回填 L2 (Redis) --- 异步,不阻塞主流程
go func() {
c.rdb.Set(context.Background(), key, jsonData, redisTTL)
}()
// 回填 L1
c.setLocal(key, jsonData)
// 反序列化返回
rawData, _ := json.Marshal(result)
return json.Unmarshal(rawData, dest)
}
func (c *MultiLevelCache) setLocal(key string, data []byte) {
c.local.Store(key, &cacheEntry{
data: data,
expiredAt: time.Now().Add(c.localTTL),
})
}
3.4.2 热点 Key 保护(Singleflight)
go
// cache/hot_key_protector.go
package cache
import (
"context"
"sync"
"golang.org/x/sync/singleflight"
)
// HotKeyProtector 热点 Key 保护器
// 防止缓存击穿:同一 Key 同时只有 1 个请求去加载 DB
type HotKeyProtector struct {
sf singleflight.Group
// 互斥锁保护器(针对极高并发场景)
mu sync.Mutex
lockKeys map[string]*sync.Mutex
}
func NewHotKeyProtector() *HotKeyProtector {
return &HotKeyProtector{
lockKeys: make(map[string]*sync.Mutex),
}
}
// ★ 方式一:singleflight(推荐,最简单)
func (p *HotKeyProtector) LoadWithSingleflight(
ctx context.Context,
key string,
cacheLoader func() (interface{}, error),
dbLoader func() (interface{}, error),
) (interface{}, error) {
// 先查缓存
if val, ok := cacheLoader(); ok {
return val, nil
}
// ★ singleflight: 同一 key 并发调用 dbLoader 时,只执行一次
val, err, shared := p.sf.Do(key, func() (interface{}, error) {
return dbLoader()
})
if shared {
log.Printf("[cache] key=%s shared singleflight result", key)
}
return val, err
}
// ★ 方式二:Redis SETNX 分布式互斥锁(跨实例)
func (p *HotKeyProtector) LoadWithDistributedLock(
ctx context.Context,
rdb *redis.Client,
key string,
loader func() (interface{}, error),
) (interface{}, error) {
lockKey := "hotkey:lock:" + key
cacheKey := "hotkey:data:" + key
// 1. 尝试从 Redis 读取(可能已被其他实例加载)
cached, err := rdb.Get(ctx, cacheKey).Result()
if err == nil {
var result interface{}
json.Unmarshal([]byte(cached), &result)
return result, nil
}
// 2. 尝试获取加载锁
locked, _ := rdb.SetNX(ctx, lockKey, "1", 5*time.Second).Result()
if locked {
// 3. 获得锁 → 从 DB 加载
data, err := loader()
if err != nil {
rdb.Del(ctx, lockKey)
return nil, err
}
// 4. 写入 Redis 并释放锁
jsonData, _ := json.Marshal(data)
pipe := rdb.Pipeline()
pipe.Set(ctx, cacheKey, jsonData, 10*time.Minute)
pipe.Del(ctx, lockKey)
pipe.Exec(ctx)
return data, nil
}
// 5. 没抢到锁 → 自旋等待,然后重试读缓存
for i := 0; i < 20; i++ {
time.Sleep(50 * time.Millisecond)
cached, err := rdb.Get(ctx, cacheKey).Result()
if err == nil {
var result interface{}
json.Unmarshal([]byte(cached), &result)
return result, nil
}
}
// 6. 超时 → 直接查 DB(兜底)
return loader()
}
// ★ 方式三:本地互斥锁(单实例,性能最高)
func (p *HotKeyProtector) LoadWithLocalLock(
key string,
loader func() (interface{}, error),
) (interface{}, error) {
p.mu.Lock()
lock, exists := p.lockKeys[key]
if !exists {
lock = &sync.Mutex{}
p.lockKeys[key] = lock
}
p.mu.Unlock()
lock.Lock()
defer lock.Unlock()
return loader()
}
3.4.3 缓存穿透/击穿/雪崩防护
go
// cache/defense.go
package cache
// CacheDefense 缓存三大问题防护
type CacheDefense struct {
rdb *redis.Client
}
// 1. ★ 缓存穿透 (请求不存在的数据)
// 方案:布隆过滤器 + 空值缓存
func (d *CacheDefense) AntiPenetration(key string, loader func() (interface{}, error)) (interface{}, error) {
// Step1: 布隆过滤器判断 Key 是否存在
if !d.bloomFilter.Exists(key) {
return nil, ErrNotFound // 直接拒绝,不查 DB
}
// Step2: 查缓存
val, err := d.rdb.Get(ctx, key).Result()
if err == nil {
if val == "__NULL__" {
return nil, ErrNotFound // 命中空值缓存
}
return val, nil
}
// Step3: 查 DB
data, err := loader()
if err != nil || data == nil {
// ★ 空值也缓存(避免频繁查 DB),TTL 短一些
d.rdb.Set(ctx, key, "__NULL__", 1*time.Minute)
d.bloomFilter.Add(key)
return nil, ErrNotFound
}
// 正常缓存
jsonData, _ := json.Marshal(data)
d.rdb.Set(ctx, key, jsonData, 10*time.Minute)
return data, nil
}
// 2. ★ 缓存击穿 (热点 Key 过期,大量请求打到 DB)
// 方案:互斥锁 / singleflight(见 3.4.2 HotKeyProtector)
// 3. ★ 缓存雪崩 (大量 Key 同时过期)
// 方案:过期时间加随机偏移
func (d *CacheDefense) SetWithRandomTTL(key string, data interface{}, baseTTL time.Duration) {
// ★ TTL = baseTTL + random(0, 0.3*baseTTL)
jitter := time.Duration(rand.Int63n(int64(baseTTL) * 3 / 10))
ttl := baseTTL + jitter
jsonData, _ := json.Marshal(data)
d.rdb.Set(ctx, key, jsonData, ttl)
}
// 4. ★ 缓存预热(系统启动/发布前主动加载热点数据)
func (d *CacheDefense) WarmUp(ctx context.Context, hotKeys []string) error {
for _, key := range hotKeys {
// 检查缓存是否存在
exists, _ := d.rdb.Exists(ctx, key).Result()
if exists > 0 {
continue
}
// 从 DB 加载
data, err := d.loadFromDB(ctx, key)
if err != nil {
log.Printf("warm-up failed: key=%s err=%v", key, err)
continue
}
// 设置随机 TTL(防雪崩)
d.SetWithRandomTTL(key, data, 10*time.Minute)
}
return nil
}
// 5. ★ 熔断降级(Redis 不可用,直接返回兜底数据)
func (d *CacheDefense) GetWithFallback(ctx context.Context, key string, fallback interface{}) (interface{}, error) {
val, err := d.rdb.Get(ctx, key).Result()
if err != nil {
if errors.Is(err, redis.Nil) {
return fallback, nil // Key 不存在,返回兜底值
}
// Redis 挂了 → 熔断,直接返回兜底
log.Printf("[cache] redis unreachable, return fallback for key=%s", key)
return fallback, nil
}
var result interface{}
json.Unmarshal([]byte(val), &result)
return result, nil
}
3.5 消息队列抗写并发
go
// mq/write_peaker.go
package mq
import (
"context"
"encoding/json"
"time"
)
// WritePeaker 写流量削峰器
// 将同步写 DB 改为异步写 MQ + 消费者批量写 DB
type WritePeaker struct {
producer MessageProducer
}
type OrderCreatedEvent struct {
OrderID int64 `json:"order_id"`
OrderNo string `json:"order_no"`
UserID int64 `json:"user_id"`
Amount float64 `json:"amount"`
Items []Item `json:"items"`
CreatedAt time.Time `json:"created_at"`
// 事件版本号(幂等消费用)
EventVersion int64 `json:"event_version"`
}
// Producer: 下单时,先发 MQ 再快速返回
func (p *WritePeaker) CreateOrderAsync(ctx context.Context, event OrderCreatedEvent) error {
body, _ := json.Marshal(event)
// ★ 快速发送 MQ,不直接写 DB
return p.producer.Send(ctx, &Message{
Topic: "order-create",
Key: event.OrderNo,
Body: body,
})
}
// Consumer: MQ 消费者批量写入 DB(削峰填谷)
func (p *WritePeaker) ConsumeAndPersist(ctx context.Context, msgs []*Message) error {
// ★ 批量写入,减少 DB 连接开销
tx, _ := db.BeginTx(ctx, nil)
defer tx.Rollback()
stmt, _ := tx.Prepare(`
INSERT INTO orders (order_no, user_id, amount, status, created_at)
VALUES (?, ?, ?, 0, NOW())
ON DUPLICATE KEY UPDATE updated_at = NOW()
`)
defer stmt.Close()
for _, msg := range msgs {
var event OrderCreatedEvent
json.Unmarshal(msg.Body, &event)
stmt.Exec(event.OrderNo, event.UserID, event.Amount)
// 同时写 order_items ...
}
return tx.Commit()
}
3.6 服务熔断与降级
go
// circuit/circuit_breaker.go
package circuit
import (
"context"
"errors"
"sync"
"time"
)
// State 熔断器状态
type State int
const (
StateClosed State = iota // 正常(关闭)
StateOpen // 熔断(打开)
StateHalfOpen // 半开(试探恢复)
)
// CircuitBreaker 熔断器
type CircuitBreaker struct {
mu sync.RWMutex
state State
failureCount int
successCount int
// 配置
failureThreshold int // 失败多少次触发熔断
successThreshold int // 半开状态成功多少次恢复
timeout time.Duration // 熔断后多久进入半开
halfOpenMax int // 半开状态允许的最大请求数
lastFailureTime time.Time
}
func NewCircuitBreaker(failureThreshold int, timeout time.Duration) *CircuitBreaker {
return &CircuitBreaker{
state: StateClosed,
failureThreshold: failureThreshold,
successThreshold: 5,
timeout: timeout,
halfOpenMax: 10,
}
}
// Execute 执行受保护的操作
func (cb *CircuitBreaker) Execute(ctx context.Context, fn func() error) error {
if !cb.allowRequest() {
return errors.New("circuit breaker is open, request rejected")
}
err := fn()
if err != nil {
cb.recordFailure()
return err
}
cb.recordSuccess()
return nil
}
func (cb *CircuitBreaker) allowRequest() bool {
cb.mu.RLock()
state := cb.state
cb.mu.RUnlock()
switch state {
case StateClosed:
return true
case StateOpen:
// 检查是否超过超时时间,可以进入半开
cb.mu.Lock()
defer cb.mu.Unlock()
if time.Since(cb.lastFailureTime) > cb.timeout {
cb.state = StateHalfOpen
cb.successCount = 0
log.Println("[circuit] state: OPEN → HALF_OPEN")
return true
}
return false
case StateHalfOpen:
cb.mu.RLock()
defer cb.mu.RUnlock()
return cb.successCount < cb.halfOpenMax
default:
return false
}
}
func (cb *CircuitBreaker) recordFailure() {
cb.mu.Lock()
defer cb.mu.Unlock()
cb.failureCount++
cb.lastFailureTime = time.Now()
if cb.failureCount >= cb.failureThreshold && cb.state == StateClosed {
cb.state = StateOpen
cb.failureCount = 0
log.Printf("[circuit] state: CLOSED → OPEN (failures=%d)", cb.failureCount)
}
}
func (cb *CircuitBreaker) recordSuccess() {
cb.mu.Lock()
defer cb.mu.Unlock()
cb.failureCount = 0 // 成功后重置失败计数
if cb.state == StateHalfOpen {
cb.successCount++
if cb.successCount >= cb.successThreshold {
cb.state = StateClosed
cb.successCount = 0
log.Println("[circuit] state: HALF_OPEN → CLOSED")
}
}
}
// StateString 返回状态字符串
func (cb *CircuitBreaker) StateString() string {
cb.mu.RLock()
defer cb.mu.RUnlock()
switch cb.state {
case StateClosed:
return "CLOSED"
case StateOpen:
return "OPEN"
case StateHalfOpen:
return "HALF_OPEN"
default:
return "UNKNOWN"
}
}
四、底层:数据一致性与保底
目标:无论上层如何削峰、异步、降级,最终落入数据库的数据必须是正确、一致的。
4.1 数据库接入前的流量平滑
go
// db/connection_pool.go
package db
import (
"context"
"database/sql"
"sync"
"time"
)
// DBPoolManager 数据库连接池管理 + 流量平滑
type DBPoolManager struct {
master *sql.DB
slaves []*sql.DB
// 流量平滑:令牌桶
writeLimiter *rateLimiter
readLimiter *rateLimiter
mu sync.RWMutex
}
// rateLimiter 简易令牌桶
type rateLimiter struct {
tokens float64
rate float64 // 每秒生成 token 数
maxTokens float64
mu sync.Mutex
lastTime time.Time
}
func newRateLimiter(rate, maxTokens float64) *rateLimiter {
return &rateLimiter{
tokens: maxTokens,
rate: rate,
maxTokens: maxTokens,
lastTime: time.Now(),
}
}
func (r *rateLimiter) Allow() bool {
r.mu.Lock()
defer r.mu.Unlock()
// 生成 token
elapsed := time.Since(r.lastTime).Seconds()
r.tokens += elapsed * r.rate
if r.tokens > r.maxTokens {
r.tokens = r.maxTokens
}
r.lastTime = time.Now()
if r.tokens >= 1 {
r.tokens -= 1
return true
}
return false
}
// ★ 限流写操作(保护主库)
func (m *DBPoolManager) WriteWithRateLimit(ctx context.Context, fn func(*sql.Tx) error) error {
if !m.writeLimiter.Allow() {
return fmt.Errorf("db write rate limit exceeded")
}
tx, _ := m.master.BeginTx(ctx, nil)
defer tx.Rollback()
if err := fn(tx); err != nil {
return err
}
return tx.Commit()
}
// ★ 读写分离
func (m *DBPoolManager) GetReader() *sql.DB {
m.mu.RLock()
defer m.mu.RUnlock()
if len(m.slaves) == 0 {
return m.master
}
// 随机选一个从库(或轮询)
idx := rand.Intn(len(m.slaves))
return m.slaves[idx]
}
// ★ 强制走主库(写后读一致性)
func (m *DBPoolManager) GetMaster() *sql.DB {
return m.master
}
4.2 幂等性设计
go
// idempotent/idempotent.go
package idempotent
import (
"context"
"crypto/md5"
"database/sql"
"encoding/hex"
"fmt"
"time"
)
// 幂等性保证的三种方案
// ★ 方案一:数据库唯一约束(最简单、最可靠)
const schema = `
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(32) NOT NULL UNIQUE COMMENT '幂等键: 业务单号',
-- UNIQUE 约束天然保证不会重复创建
user_id BIGINT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
status TINYINT NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
`
// CreateOrder 利用唯一约束保证幂等
func CreateOrder(ctx context.Context, db *sql.DB, order Order) error {
_, err := db.ExecContext(ctx, `
INSERT INTO orders (order_no, user_id, amount, status, created_at)
VALUES (?, ?, ?, 0, NOW())
`, order.OrderNo, order.UserID, order.Amount)
if err != nil {
if isDuplicateKeyError(err) {
// 已存在 → 幂等返回成功
return nil
}
return err
}
return nil
}
func isDuplicateKeyError(err error) bool {
return strings.Contains(err.Error(), "Duplicate entry") ||
strings.Contains(err.Error(), "duplicate key")
}
// ★ 方案二:幂等 Token(Redis + DB)
type IdempotentManager struct {
rdb *redis.Client
}
// GenerateToken 生成幂等 Token(客户端提交前获取)
func (m *IdempotentManager) GenerateToken(ctx context.Context) (string, error) {
token := generateUUID()
key := fmt.Sprintf("idempotent:token:%s", token)
// 存入 Redis,30 分钟过期
err := m.rdb.Set(ctx, key, "valid", 30*time.Minute).Err()
if err != nil {
return "", err
}
return token, nil
}
// CheckAndConsume 校验并消费 Token(保证一次有效)
func (m *IdempotentManager) CheckAndConsume(ctx context.Context, token string) (bool, error) {
key := fmt.Sprintf("idempotent:token:%s", token)
// ★ Lua 脚本保证原子性:检查 + 删除
script := `
if redis.call("GET", KEYS[1]) == "valid" then
return redis.call("DEL", KEYS[1])
else
return 0
end
`
result, err := m.rdb.Eval(ctx, script, []string{key}).Int64()
if err != nil {
return false, err
}
return result == 1, nil
}
// ★ 方案三:业务参数哈希(无需额外存储)
type IdempotentByHash struct {
db *sql.DB
}
// 幂等表设计
const idempotentTableSQL = `
CREATE TABLE IF NOT EXISTS idempotent_keys (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
biz_type VARCHAR(32) NOT NULL COMMENT '业务类型: create_order / refund',
hash_key CHAR(32) NOT NULL COMMENT '业务参数 MD5',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_biz_hash (biz_type, hash_key)
);
`
func (m *IdempotentByHash) ExecuteOnce(
ctx context.Context,
bizType string,
params interface{},
fn func() error,
) error {
// 1. 计算参数哈希
raw, _ := json.Marshal(params)
hash := md5.Sum(raw)
hashStr := hex.EncodeToString(hash[:])
// 2. 尝试插入幂等记录
_, err := m.db.ExecContext(ctx, `
INSERT INTO idempotent_keys (biz_type, hash_key, created_at)
VALUES (?, ?, NOW())
`, bizType, hashStr)
if err != nil {
if isDuplicateKeyError(err) {
// 已经执行过 → 直接成功
return nil
}
return err
}
// 3. 首次执行 → 执行业务逻辑
return fn()
}
4.3 乐观锁与分布式锁
go
// lock/locks.go
package lock
// ★ 乐观锁(CAS --- Compare And Swap)
// 适用场景:并发冲突概率低的场景(如用户信息更新)
// UpdateWithOptimisticLock 带乐观锁的更新
func UpdateWithOptimisticLock(ctx context.Context, db *sql.DB,
orderID int64, expectedVersion int64, newStatus int,
) (bool, error) {
result, err := db.ExecContext(ctx, `
UPDATE orders
SET status = ?, version = version + 1, updated_at = NOW()
WHERE id = ? AND version = ?
`, newStatus, orderID, expectedVersion)
if err != nil {
return false, err
}
n, _ := result.RowsAffected()
if n == 0 {
// version 不匹配 → 说明已被其他人修改
return false, ErrVersionConflict
}
return true, nil
}
// RetryWithOptimisticLock 乐观锁冲突自动重试
func RetryWithOptimisticLock(ctx context.Context, db *sql.DB,
orderID int64, maxRetries int, fn func() (int, error), // fn 返回 newStatus
) error {
for i := 0; i < maxRetries; i++ {
// 1. 读取当前版本
var version int64
err := db.QueryRowContext(ctx,
"SELECT version FROM orders WHERE id = ?", orderID,
).Scan(&version)
if err != nil {
return err
}
// 2. 执行业务逻辑
newStatus, err := fn()
if err != nil {
return err
}
// 3. CAS 更新
ok, err := UpdateWithOptimisticLock(ctx, db, orderID, version, newStatus)
if err != nil {
return err
}
if ok {
return nil // 成功
}
// 版本冲突 → 重试
time.Sleep(time.Duration(i*10) * time.Millisecond)
}
return ErrMaxRetriesExceeded
}
// ★ 分布式锁(Redis Redlock)
// 适用场景:并发冲突概率高、需要互斥的场景(如秒杀扣库存)
type RedisLock struct {
rdb *redis.Client
}
func (l *RedisLock) Lock(ctx context.Context, key string, ttl time.Duration) (string, error) {
token := generateUUID()
ok, err := l.rdb.SetNX(ctx, key, token, ttl).Result()
if err != nil {
return "", err
}
if !ok {
return "", ErrLockFailed
}
return token, nil
}
// Unlock 安全释放锁(Lua 保证原子性)
func (l *RedisLock) Unlock(ctx context.Context, key, token string) error {
script := `
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
`
_, err := l.rdb.Eval(ctx, script, []string{key}, token).Result()
return err
}
// Extend 续期(长事务场景)
func (l *RedisLock) Extend(ctx context.Context, key, token string, ttl time.Duration) error {
script := `
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("PEXPIRE", KEYS[1], ARGV[2])
else
return 0
end
`
_, err := l.rdb.Eval(ctx, script, []string{key}, token, int(ttl.Milliseconds())).Result()
return err
}
// 使用示例
func SafeDeductStock(ctx context.Context, lock *RedisLock, skuID int64, qty int) error {
lockKey := fmt.Sprintf("stock:lock:%d", skuID)
// 1. 获取分布式锁
token, err := lock.Lock(ctx, lockKey, 10*time.Second)
if err != nil {
return fmt.Errorf("acquire lock failed: %w", err)
}
defer lock.Unlock(ctx, lockKey, token)
// 2. 执行业务逻辑(锁保护下的临界区)
result, err := db.ExecContext(ctx, `
UPDATE inventory
SET stock = stock - ?, locked_stock = locked_stock + ?
WHERE sku_id = ? AND stock >= ?
`, qty, qty, skuID, qty)
if err != nil {
return err
}
if n, _ := result.RowsAffected(); n == 0 {
return ErrInsufficientStock
}
return nil
}
// ★ 数据库行锁(SELECT ... FOR UPDATE)
// 适用场景:对一致性要求极高、冲突范围可控的场景
func DeductWithDBLock(ctx context.Context, db *sql.DB, skuID int64, qty int) error {
tx, _ := db.BeginTx(ctx, nil)
defer tx.Rollback()
// ★ FOR UPDATE:锁定该行,事务提交前其他事务无法修改
var stock, lockedStock int
err := tx.QueryRowContext(ctx, `
SELECT stock, locked_stock
FROM inventory
WHERE sku_id = ?
FOR UPDATE
`, skuID).Scan(&stock, &lockedStock)
if err != nil {
return err
}
if stock < qty {
return ErrInsufficientStock
}
// 更新库存
_, err = tx.ExecContext(ctx, `
UPDATE inventory
SET stock = stock - ?, locked_stock = locked_stock + ?
WHERE sku_id = ?
`, qty, qty, skuID)
if err != nil {
return err
}
return tx.Commit()
}
4.4 补偿机制与最终一致性
go
// compensation/compensation.go
package compensation
// CompensationManager 补偿管理器
type CompensationManager struct {
db *sql.DB
mq MessageProducer
rdb *redis.Client
}
// CompensationTask 补偿任务记录
type CompensationTask struct {
ID int64
BizType string // 业务类型
BizID string // 业务 ID
Status string // pending / success / failed
RetryCount int
MaxRetries int
NextRetryAt time.Time
ErrorMessage string
CreatedAt time.Time
UpdatedAt time.Time
}
// ★ 本地消息表 + 定时补偿
const compensationTableSQL = `
CREATE TABLE IF NOT EXISTS compensation_tasks (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
biz_type VARCHAR(32) NOT NULL,
biz_id VARCHAR(64) NOT NULL COMMENT '业务ID',
status VARCHAR(16) NOT NULL DEFAULT 'pending',
retry_count INT NOT NULL DEFAULT 0,
max_retries INT NOT NULL DEFAULT 10,
next_retry_at DATETIME NOT NULL COMMENT '下次重试时间',
error_message TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_status_retry (status, next_retry_at),
UNIQUE KEY uk_biz (biz_type, biz_id)
);
`
// RegisterTask 注册补偿任务(本地事务,与业务写入同一个事务)
func (m *CompensationManager) RegisterTask(ctx context.Context, tx *sql.Tx,
bizType, bizID string) error {
_, err := tx.ExecContext(ctx, `
INSERT INTO compensation_tasks (biz_type, biz_id, status, next_retry_at)
VALUES (?, ?, 'pending', DATE_ADD(NOW(), INTERVAL 1 MINUTE))
ON DUPLICATE KEY UPDATE status = 'pending', retry_count = 0
`, bizType, bizID)
return err
}
// CompensationScanner 补偿扫描器(定时扫描未成功的任务)
type CompensationScanner struct {
manager *CompensationManager
// 各业务类型的重试处理器
handlers map[string]RetryHandler
}
type RetryHandler func(ctx context.Context, bizID string) error
func (s *CompensationScanner) RegisterHandler(bizType string, handler RetryHandler) {
s.handlers[bizType] = handler
}
func (s *CompensationScanner) Scan(ctx context.Context, batchSize int) {
rows, err := s.manager.db.QueryContext(ctx, `
SELECT id, biz_type, biz_id, retry_count
FROM compensation_tasks
WHERE status = 'pending'
AND next_retry_at <= NOW()
LIMIT ?
`, batchSize)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
var taskID int64
var bizType, bizID string
var retryCount int
rows.Scan(&taskID, &bizType, &bizID, &retryCount)
handler, ok := s.handlers[bizType]
if !ok {
continue
}
// 执行重试
err := handler(ctx, bizID)
if err == nil {
// 成功 → 标记完成
s.markSuccess(ctx, taskID)
} else {
// 失败 → 更新重试信息
retryCount++
s.markRetry(ctx, taskID, retryCount, err)
}
}
}
func (s *CompensationScanner) markSuccess(ctx context.Context, taskID int64) {
s.manager.db.ExecContext(ctx, `
UPDATE compensation_tasks
SET status = 'success', updated_at = NOW()
WHERE id = ?
`, taskID)
}
func (s *CompensationScanner) markRetry(ctx context.Context, taskID, retries int, err error) {
// 指数退避: 1min → 2min → 4min → 8min ...
delayMinutes := int(math.Pow(2, float64(retries)))
if delayMinutes > 60 {
delayMinutes = 60 // 最多 1 小时重试一次
}
s.manager.db.ExecContext(ctx, `
UPDATE compensation_tasks
SET retry_count = ?,
next_retry_at = DATE_ADD(NOW(), INTERVAL ? MINUTE),
error_message = ?,
updated_at = NOW()
WHERE id = ?
`, retries, delayMinutes, err.Error(), taskID)
}
go
// 本地消息表使用示例(下单 + 库存扣减)
func CreateOrderWithCompensation(ctx context.Context, db *sql.DB, comp *CompensationManager, order Order) error {
tx, _ := db.BeginTx(ctx, nil)
defer tx.Rollback()
// 1. 创建订单
_, err := tx.ExecContext(ctx, `
INSERT INTO orders (order_no, user_id, amount, status)
VALUES (?, ?, ?, 0)
`, order.OrderNo, order.UserID, order.Amount)
if err != nil {
return err
}
// 2. ★ 扣库存(同步操作,可能失败)
result, err := tx.ExecContext(ctx, `
UPDATE inventory SET stock = stock - 1
WHERE sku_id = ? AND stock > 0
`, order.SkuID)
if err != nil || result.RowsAffected() == 0 {
// 库存不足 → 回滚整个事务
return ErrInsufficientStock
}
// 3. ★ 注册补偿任务(发给下游服务)
// 如果这里失败,事务回滚,订单也回滚 → 一致性保证
err = comp.RegisterTask(ctx, tx, "notify_user", order.OrderNo)
if err != nil {
return err
}
// 4. 提交事务(三步原子成功)
return tx.Commit()
}
4.5 分布式事务(Saga / TCC)
go
// saga/saga.go
package saga
// Saga 编排器(用于跨服务长事务,如:订单 → 库存 → 支付 → 物流)
type SagaOrchestrator struct {
steps []SagaStep
comps []SagaStep // 对应的补偿步骤
}
type SagaStep struct {
Name string
Execute func(ctx context.Context) error
Compensate func(ctx context.Context) error // 补偿/回滚
}
func (o *SagaOrchestrator) AddStep(name string, execute, compensate func(ctx context.Context) error) {
o.steps = append(o.steps, SagaStep{
Name: name,
Execute: execute,
})
o.comps = append(o.comps, SagaStep{
Name: name,
Compensate: compensate,
})
}
// Execute 执行 Saga(正向执行,失败则逆向补偿)
func (o *SagaOrchestrator) Execute(ctx context.Context) error {
executedSteps := make([]int, 0)
for i, step := range o.steps {
log.Printf("[saga] executing step %d: %s", i+1, step.Name)
err := step.Execute(ctx)
if err != nil {
log.Printf("[saga] step %s failed: %v, starting compensation", step.Name, err)
// ★ 逆向补偿:从当前步往前,依次执行补偿
for j := i; j >= 0; j-- {
compStep := o.comps[j]
log.Printf("[saga] compensating step: %s", compStep.Name)
if compStep.Compensate != nil {
compErr := compStep.Compensate(ctx)
if compErr != nil {
log.Printf("[saga] compensation for %s also failed: %v",
compStep.Name, compErr)
// 补偿失败 → 记录到补偿表,人工介入
// compManager.RegisterTask(ctx, ...)
}
}
_ = executedSteps // 用于记录
}
return fmt.Errorf("saga failed at step %s: %w", step.Name, err)
}
executedSteps = append(executedSteps, i)
}
log.Printf("[saga] all %d steps completed successfully", len(o.steps))
return nil
}
// 使用示例:下单 Saga
func CreateOrderSaga(order Order) *SagaOrchestrator {
saga := &SagaOrchestrator{}
// Step 1: 校验库存 + 锁定库存
saga.AddStep(
"lock_inventory",
func(ctx context.Context) error {
return inventoryService.Lock(ctx, order.Items)
},
func(ctx context.Context) error {
return inventoryService.Unlock(ctx, order.Items) // 补偿:释放库存
},
)
// Step 2: 创建订单
saga.AddStep(
"create_order",
func(ctx context.Context) error {
return orderService.Create(ctx, order)
},
func(ctx context.Context) error {
return orderService.Cancel(ctx, order.ID) // 补偿:取消订单
},
)
// Step 3: 扣减优惠券
saga.AddStep(
"use_coupon",
func(ctx context.Context) error {
return couponService.Use(ctx, order.CouponID)
},
func(ctx context.Context) error {
return couponService.Return(ctx, order.CouponID) // 补偿:返还优惠券
},
)
return saga
}
五、全链路监控与可观测性
5.1 关键监控指标
go
// metrics/metrics.go
package metrics
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
// Prometheus 指标体系
var (
// 请求量
RequestTotal = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total HTTP requests",
}, []string{"method", "path", "status"})
// 请求延迟
RequestLatency = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request latency",
Buckets: []float64{.001, .005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10},
}, []string{"method", "path"})
// 各层流量
LayerTraffic = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "layer_traffic_qps",
Help: "Traffic QPS at each defense layer",
}, []string{"layer"}) // edge, gateway, service, cache, mq, db
// 熔断状态
CircuitBreakerState = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "circuit_breaker_state",
Help: "Circuit breaker state: 0=closed, 1=open, 2=half_open",
}, []string{"name"})
// 数据库连接池
DBConnPool = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "db_connection_pool",
Help: "DB connection pool stats",
}, []string{"db", "type"}) // type: active, idle, waiting
// 缓存命中率
CacheHitRatio = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "cache_hit_ratio",
Help: "Cache hit ratio",
}, []string{"level"}) // L1, L2
// 消息队列
MQMessageLag = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "mq_message_lag",
Help: "MQ consumer message lag",
}, []string{"topic", "consumer_group"})
// 补偿任务
CompensationTaskCount = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "compensation_task_count",
Help: "Pending compensation task count",
}, []string{"biz_type"})
)
// ReportLayerTraffic 上报各层流量
func ReportLayerTraffic() {
// edge: 从 Nginx 日志统计
// gateway: 从网关统计
// service: 从 RPC 统计
// cache: Redis QPS
// mq: MQ 吞吐
// db: DB QPS
LayerTraffic.WithLabelValues("edge").Set(edgeQPS)
LayerTraffic.WithLabelValues("service").Set(serviceQPS)
LayerTraffic.WithLabelValues("db").Set(dbQPS)
}
5.2 健康检查与就绪探测
go
// health/health.go
package health
// HealthChecker 健康检查器
type HealthChecker struct {
checks map[string]HealthCheck
}
type HealthCheck func(ctx context.Context) error
func NewHealthChecker() *HealthChecker {
return &HealthChecker{
checks: make(map[string]HealthCheck),
}
}
func (h *HealthChecker) Register(name string, check HealthCheck) {
h.checks[name] = check
}
// CheckAll 检查所有组件健康状态
func (h *HealthChecker) CheckAll(ctx context.Context) map[string]string {
results := make(map[string]string)
for name, check := range h.checks {
err := check(ctx)
if err != nil {
results[name] = "unhealthy: " + err.Error()
} else {
results[name] = "healthy"
}
}
return results
}
// 注册各层健康检查
func RegisterHealthChecks(db *sql.DB, rdb *redis.Client, mq MessageProducer) *HealthChecker {
hc := NewHealthChecker()
// 数据库检查
hc.Register("mysql", func(ctx context.Context) error {
return db.PingContext(ctx)
})
// Redis 检查
hc.Register("redis", func(ctx context.Context) error {
return rdb.Ping(ctx).Err()
})
// MQ 检查
hc.Register("mq", func(ctx context.Context) error {
return mq.Ping()
})
// CDN / Nginx 依赖的外部检查
hc.Register("external_api", func(ctx context.Context) error {
// 检查第三方支付网关等
return nil
})
return hc
}
六、压测与容量规划
6.1 压测金字塔
scss
┌──────────────┐
│ 全链路压测 │ ← 生产环境影子流量
│ (最真实) │
├──────────────┤
│ 组件压测 │ ← 各组件极限 QPS
│ Redis/MQ/DB │
├──────────────┤
│ 单服务压测 │ ← 单 Pod 极限 QPS
│ 接口级 │
├──────────────┤
│ 单元测试 │ ← 并发安全验证
│ (race) │
└──────────────┘
6.2 Go 并发压测工具集成
go
// benchmark/order_bench_test.go
package benchmark
import (
"sync"
"sync/atomic"
"testing"
"time"
)
// TestConcurrentOrderCreation 并发创建订单压测
func TestConcurrentOrderCreation(t *testing.T) {
concurrency := 1000 // 1000 并发
totalReqs := 100000 // 总共 10 万请求
var (
successCount atomic.Int64
failCount atomic.Int64
totalLatency atomic.Int64 // 纳秒
)
start := time.Now()
var wg sync.WaitGroup
semaphore := make(chan struct{}, concurrency)
for i := 0; i < totalReqs; i++ {
wg.Add(1)
semaphore <- struct{}{}
go func(idx int) {
defer wg.Done()
defer func() { <-semaphore }()
reqStart := time.Now()
// 发送创建订单请求
err := createOrderRequest(idx)
latency := time.Since(reqStart).Nanoseconds()
totalLatency.Add(latency)
if err != nil {
failCount.Add(1)
} else {
successCount.Add(1)
}
}(i)
}
wg.Wait()
elapsed := time.Since(start).Seconds()
t.Logf("=== Benchmark Results ===")
t.Logf("Total Requests: %d", totalReqs)
t.Logf("Concurrency: %d", concurrency)
t.Logf("Duration: %.2fs", elapsed)
t.Logf("QPS: %.0f", float64(totalReqs)/elapsed)
t.Logf("Success: %d, Fail: %d", successCount.Load(), failCount.Load())
t.Logf("Avg Latency: %.2fms", float64(totalLatency.Load())/float64(totalReqs)/1e6)
t.Logf("Success Rate: %.2f%%", float64(successCount.Load())/float64(totalReqs)*100)
}
6.3 容量规划公式
ini
单 Pod QPS = 1000
目标 QPS = 10000
核心服务 Pod 数 = ceil(10000 / 1000) * 2 = 20 (2倍冗余)
数据库连接数:
maxOpenConns = Pod数 * 每个Pod最大连接数 = 20 * 20 = 400
(主库 max_connections 需 > 400)
Redis:
缓存命中率 90% → 实际打到 DB 的 QPS = 10000 * 0.1 = 1000
DB 需要承受 1000 QPS → 确保 DB 连接池和配置满足
MQ:
峰值写 QPS = 5000
MQ 消费者数 ≥ ceil(5000 / 单消费者处理能力)
单消费者批量处理能力 = 100/s → 消费者数 = 50
七、完整架构落地 CheckList
7.1 最外层 CheckList
- CDN 缓存静态资源,正确设置
Cache-Control头 - Nginx 配置
limit_conn和limit_req限流 - 敏感接口(登录/秒杀)单独限制速率
- WAF 规则生效(SQL注入/XSS/CC攻击拦截)
- IP 黑名单机制就绪(自动封禁 + 手动解封)
- 防爬签名验证对关键 API 生效
- 网关统一鉴权(JWT Token 校验)
7.2 中层 CheckList
- 核心/非核心服务隔离部署(不同 K8s namespace/node pool)
- 核心/非核心链路协程池隔离
- Redis 集群部署,主从 + 哨兵/Cluster 模式
- 多级缓存策略落地(L1 本地 + L2 Redis)
- 热点 Key 保护(singleflight / 互斥锁)
- 缓存穿透/击穿/雪崩防护措施就绪
- MQ 集群部署(RocketMQ / Kafka)
- 写流量削峰(同步改异步的接口已确认)
- 熔断器配置合理(失败阈值、超时时间、半开策略)
- 降级策略已制定(每个接口有降级兜底返回值)
7.3 底层 CheckList
- 数据库读写分离(主库写、从库读)
- 分库分表策略(若订单量超过千万级)
- 数据库连接池大小合理(不超过 DB max_connections)
- 所有写接口有幂等保障(唯一约束 / Token / 参数哈希)
- 高冲突场景使用乐观锁 + 重试
- 极高冲突场景使用分布式锁 / DB行锁
- 本地消息表 + 补偿任务就绪
- 跨服务调用有 Saga/TCC 编排
- 每日凌晨全量对账脚本
- 死信队列有人工处理流程
7.4 可观测性 CheckList
- Prometheus 指标采集(请求量/延迟/各层流量)
- Grafana 大盘已搭建(QPS/延迟/错误率/缓存命中率)
- 链路追踪接入(Jaeger / Zipkin)
- 告警规则配置(DB连接数/缓存命中率/MQ积压/熔断)
- 健康检查接口(/health, /ready)
- 日志结构化 + 集中采集(ELK / Loki)
最终总结:
高并发防御不是某一个技术点的胜利,而是从边缘到核心层层衰减的系统工程:
scss边缘层 (CDN/Nginx/WAF):把 10000 QPS 拦到 3000 ↓ 缓冲层 (Redis/MQ/熔断):把 3000 QPS 缓冲到 300 ↓ 保底层 (幂等/乐观锁/补偿):300 QPS 安全落地到 DB每一层都不可替代,层层设防,才能让系统在高并发场景下稳定运行。