熔断+限流+缓存降级是如何保证服务请求的高可用性的?(实现原理+代码示例)

在这里插入图片描述

前言:

本文介绍了 应用程序中当请求来临时,后端服务对于其响应逻辑的一个完整处理流程(在go环境下 借助于 redis+本地缓存 封装为gin框架的请求路由中间价,对外部请求实现了 限流 → 缓存 → 熔断 → 降级 → DB)的多层级响应,保证了请求响应服务的高可用性

请求处理逻辑图

在这里插入图片描述

设计要点摘要说明

1、分层防护体系:

  • 限流 → 缓存 → 熔断 → 降级 → DB

  • 每层拦截减少后端压力

2、优雅降级:

  • 优先返回降级数据而非直接报错

  • 异步补更新机制确保最终一致性

3、熔断恢复:

  • 半开状态平滑恢复服务

  • 渐进式增加流量避免二次雪崩

4、资源隔离:

  • 本地缓存作为最后防线

  • 避免分布式缓存污染

5、自动修复:

  • 服务恢复后自动补更新缓存

  • 熔断器自动状态转换

相关概念解释

服务限流:

  • 定义:对流量实施的一种限制性机制,旨在防范流量过大所引发的系统过载乃至崩溃现象。
  • 实现方式:可以基于请求计数、请求频率或连接数来实施限流策略。
  • 作用:在高并发和突发流量的场景下,确保服务的正常运行,防止系统因流量过大而崩溃。

熔断机制:

  • 定义:一种保护机制,用于防止故障的扩散,类似于电路中的电阻器。
  • 实现原理:根据监测服务的响应时间或错误率来自动触发熔断机制。一旦这些指标超过预设的阈值,熔断机制将自动启动,迅速终止对故障服务的请求,直至该服务恢复正常。
  • 作用:防止微服务架构中的“雪崩效应”,即某个服务出现故障导致整个链路受影响,进而造成越来越多的服务不可用。

缓存降级:

  • 定义:在系统负载过高或出现故障时,通过暂时屏蔽部分非核心功能或降低服务质量来确保核心功能的持续、稳定运作。
  • 实现方式:例如,当Redis服务出现问题时,可以将请求从Redis引导到其他存储系统或直接返回预设的默认值。
  • 作用:快速响应系统故障,减少服务中断时间,保护核心服务的稳定性。

架构设计图

在这里插入图片描述

我是都写在一起了,毕竟是个演示demo,不过我都加了相关的代码注释,感兴趣的可以自己做个拆分,然后互相进行包调用即可,可以修改修改,集成进自己的生产项目使用

关键注释说明

1. 熔断器状态机

— 状态转换说明:

  • Closed → Open: 失败达到阈值 (failCount >= maxFail)
  • Open → HalfOpen: 熔断时间结束 (time.Since > recoverWindow)
  • HalfOpen → Closed: 测试请求成功 (successCount >= halfOpenReq)
  • HalfOpen → Open: 测试请求失败

2. 中间件处理流程

— 处理流程:

    1. 参数校验 → 2. 限流检查 → 3. Redis缓存 → 4. 熔断检查 → 5. 数据库查询 → 6. 正常响应

— 关键点:

  • 每层防护都提前终止流程(c.Abort())
  • Redis命中后必须终止后续处理
  • 熔断期间使用本地降级数据
  • 数据库失败后尝试降级并异步更新缓存

3. 缓存更新策略

— 缓存更新策略:

  • 正常流程: 同步更新Redis和本地缓存
  • 异常流程(数据库失败但有降级数据):
    •    立即返回降级数据
      
    • 异步尝试更新Redis(当服务恢复后)
      

— 设计优点:

  • 避免熔断期间污染分布式缓存
  • 服务恢复后自动修复缓存
  • 最终一致性保障

4. 熔断器半开状态

— 半开状态设计:

  • 目的: 平滑恢复服务,避免二次雪崩
  • 机制:
    •     允许少量请求(halfOpenReq)通过
      
    •     成功达到阈值 → 关闭熔断
      
    •      任何失败 → 重新开启熔断
      
  • 优势: 渐进式恢复服务,避免突发流量冲击

具体代码实现(golang–demo中gin+redis+本地缓存)

封装为自定义请求访问中间件

package main

import (
	"fmt"
	"math/rand"
	"net/http"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
)

// ========================
// 限流器实现(令牌桶算法)
// ========================

// RateLimiter 基于令牌桶算法的限流器
// tokens: 令牌通道,每个空结构体代表一个可用令牌
// ratePerSec: 每秒生成的令牌数量
// refillTicker: 定时补充令牌的定时器
type RateLimiter struct {
	tokens       chan struct{}    // 令牌通道(缓冲通道)
	ratePerSec   int              // 每秒令牌生成速率
	refillTicker *time.Ticker     // 令牌补充定时器
}

// NewRateLimiter 创建新的限流器实例
// rps: 每秒允许的最大请求数(令牌生成速率)
func NewRateLimiter(rps int) *RateLimiter {
	// 初始化限流器
	rl := &RateLimiter{
		tokens:     make(chan struct{}, rps), // 创建容量为rps的缓冲通道
		ratePerSec: rps,
	}
	
	// 创建每秒触发一次的定时器
	rl.refillTicker = time.NewTicker(time.Second)
	
	// 启动后台goroutine定期补充令牌
	go func() {
		for range rl.refillTicker.C { // 每秒触发一次
			for i := 0; i < rl.ratePerSec; i++ { // 补充ratePerSec个令牌
				select {
				case rl.tokens <- struct{}{}: // 尝试放入令牌
				default: // 当令牌桶已满时跳过
				}
			}
		}
	}()
	return rl
}

// Allow 检查是否允许请求通过
// 返回值: true表示有可用令牌,允许请求;false表示无可用令牌,拒绝请求
func (rl *RateLimiter) Allow() bool {
	select {
	case <-rl.tokens: // 成功获取令牌
		return true
	default: // 无可用令牌
		return false
	}
}

// ========================
// 熔断器实现(增强版)
// ========================

// CircuitState 定义熔断器状态类型
type CircuitState int

const (
	StateClosed CircuitState = iota   // 状态:关闭(正常服务)
	StateOpen                         // 状态:开启(熔断中)
	StateHalfOpen                     // 状态:半开(尝试恢复)
)

// CircuitBreaker 熔断器结构体
// state: 当前熔断状态
// failCount: 连续失败次数
// successCount: 半开状态下的成功次数
// maxFail: 触发熔断的失败阈值
// recoverWindow: 熔断持续时间窗口
// halfOpenReq: 半开状态允许的测试请求数
// lastEventTime: 最后状态变更时间
// lock: 互斥锁保证并发安全
type CircuitBreaker struct {
	state         CircuitState      // 当前状态
	failCount     int               // 连续失败计数
	successCount  int               // 半开状态成功计数
	maxFail       int               // 熔断触发阈值
	recoverWindow time.Duration     // 熔断恢复时间窗
	halfOpenReq   int               // 半开状态允许请求数
	lastEventTime time.Time         // 最后状态变更时间
	lock          sync.RWMutex      // 状态读写锁
}

// NewCircuitBreaker 创建新的熔断器实例
// maxFail: 触发熔断的连续失败次数
// window: 熔断状态持续时间
func NewCircuitBreaker(maxFail int, window time.Duration) *CircuitBreaker {
	return &CircuitBreaker{
		state:         StateClosed,
		maxFail:       maxFail,
		recoverWindow: window,
		halfOpenReq:   3, // 默认允许3个测试请求
	}
}

// Allow 检查是否允许请求通过
// 返回值: true允许请求,false拒绝请求
func (cb *CircuitBreaker) Allow() bool {
	cb.lock.RLock() // 读锁
	defer cb.lock.RUnlock()

	switch cb.state {
	case StateClosed:
		return true // 关闭状态:允许所有请求
	case StateOpen:
		// 检查是否达到恢复时间
		if time.Since(cb.lastEventTime) > cb.recoverWindow {
			// 尝试切换到半开状态
			cb.lock.RUnlock()
			cb.tryHalfOpen()
			cb.lock.RLock()
			return cb.state == StateHalfOpen
		}
		return false // 仍在熔断期内
	case StateHalfOpen:
		// 半开状态:允许少量测试请求
		return cb.successCount < cb.halfOpenReq
	default:
		return false
	}
}

// RecordFail 记录一次失败请求
func (cb *CircuitBreaker) RecordFail() {
	cb.lock.Lock() // 写锁
	defer cb.lock.Unlock()

	cb.failCount++ // 增加失败计数
	
	// 关闭状态下达到失败阈值
	if cb.state == StateClosed && cb.failCount >= cb.maxFail {
		cb.state = StateOpen // 开启熔断
		cb.lastEventTime = time.Now()
		return
	}
	
	// 半开状态下失败
	if cb.state == StateHalfOpen {
		cb.state = StateOpen // 重新开启熔断
		cb.lastEventTime = time.Now()
		cb.successCount = 0 // 重置成功计数
	}
}

// RecordSuccess 记录一次成功请求
func (cb *CircuitBreaker) RecordSuccess() {
	cb.lock.Lock()
	defer cb.lock.Unlock()

	// 半开状态下的成功
	if cb.state == StateHalfOpen {
		cb.successCount++ // 增加成功计数
		
		// 成功次数达到阈值则关闭熔断
		if cb.successCount >= cb.halfOpenReq {
			cb.state = StateClosed // 关闭熔断
			cb.failCount = 0       // 重置失败计数
		}
		return
	}
	
	// 关闭状态下的成功:重置失败计数
	if cb.state == StateClosed {
		cb.failCount = 0
	}
}

// tryHalfOpen 尝试切换到半开状态(内部方法)
func (cb *CircuitBreaker) tryHalfOpen() {
	cb.lock.Lock()
	defer cb.lock.Unlock()
	
	// 熔断状态且超过恢复时间窗口
	if cb.state == StateOpen && time.Since(cb.lastEventTime) > cb.recoverWindow {
		cb.state = StateHalfOpen // 切换到半开状态
		cb.successCount = 0      // 重置成功计数
		cb.lastEventTime = time.Now()
	}
}

// ========================
// 缓存实现(线程安全)
// ========================

// SafeCache 线程安全缓存结构
// data: 实际存储数据的map
// mutex: 读写锁保证并发安全
// ttl: 缓存条目生存时间
type SafeCache struct {
	data  map[string]string // 数据存储
	mutex sync.RWMutex      // 读写锁
	ttl   time.Duration     // 缓存生存时间
}

// NewSafeCache 创建安全缓存实例
// ttl: 缓存条目生存时间
func NewSafeCache(ttl time.Duration) *SafeCache {
	c := &SafeCache{
		data: make(map[string]string),
		ttl:  ttl,
	}
	// 启动后台清理协程
	go c.cleanup()
	return c
}

// Get 从缓存获取数据
// key: 缓存键
// 返回值: (值, 是否存在)
func (c *SafeCache) Get(key string) (string, bool) {
	c.mutex.RLock()         // 读锁
	defer c.mutex.RUnlock() // 确保解锁
	val, ok := c.data[key]
	return val, ok
}

// Set 设置缓存数据
// key: 缓存键
// value: 缓存值
func (c *SafeCache) Set(key, value string) {
	c.mutex.Lock()         // 写锁
	defer c.mutex.Unlock() // 确保解锁
	c.data[key] = value
}

// cleanup 定期清理过期缓存(后台协程)
func (c *SafeCache) cleanup() {
	ticker := time.NewTicker(5 * time.Minute) // 每5分钟清理一次
	for range ticker.C {
		c.mutex.Lock()
		// 生产环境应基于时间戳清理,这里简化:随机清理1%条目
		for k := range c.data {
			if rand.Intn(100) == 0 { // 1%概率清理
				delete(c.data, k)
			}
		}
		c.mutex.Unlock()
	}
}

// ========================
// 全局变量
// ========================
var (
	localCache = sync.Map{}                     // 本地降级缓存(线程安全)
	redisCache = NewSafeCache(10 * time.Minute) // Redis模拟缓存(10分钟TTL)
	dbData     = map[string]string{             // 模拟数据库数据
		"id:1": "User-A",
		"id:2": "User-B",
	}
)

// ========================
// 数据库模拟函数
// ========================

// queryDatabaseWithRandomFailure 模拟数据库查询
// id: 查询的键
// 返回值: (查询结果, 错误信息)
// 特性: 30%概率模拟失败,随机延迟模拟网络波动
func queryDatabaseWithRandomFailure(id string) (string, error) {
	// 30%概率模拟失败
	if rand.Intn(10) < 3 {
		return "", fmt.Errorf("DB查询失败")
	}
	
	// 模拟数据库延迟(0-100ms)
	time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
	
	// 返回模拟数据
	if val, ok := dbData[id]; ok {
		return val, nil
	}
	return "", fmt.Errorf("数据不存在")
}

// ========================
// 保护中间件(核心逻辑)
// ========================

// ProtectMiddleware 创建保护中间件
// rl: 限流器实例
// cb: 熔断器实例
// 返回值: Gin中间件处理函数
func ProtectMiddleware(rl *RateLimiter, cb *CircuitBreaker) gin.HandlerFunc {
	return func(c *gin.Context) {
		// 1. 获取请求参数
		id := c.Query("id")
		if id == "" {
			c.JSON(http.StatusBadRequest, gin.H{"msg": "参数错误"})
			c.Abort() // 终止请求处理链
			return
		}
		
		// 构建缓存键
		key := "id:" + id

		// 2. 限流检查
		if !rl.Allow() {
			c.JSON(http.StatusTooManyRequests, gin.H{"msg": "限流中,请稍后再试"})
			c.Abort()
			return
		}

		// 3. Redis缓存检查
		if val, ok := redisCache.Get(key); ok {
			c.JSON(http.StatusOK, gin.H{"msg": "缓存命中", "data": val})
			c.Abort() // 关键:必须终止后续处理
			return
		}

		// 4. 熔断状态检查
		if !cb.Allow() {
			// 尝试本地降级缓存
			if v, ok := localCache.Load(key); ok {
				c.JSON(http.StatusOK, gin.H{"msg": "熔断触发,返回降级缓存", "data": v})
			} else {
				c.JSON(http.StatusServiceUnavailable, gin.H{"msg": "系统繁忙,服务降级中"})
			}
			c.Abort()
			return
		}

		// 5. 数据库查询
		val, err := queryDatabaseWithRandomFailure(key)
		if err != nil {
			// 记录失败
			cb.RecordFail()
			
			// 尝试本地降级
			if v, ok := localCache.Load(key); ok {
				c.JSON(http.StatusOK, gin.H{"msg": "数据库失败,返回降级缓存", "data": v})
				
				// 服务恢复后异步更新Redis缓存(最终一致性)
				go func() {
					if cb.Allow() { // 确保熔断已关闭
						redisCache.Set(key, v.(string))
					}
				}()
			} else {
				c.JSON(http.StatusInternalServerError, gin.H{"msg": "系统异常,请稍后重试"})
			}
			c.Abort()
			return
		}

		// 6. 正常处理流程
		redisCache.Set(key, val)       // 更新Redis缓存
		localCache.Store(key, val)     // 更新本地降级缓存
		cb.RecordSuccess()             // 记录成功(熔断器状态管理)
		
		// 返回成功响应
		c.JSON(http.StatusOK, gin.H{"msg": "成功获取数据", "data": val})
	}
}

// ========================
// 服务启动入口
// ========================
func main() {
	// 初始化随机种子
	rand.Seed(time.Now().UnixNano())
	
	// 创建Gin引擎
	r := gin.Default()

	// 初始化保护组件
	rl := NewRateLimiter(5)                 // 限流器:5 QPS
	cb := NewCircuitBreaker(3, 5*time.Second) // 熔断器:3次失败触发5秒熔断

	// 注册路由
	r.GET("/user", ProtectMiddleware(rl, cb))

	// 启动服务
	fmt.Println("服务启动于: http://localhost:8080/user?id=1")
	r.Run(":8080")
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卜锦元

白嫖是人类的终极快乐,我也是

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值