每日一Go-53、Go微服务--限流与降级

在微服务架构中,有一句话非常残酷,但极其真实:系统不是被"慢"拖死的,而是被"瞬时洪峰"打死的。

昨天我们讲了 超时 + 熔断 ,那是"服务已经出问题时的自我保护";而今天的 限流与降级,是更靠前的一道防线------在问题发生之前,先把系统保住。

🫱🫱🫱文末有源码🫲🫲🫲

一、什么是限流?什么是降级?

  1. 限流:限流的本质就一句话--系统吃得下多少请求,就只给多少。超过处理能力的请求直接拒绝;防止CPU、连接池、下游服务被瞬间打爆。限流通常发生在:API网关、服务入口(Gin中间件)、核心接口(登录、下单、支付)

  2. 降级:降级不是失败,而是主动放弃"不重要的部分"。

二、Gin中实现限流--令牌桶模型

  1. 核心思路

桶里有n个令牌,每秒补充m个,每个请求消耗一个,没令牌就直接拒绝。

  1. 限流器实现
go 复制代码
package limiter
import (
    "sync"
    "time"
)
// TokenBucket 实现了令牌桶限流算法
type TokenBucket struct {
    capacity int        // 令牌桶容量
    tokens   int        // 当前令牌数量
    rate     int        // 每秒生成令牌速率
    lock     sync.Mutex // 互斥锁,保证线程安全
}
// NewTokenBucket 创建一个新的令牌桶限流器
// capacity: 令牌桶容量,表示最多可以存储多少个令牌
// rate: 每秒生成的令牌数量
func NewTokenBucket(capacity, rate int) *TokenBucket {
    tb := &TokenBucket{
        capacity: capacity, // 设置令牌桶容量
        tokens:   capacity, // 初始时令牌桶是满的
        rate:     rate,     // 设置每秒生成令牌的速率
    }
    // 启动令牌生成协程
    go tb.refill()
    return tb
}
// refill 定时生成令牌的方法
func (tb *TokenBucket) refill() {
    // 创建一个每秒触发一次的定时器
    ticker := time.NewTicker(time.Second)
    // 无限循环,每秒向令牌桶中添加令牌
    for range ticker.C {
        tb.lock.Lock()
        // 向令牌桶中添加rate个令牌
        tb.tokens += tb.rate
        // 确保令牌数量不超过桶容量
        if tb.tokens > tb.capacity {
            tb.tokens = tb.capacity
        }
        tb.lock.Unlock()
    }
}
// Allow 判断是否允许请求通过
// 返回true表示允许,false表示拒绝
func (tb *TokenBucket) Allow() bool {
    tb.lock.Lock()
    defer tb.lock.Unlock()
    // 如果有可用令牌,消耗一个令牌并返回true
    if tb.tokens > 0 {
        tb.tokens--
        return true
    }
    // 没有可用令牌,返回false
    return false
}
  1. Gin中间件接入限流
go 复制代码
package middleware
import (
    "day53/limiter"
    "net/http"
    "github.com/gin-gonic/gin"
)
// RateLimit 创建一个基于令牌桶的Gin限流中间件
// tb: 令牌桶限流器实例
func RateLimit(tb *limiter.TokenBucket) gin.HandlerFunc {
    return func(c *gin.Context) {
        // 检查是否允许请求通过限流器
        if !tb.Allow() {
            // 如果请求被限流,返回429 Too Many Requests状态码
            c.JSON(http.StatusTooManyRequests, gin.H{
                "error": "too many requests",
            })
            // 终止请求处理链
            c.Abort()
            return
        }
        // 如果允许请求通过,继续处理下一个中间件
        c.Next()
    }
}

三、Gin中实现服务降级

降级的关键不是考虑代码怎么写,而是要承认一件事:有些功能,在系统压力下,不值得活。

  1. 降级策略
  • 系统繁忙->返回简化数据

  • 系统正常->返回完整数据

  1. 示例接口
go 复制代码
package main
import (
    "fmt"
    "math/rand/v2"
    "net/http"
    "time"
    "day53/limiter"
    "day53/middleware"
    "github.com/gin-gonic/gin"
)
// main 函数是程序的入口点
func main() {
    // 打印欢迎信息
    fmt.Println("Hello, Codee君!\nWelcome to golang_per_day")
    r := gin.Default()
    // 创建令牌桶限流器:容量为10,每秒生成5个令牌
    tb := limiter.NewTokenBucket(10, 5)
    // 将限流中间件应用到所有路由
    r.Use(middleware.RateLimit(tb))
    r.GET("/profile/:id", func(c *gin.Context) {
        id := c.Param("id")
        // 模拟下游服务返回 503 触发降级
        if rand.Float32() < 0.4 {
            // 触发降级
            c.JSON(http.StatusOK, gin.H{
                "user_id": id,
                "name":    "anonymous",
                "remark":  "degraded response",
            })
            return
        }
        // 正常完整响应
        time.Sleep(100 * time.Millisecond)
        c.JSON(http.StatusOK, gin.H{
            "user_id": id,
            "name":    "Tom",
            "age":     28,
            "email":   "tom@example.com",
        })
    })
    r.Run(":8080")
}

四、限流+降级=微服务的最后防线

在真实系统中,顺序通常是:

限流->超时->熔断->降级

  • 限流:挡住洪水

  • 超时:不被慢服务拖死

  • 熔断:隔离失败

  • 降级:核心功能活下来

五、人生比喻

  1. 限流,是你对人生的清醒认知;你每天的精力是有限的,不是什么请求都要答应,不是什么事情都值得立刻处理。学会拒绝,是成熟的开始。

  2. 降级,是你对现实的妥协智慧;人生不顺时,不要求完美输出,先保证还能继续走。系统可以返回简化数据,人也可以暂时降低标准。

  3. 高可用的人生:真正厉害的人,不是永远满负荷运行,而是在压力来临时,主动限流;在扛不住时,果断降级;但核心价值,永远不崩。

六、一句话总结

限流,是对能力边界的尊重;

降级,是对现实压力的妥协;

而能长期稳定运行的系统和人生,都懂得什么时候该"少做一点"。

*源码地址*

源码私信给


如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!

相关推荐
阿里嘎多学长3 小时前
2026-04-17 GitHub 热点项目精选
开发语言·程序员·github·代码托管
Wadli3 小时前
集群C++聊天服务器
服务器·开发语言·c++
凭君语未可3 小时前
为什么需要代理?从一个基础问题理解 JDK 静态代理
java·开发语言
luoqice3 小时前
利用flv库读取flv文件时长c程序
c语言·开发语言
NotFound4864 小时前
Go语言中的图形界面开发实战解析:从GUI到WebAssembly
开发语言·golang·wasm
Rust研习社4 小时前
Rust Default 特征详解:轻松实现类型默认值
开发语言·后端·rust
jiayong234 小时前
第 25 课:给学习笔记页加上搜索、标签筛选和 URL 同步
开发语言·前端·javascript·vue.js·学习
想唱rap4 小时前
C++11之包装器
服务器·开发语言·c++·算法·ubuntu