当前位置: 首页 > news >正文

Golang服务的请求调度

文章目录

  • 1. 写在前面
  • 2. SheddingHandler的实现原理
  • 3. 相关方案的对比
  • 4. 小结

1. 写在前面

最近在看相关的Go服务的请求调度的时候,发现在gin中默认提供的中间件中,不含有请求调度相关的逻辑中间件,去github查看了一些服务框架,发现在go-zero中,有一个SheddingHandler的中间件来帮助服务请求进行调度,防止在流量徒增的时候,服务出现滚雪球进一步恶化,导致最后服务不可用的现象出现。

SheddingHandler中间件存在的意义就是尽量保证服务可用的情况下尽可能多的处理请求,而在流量突增的时候,丢弃部分请求以确保服务可用,防止服务因为流量过大而崩溃。

2. SheddingHandler的实现原理

SheddingHandler简单来说就是维持了一套指标,在每个请求进入系统的时候,利用指标进行计算,判断当前的请求是否允许被进入系统,如果允许则请求通过中间件继续向下被服务处理,如果不被允许则在中间件层面就丢弃掉(正是这个丢弃,保证了在流量突增时服务的稳定)。

具体看源码:

// SheddingHandler returns a middleware that does load shedding.
func SheddingHandler(shedder load.Shedder, metrics *stat.Metrics) func(http.Handler) http.Handler {if shedder == nil {return func(next http.Handler) http.Handler {return next}}ensureSheddingStat() // 负责每分钟打印shedding相关的数据return func(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {sheddingStat.IncrementTotal()promise, err := shedder.Allow() // 判断是否允许此请求进入下一步if err != nil {metrics.AddDrop() // drop掉请求,在中间件层面就拒绝了请求sheddingStat.IncrementDrop()logx.Errorf("[http] dropped, %s - %s - %s",r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent())w.WriteHeader(http.StatusServiceUnavailable)// 返回503,提示服务不可用return}cw := response.NewWithCodeResponseWriter(w)defer func() {if cw.Code == http.StatusServiceUnavailable {promise.Fail() // 相关指标记录} else {sheddingStat.IncrementPass()promise.Pass() // 相关指标记录}}()next.ServeHTTP(cw, r)})}
}

可以看到请求是否可以继续向下,取决于Allow()这个方法,这个方法的实现如下:

// Allow implements Shedder.Allow.
func (as *adaptiveShedder) Allow() (Promise, error) {if as.shouldDrop() {// 判断是否应该丢弃as.droppedRecently.Set(true)return nil, ErrServiceOverloaded// 丢弃}as.addFlying(1) // 通过校验return &promise{start:   timex.Now(),shedder: as,}, nil
}

继续看shouldDrop()方法:

func (as *adaptiveShedder) shouldDrop() bool {if as.systemOverloaded() || as.stillHot() {// 如果任一满足,这个请求都会被过载if as.highThru() {flying := atomic.LoadInt64(&as.flying)as.avgFlyingLock.Lock()avgFlying := as.avgFlyingas.avgFlyingLock.Unlock()msg := fmt.Sprintf("dropreq, cpu: %d, maxPass: %d, minRt: %.2f, hot: %t, flying: %d, avgFlying: %.2f",stat.CpuUsage(), as.maxPass(), as.minRt(), as.stillHot(), flying, avgFlying)logx.Error(msg)stat.Report(msg)return true}}return false
}func (as *adaptiveShedder) systemOverloaded() bool {if !systemOverloadChecker(as.cpuThreshold) { // 校验CPU的负载是否超出设定值return false}as.overloadTime.Set(timex.Now())// 超出设定值,记录当前的时间(这主要是为了后续流量减小,系统的恢复用)return true
}func (as *adaptiveShedder) stillHot() bool {if !as.droppedRecently.True() {// 如果这个请求之前有请求被drop这里值为true,反之为falsereturn false// 之前的请求没有被drop表示系统可能没有遇到过载的问题,返回false}overloadTime := as.overloadTime.Load()// 如果之前有请求被drop,表示存在过载if overloadTime == 0 {// 看看是否有记录过载的时间return false}if timex.Since(overloadTime) < coolOffDuration {// 如果小于冷却时间,表示系统依然是过载状态return true}as.droppedRecently.Set(false)// 表示CPU过载,上一次过载过了冷却器,这个请求可以继续执行,设置为falsereturn false
}

可以看到请求被drop的前置条件有两个:

  1. 系统的CPU负载超出了设定值,目前go-zero设置的默认值为90%,即系统CPU负载达到90%后,就意味着系统过载了,只要是过载,请求会被直接拒绝;否则判断第二个条件
  2. 因为过载可能会随着流量减小而恢复,或者丢弃的请求太多,系统CPU会慢慢的恢复正常水平(90%以下),所以需要看一下过载时间,如果超过了冷却时间,而第一个条件又表示系统CPU负载正常,此时我们会认定系统恢复了,这个请求可以处理。

满足上述任一条件,此请求就会进入最后的highThru()方法判断环节,如果满足了,此请求就会被丢弃。

从上面我们可以得到,我们判断服务是否过载,是依靠CPU的使用率去判断的,那么我们如何动态的计算CPU的使用率呢?

在go-zero里面,采用的是直接获取linux机器上的cpu的相关文件,然后通过代码逻辑将相关的文件进行解析并计算出CPU使用率。可以参考:[cgroup_linux.go]
linux cgroup信息

这里为了效率问题,并不是实时去计算的,而是在启动的时候,启动了一个goroutine每250ms进行以此CPU使用率数据的刷新。

const (// 250ms and 0.95 as beta will count the average cpu load for past 5 secondscpuRefreshInterval = time.Millisecond * 250allRefreshInterval = time.Minute// moving average beta hyperparameterbeta = 0.95
)var cpuUsage int64func init() {go func() {cpuTicker := time.NewTicker(cpuRefreshInterval)defer cpuTicker.Stop()allTicker := time.NewTicker(allRefreshInterval)defer allTicker.Stop()for {select {case <-cpuTicker.C:threading.RunSafe(func() {curUsage := internal.RefreshCpu() // 刷新CPU使用率数据prevUsage := atomic.LoadInt64(&cpuUsage)// cpu = cpuᵗ⁻¹ * beta + cpuᵗ * (1 - beta)usage := int64(float64(prevUsage)*beta + float64(curUsage)*(1-beta))atomic.StoreInt64(&cpuUsage, usage)})case <-allTicker.C:if logEnabled.True() {printUsage()}}}}()
}

最后再来看highThru()方法,这个方法相对来说比较复杂:

func (as *adaptiveShedder) addFlying(delta int64) {flying := atomic.AddInt64(&as.flying, delta)// 请求通过检验进入后会加1,请求被服务处理完后会减1if delta < 0 {as.avgFlyingLock.Lock()// 平均请求数计算为当前平均请求数*0.9 + 当前运行请求数*0.1as.avgFlying = as.avgFlying*flyingBeta + float64(flying)*(1-flyingBeta)as.avgFlyingLock.Unlock()}
}func (as *adaptiveShedder) highThru() bool {as.avgFlyingLock.Lock()avgFlying := as.avgFlying // 运行中的平均请求数as.avgFlyingLock.Unlock()maxFlight := as.maxFlight()// 运行的最大的请求数// 如果运行的平均请求数>最大的请求数且当前运行的请求数>最大的请求数,表示依旧高负载return int64(avgFlying) > maxFlight && atomic.LoadInt64(&as.flying) > maxFlight
}func (as *adaptiveShedder) maxFlight() int64 {// windows = buckets per second// maxQPS = maxPASS * windows// minRT = min average response time in milliseconds// maxQPS * minRT / milliseconds_per_second// 最大的运行数的计算为最大请求数*窗口的长度*最小的处理时间return int64(math.Max(1, float64(as.maxPass()*as.windows)*(as.minRt()/1e3)))
}

上面关于flying的计算,在SheddingHandler中有两个count统计器在统计这通过的总请求数以及请求的平均耗时。默认会在5s的时间内启动50个大小的bucket来循环滚动,即每个bucket统计100ms内的请求数。

这里利用窗口统计请求数大小的判断主要是为了规避在负载的情况下,丢弃了太多的请求导致系统实际运行的请求数减少的太多,所以加了这一层判断,这个可以保证在系统高负载丢弃了大量的请求的情况下,系统尽可能多的处理更多的请求,而不是负载一高就直接丢弃。

func (as *adaptiveShedder) maxPass() int64 {var result float64 = 1as.passCounter.Reduce(func(b *collection.Bucket) {if b.Sum > result {result = b.Sum}})return int64(result)
}func (as *adaptiveShedder) minRt() float64 {result := defaultMinRtas.rtCounter.Reduce(func(b *collection.Bucket) {if b.Count <= 0 {return}avg := math.Round(b.Sum / float64(b.Count))if avg < result {result = avg}})return result
}

3. 相关方案的对比

在调度请求这一块,go-zero的方案确实很棒,结合了CPU使用率和过载冷缺以及请求数大小因素,不仅保证了系统高负载下服务的正常,还确保了系统能够尽可能多的处理请求。

但从我们目前的调度模式以及执行单元的状态角度出发,我们会发现服务接收到一个请求后会解析请求读取请求的内容,然后调度此请求给到执行单元,这个执行单元可能是一个线程或者一个Goroutine,从执行单元的角度来看,以线程为例,线程的生命周期会有如下图所示的几个阶段:

  • 新建
  • 就绪
  • 运行
  • 阻塞
  • 死亡
    线程的生命周期

我们再从系统服务的限制方面考虑,一般系统的限制包括I/O限制和CPU限制,I/O限制指代I/O密集型的应用程序的限制,而CPU限制则是CPU密集型应用程序的限制:

  • I/O密集型:表示服务需要进行大量的I/O操作,如磁盘读写、网络传输等,这类服务不需要进行大量的计算,但需要等待I/O操作完成,所以一般CPU占用率很低。
  • CPU密集型:表示服务需要进行大量的CPU操作,如数据处理、图像处理、加密解密等,这类服务需要进行大量的计算,但不需要进行太多I/O相关的操作,所以I/O等待时间短,CPU占用率高。

在目前的服务应用中,绝大部分的应用程序是CPU密集型。

而CPU密集型服务,要想最大限度的利用CPU,最理想的情况所有的执行单元都处于运行和等待的状态,但等待和运行之间有个就绪的中间态,这也就意味着,如果想让所有的执行单元都处于运行和代码状态,我们就需要最小化就绪的执行单元数量。而就绪单元一旦获取到CPU资源(时间片)就会进入Running状态。

如果处于就绪的单元不断增多,在某种意义上意味着程序的CPU资源不足,即CPU过负载。从这个角度出发,我们可以利用执行单元处于就绪态的数量来判断服务是否过载。

在Golang的GMP模型中,P的数量是一定的,M的数量最多不超过10000个,而Goroutine的数量几乎是不定的。从上面利用就绪态(在Golang中是GRunnable状态)的数量来判断系统过载,也给我们提供了一个新的方案:判断系统所有P上(本地队列)的Goroutine处于GRunnable的数量,如果数量超过一个界定值,表示CPU资源不足,即过载。

4. 小结

在刚开始接触到服务的请求调度的时候,就想着看看是否有开源的方案来解决这个问题,果不其然,你能够想到的,大家曾经都想到过并付诸了时间和精力去给出了具体的方案设计,无论是SheddingHandler的设计,还是利用Goroutine的状态来判断系统是否过载,它们都有各自的理论为依托,但从精确度来说go-zero的SheddingHandler的设计相对来说更为准确,因为从CPU的真实数据出发,得到具体的CPU是否负载是最为可靠直观的。

判断Goroutine的就绪态数量这个方案,在最开始的接触中,自己是不太理解的,但从具体理论出发,包括后续自己也进行了相关的压测,以及Golang的trace.out文件的分析,在某种程度上,这种方案也是可行的,不禁感叹自己还是太弱了,还是要多学习,加油!

相关文章:

Golang服务的请求调度

文章目录 1. 写在前面2. SheddingHandler的实现原理3. 相关方案的对比4. 小结 1. 写在前面 最近在看相关的Go服务的请求调度的时候&#xff0c;发现在gin中默认提供的中间件中&#xff0c;不含有请求调度相关的逻辑中间件&#xff0c;去github查看了一些服务框架&#xff0c;发…...

Jenkins的流水线启动jar后未执行问题处理

现象 在流水线里配置了启动脚本例如&#xff0c;nohup java -jar xxx.jar >nohup.out 2>&1 & 但是在服务器发现服务并未启动,且nohup日志里没输出日志,这样的原因是jenkins在执行完脚本后&#xff0c;就退出了这个进程。 在启动脚本执行jar命令的上一步加入以下…...

智慧工地平台工地人员管理系统 可视化大数据智能云平台源码

智慧工地概述&#xff1a; 智慧工地管理平台是以物联网、移动互联网技术为基础&#xff0c;充分应用大数据、人工智能、移动通讯、云计算等信息技术&#xff0c;利用前端信息采通过人机交互、感知、决策、执行和反馈等&#xff0c;实现对工程项目內人员、车辆、安全、设备、材…...

外包干了2个月测试,技术退步明显...

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…...

神经网络基础-神经网络补充概念-19-向量化实现的解释

概念 向量化是一种优化技术&#xff0c;通过使用数组操作代替显式的循环&#xff0c;可以大大提高代码的性能和效率。在机器学习和数据分析领域&#xff0c;向量化是一种常见的实践&#xff0c;它允许你在处理大量数据时更快地进行计算。 一般操作 数组操作&#xff1a;向量…...

四层和七层负载均衡的区别

一、四层负载均衡 四层就是ISO参考模型中的第四层。四层负载均衡器也称为四层交换机&#xff0c;它主要时通过分析IP层和TCP/UDP层的流量实现的基于“IP端口”的负载均衡。常见的基于四层的负载均衡器有LVS、F5等。 以常见的TCP应用为例&#xff0c;负载均衡器在接收到第一个来…...

Scala 如何调试隐式转换--隐式转换代码的显示展示

方法1 在需要隐式转换的地方&#xff0c;把需要的参数显示的写出。 略方法2&#xff0c;查看编译代码 在terminal中 利用 scalac -Xprint:typer xxx.scala方法打印添加了隐式值的代码示例。 对于复杂的工程来说&#xff0c;直接跑到terminal执行 scalac -Xprint:typer xxx.…...

Rust交叉编译简述 —— Arm

使用系统&#xff1a;WSL2 —— Kali(Microsoft Store) 命令列表 rustup target list # 当前官方支持的构建目标架构列表 rustup target add aarch64-unknown-linux-gnu # 添加目标架构sudo apt-get install gcc-13-aarch64-linux-gnu gcc-13-aarch64-linux-gnu # 下载目标工具…...

算法与数据结构(二十三)动态规划设计:最长递增子序列

注&#xff1a;此文只在个人总结 labuladong 动态规划框架&#xff0c;仅限于学习交流&#xff0c;版权归原作者所有&#xff1b; 也许有读者看了前文 动态规划详解&#xff0c;学会了动态规划的套路&#xff1a;找到了问题的「状态」&#xff0c;明确了 dp 数组/函数的含义&a…...

相机的位姿在地固坐标系ECEF和ENU坐标系的转换

在地球科学和导航领域&#xff0c;通常使用地心地固坐标系&#xff08;ECEF&#xff0c;Earth-Centered, Earth-Fixed&#xff09;和东北天坐标系&#xff08;ENU&#xff0c;East-North-Up&#xff09;来描述地球上的位置和姿态。如下图所示&#xff1a; ​地心地固坐标ecef和…...

RFID技术助力汽车零配件装配产线,提升效率与准确性

随着科技的不断发展&#xff0c;越来越多的自动化设备被应用到汽车零配件装配产线中。其中&#xff0c;射频识别&#xff08;Radio Frequency Identification&#xff0c;简称RFID&#xff09;技术凭借其独特的优势&#xff0c;已经成为了这一领域的重要技术之一。本文将介绍RF…...

应用高分辨率 GAN 对扰动文档图像去扭曲的深度Python实践

1. 引言 随着技术的不断发展&#xff0c;图像处理在各种场景中的应用也变得越来越广泛。高分辨率 GAN (Generative Adversarial Network) 是近年来图像处理领域的热点技术&#xff0c;它能够生成极高分辨率的图像&#xff0c;与此同时&#xff0c;它也可以用于各种修复和增强任…...

【BASH】回顾与知识点梳理(二十六)

【BASH】回顾与知识点梳理 二十六 二十六. 二十一至二十五章知识点总结及练习26.1 总结26.2 模拟26.3 简答题 该系列目录 --> 【BASH】回顾与知识点梳理&#xff08;目录&#xff09; 二十六. 二十一至二十五章知识点总结及练习 26.1 总结 Linux 操作系统上面&#xff0c…...

React下载文件的两种方式

React下载文件的两种方式 - 代码先锋网 不知道有用没用看着挺整齐 没试过 1、GET类型下载 download url > {const eleLink document.createElement(a);eleLink.style.display none;// eleLink.target "_blank"eleLink.href url;// eleLink.href record;d…...

python入门知识:分支结构

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 1.内容导图 &#x1f447; &#x1f447; &#x1f447; 更多精彩机密、教程&#xff0c;尽在下方&#xff0c;赶紧点击了解吧~ python资料、视频教程、代码、插件安装教程等我都准备好了&#xff0c;直接在文末名片自…...

DNS协议及其工作原理

DNS是域名系统&#xff08;Domain Name System&#xff09;的缩写&#xff0c;它是一种用于将域名转换为IP地址的分布式数据库系统。它是因特网的基石&#xff0c;能够使人们通过域名方便地访问互联网&#xff0c;而无需记住复杂的IP地址。 DNS的历史可以追溯到1983年&#xf…...

调用被fishhook的原函数

OC类如果通过runtime被hook了&#xff0c;可以通过逆序遍历方法列表的方式调用原方法。 那系统库的C函数被fish hook了该怎么办呢&#xff1f; 原理和OC类异曲同工&#xff0c;即通过系统函数dlopen()获取动态库&#xff0c;以动态库为参数通过系统函数dlsym()即可获取目标系统…...

java语言B/S架构云HIS医院信息系统源码【springboot】

医院云HIS全称为基于云计算的医疗卫生信息系统( Cloud- Based Healthcare Information System)&#xff0c;是运用云计算、大数据、物联网等新兴信息技术&#xff0c;按照现代医疗卫生管理要求&#xff0c;在一定区域范围内以数字化形式提供医疗卫生行业数据收集、存储、传递、…...

go文件基本操作

一、文件读操作 文件内容如下&#xff1a; 水陆草木之花&#xff0c;可爱者甚蕃。 晋陶渊明独爱菊。自李唐来&#xff0c;世人甚爱牡丹。 予独爱莲之出淤泥而不染&#xff0c;濯清涟而不妖&#xff0c;中通外直&#xff0c;不蔓不枝&#xff0c;香远益清&#xff0c;亭亭净植…...

每日一学——应用层

以下是一份关于应用层协议的学习资料&#xff1a; DNS (Domain Name System)&#xff1a;DNS是互联网上最常用的应用层协议之一&#xff0c;它将域名转换为对应的IP地址。你可以了解DNS的工作原理、域名解析过程和常见的DNS记录类型。 DHCP (Dynamic Host Configuration Proto…...

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站&#xff0c;会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后&#xff0c;网站没有变化的情况。 不熟悉siteground主机的新手&#xff0c;遇到这个问题&#xff0c;就很抓狂&#xff0c;明明是哪都没操作错误&#x…...

ES6从入门到精通:前言

ES6简介 ES6&#xff08;ECMAScript 2015&#xff09;是JavaScript语言的重大更新&#xff0c;引入了许多新特性&#xff0c;包括语法糖、新数据类型、模块化支持等&#xff0c;显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var&#xf…...

模型参数、模型存储精度、参数与显存

模型参数量衡量单位 M&#xff1a;百万&#xff08;Million&#xff09; B&#xff1a;十亿&#xff08;Billion&#xff09; 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的&#xff0c;但是一个参数所表示多少字节不一定&#xff0c;需要看这个参数以什么…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

23-Oracle 23 ai 区块链表(Blockchain Table)

小伙伴有没有在金融强合规的领域中遇见&#xff0c;必须要保持数据不可变&#xff0c;管理员都无法修改和留痕的要求。比如医疗的电子病历中&#xff0c;影像检查检验结果不可篡改行的&#xff0c;药品追溯过程中数据只可插入无法删除的特性需求&#xff1b;登录日志、修改日志…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

OkHttp 中实现断点续传 demo

在 OkHttp 中实现断点续传主要通过以下步骤完成&#xff0c;核心是利用 HTTP 协议的 Range 请求头指定下载范围&#xff1a; 实现原理 Range 请求头&#xff1a;向服务器请求文件的特定字节范围&#xff08;如 Range: bytes1024-&#xff09; 本地文件记录&#xff1a;保存已…...

Matlab | matlab常用命令总结

常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...

重启Eureka集群中的节点,对已经注册的服务有什么影响

先看答案&#xff0c;如果正确地操作&#xff0c;重启Eureka集群中的节点&#xff0c;对已经注册的服务影响非常小&#xff0c;甚至可以做到无感知。 但如果操作不当&#xff0c;可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)

推荐 github 项目:GeminiImageApp(图片生成方向&#xff0c;可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...