当前位置: 首页 > 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…...

SkyWalking 10.2.0 SWCK 配置过程

SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外&#xff0c;K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案&#xff0c;全安装在K8S群集中。 具体可参…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

云原生玩法三问:构建自定义开发环境

云原生玩法三问&#xff1a;构建自定义开发环境 引言 临时运维一个古董项目&#xff0c;无文档&#xff0c;无环境&#xff0c;无交接人&#xff0c;俗称三无。 运行设备的环境老&#xff0c;本地环境版本高&#xff0c;ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

中医有效性探讨

文章目录 西医是如何发展到以生物化学为药理基础的现代医学&#xff1f;传统医学奠基期&#xff08;远古 - 17 世纪&#xff09;近代医学转型期&#xff08;17 世纪 - 19 世纪末&#xff09;​现代医学成熟期&#xff08;20世纪至今&#xff09; 中医的源远流长和一脉相承远古至…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

腾讯云V3签名

想要接入腾讯云的Api&#xff0c;必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口&#xff0c;但总是卡在签名这一步&#xff0c;最后放弃选择SDK&#xff0c;这次终于自己代码实现。 可能腾讯云翻新了接口文档&#xff0c;现在阅读起来&#xff0c;清晰了很多&…...