限流算法,基于go的gRPC 实现的

目录

一、单机限流

1、令牌桶算法

3、固定窗口限流算法

4、滑动窗口

二、集群限流

1、分布式固定窗口 (基于redis)

2、分布式滑动窗口


一、单机限流

1、令牌桶算法

令牌桶算法是当流量进入系统前需要获取令牌,没有令牌那么就要进行限流

这个算法是怎么实现的呢

  1. 定义一个后台协程按照一定的频率去产生token

  2. 后台协程产生的token 放到固定大小容器里面

  3. 有流量进入系统尝试拿到token,没有token 就需要限流了


type TokenBucketLimiter struct {
   token chan struct{}
   stop  chan struct{}
}
​
func NewTokenBucket(capactity int, timeInternal time.Duration) *TokenBucketLimiter {
   te := make(chan struct{}, capactity)
   stop := make(chan struct{})
   ticker := time.NewTicker(timeInternal)
   go func() {
      defer ticker.Stop()
      for {
         select {
         case <-ticker.C:
            select {
            case te <- struct{}{}:
            default:
​
            }
         case <-stop:
            return
         }
      }
   }()
   return &TokenBucketLimiter{
      token: te,
      stop:  stop,
   }
}
​
func (t *TokenBucketLimiter) BuildServerInterceptor() grpc.UnaryServerInterceptor {
   return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
      select {
      case <-ctx.Done():
         err = ctx.Err()
         return
      case <-t.token:
         return handler(ctx, req)
      case <-t.stop:
         err = errors.New("缺乏保护")
         return
      }
   }
}
​
func (t *TokenBucketLimiter) Stop() {
   close(t.stop)
}

3、固定窗口限流算法

什么是固定窗口限流算法

固定窗口限流算法(Fixed Window Rate Limiting Algorithm)是一种最简单的限流算法,其原理是在固定时间窗口(单位时间)内限制请求的数量。该算法将时间分成固定的窗口,并在每个窗口内限制请求的数量。具体来说,算法将请求按照时间顺序放入时间窗口中,并计算该时间窗口内的请求数量,如果请求数量超出了限制,则拒绝该请求。

优点:实现简单

缺点:对于瞬时流量没发处理,也就是临界问题,比如下图在20t前后,在16t以及26t有大量流量进来,在这10t中,已经超过了流量限制,没法限流

实现如下

type fixWindow1 struct {
   lastVistTime int64
   vistCount    int64
   interval     int64
   maxCount     int64
}
​
func NewfixWindow1(macCount int64) *fixWindow1 {
   t := &fixWindow1{
      maxCount: macCount,
   }
   return t
}
​
func (f *fixWindow1) FixWindow1() grpc.UnaryServerInterceptor {
   return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
      current := time.Now().UnixNano()
      lasttime := atomic.LoadInt64(&f.lastVistTime)
      if lasttime+f.interval > current {
         if atomic.CompareAndSwapInt64(&f.lastVistTime, lasttime, current) {
            atomic.StoreInt64(&f.lastVistTime, current)
            atomic.StoreInt64(&f.maxCount, 0)
         }
      }
      count := atomic.AddInt64(&f.vistCount, 1)
      if count > f.maxCount {
         return gen.GetByIDResp{}, errors.New("触发限流")
      }
      return handler(ctx, req)
   }
}

4、滑动窗口

什么是滑动窗口算法:

滑动窗口限流算法是一种常用的限流算法,用于控制系统对外提供服务的速率,防止系统被过多的请求压垮。它将单位时间周期分为n个小周期,分别记录每个小周期内接口的访问次数,并且根据时间滑动删除过期的小周期。它可以解决固定窗口临界值的问题

type slideWindow struct {

   timeWindow *list.List
   interval   int64
   maxCnt     int
   lock       sync.Mutex
}
​
func NewSlideWindow(interval time.Duration, maxCnt int) *slideWindow {
   t := &slideWindow{
      timeWindow: list.New(),
      interval:   interval.Nanoseconds(),
      maxCnt:     maxCnt,
   }
   return t
}
​
func (s *slideWindow) SlideWinowlimit() grpc.UnaryServerInterceptor {
   return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
      s.lock.Lock()
      now := time.Now().UnixNano()
      // 快路径
      if s.timeWindow.Len() < s.maxCnt {
         resp, err = handler(ctx, req)
         s.timeWindow.PushBack(now)
         s.lock.Unlock()
         return
      }
      front := s.timeWindow.Front()
      for front != nil && front.Value.(int64)+s.interval < now {
         s.timeWindow.Remove(front)
         front = s.timeWindow.Front()
      }
      if s.timeWindow.Len() >= s.maxCnt {
         s.lock.Unlock()
         return &gen.GetByIdReq{}, errors.New("触发限流")
      }
      s.lock.Unlock()
      resp, err = handler(ctx, req)
      s.timeWindow.PushBack(now)
      return
   }
}

二、集群限流

下面是分布式限流,为啥是分布式限流,单机限流只能对单台服务器进行限流,没发对集权进行限流,需要用分布式限流来进行集权限流

1、分布式固定窗口 (基于redis)
type redisFix struct {

   serName  string
   interVal int
   limitCnt int
   redis    redis.Cmdable
}
​
//go:embed lua/fixwindow.lua
var lua_redis_fix string
​
func NewRedisFix(serName string, interval int, limitCnt int, redis redis.Cmdable) *redisFix {
   t := &redisFix{
      serName:  serName,
      interVal: interval,
      limitCnt: limitCnt,
      redis:    redis,
   }
   return t
}
​
func (r *redisFix) RedisFix() grpc.UnaryServerInterceptor {
   return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
      res, err := r.limit(ctx)
      if err != nil {
         return &gen.GetByIDResp{}, err
      }
      if res {
         return &gen.GetByIdReq{}, errors.New("触发限流")
      }
      return handler(ctx, req)
   }
}
​
func (r *redisFix) limit(ctx context.Context) (res bool, err error) {
   keys := []string{r.serName}
   res, err = r.redis.Eval(ctx, lua_redis_fix, keys, r.interVal, r.limitCnt).Bool()
   return
}

lua

local key = KEYS[1]

local limitCnt = tonumber(ARGV[2])
local val = redis.call('get',key)
if val==false then
    if limitCnt<1 then
        return "true"
    else
        redis.call('set',key,1,'PX',ARGV[1])
        return "false"
    end
elseif tonumber(val)<limitCnt then
    redis.call('incr',key)
    return "false"
else
    return "true"
end
2、分布式滑动窗口
//go:embed lua/slidewindow.lua

var slideWindLua string
​
type redisSlib struct {
   serverName string
   interVal   time.Duration
   maxCnt     int64
   redis      redis.Cmdable
}
​
func NewRedisSlib(interval time.Duration, maxCnt int64, serverName string, clientCmd redis.Cmdable) *redisSlib {
   t := &redisSlib{
      serverName: serverName,
      interVal:   interval,
      maxCnt:     maxCnt,
      redis:      clientCmd,
   }
   return t
}
​
func (r *redisSlib) RedisSlibLimt() grpc.UnaryServerInterceptor {
   return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
      limt, err := r.limt(ctx)
      if err != nil {
         return nil, err
      }
      if limt {
         return nil, errors.New("限流")
      }
      return handler(ctx, req)
   }
}
​
func (r *redisSlib) limt(ctx context.Context) (bool, error) {
   now := time.Now().UnixMilli()
   return r.redis.Eval(ctx, slideWindLua, []string{r.serverName}, r.interVal.Milliseconds(), r.maxCnt, now).Bool()
}

lua

local key = KEYS[1]
local window = tonumber(ARGV[1])
local maxCnt = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
​
--- 窗口的最小边界
local min = now-window
​
redis.call('ZREMRANGEBYSCORE',key,'-inf',min)
​
local cnt = redis.call('ZCOUNT',key,'-inf','+inf')
​
if cnt>=maxCnt then
    return "true"
else
    redis.call('ZADD',key,now,now)
    redis.call('PEXPIRE',key,window)
    return "false"
end

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Docker+jenkins+gitlab实现持续集成

1.安装环境 服务器ip虚拟机版本192.168.5.132centos7.6192.168.5.152centos7.6 2. 安装docker 安装必要的一些系统工具 yum install -y yum-utils device-mapper-persistent-data lvm2添加软件源信息&#xff0c;要确保centos7能上外网 yum-config-manager --add-repo http:…

什么是数据清洗、特征工程、数据可视化、数据挖掘与建模?

1.1什么是数据清洗、特征工程、数据可视化、数据挖掘与建模&#xff1f; 视频为《Python数据科学应用从入门到精通》张甜 杨维忠 清华大学出版社一书的随书赠送视频讲解1.1节内容。本书已正式出版上市&#xff0c;当当、京东、淘宝等平台热销中&#xff0c;搜索书名即可。内容涵…

智篆商业的电商运营课程:超越传统,引领未来

智篆商业一直以来都是电商运营课程领域的领军者&#xff0c;其成功之处在于超越传统&#xff0c;不断引领未来。探究智篆商业的成功之道&#xff0c;我们将发现其可信度和培训专业性是学员信赖的根本原因。 首先&#xff0c;智篆商业注重构建强大的导师团队。导师们不仅拥有丰富…

持续集成交付CICD:Jenkins使用GitLab共享库实现自动更新前后端项目质量配置

目录 一、实验 1.Jenkins使用GitLab共享库实现自动更新后端项目质量配置 2.Jenkins使用GitLab共享库实现自动更新前端项目质量配置 二、问题 1.Sonarqube如何添加自定义质量阈 一、实验 1.Jenkins使用GitLab共享库实现自动更新后端项目质量配置 (1)修改GitLab的Sonar.gr…

HBase-架构与设计

HBase架构与设计 一、背景二、HBase概述1.设计特点2.适用场景2.1 海量数据2.2 稀疏数据2.3 多版本数据2.4 半结构或者非结构化数据 三、数据模型1.RowKey2.Column Family3.TimeStamp 四、HBase架构图1.Client2.Zookeeper3.HMaster4.HRegionServer5.HRegion6.Store7.StoreFile8.…

flink使用事件时间时警惕kafka不同分区的事件时间倾斜问题

背景 flink和kafka的消息组合消费模式几乎是实时流处理的标配&#xff0c;然后当在flink中使用事件时间处理时&#xff0c;需要注意kafka不同分区元素之间时间相差太大的问题&#xff0c;这样有可能会导致严重的数据堆积问题 kafka不同分区元素事件时间差异较大导致的问题 总…

分布式光伏电站监控运维系统的简单介绍-安科瑞黄安南

摘要&#xff1a;设计了一套更高性价比&#xff0c;且容易操作的电站监控系统。该系统融合了互联网和物联网&#xff0c;并为光伏电数据的传输构建了相应的通道&#xff0c;可支持云存储等功能&#xff0c;同时也为用户提供了多元化的查询功能。 关键词&#xff1a;分布式太阳能…

gitLab 和Idea分支合并

以下二选1即可完成分支合并建议第一种简单有效 Idea合并方式 切换到被合并的分支&#xff0c;如我想把0701的内容合并到dev&#xff0c;切换到dev分支&#xff0c;然后再点击merge然后选择要合并的分支&#xff0c;即可,此时git上的代码没有更新只是把代码合到本地需要pull才…

ELK的日志解决方案

ELK的日志解决方案 一、ELK 介绍 ELK是一个流行的日志解决方案&#xff0c;它由三个开源工具组成&#xff1a;Elasticsearch、Logstash和Kibana。下面是这些工具的简单介绍以及它们在日志解决方案中的作用&#xff1a; Elasticsearch&#xff1a;Elasticsearch是一个分布式的搜…

设置webstorm和idea符合Alibaba规范

只格式化自己更改的代码 ctrlShiftAltL 插件建议 Alibaba Java Coding Guidelines&#xff08;新版本的idea不支持&#xff0c;有其他同名的非官方版可代替&#xff09;&#xff0c;使用方法在此不赘述 1、设置webstorm 包含 设置两个空格缩进&#xff0c;去掉行尾分号&#…

IDEA加载阿里Java规范插件

IDEA加载阿里巴巴Java开发手册插件&#xff0c;在写代码的时候会自动扫描代码规范。 1、打开Settings 2、打开Plugins 3、搜索Alibaba Java Code Guidelines&#xff08;XenoAmess TPM&#xff09;插件&#xff0c;点击Install进行安装&#xff0c;然后重启IDE生效。 4、鼠标右…

基于OpenCV+CNN+IOT+微信小程序智能果实采摘指导系统——深度学习算法应用(含pytho、JS工程源码)+数据集+模型(二)

目录 前言总体设计系统整体结构图系统流程图 运行环境Python环境TensorFlow 环境Jupyter Notebook环境Pycharm 环境微信开发者工具OneNET云平台 相关其它博客工程源代码下载其它资料下载 前言 本项目基于Keras框架&#xff0c;引入CNN进行模型训练&#xff0c;采用Dropout梯度…

uniapp 打包H5页面时候清除手机缓存问题

最近遇到一个情况&#xff1a; uniapp 写了一个H5 页面&#xff0c;挂在一个小程序上面&#xff0c;但是每次更新代码&#xff0c;新增新功能&#xff0c;总是有的用户看到的还是上一个版本的样式&#xff0c;前端打包的时候&#xff0c;已经在Uniapp项目的根目录下面新建了一个…

Hive数据库系列--Hive数据类型/Hive字段类型/Hive类型转换

文章目录 一、Hive数据类型1.1、数值类型1.2、字符类型1.3、日期时间类型1.4、其他类型1.5、集合数据类型1.5.1、Struct举例1.5.2、Array举例1.5.3、Map举例 二、数据类型转换2.1、隐式转换2.2、显示转换 本章主要讲解hive的数据类、字段类型。官网文档地址见https://cwiki.apa…

Java代码审计之SpEL表达式注入漏洞分析

文章目录 前言CVE-2022-22963漏洞简述环境搭建反弹shell CVE漏洞调试分析本地搭建调试分析补丁分析 总结 前言 表达式注入是 Java 安全中一类常见的能够注入命令并形成 RCE 的漏洞&#xff0c;而常见的表达式注入方式有 EL 表达式注入、SpEL 表达式注入和 OGNL 表达式注入等。…

【Docker】从零开始:17.Dockerfile基本概念

【Docker】从零开始&#xff1a;17.Dockerfile 概述1.什么是Dockerfile2.Dockerfile构建三大步骤3.Docker执行Dockerfile流程 一张图理解Dockerfile常用保留指令~FROM~~MAINTAINER~~RUN~两种格式 ~EXPOSE~~WORKDIR~~USER~~ENV~~ADD~~COPY~两种格式 ~VOLUME~~CMD~两种格式注意 ~…

在Spring Cloud中使用组件Ribbon和Feign,并分别创建子模块注册到Eureka中去

ok&#xff0c;在上篇文章中我们讲了在Spring cloud中使用Zuul网关&#xff0c;这篇文章我们将Spring Cloud的五大核心组件的Ribbon和Feign分别创建一个微服务模块。 题外话&#xff0c;本篇博客就是配置子模块&#xff0c;或者说是微服务&#xff0c;然后将微服务正式启动之前…

竞赛选题 题目:基于深度学习的中文对话问答机器人

文章目录 0 简介1 项目架构2 项目的主要过程2.1 数据清洗、预处理2.2 分桶2.3 训练 3 项目的整体结构4 重要的API4.1 LSTM cells部分&#xff1a;4.2 损失函数&#xff1a;4.3 搭建seq2seq框架&#xff1a;4.4 测试部分&#xff1a;4.5 评价NLP测试效果&#xff1a;4.6 梯度截断…

多人聊天Java

服务端 import java.io.*; import java.net.*; import java.util.ArrayList; public class Server{public static ServerSocket server_socket;public static ArrayList<Socket> socketListnew ArrayList<Socket>(); public static void main(String []args){try{…

区块链技术在数字营销中有哪些应用?

用于所有加密货币交易的数字账本称为区块链。随着更多图像被添加到“就绪”块中&#xff0c;它将继续扩展。每个区块都包含交易信息、时间戳和前一个区块的加密签名。为了辨别真正的比特币交易所和试图重印已经在其他地方发布的硬币&#xff0c;比特币节点使用区块链。 随着在…