go语言后端开发学习(七)——如何在gin框架中集成限流中间件

news2024/12/31 7:39:21

一.什么是限流

限流又称为流量控制(流控),通常是指限制到达系统的并发请求数。
我们生活中也会经常遇到限流的场景,比如:某景区限制每日进入景区的游客数量为8万人;沙河地铁站早高峰通过站外排队逐一放行的方式限制同一时间进入车站的旅客数量等。
限流虽然会影响部分用户的使用体验,但是却能在一定程度上报障系统的稳定性,不至于崩溃(大家都没了用户体验)。
而互联网上类似需要限流的业务场景也有很多,比如电商系统的秒杀、微博上突发热点新闻、双十一购物节、12306抢票等等。这些场景下的用户请求量通常会激增,远远超过平时正常的请求量,此时如果不加任何限制很容易就会将后端服务打垮,影响服务的稳定性。
此外,一些厂商公开的API服务通常也会限制用户的请求次数,比如百度地图开放平台等会根据用户的付费情况来限制用户的请求数等。

二.常见的限流算法

2.1 漏桶算法

2.1.1 漏桶算法的原理

漏桶法的原理比较简单,假设我们有一个水桶按固定的速率向下方滴落一滴水,无论有多少请求,请求的速率有多大,都按照固定的速率流出,对应到系统中就是按照固定的速率处理请求。原理图如下:
在这里插入图片描述

漏桶法的关键点在于漏桶始终按照固定的速率运行,但是它并不能很好的处理有大量突发请求的场景,毕竟在某些场景下我们可能需要提高系统的处理效率,而不是一味的按照固定速率处理请求。

关于漏桶算法,在开发中我们可以使用三方的开源框架,uber团队有一个开源的github.com/uber-go/ratelimit库,下面网站是由漏桶算法集成以下如何简单的在gin中集成一个由漏桶算法实现的限流中间(这里我用的是我尝试自己编写的一个漏桶算法代码,大家可以选择自己写也可以选择使用上面的开源框架):

//开源框架版
package main

import (
	"time"

	"github.com/gin-gonic/gin"
	"go.uber.org/ratelimit"
)

func pong(c *gin.Context) {
	c.JSON(200, gin.H{
		"code":    200,
		"message": "pong",
	})

}

func LimitHandler() gin.HandlerFunc {
	return func(c *gin.Context) {
		r := ratelimit.New(1)  //每秒1个请求
		//每次滴水允许通过的请求数量
		// 限流
		if r.Take().Sub(time.Now()) > 0 {
			c.JSON(200, gin.H{
				"code":    429,
				"message": "Server busy",
			})
			c.Abort()
		}
	}
}

func main() {
	r := gin.Default()
	r.Use(LimitHandler())
	{
		r.GET("/ping", pong)
	}
	r.Run(":8080")
}
//自己实现版

//main.go
package main

import (
	"awesomeProject1/limit"
	"time"

	"github.com/gin-gonic/gin"
	"go.uber.org/ratelimit"
)

func pong(c *gin.Context) {
	c.JSON(200, gin.H{
		"code":    200,
		"message": "pong",
	})

}

func main() {
	r := gin.Default()
	r.Use(limit.LimitMiddleware())
	{
	}
	r.Run(":8080")
}

//limit.go
package limit

import (
	"sync"
	"time"

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

type Bucket struct {
	sync.Mutex
	lastAccess  time.Time
	requests    int64         // 当前已经接收请求次数
	MaxRequests int64         // 最大可接受请求次数
	interval    time.Duration // 时间间隔
}

func NewBucket(maxRequests int64, interval time.Duration) *Bucket {
	return &Bucket{
		lastAccess:  time.Now(),
		requests:    0,
		MaxRequests: maxRequests,
		interval:    interval,
	}
}

func (b *Bucket) Allow() bool {
	b.Lock() //加锁
	defer b.Unlock()
	now := time.Now()
	if now.Sub(b.lastAccess) > b.interval {
		b.requests = 0
		b.lastAccess = now
	}
	if b.requests < b.MaxRequests {
		b.requests++
		return true
	}
	return false
}

func LimitMiddleware() gin.HandlerFunc {
	bucket := NewBucket(1, 10*time.Second)
	return func(c *gin.Context) {
		if !bucket.Allow() {
			c.JSON(200, gin.H{
				"code":    429,
				"message": "Server busy",
			})
			c.Abort()
		}
		c.Next()
	}
}

2.2 令牌桶算法

2.2 令牌桶算法的原理

令牌桶其实和漏桶的原理类似,令牌桶会按固定的速率往桶里放入令牌,并且只要能从桶里取出令牌就能通过,令牌桶支持突发流量的快速处理。原理图如下:
在这里插入图片描述
当我们在令牌桶里面取不到令牌时我们就会选择拒绝该次请求。

2.2.2 基于令牌桶实现的限流中间件

和上面的漏桶限流一样,这里有关令牌桶的限流博主还是给出两个版本,一个是开源第三方库,同时博主也会写一个自己实现的限流中间件供大家参考:

  • 首先是第三方库,这里我们可以考虑使用github.com/juju/ratelimit这一第三方库,下面我们来看一下如何基于这一第三方库封装出外面的限流中间件:
//     filepath:/limit/limiter
package limit

import (
	"time"

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

// 考虑到我们可能会对不同的请求做不同的限流,因此需要一个通用的实现接口
type LimiterInterface interface {
	Key(c *gin.Context) string                              //基于context实现获取对应限流器键值对
	GetBucket(key string) (*ratelimit.Bucket, bool)         // 获取限流器
	AddBuckets(rules ...LimiterBucketRule) LimiterInterface // 添加限流器规则
}

type Limiter struct{  // 限流器(用来记录不同接口对应的不同限流策略)
	LimiterBuckets map[string]*ratelimit.Bucket 
}

type LimiterBucketRule struct {  // 限流器规则
	Key          string        //
	FillInterval time.Duration // 时间间隔
	Capacity     int64         // 容量
	Quantum      int64         // 每次放置的令牌量
}


//  接口的具体实现   filepath:/limit/method_limiter.go
package limit

import (
	"strings"

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

type MethodLimiter struct {
	*Limiter
}

func NewMethodLimiter() LimiterInterface {
	l := &Limiter{LimiterBuckets: make(map[string]*ratelimit.Bucket)}
	return &MethodLimiter{Limiter: l}
}

func (l MethodLimiter) Key(c *gin.Context) string {
	url := c.Request.RequestURI
	index := strings.Index(url, "?")
	if index != -1 {
		url = url[:index]
	}
	return url
}

func (l MethodLimiter) GetBucket(key string) (*ratelimit.Bucket, bool) {
	bucket, ok := l.LimiterBuckets[key]
	return bucket, ok
}

func (l MethodLimiter) AddBuckets(rules ...LimiterBucketRule) LimiterInterface {
	for _, rule := range rules {
		if bucket, ok := l.LimiterBuckets[rule.Key]; !ok {
			bucket = ratelimit.NewBucketWithQuantum(rule.FillInterval, rule.Capacity, rule.Quantum)
			l.LimiterBuckets[rule.Key] = bucket
		}
	}
	return l
}

// 在gin框架中集成限流中间件        filepath: /middleware/limiter.go
package middleware

import (
	"awesomeProject1/limit"
	"fmt"

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

func LimitHandler(l limit.LimiterInterface) gin.HandlerFunc {
	return func(c *gin.Context) {
		key := l.Key(c)
		if bucket, ok := l.GetBucket(key); ok {
			count := bucket.TakeAvailable(1)
			fmt.Println("key", key, "count", count)
			if count == 0 {
				c.JSON(429, gin.H{
					"code": 429,
					"msg":  "too many request",
				})
				c.Abort()
			}
		}
		c.Next()
	}
}

//测试样例 main.go
package main

import (
	"awesomeProject1/limit"
	"awesomeProject1/middleware"
	"time"

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

func pong(c *gin.Context) {
	c.JSON(200, gin.H{
		"code":    200,
		"message": "pong",
	})

}

func main() {
	r := gin.Default()
	limiter := limit.NewMethodLimiter()
	limiter.AddBuckets(
		limit.LimiterBucketRule{
			Key:          "/ping",
			FillInterval: 10 * time.Second,
			Capacity:     1,
			Quantum:      1,
		},
	)
	r.Use(middleware.LimitHandler(limiter))
	{
		r.GET("/ping", pong)
	}
	r.Run(":8080")
}

大家可以自己测试一下

结语

上面就是一些常见的限流策略,虽然说现在限流策略已经不再是单体架构而是迈向分布式,但是万变不离其宗,主要还是基于上面所说的策略进行拓展

参考文章:
李文周博客——常用限流策略——漏桶与令牌桶介绍

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2136871.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ElementUI 快速入门:使用 Vue 脚手架搭建项目

文章目录 一 . ElementUI 的基本安装1.1 通过 Vue 脚手架创建项目1.2 在 vue 脚手架中安装 ElementUI1.3 编写页面 ElementUI 是 Vue.js 的强大 UI 框架&#xff0c;让前端界面开发变得简单高效。本教程将带你从安装到实战&#xff0c;快速掌握 ElementUI 的核心技巧。 核心内容…

解决antd-design-vue给选择组件a-select下拉菜单ant-select-dropdown设置样式不生效

实现效果&#xff1a;正常a-select会根据分辨率、缩放比例动态计算位置等&#xff0c;现在web端已经实现自适应分辨率&#xff0c;需要给下拉菜单设置固定的定位和宽度等样式&#xff0c;不让组件自动瞎设置定位、大小 1、a-select组件加上:getPopupContainer"(triggerNo…

IP地址、地址分类、子网掩码、子网划分、使用Python计算子网划分

IP 地址&#xff08;Internet Protocol Address&#xff09;乃是用于明确标识网络中各类设备的独一无二的地址。IP 地址主要存在两种重要类型&#xff0c;即 IPv4 和 IPv6 。 IPv4地址 IPv4 地址实则是一个由 32 位二进制数字所构成的标识&#xff0c;通常会以四个十进制数字…

如何精细优化网站关键词排名:实战经验分享

在数字营销日益激烈的今天&#xff0c;我深知每一个关键词的排名都关乎着网站的流量与转化。凭借多年的实战经验&#xff0c;我深刻体会到&#xff0c;要想在浩如烟海的网络世界中脱颖而出&#xff0c;精细化的关键词优化策略至关重要。今天&#xff0c;我将从实战角度出发&…

WPF利用Path自定义画头部导航条(TOP)样式

1;新建两个多值转换器&#xff0c;都有用处&#xff0c;用来动态确定PATH的X,Y州坐标的。 EndPointConverter 该转换器主要用来动态确定X轴&#xff0c;和Y轴。用于画线条的。 internal class EndPointConverter : IMultiValueConverter {public object Convert(object[] val…

PD虚拟机占用多少内存?使用电脑的虚拟内存会损害电脑吗

当我们讨论虚拟机及其对电脑性能的影响时&#xff0c;常常会出现两个关键问题&#xff1a;“PD虚拟机需要占用多少内存&#xff1f;”以及“启用电脑的虚拟内存是否会损害硬件&#xff1f;”对于依赖虚拟机进行日常工作的用户而言&#xff0c;这些问题尤为重要。 在本文中&…

Docker上安装mysql

获取 MySQL 镜像 获取镜像。使用以下命令来拉取镜像&#xff1a; 1docker pull mysql:latest 这里拉取的是最新版本的 MySQL 镜像。你也可以指定特定版本&#xff0c;例如&#xff1a; 1docker pull mysql:8.0 运行 MySQL 容器 运行 MySQL 容器时&#xff0c;你需要指定一些…

Linux与Ubuntu:内核与发行版的关系

在计算机科学的领域内&#xff0c;Linux和Ubuntu这两个术语频繁出现&#xff0c;但它们之间的确切联系往往不为大众所熟知。本文旨在深入探讨Linux内核与Ubuntu操作系统发行版之间的技术关系&#xff0c;并阐明它们各自的独特性质。 Linux内核&#xff1a;操作系统的基石 Lin…

R语言统计分析——功效分析(选择效应值)

参考资料&#xff1a;R语言实战【第2版】 功效分析中&#xff0c;预期效应值时最难决定的参数。它通常要求我们对研究主题有一定的了解&#xff0c;并由相应的测量经验。例如&#xff0c;过去研究中的数据可以用来计算效应值&#xff0c;这能为后来深层次的研究提供一些参考。 …

虚拟机Linux+Ubuntu操作系统 如何在虚拟机上安装docker VMPro 2024在线激活资源

一般情况下 不建议在windows系统上安装docker Windows本身就自带一个虚拟机叫WSL 但是不推荐在日常使用的电脑上安装 我们要下一个虚拟机 我们在window上安装docker会被告知WSL内核太老 我们要一个专业的 隔离的虚拟机软件 推荐使用虚拟机 这是我们的虚拟机软件 我们这边…

爬虫代理API的全面解析:让数据抓取更高效

在大数据时代&#xff0c;网络爬虫已经成为收集和分析数据的重要工具。然而&#xff0c;频繁的请求会导致IP被封禁&#xff0c;这时候爬虫代理API就显得尤为重要。本文将详细介绍爬虫代理API的作用、优势及如何使用&#xff0c;帮助你更高效地进行数据抓取。 什么是爬虫代理AP…

Python | Leetcode Python题解之第403题青蛙过河

题目&#xff1a; 题解&#xff1a; def canCross(stones: List[int]) -> bool:lru_cache(None)def dfs(pos,step):if posstones[-1]: return Truefor d in [-1,0,1]:if stepd>0 and posstepd in set(stones):if dfs(posstepd, stepd):return Truereturn Falsepos, step…

移动UI:看看筛选页面的作用和示例啦。

在移动UI中&#xff0c;筛选页面通常用于允许用户根据特定条件或标准来过滤和查找数据。筛选页面在移动UI中起到了提供数据过滤、排序和个性化展示的功能&#xff0c;帮助用户快速找到所需信息&#xff0c;并提供更好的用户体验。 1. 数据过滤&#xff1a; 允许用户根据各种条…

服务器数据增量迁移方案-—SAAS本地化及未来之窗行业应用跨平台架构

一、数据迁移增量同步具有以下几个优点&#xff1a; 1. 减少数据传输量&#xff1a;只传输自上次同步以来更改的数据&#xff0c;而不是整个数据集&#xff0c;这显著降低了网络带宽的使用和传输时间。 2. 提高同步效率&#xff1a;由于处理的数据量较小&#xff0c;同步过程…

Java应用压测工具JMeter

目录 1、下载JMeter 2、配置环境变量 3、配置语音 4、使用 1、下载JMeter Apache JMeter - Apache JMeter™ 千万别下载这个&#xff0c;会报错、 千万别下载这个&#xff0c;会报错、 千万别下载这个&#xff0c;会报错 下载这个&#xff0c;失败多下载几次 2、配置环…

京东广告投放平台整洁架构演进之路

前言 从去年开始京东广告投放系统做了一次以领域驱动设计为思想内核的架构升级&#xff0c;在深入理解DDD思想的同时&#xff0c;我们基于广告投放业务的本质特征大胆地融入了自己的理解和改造。新架构是从设计思想到落地框架都进行了彻底的革新&#xff0c;涉及内容比较多&am…

lvs-dr模式实验详解

华子目录 lvs-dr&#xff08;企业当中最常用&#xff09;dr模式数据逻辑dr模式数据传输过程dr模式的特点实验拓扑实验主机准备实验步骤1.client的ip设定2.router上的ip设定3.router开启路由转发功能4.lvs主机中的ip设定5.webserver1主机中的ip设定6.webserver2主机中的ip设定7.…

6.6高斯噪声

在OpenCV联合C中给一张图片添加高斯噪声&#xff08;Gaussian Noise&#xff09;&#xff0c;可以通过生成随机数并在图像的每个像素上加上这些随机数来实现。高斯噪声是一种统计分布服从正态分布的噪声&#xff0c;通常用于模拟自然界的许多物理现象。 示例代码 以下是一个使…

设计师私藏的 PDF 转 JPG 利器

你平常会通过扫描来发送文件吗&#xff1f;为了保证图片的清晰度一般都会采用PDF格式来转发&#xff0c;但是要插入到一些文件里的时候PDF格式不是那么好用。这时候就很需要PDF转jpg工具了。今天我就分享几款我用过的PDF转jpg的工具&#xff0c;有兴趣就接着往下看吧。 1.福昕…

Win10下64位Python连接32位Oracle数据库

之前看了15分钟解决此问题&#xff0c;DPI-1047: Cannot locate a 64-bit Oracle Client library: “The specified module could not be-CSDN博客的办法&#xff0c;确实能用。但是打开PL/SQL Developer就会出现错误。 上面是我的version。原文章里面用了instantclient_11_2&a…