微服务-限流_微服务限流原理-程序员宅基地

技术标签: 分布式与微服务  

一.介绍

       互联网应用发展到今天,从单体应用架构到SOA以及今天的微服务,随着微服务化的不断升级进化,服务和服务之间的稳定性变得越来越重要,分布式系统之所以复杂,主要原因是分布式系统需要考虑到网络的延时和不可靠,微服务很重要的一个特质就是需要保证服务幂等,保证幂等性很重要的前提需要分布式锁控制并发,同时缓存、降级和限流是保护微服务系统运行稳定性的三大利器。

       限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务(定向到错误页或告知资源没有了)、排队或等待(比如秒杀、评论、下单)、降级(返回兜底数据或默认数据,如商品详情页库存默认有货)。

       一般开发高并发系统常见的限流有:限制总并发数(比如数据库连接池、线程池)、限制瞬时并发数(如nginx的limit_conn模块,用来限制瞬时并发连接数)、限制时间窗口内的平均速率(如Guava的RateLimiter、nginx的limit_req模块,限制每秒的平均速率);其他还有如限制远程接口调用速率、限制MQ的消费速率。另外还可以根据网络连接数、网络流量、CPU或内存负载等来限流。

 

二.常见的限流算法

2.1 漏桶

       水(也就是请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率。

2.2 令牌桶

       随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入令牌(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了。新请求来临时,会各自拿走一个令牌,如果没有令牌可拿了就阻塞或者拒绝服务。

2.3 算法对比

  • 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝。
  • 漏桶限制的是常量流出速率(即流出速率是一个固定常量值不能修改),从而平滑突发流入速率;令牌桶限制的是平均流入速率(允许突发请求,只要有令牌就可以处理,支持一次拿3个令牌,4个令牌),漏桶算法能够强行限制数据的传输速率,令牌桶算法能够在限制数据的平均传输速率的同时还允许某种程度的突发情况。令牌桶还有一个好处是可以方便的改变速度。一旦需要提高速率,则按需提高放入桶中的令牌的速率。所以,限流框架的核心算法还是以令牌桶算法为主。
  • 两个算法方向是相反的,对于相同的参数得到的限流效果是一样的。

三.限流策略

       对于一个应用系统来说一定会有极限并发/请求数,即总有一个TPS/QPS阀值,如果超了阀值则系统就会不响应用户请求或响应的非常慢,因此我们最好进行过载保护,防止大量请求涌入击垮系统。

3.1 限制总资源数

       如果有的资源是稀缺资源(如数据库连接、线程),而且可能有多个系统都会去使用它,那么需要限制应用;可以使用池化技术来限制总资源数:连接池、线程池。比如分配给每个应用的数据库连接是100,那么本应用最多可以使用100个资源,超出了可以等待或者抛异常。

      

3.2 限流某个接口的总并发/请求数

       如果接口可能会有突发访问情况,但又担心访问量太大造成崩溃,如抢购业务;这个时候就需要限制这个接口的总并发/请求数总请求数了;因为粒度比较细,可以为每个接口都设置相应的阀值。

package main

import (
    "sync"
    "net"
    "strconv"
    "fmt"
    "log"

)

const (
    MAX_CONCURRENCY = 10000 
    CHANNEL_CACHE = 200
)

var tmpChan = make(chan struct{}, MAX_CONCURRENCY)
var waitGroup sync.WaitGroup

func main(){
    concurrency()
    waitGroup.Wait()
}

//进行网络io
func request(currentCount int){
    fmt.Println("request" + strconv.Itoa(currentCount) + "\r")
    tmpChan <- struct{}{}
    conn, err := net.Dial("tcp",":8080")
    <- tmpChan
    if err != nil { log.Fatal(err) }
    defer conn.Close()
    defer waitGroup.Done()
}

//并发
func concurrency(){
    for i := 0;i < MAX_CONCURRENCY;i++ {
        waitGroup.Add(1)
        go request(i)
    }
}

      适合对业务无损的服务或者需要过载保护的服务进行限流,如抢购业务,超出了大小要么让用户排队,要么告诉用户没货了,对用户来说是可以接受的。而一些开放平台也会限制用户调用某个接口的试用请求量,也可以用这种计数器方式实现。这种方式也是简单粗暴的限流,没有平滑处理,需要根据实际情况选择使用。

3.3 限制某个接口的时间窗请求数

       即一个时间窗口内的请求数,如想限制某个接口/服务每秒/每分钟/每天的请求数/调用量。如一些基础服务会被很多其他系统调用,比如商品详情页服务会调用基础商品服务调用,但是怕因为更新量比较大将基础服务打挂,这时我们要对每秒/每分钟的调用量进行限速。

3.4 平滑限流某个接口的请求数

       之前的限流方式都不能很好地应对突发请求,即瞬间请求可能都被允许从而导致一些问题;因此在一些场景中需要对突发请求进行整形,整形为平均速率请求处理(比如5r/s,则每隔200毫秒处理一个请求,平滑了速率)。这个时候有两种算法满足我们的场景:令牌桶和漏桶算法。

package main

import (
  "net/http"
  "golang.org/x/time/rate"
)
  
var limiter = rate.NewLimiter(2, 5)
func limit(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    if limiter.Allow() == false {
      http.Error(w, http.StatusText(429), http.StatusTooManyRequests)
      return
    }
    next.ServeHTTP(w, r)
  })
}
  
func main() {
  mux := http.NewServeMux()
  mux.HandleFunc("/", okHandler)
  // Wrap the servemux with the limit middleware.
  http.ListenAndServe(":4000", limit(mux))
}
  
func okHandler(w http.ResponseWriter, r *http.Request) {
  w.Write([]byte("OK"))
}

3.5 分布式限流

     当应用为单点应用时,只要应用进行了限流,那么应用所依赖的各种服务也都得到了保护。 

       但线上业务出于各种原因考虑,多是分布式系统,单节点的限流仅能保护自身节点,但无法保护应用依赖的各种服务,并且在进行节点扩容、缩容时也无法准确控制整个服务的请求限制。   

       而如果实现了分布式限流,那么就可以方便地控制整个服务集群的请求限制,且由于整个集群的请求数量得到了限制,因此服务依赖的各种资源也得到了限流的保护。

3.5.1 Redis方案

   实现原理其实很简单。既然要达到分布式全局限流的效果,那自然需要一个第三方组件来记录请求的次数。

   其中 Redis 就非常适合这样的场景。

  • 每次请求时将方法名进行md5加密后作为Key 写入到 Redis 中,超时时间设置为 2 秒,Redis 将该 Key 的值进行自增。
  • 当达到阈值时返回错误。
  • 写入 Redis 的操作用 Lua 脚本来完成,利用 Redis 的单线程机制可以保证每个 Redis 请求的原子性。
local key = KEYS[1] --限流KEY(一秒一个)
local limit = tonumber(ARGV[1])        --限流大小
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limit then --如果超出限流大小
   return 0
else  --请求数+1,并设置2秒过期
   redis.call("INCRBY", key,"1")
   redis.call("expire", key,"2")
   return 1
end

      方案的缺点显而易见,每取一次令牌都会进行一次网络开销,而网络开销起码是毫秒级,所以这种方案支持的并发量是非常有限的。

3.5.2 QPS分配

       举个例子,我们有两台服务器实例,对应的是同一个应用程序(Application.name相同),程序中设置的QPS为100,将应用程序与同一个控制台程序进行连接,控制台端依据应用的实例数量将QPS进行均分,动态设置每个实例的QPS为50,若是遇到两个服务器的配置并不相同,在负载均衡层的就已经根据服务器的优劣对流量进行分配,例如一台分配70%流量,另一台分配30%的流量。面对这种情况,控制台也可以对其实行加权分配QPS的策略。

      客观来说,这是一种集群限流的实现方案,但依旧存在不小的问题。该模式的分配比例是建立在大数据流量下的趋势进行分配,实际情况中可能并不是严格的五五分或三七分,误差不可控,极容易出现用户连续访问某一台服务器遇到请求驳回而另一台服务器此刻空闲流量充足的尴尬情况。

3.5.3 发票服务器

       这种方案的思想是建立在Redis令牌桶方案的基础之上的。如何解决每次取令牌都伴随一次网络开销,该方案的解决方法是建立一层控制端,利用该控制端与Redis令牌桶进行交互,只有当客户端的剩余令牌数不足时,客户端才向该控制层取令牌并且每次取一批。

       这种思想类似于Java集合框架的数组扩容,设置一个阈值,只有当超过该临界值时,才会触发异步调用。其余存取令牌的操作与本地限流无二。虽然该方案依旧存在误差,但误差最大也就一批次令牌数而已。

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_35703848/article/details/103463366

智能推荐

Update批量更新(高性能、动态化)_批量update-程序员宅基地

文章浏览阅读7.6k次,点赞7次,收藏20次。文章目录前言一、环境开发环境测试环境二、灵光乍现MyBatis-Plus源码2.初见真正的批量更新语法三、开工基础类搭建SysUser(表sys_user实体类)Stash(拼接SQL服务,内部类)TableCacheDTO(数据表信息存储)TableCache(表信息缓存)MySQL拼接常量类缓存数据库表信息1. 继承AbstractMethod2. 自定义sql注入器3. 自定义注入器生效事务工具类制作SQL工具类SQL执行类四、测试100条测试数据1千条测试数据1万条测试数据10万条测试数据五、弊端总_批量update

PID优化系列之目标值平滑(斜坡函数梯形图+完整SCL代码)_控制斜坡pid-程序员宅基地

文章浏览阅读2.2k次,点赞4次,收藏6次。作为PID系列专题,这些文章,我都会给出PLC梯形图的源代码和SCL代码方便大家对比学习,文章中的错误和不严谨之处,也请大家指正。1、专题1:设定值响应问题 2、PLC的梯形图代码,这部分我们可以做成功能块,启用PID运算时,我们可以对设定值进行线性化平滑处理,也可以不处理。......_控制斜坡pid

编程实现36进制和10进制之间的相互转换_36进制转换10进制-程序员宅基地

文章浏览阅读1w次,点赞3次,收藏4次。36进制转换成10进制的方法,以R9和10Y为例R9就是 27 * 36^1 + 9*36^0 = 98110Y 就是 1* 36^2 + 0 * 36^1 + 34*36^0 =133010进制转换成36进制的方法,以1079和52360为例(1079/36^0) % 36 = 35(1079/36^1) % 36 = 29(1079/36^2) 所以_36进制转换10进制

Linux系统安装Python3环境(超详细)_linux 安装python3_linux安装python3-程序员宅基地

文章浏览阅读970次,点赞29次,收藏11次。安装时报错ModuleNotFoundError: No module named '_ctypes’的解决办法2、从"./configure …"重新安装① Python所有方向的学习路线图,清楚各个方向要学什么东西② 600多节Python课程视频,涵盖必备基础、爬虫和数据分析③ 100多个Python实战案例,含50个超大型项目详解,学习不再是只会理论④ 20款主流手游迫解 爬虫手游逆行迫解教程包⑤ 爬虫与反爬虫攻防教程包,含15个大型网站迫解。_linux安装python3

爬虫处理数据流程图_利用网络爬虫技术对交通数据进行采集里边的流程图-程序员宅基地

文章浏览阅读5.4k次,点赞2次,收藏21次。爬虫处理数据流程图_利用网络爬虫技术对交通数据进行采集里边的流程图

服务计算作业:简单 web 服务与客户端开发实战_服务计算与软件服务工程-程序员宅基地

文章浏览阅读327次。服务计算作业:简单 web 服务与客户端开发实战作业要求概述利用 web 客户端调用远端服务是服务开发本实验的重要内容。其中,要点建立 API First 的开发理念,实现前后端分离,使得团队协作变得更有效率。作业要求开发过程本次作业我们选择实现见单个人博客网站:项目地址以下是本人承担的工作.使用Github建立组织在github创建本次作业的组织Simple-Blog,创建三个仓..._服务计算与软件服务工程

随便推点

渗透测试17---Metasploit (MSF) 部署与功能_msf war包部署 渗透-程序员宅基地

文章浏览阅读7.5k次。MetasploitMetasploit Framework简称MSF(ruby语言开发的)实验环境准备Metasploit的使用第一次使用要进行数据库的初始化msfdb init用的时候就:msfconsole也可以:msfdb run (就等于msfdb init 和msfconsole)Metasploit指令search ms17_010 会列出许多模..._msf war包部署 渗透

python实战:将cookies添加到requests.session中实现淘宝的模拟登录_浏览器登录淘宝后使用其cookie再用requests登录-程序员宅基地

文章浏览阅读1.4w次,点赞11次,收藏89次。将cookies添加到requests.session中实现淘宝的模拟登录声明:本文仅供学习用,旨在分享我们知道现在爬取淘宝商品是必须要登录的,在没有登录的情况下搜索商品也会自动重定向到登录页面。之前学着用selenium,pyppeteer等自动化框架模拟登录淘宝,但是无论怎么滑动滑块验证都失败。然而就像星爷《新喜剧之王》中所说得:只要不投降就是成功,同时为了安慰自己受伤的小心灵,决定用co..._浏览器登录淘宝后使用其cookie再用requests登录

[NLP]使用LDA模型计算文档相似度_lda关键词提取怎么和文本进行相似度-程序员宅基地

文章浏览阅读1.6w次,点赞2次,收藏68次。定义wiki关于lda的定义:隐含狄利克雷分布简称LDA(Latent Dirichlet allocation),是一种主题模型,它可以将文档集中每篇文档的主题按照概率分布的形式给出。同时它是一种无监督学习算法,在训练时不需要手工标注的训练集,需要的仅仅是文档集以及指定主题的数量k即可。此外LDA的另一个优点则是,对于每一个主题均可找出一些词语来描述它。LDA首先由Blei, David M.、_lda关键词提取怎么和文本进行相似度

python数据分析报告_Python一行命令生成数据分析报告-程序员宅基地

文章浏览阅读80次。一般在python进行数据分析/统计分析时,第一步总是对数据进行一些描述性分析、相关性分析,但是总会是有一大堆代码,那么今天就介绍一个神器pandas_profiling,一行命令就能搞定大部分描述性分析!安装pip install pandas_profiling使用那么我们继续使用之前文章中使用过很多次的NBA数据集,还记得我们在介绍pandas使用的那篇文章中分很多章节去讲解如何使用pand..._python生成数据分析报告

‘Three-Phase V-I Measurement/Vabc‘ has unapplied changes. Please apply or cancel these changes.-程序员宅基地

文章浏览阅读415次,点赞10次,收藏9次。复制一个这个模块,把原来的模块删除,解决了。_has unapplied changes. please apply or cancel these changes before running t

cad卸载不干净_流氓软件卸载不干净?强烈推荐这个工具:Uninstall Tool-程序员宅基地

文章浏览阅读2.4k次,点赞4次,收藏10次。当不想使用一款软件的时候,大部分人都会选择在控制面板里直接卸载它,通常这么做是没什么问题的。然而许多流氓软件可不会轻易地就甘心从你的电脑里消失,它们不想着尽力完善本身应该实现的功能,却把重心都放在了怎么压榨你的电脑资源,怎么给你弹更多的广告,怎么捆绑更多的组件,在你想要卸载时还要可怜兮兮的恳求留下它。(国产大部分视频软件客户端)更过分的还有直接卸载不干净的,无论怎么通过正常的途径去卸载,..._cad uninstall tool

推荐文章

热门文章

相关标签