在微服务架构中,有一句话非常残酷,但极其真实:系统不是被“慢”拖死的,而是被“瞬时洪峰”打死的。
昨天我们讲了 超时 + 熔断,那是“服务已经出问题时的自我保护”;而今天的 限流与降级,是更靠前的一道防线——在问题发生之前,先把系统保住。
🫱🫱🫱文末有源码🫲🫲🫲
一、什么是限流?什么是降级?
已关注
关注
重播 分享 赞
关闭
观看更多
更多
退出全屏
切换到竖屏全屏**退出全屏
Codee君已关注
分享视频
,时长00:04
0/0
00:00/00:04
切换到横屏模式
继续播放
进度条,百分之0
00:00
/
00:04
00:04
全屏
倍速播放中
您的浏览器不支持 video 标签
继续观看
每日一Go-53、Go微服务--限流与降级
观看更多
转载
,
每日一Go-53、Go微服务--限流与降级
Codee君已关注
分享点赞在看
已同步到看一看写下你的评论
1. 限流:限流的本质就一句话--系统吃得下多少请求,就只给多少。超过处理能力的请求直接拒绝;防止CPU、连接池、下游服务被瞬间打爆。限流通常发生在:API网关、服务入口(Gin中间件)、核心接口(登录、下单、支付)
2. 降级:降级不是失败,而是主动放弃“不重要的部分”。
二、Gin中实现限流--令牌桶模型
1. 核心思路
桶里有n个令牌,每秒补充m个,每个请求消耗一个,没令牌就直接拒绝。
2. 限流器实现
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
}
3. Gin中间件接入限流
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. 降级策略
-
系统繁忙->返回简化数据
-
系统正常->返回完整数据
2. 示例接口
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. 高可用的人生:真正厉害的人,不是永远满负荷运行,而是在压力来临时,主动限流;在扛不住时,果断降级;但核心价值,永远不崩。
六、一句话总结
限流,是对能力边界的尊重;
降级,是对现实压力的妥协;
而能长期稳定运行的系统和人生,都懂得什么时候该“少做一点”。
*源码地址*
1、公众号“Codee君”回复“每日一Go”获取源码
如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!