Go 语言 API 限流实战:保障系统稳定性的护盾

文章精选推荐

1 JetBrains Ai assistant 编程工具让你的工作效率翻倍

2 Extra Icons:JetBrains IDE的图标增强神器

3 IDEA插件推荐-SequenceDiagram,自动生成时序图

4 BashSupport Pro 这个ides插件主要是用来干嘛的 ?

5 IDEA必装的插件:Spring Boot Helper的使用与功能特点

6 Ai assistant ,又是一个写代码神器

文章正文

为什么要进行 API 限流?

API 限流是控制和管理应用程序访问量的重要手段,旨在防止恶意滥用、保护后端服务的稳定性和可用性,并确保系统能够有效处理请求。API 限流不仅能提高系统的性能,还能优化用户体验。下面是为什么要进行 API 限流的一些原因:

  1. 防止服务过载
  • 当 API 接口的访问量过大时,后端服务可能无法处理所有请求,导致服务崩溃或响应迟缓。
  • 限流能够保证后端服务不会因为高并发请求而被击垮,从而提升系统的可靠性。
  1. 保护资源
  • API 服务通常需要访问数据库、缓存、第三方 API 等资源。通过限流,可以控制这些资源的访问频率,防止它们被过度消耗。
  • 防止用户发送大量无效请求或重复请求,避免不必要的资源浪费。
  1. 提高用户体验
  • API 限流能够确保每个用户公平地获得访问权限,避免某些用户发送过多请求从而影响其他用户的体验。
  1. 防止恶意攻击
  • 限流是抵御 DDoS(分布式拒绝服务)攻击的一种手段,可以有效减缓大量请求的冲击。
  • 防止 暴力破解(如密码暴力破解、账户滥用等)攻击,限制某些频繁请求的行为。
  1. 成本控制
  • 许多服务(如云服务、第三方 API)基于请求量计费。通过限流,能够在一定程度上减少不必要的成本。

常用的 API 限流算法

以下是一些常见的 API 限流算法,每种算法有不同的优缺点和适用场景:

1. 令牌桶算法(Token Bucket)
  • 原理:令牌桶算法维护一个桶,每个请求需要获取一个令牌。令牌会以固定速率放入桶中,桶有最大容量。如果桶满了,新的令牌将会丢弃。当请求到达时,若桶中有令牌,说明可以处理该请求,令牌被取走;若桶中没有令牌,则拒绝请求。

  • 特点

    • 能够处理突发流量,因为桶中可以积累一定数量的令牌。
    • 适用于允许请求量有一定波动的场景。
  • 适用场景:适用于限流场景中需要允许短时间内的流量突增,例如某些高并发的 API 服务。

2. 漏桶算法(Leaky Bucket)
  • 原理:漏桶算法也使用一个桶来存储请求,当请求到达时,它们会依次进入桶中。如果桶没有满,水会以恒定的速率流出。如果桶已满,则新请求会被丢弃。

  • 特点

    • 平滑的流量控制,能确保请求按照固定速率流出。
    • 不允许短时间内出现突发流量,所有的请求流量必须按固定速率流出。
  • 适用场景:适用于需要平滑流量、避免短时间内的突发请求影响系统的场景。

3. 固定窗口计数算法(Fixed Window Counter)
  • 原理:在固定的时间窗口内(如每分钟或每小时),允许一定数量的请求通过。每当时间窗口结束时,计数器会重置。超出最大请求数的请求将被拒绝。

  • 特点

    • 简单易实现,但容易受到时间窗口边界问题的影响(即在窗口切换时,可能会允许过多的请求通过)。
  • 适用场景:适用于流量相对平稳的场景,通常在请求量较低的应用中使用。

4. 滑动窗口计数算法(Sliding Window Counter)
  • 原理:与固定窗口计数算法类似,但是它不是固定的时间窗口,而是以滑动的方式进行计数。每次请求都会在时间轴上记录,滑动窗口会随着时间的推移进行更新。

  • 特点

    • 能够更加平滑地控制请求速率,避免固定窗口切换时的突发流量。
  • 适用场景:适用于需要精确限流的场景,减少边界效应。

5. 基于 Redis 的限流
  • 原理:使用 Redis 的键值存储特性,可以结合 Redis 的过期时间来实现限流。例如,在 Redis 中记录每个用户或 IP 的请求次数,并设置过期时间,来实现请求的限制。

  • 特点

    • 分布式限流,非常适合分布式系统。
    • 高效、易扩展。
  • 适用场景:适用于分布式系统,尤其是微服务架构中的 API 限流。

使用 Go 实现 API 限流

以下是如何在 Go 中实现 API 限流的几种常见方式:

1. 基于 Token Bucket 算法实现限流
go 复制代码
package main

import (
	"fmt"
	"time"
)

type TokenBucket struct {
	capacity int           // 桶的最大容量
	tokens   int           // 当前桶中令牌数量
	rate     time.Duration // 令牌生成速率
	lastTime time.Time     // 上次令牌生成时间
}

func NewTokenBucket(capacity int, rate time.Duration) *TokenBucket {
	return &TokenBucket{
		capacity: capacity,
		tokens:   capacity,
		rate:     rate,
		lastTime: time.Now(),
	}
}

func (tb *TokenBucket) Allow() bool {
	// 计算令牌的生成数量
	now := time.Now()
	elapsed := now.Sub(tb.lastTime)
	tokensToAdd := int(elapsed / tb.rate)
	tb.lastTime = now

	// 如果有令牌生成,更新桶中的令牌数量
	if tokensToAdd > 0 {
		tb.tokens = min(tb.capacity, tb.tokens+tokensToAdd)
	}

	// 如果有令牌,则消费一个令牌并返回 true
	if tb.tokens > 0 {
		tb.tokens--
		return true
	}
	// 否则返回 false,拒绝请求
	return false
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func main() {
	tb := NewTokenBucket(5, time.Second)

	// 模拟请求
	for i := 0; i < 10; i++ {
		if tb.Allow() {
			fmt.Println("Request", i, "allowed")
		} else {
			fmt.Println("Request", i, "denied")
		}
		time.Sleep(200 * time.Millisecond)
	}
}
2. 基于 Redis 实现限流(使用 Go Redis 客户端)

使用 Redis 进行限流实现时,常用的是 SETNX (设置一个键值对,如果键不存在则设置,防止重复计数)和 EXPIRE(设置过期时间)来控制访问频率。

go 复制代码
package main

import (
	"fmt"
	"log"
	"time"

	"github.com/go-redis/redis/v8"
	"golang.org/x/net/context"
)

var rdb *redis.Client
var ctx = context.Background()

func init() {
	// 初始化 Redis 客户端
	rdb = redis.NewClient(&redis.Options{
		Addr: "localhost:6379", // Redis 地址
	})
}

func limitRequest(userID string) bool {
	// 使用 Redis 来存储访问记录
	key := fmt.Sprintf("rate_limit:%s", userID)
	// 设置每个用户的最大请求次数限制
	maxRequests := 5
	// 设置过期时间为 60 秒
	expiration := 60 * time.Second

	// 获取当前用户的请求次数
	count, err := rdb.Get(ctx, key).Int()
	if err != nil && err != redis.Nil {
		log.Fatalf("Error retrieving count from Redis: %v", err)
	}

	// 如果请求次数超过限制,拒绝请求
	if count >= maxRequests {
		return false
	}

	// 增加请求次数
	err = rdb.Incr(ctx, key).Err()
	if err != nil {
		log.Fatalf("Error incrementing count: %v", err)
	}

	// 设置过期时间,避免无限制的请求
	rdb.Expire(ctx, key, expiration)

	return true
}

func main() {
	userID := "user123"
	for i := 0; i < 10; i++ {
		if limitRequest(userID) {
			fmt.Println("Request allowed")
		} else {
			fmt.Println("Request denied due to rate limit")
		}
		time.Sleep(5 * time.Second)
	}
}

3. 总结

  • 限流的必要性:API 限流不仅能提高系统的可靠性,防止服务过载,还能优化用户体验和控制

资源消耗。

  • 常用限流算法:包括令牌桶、漏桶、固定窗口计数、滑动窗口计数等,每种算法适用于不同的场景。
  • Go 中实现限流:可以通过直接编写限流算法或使用 Redis 等第三方工具来实现 API 限流。使用 Redis 进行限流特别适合分布式系统。

在实际开发中,可以根据需求选择适合的限流算法和实现方式,从而确保 API 服务的稳定性和安全性。

相关推荐
aramae36 分钟前
MySQL内置函数(7)
开发语言·笔记·后端·mysql·其他
徐小夕1 小时前
存量.doc 文档怎么办?JitWord 开源转换库打通旧文档到在线协同全链路
后端·架构·github
ee又momo2 小时前
一次 JWT Token 过期续期的踩坑记录
后端
五彩小白2 小时前
GO语言装饰器语法
golang
二月龙2 小时前
App 瘦身实战:图片、so 库、资源压缩,安装包体积减小 40%
后端
大勇前进2 小时前
Kotlin 协程实战避坑:90% 的人都会踩的生命周期泄漏问题
后端
苏三说技术2 小时前
Spring AI、LangChain4j、AgentScope、Embabel,哪个AI框架更好?
后端
烽学长2 小时前
(附源码)基于Springboot+vue的图书阅读分享系统的设计与实现
java·spring boot·后端
isfox2 小时前
Python 单例模式:一个类只能有一个实例,到底怎么实现?
后端
青山木3 小时前
RocketMQ 入门到原理(三):消息存储原理
java·后端·中间件·架构·rocketmq