首页 > 其他分享 >微服务-限流整流之golang RateLimiter

微服务-限流整流之golang RateLimiter

时间:2022-12-22 10:59:18浏览次数:48  
标签:令牌 RateLimiter err golang 算法 限流 time 请求

前言

分布式环境下应对高并发保证服务稳定,优先级从高到低分别为缓存、限流、降级、熔断,本文重点就讲讲限流这部分。

其实服务降级、熔断本身也是限流的一种,因为它们本质上也是阻断了流量进来,但是本文希望大家可以把限流当做一个单纯的名词来理解,看一下对请求做流控的几种算法及具体实现方式。

 

为什么要限流

其实很好理解的一个问题,为什么要限流,自然就流量过大了呗,一个对外服务有很多场景都会流量增大:

  • 业务用户量不断攀升
  • 各种促销
  • 网络爬虫
  • 恶意刷单

注意这个"大",1000QPS大吗?5000QPS大吗?10000QPS大么?没有答案,因为没有标准,因此,"大"一定是和正常流量相比的大。流量一大,服务器扛不住,扛不住就挂了,挂了没法提供对外服务导致业务直接熔断。怎么办,最直接的办法就是从源头把流量限制下来,例如服务器只有支撑1000QPS的处理能力,那就每秒放1000个请求,自然保证了服务器的稳定,这就是限流。

下面看一下常见的两种限流算法。

 

漏桶算法

漏桶算法的原理比较简单,水(请求)先进入到漏桶里,人为设置一个最大出水速率,漏桶以<=出水速率的速度出水,当水流入速度过大会直接溢出(拒绝服务):

因此,这个算法的核心为:

  • 存下请求
  • 匀速处理
  • 多于丢弃

因此这是一种强行限制请求速率的方式,但是缺点非常明显,主要有两点:

  • 无法面对突发的大流量----比如请求处理速率为1000,容量为5000,来了一波2000/s的请求持续10s,那么后5s的请求将全部直接被丢弃,服务器拒绝服务,但是实际上网络中突发一波大流量尤其是短时间的大流量是非常正常的,超过容量就拒绝,非常简单粗暴
  • 无法有效利用网络资源----比如虽然服务器的处理能力是1000/s,但这不是绝对的,这个1000只是一个宏观服务器处理能力的数字,实际上一共5秒,每秒请求量分别为1200、1300、1200、500、800,平均下来qps也是1000/s,但是这个量对服务器来说完全是可以接受的,但是因为限制了速率是1000/s,因此前面的三秒,每秒只能处理掉1000个请求而一共打回了700个请求,白白浪费了服务器资源

所以,通常来说利用漏桶算法来限流,实际场景下用得不多。

 

令牌桶算法

令牌桶算法是网络流量整形(Traffic Shaping)和限流(Rate Limiting)中最常使用的一种算法,它可用于控制发送到网络上数据的数量并允许突发数据的发送。

从某种意义上来说,令牌桶算法是对漏桶算法的一种改进,主要在于令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用,来看下令牌桶算法的实现原理:

整个的过程是这样的:

  • 系统以恒定的速率产生令牌,然后将令牌放入令牌桶中
  • 令牌桶有一个容量,当令牌桶满了的时候,再向其中放入的令牌就会被丢弃
  • 每次一个请求过来,需要从令牌桶中获取一个令牌,假设有令牌,那么提供服务;假设没有令牌,那么拒绝服务

那么,我们再看一下,为什么令牌桶算法可以防止一定程度的突发流量呢?可以这么理解,假设我们想要的速率是1000QPS,那么往桶中放令牌的速度就是1000个/s,假设第1秒只有800个请求,那意味着第2秒可以容许1200个请求,这就是一定程度突发流量的意思,反之我们看漏桶算法,第一秒只有800个请求,那么全部放过,第二秒这1200个请求将会被打回200个。

注意上面多次提到一定程度这四个字,这也是我认为令牌桶算法最需要注意的一个点。假设还是1000QPS的速率,那么5秒钟放1000个令牌,第1秒钟800个请求过来,第2~4秒没有请求,那么按照令牌桶算法,第5秒钟可以接受4200个请求,但是实际上这已经远远超出了系统的承载能力,因此使用令牌桶算法特别注意设置桶中令牌的上限即可。

总而言之,作为对漏桶算法的改进,令牌桶算法在限流场景下被使用更加广泛。

 

RateLimiter Wait使用

模拟一下每2秒最多过4个请求:

package main

import (
	"context"
	"fmt"
	"time"

	"golang.org/x/time/rate"
)

func main() {
	// 第一个参数:每秒往桶中放多少令牌, 第二个参数:桶的容量/ go官方的实现中,初始化后桶中的令牌是满的
	l := rate.NewLimiter(2, 4)
	for {
		l.Wait(context.Background())
		fmt.Println(time.Now().Format("04:05.000"))
	}
}

输出:

可以看到31:19秒的时候有4个输出,那是因为初始化桶是4个令牌;

以后每秒是2个请求,那是因为每秒投放的令牌是2

 

RateLimiter allow使用

模拟一下每2秒最多过4个请求:

package main

import (
	"fmt"
	"time"

	"golang.org/x/time/rate"
)

func main() {
	// 第一个参数:每秒往桶中放多少令牌, 第二个参数:桶的容量/ go官方的实现中,初始化后桶中的令牌是满的
	l := rate.NewLimiter(2, 4)

	// 缓存3次后,每秒执行一次
	for {
		if l.Allow() {
			fmt.Println(time.Now().Format("allow 04:05.000"))
		} else {
			time.Sleep(time.Second / 10)
			fmt.Println(time.Now().Format("refuse 04:05.000"))
		}
	}
}

输出:

可以看到35:24秒的时候有4个allow,那是因为初始化桶是4个令牌;

以后每秒是2个allow,那是因为每秒投放的令牌是2,其他情况都是refuse

 

 

RateLimiter 结合go-kit使用

模拟一下每2秒最多过4个请求:

package main

import (
	"bytes"
	"crypto/tls"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"time"

	"context"
	"encoding/json"

	"github.com/bitly/go-simplejson"
	"github.com/go-kit/kit/ratelimit"
	httptransport "github.com/go-kit/kit/transport/http"
)

type JsonResponder struct {
	Status  int              `json:"status"`
	Code    string           `json:"code"`
	Message string           `json:"message"`
	Data    *simplejson.Json `json:"data"`
}

func AllFactoryReq(Limit ratelimit.Allower) (*JsonResponder, error) {
	Url := "http://localhost:8900/erp/v1/factory"
	Method := "GET"
	UserId := 1
	EncodeFunc := func(_ context.Context, r *http.Request, request interface{}) error {
		params := request.(url.Values)
		r.Body = io.NopCloser(bytes.NewBuffer([]byte(params.Encode())))
		return nil
	}
	DecodeFunc := func(_ context.Context, r *http.Response) (interface{}, error) {
		var response JsonResponder
		if err := json.NewDecoder(r.Body).Decode(&response); err != nil {
			return nil, err
		}
		response.Status = r.StatusCode
		return &response, nil
	}

	u, err := url.Parse(Url)
	if err != nil {
		panic(err.(any))
	}

	var options = []httptransport.ClientOption{
		httptransport.SetClient(&http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}),
		httptransport.ClientBefore(httptransport.SetRequestHeader("User-Id", strconv.Itoa(UserId))),
	}

	e := httptransport.NewClient(Method, u, EncodeFunc, DecodeFunc, options...).Endpoint()
	e = ratelimit.NewErroringLimiter(Limit)(e)
	res, err := e(context.Background(), url.Values{})
	if err != nil {
		return nil, err
	}
	return res.(*JsonResponder), nil
}

func main() {
	// 第一个参数:每秒往桶中放多少令牌, 第二个参数:桶的容量/ go官方的实现中,初始化后桶中的令牌是满的
	lim := NewLimiter(2, 4)

	for {
		go func() {
			_, err := AllFactoryReq(lim)
			if err == ratelimit.ErrLimited {
				fmt.Printf("%s ErrLimited\n", time.Now().Format("allow 04:05.000"))
			} else {
				fmt.Printf("%s success\n", time.Now().Format("allow 04:05.000"))
			}

		}()
		time.Sleep(time.Millisecond * 100)
	}
}

输出:

可以看到36:54秒的时候有4个allow(因为打印时间和执行之间有误差,所以看起来秒数不对),那是因为初始化桶是4个令牌;

以后每秒是2个allow,那是因为每秒投放的令牌是2,其他情况都是refuse

 

 

部分描述引用自: https://www.cnblogs.com/xrq730/p/11025029.html

标签:令牌,RateLimiter,err,golang,算法,限流,time,请求
From: https://www.cnblogs.com/zhanchenjin/p/16997880.html

相关文章

  • 【Golang 快速入门】项目实战:即时通信系统
     Golang快速入门即时通信系统-服务端版本一:构建基础Server版本二:用户上线功能版本三:用户消息广播机制版本四:用户业务层封装版本五:在线用户查询版本六:修......
  • Golang实战项目-B2C电商平台项目(3)
    Golang实战项目-B2C电商平台项目(3)总体页面的显示由于在main中声明的全局对象无法被其他包调用,所以在commons文件夹下新建CommonVars.go,保证整个项目任何包都可以......
  • Golang 项目部署实战
    一直认为不懂部署的开发工程师不是好的开发工程师,以下以一些实例讲解自己在项目中的Golang后端部署的情况。一般部署脚本应该具有构建、启动、停止、回滚已经查看记录......
  • Golang比php开发好在哪?
    Go语言与PHP有许多不同之处,并且有着天然的优势,以下是Go语言与PHP的一些比较Go语言是一种静态类型的编程语言,而PHP是动态类型的编程语言。这意味着Go语言编译器会在编译......
  • golang对比java的优势_golang语言和其它编程语言的对比
    golang对比java的优势_golang语言和其它编程语言的对比 在软件行业做过一段时间的人都知道,没有万能的编程语言,也没有万能开发框架,更没有万能的解决方案。任何新技术的产......
  • golang接口
    1.ConstrainedGenericTypepackagemainimport( "fmt")typeStringer=interface{String()string}typeIntegerintfunc(iInteger)String()str......
  • golang如何打印变量类型,golang list如何把元素转换为可用类型
    golang打印变量类型方法glist=list.New()fmt.Printf("%T",mylist)结果:*list.Listgolang把list中的any类型转化为目的类型zz:=new(XNSSS)glist.PushBack(zz)f......
  • golang 范型编程初学
     写了一个批次执行函数的功能,采用范型,直接上代码 batch实现代码:packageutilsimport( "time")typebatch[Tany]struct{ Array[]T Funcfunc(arr......
  • 区块链,中心去,何曾着眼看君王?用Go语言实现区块链技术,通过Golang秒懂区块链
    区块链技术并不是什么高级概念,它并不比量子力学、泡利不相容原则、哥德巴赫猜想更难以理解,但却也不是什么类似“时间就是金钱”这种妇孺皆知的浅显道理。区块链其实是一套......
  • 如何在 Linux 下使用 TC 优雅的实现网络限流
    1.Linux下的流量控制原理通过对包的排队,我们可以控制数据包的发送方式。这种控制,称之为数据整形,shapethedata,包括对数据的以下操作:增加延时丢包重新排列重复、损坏速率......