如何利用 Go 语言开发高可用服务

news2024/9/23 1:06:39

        高可用的含义是尽量减少服务的不可用(日常维护或者突发系统故障)时长,提升服务的可用时长。如何衡量一个服务的可用性呢?或许你也听说过,通常企业可能会要求服务的可用性能能够达到三个 9(也就是 99.9%)或者 4个 9 (也就是 99.99%),可是你知道这是如何计算的吗?需要重点强调的是,可用性是每一个 Go 开发者都必须关注的事情。

1. 可用性定义与高可用性三板斧

        我们的目标是构建高可用的 Go 服务,那如何定义服务的可用性呢?我们又该如何提升服务的可用性呢?其实这些都是有固定套路的。

1.1 可用性定义

        我们总说提升服务的可用性,可是如何衡量服务的可用性呢?如果没有一个量化的指标,你又怎么知道服务的可用性是提升了还是降低了。我们可以按照下面的方式定义服务的可用性:

        其中 MTTF (Mean Time To Failure) 指的是服务的平均无故障时间,即服务从正常运行到出现故障的平均时间:MTTR ( Mean Time To Repair)指的是服务的平均修复时间(故障时间),即服务从出现故障到修复成功的平均时间。根据这个定义,我们可以计算出在不同的可用性目标下,全年服务可以接受的最长故障时间,如下表所示:

可用性级别可用性目标全年故障时间每天故障时间
190%36.5天2.4h
299%3.65 天14min
399.9%8.76h86s
499.99%53min8.6s

        当我们的可用性目标是 4 个 9(也就是 99.99%)时,全年故障时间为 53min,每天故障时间为 8.6s。也就是说,如果全年的故障时间超过 53min,那么当年的可用性指标肯定就无法达到 99.99% 了。

        根据上述可用性的定义,提升服务可用性最直观的方式就是减少故障时长。如何减少呢?我们可以从两方面入手:

1)预防:尽可能避免服务发生故障。

2)故障处理:当服务发生故障时,尽可能快速地恢复服务的正常运行。当然,这样的描述还是过于泛化,具体的可用性提升方案还需要进一步细化,可以参考下表:

预防故障处理
质量提升

1. 研发质量提升:比如技术方案评审,代码评审,单元测试等

2. 测试质量提升:比如白盒测试,自动化测试,仿真环境建设等

变更管控

1. 变更窗口:比如业务高峰期禁止线上变更

2. 任何变更都需要双重检查,都需要有回滚方案,都需要有检查清单

3. 灰度发布 ,小流量发布

容错设计

1. 资源隔离:比如服务部署隔离、网关侧隔离、数据库隔离等

2. 错误隔离:比如请求级的错误隔离、进程级的错误隔离、服务级的错误隔离等

3. 限流、熔断与降级:非核心功能可降级,非核心依赖可熔断

4. 故障演练

冗余设计

1. 避免单点:主备/集群化部署,服务可快速扩容

2. 容量冗余:性能评估与性能压测

3. 异地多活

发现

1.核心业务监控与报警:比如订单服务、支付服务

2. 服务可用性监控与报警:比如服务错误日志、网关侧异常状态码等

3. 基本指标的监控与报警:比如 CPU、内存、网络等

定位

1. 日志与全链路追踪:业务日志、网关侧访问日志、全链路日志

2. 监控:多维度多特征监控、容量监控、基本指标监控、端到端监控

止损

1. 流量调度:主要针对集群故障、机房故障

2.限流、熔断与降级:紧急限流、非核心功能降级、非核心依赖熔断

3. 变更回滚:针对变更引起的异常

4. 快速扩容:针对容量不足的情况

5. 服务重启

恢复

1. 复原:问题修复,数据修复,执行过的止损操作还原

2. 复盘:分析根本原因,制订改进计划

1.2 高可用三板斧

        高可用三板斧指的是限流、熔断与降级。限流是通过对并发请求进行限速来保护自身服务;熔断是为了避免依赖的第三方服务影响自身服务;降级是通过牺牲非核心功能来保障核心功能。为什么要单独介绍限流、熔断与降级呢?因为这是构建高可用服务不可缺少的三种手段。下面将分别介绍限流、熔断与降级的基本原理。

1.2.1 限流

        首先来说限流,限流是通过对并发请求进行限速来保护自身服务。当请求速率超过限制速率时,服务端可以直接拒绝请求(返回固定的错误,或者定向到错误页面),或者将请求排队等待后续处理。常见的限流方式有:限制瞬时并发数,限制单位时间窗口内的平均速率。当然,也可以根据网络连接数、网络流量、CPU 和 内存负载等进行限流。

        以限制单位时间窗口内的平均速率为例,常用的限流算法有计数器算法、漏桶算法、令牌桶算法。下面我们详细介绍这几种限流算法。

(1)计数器算法

        计数器算法是限流算法中最简单且最容易实现的一种算法。例如,假设我们规定某个接口的平均每秒访问速率不能超过 1000 次,为了实现这个限制,我们可以为该接口维护一个计数器,其有效时间为 1s。每当有请求到达时,计数器就会加 1,如果计数器的值超过了 1000,就表示请求速率超过了限制值。

        需要说明的是,尽管这个算法很简单,但准确度却很差。我们将 1s 的时间段划分为 1000ms。假设在第 999ms 时,有 1000 个请求到达,显然不会触发限流(0~1000ms 是一个计数周期)。而在第 1001 ms 时,又有 1000 个请求到达,同样不会出触发限流(1000~2000ms是一个计数周期)。但是你会发现,瞬时的请求速率非常高。在从 999ms 到 1001ms 这 2ms 内,就需要处理 2000 个请求,这可能已经超过了服务的处理能力。

(2)漏桶算法

漏桶算法的基本原理可以参考下图,

有一个固定容量的漏桶,该漏桶可以按照固定速率流出水滴,如果漏桶是空的,则不会流出水滴。流入漏桶的水流速度(请求速率)是随意的,如果流入的水(请求)超出了漏桶的容量,则会溢出(请求被丢失)。可以看到,漏桶算法的最大请求速率是恒定的。

(3)令牌桶算法

        令牌桶算法示意图如下所示:

        思考一下,如果令牌桶的容量是 0,并且排队等待的请求队列长度是有限制的,这时候令牌桶算法是不是和漏桶算法非常类似呢?

1.2.2. 熔断与降级

        熔断是为了避免依赖的第三方服务影响自身服务,如何避免呢?当然是不再依赖异常的第三方服务了,比如当需要请求第三方服务时,直接返回默认的数据即可。为什么需要熔断呢?假设有这么一个业务场景:A 服务依赖了 B 服务,B 服务的平均响应时间是 100ms,此时 A 服务的 QPS 可以达到 1 万;某一个业务高峰, B 服务突然变慢了,平均响应时间变成了 500ms, 此时 A 服务的平均响应时间肯定也会变长,那么其 QPS 还能达到 1 万吗?通常情况下远远达不到的。

        这还不是最糟糕的,要知道这可是处于业务高峰时期,A 服务的性能变差之后,大量的业务请求将会失败,而通常请求失败时还会有一些重试(用户重试或者自动重试),这就导致 A 服务需要承担更大的请求压力,极端情况下甚至可能会压垮 A 服务,进而拖垮 B 服务。

        当然,如果 B 服务是 A 服务的弱依赖(也就是说,即使没有 B 服务,A 服务也可以正常运行),当 B 服务出现异常(比如变慢)时,A 服务可以采取熔断策略,不再依赖 B 服务,以此保证自身服务的正常运行。

        最后说一下降级,降级的目的是降低系统压力,保障核心服务的正常运行。比如每年的双 11、618等购物节,你会发现当天这些电商平台的部分功能是不可用的(被降级了),而这些功能基本上是不会影响用户的购物体验的。通常什么场景下需要降级呢?比如业务高峰期,机器资源不足时,可以将非核心服务降级,以保障核心服务的资源充足;比如系统出现异常并且无法快速定位时,同样可以将非核心服务降级,以避免非核心服务影响核心服务。

 2. 流量治理组件 Sentinel

        Sentinel 是阿里技术团队开源的流量治理组件,其主要以流量为切入点,从流量控制、流量整形、熔断降级、系统自适应过载保护等多个维度来帮助开发者保障服务的稳定性。2012 年 Sentinel 就诞生了,只是这时候只有 java 版本,直到 2020 年阿里团队才推出了 Go 版本。

2.1 Sentinel 快速入门

        在讲解Sentinel 的使用之前,先介绍一个基本概念:资源(resource),这是 Sentinel 中的最核心概念之一。Sentinel 中所有的限流熔断机制都是基于资源生效的,不同资源的限流熔断规则互相隔离互不影响。在 Sentinel 中,用户可以灵活地定义资源,比如可以将应用、接口、函数,甚至是一段代码等定义为一种资源,而流量治理的目的就是保护资源如预期一样运行。

        用户可以通过 Sentinel 提供的接口将资源访问包装起来,这一步称为 “埋点”。每个埋点都有一个资源名称(resource),代表触发了这个资源的调用或访问,有了资源埋点之后,我们就可以针对资源埋点配置流量治理规则。Sentinel 支持多种类型的流量治理规则,如流量控制规则、流量隔离规则、熔断降级规则、自适应过载保护规则以及热点参数流量控制规则。

        那么如何使用 Sentinel 呢?可以参考 Sentinel 官方给出的基于 QPS 限流示例,代码如下所示:

func main(){
	//务必先进行初始化
	err := sentinel.InitDefault()
	//配置一条限流规则
	_,err = flow.LoadRules([]*flow.Rule){{
					Resource:	"some-test",
					Threshold:	10,
					.....},})
	//模拟并发访问
	for i :=0; i <10;i++{
		go func(){
			for {
				//埋点逻辑,埋点资源名称为 some-test
				e,b := sentinel.Entry("some-test")
				if b != nil {
					//请求被拒绝,在此进行处理
				}else {
					//请求允许通过,此直编写业务逻辑
					fmt.Println(util.CurrentTimeMillis(),"Passed")
						e.Exit() //务比何证业务结束后调用 Exit
				}
			}
		}()
	}
......
}

2.2 流量控制

        流量控制的目的是避免服务被瞬时的流量高峰冲垮,其原理是根据令牌计算策略来计算可用令牌的资源,并根据流量控制策略对请求进行控制(拒绝或者排队等待)。Sentinel 的流量控制规则定义如下所示:

type Rule struct {
	ID string
	Resource string										//规则ID
	TokenCalculateStrategy TokenCalculateStrategy		//资源名称
	ControlBehavior	ControlBehavior						//令牌计算策略
	Threshold				float64						//流量控制行为
	MaxQueueingTimeMS		uint32						//限流阈值
	WarmUpPeriodSec			uint32						//请求排队的最长等待时间
	WarmUpColdFactor		unit32						//预热的时间长度
	StatIntervalInMs 		uint32						//预热因子,该值会影响预热速度
	LowMemUsageThreshold 	int64						//流量控制器的统计周期,单位是ms
	HighMemUsageThreshold 	int64						//内存使用小于低水位时的限流阈值
	MemLowWaterMarkBytes 	int64						//内存低水位
	MemHighWaterMarkBytes 	int64						//内存高水位
}

        流量控制规则的字段还是比较多的,这里就不一一介绍了。我们主要介绍一下令牌计算策略、流量控制行为以及内存自适应流量控制。

 Sentinel 支持两种类型的令牌计算策略。

2.3 系统自适应流量控制

        Sentinel 支持系统自适应流量控制,什么意思呢?就是结合系统的负载、CPU 利用率、服务的入口 QPS、服务的平均响应时间、并发数等几个维度的监控指标,通过自适应的方式进行流量控制。系统自适应流量控制的目的是平衡入口流量与系统负载,在保障系统稳定性的前提下使得系统尽可能地对外提供服务。需要注意的是,系统自适应流量控制是面向整个服务的,而不是单个接口的,并且仅对入口流量生效。

Sentinel 系统自适应流量控制目前支持 5 种类型的流量控制方式,定义如下:

1)负载:负载是对 CPU 工作量的度量,指的是单位时间内系统中的平均活跃进程数;负载可以用 3 个指标衡量,load1、load5 与 load15,分别表示过去 1min、5min 与 15min 的平均负载,Sentinel 使用的是 load1 来进行系统自适应流量控制。

2)CPU 利用率:CPU 利用率用于衡量 CPU 的繁忙程度,其定义是除了空闲时间外的其他时间占总 CPU 时间的百分比。

3)平均响应时间:Sentinel 会统计所有入口流量总的响应时间,总响应时间除以总的请求数就是平均响应时间。

4)并发数:当请求通过时并发数加1,当请求执行结束时并发数减1。

5)入口 QPS:入口 QPS 的统计同样是基于滑动窗口实现的。

        Sentinel 系统自适应流量控制的规则定义如下所示:

type Rule struct {
	ID 				string				//规则ID
	MetricType 		MetricType			//流量控制指标类型
	TriggerCount 	float64				//流量控制触发阈值
	Strategy 		AdaptiveStrategy	//自适应流量控制策略
}

        在上面的代码中,字段 MetricType 就是我们介绍的流量控制指标类型,字段 Strategy 表示自适应流量控制策略。Sentinel 提供了两种类型的自适应流量控制策略:NoAdaptive 与 BBR。NoAdaptive 类型的自适应流量控制策略非常简单,只需要对应的指标,如负载大于阈值就拒绝请求。那什么是 BBR 呢?思考一下,仅仅根据系统负载或者 CPU 利用率进行流量控制合适吗?换一个思路,系统负载或者 CPU 利用率其实是结果,是系统容量无法支持高并发请求的结果。如果我们仅仅根据系统负载或者 CPU 利用率进行流量控制,就始终存在一定的延迟。

        为了解决上面的问题,Sentinel 参考了 TCP 拥塞控制算法(BBR)。TCP 拥塞控制算法的初衷同样是在保证通信质量的前提下尽可能提升链路带宽利用率。该算法认为当同时满足最大带宽和最小延迟时,整个网络处于最优工作状态,此时网络中的数据包总量等于最大带宽乘最小时延。参考 TCP 拥塞控制算法,Sentinel 实现的 BBR 自适应流量控制策略如下所示:

func checkBbrSimple() bool {
	concurrency := stat.InboundNode().CurrentConcurrency()
	minRt := stat.InboundNode().MinRT()
	maxComplete := stat.InboundNode().GetMaxAvg(base.MetricEventComplete)
	if concurrency > 1 && float64(concurrency) > maxComplete*minRt/1000.0 {
		return false
	}
	return true
}

        在上面的代码中,变量 concurrency 表示当前系统处理的并发请求数,minRt表示最小时延,maxComplete 表示每秒最大处理的请求数。这一逻辑与 TCP 拥塞控制算法是一致的。

        系统自适应流量控制的使用还是比较简单的,只需要定义好流量控制规则就可以了,只是别忘了在请求入口将所有资源的访问包起来。

 2.4 熔断降级

        熔断与降级其实是非常类似的,本质上都是切断不稳定的弱依赖服务调用(只是熔断是从服务消费者视角出发的,降级是从服务提供者视角出发的),所以 Sentinel 将熔断降级合成了一种规则。

        Sentinel 的熔断降级基于熔断器模式实现,内部维护了一个熔断器的状态机,状态机的转换关系如下图所示: 

熔断器有 3 种状态。3 种状态含义如下所示:

1)CLOSED:关闭状态,这也是初始状态,当熔断器处于这一状态时,所有请求都会通过。

2) OPEN: 断开状态,当熔断器统计的指标数据,比如失败比例、平均响应时间(RT,response time)、异常请求数等超过阈值时,熔断器将 CLOSED 状态转移到 OPEN 状态。当熔断器处于 OPEN 状态时,所有请求都会被拒绝。

3) HALF-OPEN:半断开状态,处于 OPEN 状态的熔断器有一个熔断超时时间,超过熔断时长之后熔断器将转移到 HALF-OPEN 状态。这时候熔断器会周期性地允许一些探测请求通过,如果探测请求能够成功返回,则熔断器转移到 CLOSED 状态,如果探测失败,则熔断器转移到 OPEN 状态。 

2.5 Sentinel 原理浅析

        Sentinel 为我们提供了多种流量治理策略,如流量控制、系统自适应流量控制、熔断降级等。但是,如果 Sentinel 原生的流量治理策略无法满足业务需求,该怎么办呢?这时候有可能就需要我们自定义流量治理策略了,如何自定义呢?这就需要我们对 Sentinel 的原理有一定了解。

        Sentinel 的主框架是基于责任链模式实现的,每一个请求都需要经过多个请求处理器,理论上每一个请求处理器都会根据自身的流量治理策略以及流量治理规则判断是否允许请求通过。当然,Sentinel 其实提供了三种类型的请求处理器:第一种请求处理器主要用于执行一些初始化操作;第二种请求处理器就是 Sentinel 提供的各种类型的流量治理策略;第三种请求处理器主要用于统计指标。这三种类型的请求处理器接口定义如下:

//执行一些初始化工作
type StatPrepareSlot interface {
	Prepare(ctx *EntryContext)
}

//流量治理策略,方法 Check 返回是否允许请求通过
type RuleCheckSlot interface {
	Check(ctx *EntryContext) *TokenResult
}

//统计指标
type StatSlot interface {
	OnEntryPassed(ctx *EntryContext)
	OnEntryBlocked(ctx *EntryContext,blockError *BlockError)
	OnCompleted(ctx *EntryContext)
}

        在上面的代码中,如果你想自定义流量治理策略,其实只需要实现接口 RuleCheckSlot 就可以了,当然如果你的流量治理策略还依赖于一些特殊的指标,那么你还需要实现接口 StatSlot。Sentinel 默认初始化的请求处理链如下所示:

func BuildDefaultSlotChain() *base.SlotChain {
	sc := base.NewSlotChain()
	sc.AddStatPrepareSlot(stat.DefaultResourceNodePrepareSlot)
	//流量治理策略
	sc.AddRuleCheckSlot(system.DefaultAdaptiveSlot)
	sc.AddRuleCheckSlot(flow.DefaultSlot)
	sc.AddRuleCheckSlot(isolation.DefaultSlot)
	sc.AddRuleCheckSlot(hotspot.DefaultSlot)
	sc.AddRuleCheckSlot(circuitbreaker.DefaultSlot)
	//统计指标
	sc.AddStatSlot(stat.DefaultSlot)
	sc.AddStatSlot(log.DefaultSlot)
	sc.AddStatSlot(flow.DefaultStandaloneStatSlot)
	sc.AddStatSlot(hotspot.DefaultConcurrencyStatSlot)
	sc.AddStatSlot(circuitbreaker.DefaultMetricStatSlot)
	return sc
}

        在上面的代码中,我们可以看到前面所介绍的几种流量治理策略,其中 system.DefaultAdaptiveSlot 表示系统自适应流量控制策略,flow.DefaultSlot 表示流量控制策略,circuitbreaker.DefaultSlot 表示熔断降级策略等。

        总的来说,Sentinel 的原理其实就是遍历上述请求处理链,执行每一个请求处理器罢了。自定义流量治理策略也非常简单,一来只需要实现自定义的请求处理器(也就是实现接口 RuleCheckSlot 与 StatSlot),二来将自定义的请求处理器添加到全局请求处理链就可以了。

3. Go 服务监控

        在故障处理时,完善的监控与报警体系可以帮助我们快速地发现问题与定位问题。对 Go 服务而言,如何监控 Go 服务的核心指标呢?比如协程数、内存使用量、线程数等。

3.1 运行时监控

        如何监控 Go 服务的运行时指标呢?

        第一步,当然是采集 Go 服务的运行时指标了,常用的运行时指标包括线程数、协程数、内存使用量、GC耗时等。如何采集呢?幸运的是,Go 语言为我们提供了 SDK,通过这些 SDK 我们可以很方便地获取到这些运行时指标

        第二步,如何导出与查看这些运行时指标呢?我们可以借助 Prometheus,这是一款开源的监控与报警系统,并且提供了多种语言的客户端库,其中就包括 Go 语言。

3.2 自定义监控

        3.1 小节讲解了如何监控 Go 服务的运行时指标,那如果我们想自定义一些监控指标该如何实现呢?比如服务或者接口的访问 QPS、响应时间等。这就需要我们对 Prometheus 的几种指标类型以及 Prometheus 客户端库的使用有一些了解。

4. 其他

        在 Go 项目开发过程中,有两个细节特别容易忽视:超时控制与错误处理。不合理的超时时间错误处理可能导致服务因为一些轻微的异常而崩溃。因此,在依赖第三方资源时一定要注意设置合理的的超时时间,并且在项目开发过程中要有完善的错误处理机制。

4.1 超时控制

        大部分Web 服务通常都会依赖 HTTP 服务、数据库以及 Redis等。下面将分别介绍在依赖这三种类型的资源时,如何设置合理的超时时间。

4.1.1 HTTP服务

        当我们使用 Go 语言原生的 HTTP 客户端访问第三方服务时,可以通过两种方式设置超时时间。第一种方式是基于 HTTP 客户端的 Timeout 字段实现的,第二种方式是基于上下文 context 实现的。

4.1.2 数据库

        Go 服务操作数据库通常都是基于长连接,因此数据库的超时时间可以分为建立连接的超时时间与处理请求的超时时间。

4.1.3 Redis

        Go 服务操作 Redis 通常是基于长连接,因此 Redis 的超时时间也分为建立连接的超时时间与处理请求的超时时间。

4.2 错误处理

        Go 语言将错误分为两种类型:一种是普通错误,也就是我们常用的类型 error; 一种是严重错误,通常我们用关键字 panic 声明发生了严重错误。下面分别介绍这两种类型的错误。

4.2.1 error

        在实际项目开发过程中,你会发现很多函数都会有多个返回值,通常第一个返回值用于返回真正的结果,第二个返回值是类型 error,表示是否发生了错误。代码如下所示:

resp,err := client.Do(req)
if err != nil {
	fmt.Println(fmt.Sprintf("client error:%v",err))
	return
}

        在上面的代码中,我们在调用一些函数之后通常都会判断返回值 error 是否为空:如果不为空,则说明发生了错误,此时需要执行一些错误处理操作,比如记录错误日志等;如果为空,说明函数调用成功,则继续执行后续流程。

4.2.2 panic

        关键字 panic 通常用于声明发生了严重错误,需要特别注意的是,panic 会导致程序异常退出。以下面程序为例:

package main

import "fmt"

func main() {
	fmt.Println("test1 start")
	panic("this is a panic")
	fmt.Println("panic 1")
}

执行上面的程序之后,控制台会输出如下信息:

test1 start
panic: this is a panic

//协程栈桢
goroutine 1 [running]:
main.main()

//程序异常退出
错误: 进程退出代码 2.

        参考上面的输出结果,当我们通过 panic 声明错误之后,程序直接异常退出了,panic 语句之后的输出语句并没有执行。幸运的是,当程序因为 panic 异常退出时,会输出异常信息以及协程栈帧,通过这些信息我们基本上就能排查出问题所在了。当然,在实际项目开发过程中,我们可能需要避免程序因为 panic 异常退出,毕竟不能因为一个请求异常影响整个服务,这时候可以使用延迟调用defer 捕获异常,代码如下所示:

//该语句在调用函数 test 之前
defer func(){
	fmt.Println("defer 1")
	if rec := recover(); rec != nil {
		fmt.Println(rec)
	}
	fmt.Println("defer 2")
}()

程序输出如下: 

test1 start
defer 1
this ia a panic
defer 1
//程序正常退出
Process finished with the exit code 0

        在上面的代码中,当我们使用延迟调用 defer 捕获异常之后,程序就能够从 defer 语句开始恢复执行,最终程序也会正常退出。

        最后补充一下,在一些业务场景中,当发生错误时,可能会进行重试(重试可以在一定程度上解决部分问题),

        思考一下,为什么需要采用指数退避算法进行重试呢?因为当第三方服务返回错误时,如果我们立即重试,很有可能还会得到一个错误的响应,并且频繁地重试对第三方服务的压力也比较大,所以可以稍微等一段时间再重试。

        另外需要注意的是,重试需谨慎,不合理的重试可能会导致服务的雪崩。为什么呢?这里举两个具体的例子。

        第一个例子,假设 A 服务依赖了 B 服务,在某个业务高峰期间,B 服务出现了异常(大量返回错误或者大量超时),此时 A 服务在调用 B 服务时,发现返回了错误,于是又进行重试。这会导致什么呢?A 服务的重试会进一步增加 B 服务的负载,甚至导致 B 服务的崩溃。

        第二个例子,整个请求的链路其实是非常复杂的,一个客户端请求到达 A 服务可能需要经过全站加速,接入层网关,容器 Ingress 等,A 服务可能依赖 B 服务,B 服务还有可能依赖 C 服务等。注意,每一条链路都有可能配置重试,那么客户端的一次重试请求, C 服务可能会收到多个请求(甚至数十个)。可以看到,这存在明显的请求放大情况,极端情况下,C 服务需要承载可能被放大了数十倍。

        看到了吧,不合理的重试在某些情况下可能会导致非常严重的影响,一定要考虑哪些情况可以重试,哪些情况不能重试;另外在制定重试策略时,应该从全局去分析考虑,不能局限于局部的调用链路。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

手机三要素验证API接口,选择的时候应该注意什么?

在选择手机三要素验证API接口时&#xff0c;为了确保接口的安全性、可靠性和适用性&#xff0c;需要注意以下几个方面&#xff1a; 服务商的合法性和资质 合法性&#xff1a;确保服务商具有合法的经营资质和业务范围&#xff0c;以避免法律风险。 资质认证&#xff1a;查看服务…

运维的理解、定位及其在现代企业中的重要性

在当今数字化转型的大潮中&#xff0c;运维&#xff08;Operation & Maintenance, O&M&#xff09;作为企业IT架构中不可或缺的一环&#xff0c;其角色与职责正经历着深刻的变革。运维不再仅仅是技术层面的支持与维护&#xff0c;而是逐渐演变为一种集技术支持、业务服…

ORM框架:Mybatis与Hibernate

认识ORM ORM, Object-Relationl Mapping&#xff0c;对象关系映射。它的作用是在关系型数据库和对象之间作一个映射。 可以在对象模型和关系型数据库的表之间建立一座桥梁&#xff0c;程序员使用 API 直接操作 JavaBean 对象就可以实现数据的存储、查询、更改和删除等操作。 常…

okhttp异步请求连接阻塞问题排查

表现&#xff1a; 使用okhttp请求外部大模型接口时&#xff0c;当并发在2-5左右&#xff0c;出现请求被阻塞在建立http连接之前&#xff0c;阻塞时间超长&#xff08;>20s&#xff0c;从日志看有160s存在&#xff09;。但是httpconfig的connTimeout时间配置为100s&#xff…

在线客服系统PHP源码免费开源 (搭建教程+全新UI)

安装环境 宝塔面板 php>8.0 mysql5.7 安装搭建 1.建站点 上传程序 2.建数据库 导入数据 3.网站目录/public 伪静态设置 4.修改数据库配置信息 5.修改config.js里的配置信息 6.启动wokerman命令 更详细的搭建教程请下载源码根目录下 安装教程.docx 产品亮点: …

综合评价 | 基于层次-熵权-变异系数-正态云组合法的综合评价模型(Matlab)

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 综合评价 | 基于层次-熵权-变异系数-正态云组合法的综合评价模型&#xff08;Matlab&#xff09; AHP层次分析法是一种解决多目标复杂问题的定性和定量相结合进行计算决策权重的研究方法。该方法将定量分析与定性分析…

生信之家:生物信息学爱好者的温馨交流与科研合作社区

介绍 在生物信息学这个迅速发展的领域&#xff0c;交流与合作显得尤为重要。生信之家正是为此而生的&#xff0c;它是一个专为生物信息学研究人员、学生以及爱好者打造的在线社区。在这里&#xff0c;用户可以分享知识、讨论技术、寻找解决方案&#xff0c;并与来自全球的同行…

页面卡顿检测方案

引言 卡顿现象在早期的开发项目中是一个非常值得注意的问题。随着应用功能的不断增加&#xff0c;代码复杂度也在不断提升&#xff0c;特别是在较为低端的机型上&#xff0c;稍有不慎就可能引发卡顿现象。虽然近年来新发布的设备性能显著提升&#xff0c;但卡顿问题仍然不容忽…

kettle定时发送邮件功能怎样集成到系统中?

kettle定时发送邮件的配置步骤&#xff1f;如何设置kettle发信&#xff1f; kettle不仅能够及时通知相关人员数据处理的状态&#xff0c;还能确保系统的自动化和高效运作。AokSend将详细探讨如何将kettle定时发送邮件功能无缝集成到系统中&#xff0c;让您的数据处理流程更加智…

idea2024设置中文

今天下载idea2024.2版本&#xff0c;发现已经装过中文插件&#xff0c;但是还是不显示中文&#xff0c;找了八天原来还需要设置中文选项 方案一 点击文件 -> 关闭项目 点击自定义 -> 选择语言 方案二 点击文件 -> 设置 外观与行为 -> 系统设置 -> 语言和地区…

C语言迷宫制造

目录 开头程序程序的流程图程序的效果我推荐要制造的迷宫下一篇博客要讲的东西 开头 大家好&#xff0c;我叫这是我58。 程序 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <stdlib.h> #include <string.h> void printmaze(const cha…

android关于binder的简单通信过程

文章目录 简述aidl文件服务端的实现客户端的实现验证过程 简述 主要实现的是两个应用之间跨进程通信的过程&#xff0c;client端调用server端的具体实现&#xff0c;然后server端给client回调数据&#xff0c;详细如下所示 aidl文件 以下的文件需要在服务端与客户端都配置一…

使用极狐GitLab进行K3S集群的维护与控制

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门面向中国程序员和企业提供企业级一体化 DevOps 平台&#xff0c;用来帮助用户实现需求管理、源代码托管、CI/CD、安全合规&#xff0c;而且所有的操作都是在一个平台上进行&#xff0c;省事省心省钱。可以一键安装极狐GitL…

企业图纸防泄密怎么做?10款好用的图纸加密软件推荐

在当今竞争激烈的市场环境中&#xff0c;企业图纸作为核心技术和商业机密的重要组成部分&#xff0c;其安全性直接关系到企业的竞争力和市场地位。因此&#xff0c;采取有效措施防止图纸泄密至关重要。本文将探讨企业图纸防泄密的综合策略&#xff0c;并推荐10款优质的图纸加密…

工业企业能源管理系统

进入新世纪以来&#xff0c;我国的社会主义市场经济持续繁荣&#xff0c;在经济发展的同时&#xff0c;能源耗费量与日俱增&#xff0c;在很大程度上阻碍了我国经济的可持续发展。为了实现节能减排的目标&#xff0c;大量企业进行了产业结构调整和优化升级&#xff0c;促进了资…

OpenAI Embeddings API: How to change the embedding output dimension?

题意&#xff1a;OpenAI Embeddings API&#xff1a;如何更改嵌入输出维度&#xff1f; 问题背景&#xff1a; In the official OpenAI node library Create embeddings if for example using the model text-embedding-ada-002 the embeddings returned is an array of aroun…

PHP宿舍外面点单系统---附源码97171

目 录 摘 要 Abstract 1 绪论 1.1 研究背景 1.2国内外研究现状 1.3论文结构与章节安排 2 宿舍外卖点单系统分析 2.1可行性分析 2.1.1 技术可行性分析 2.1.2经济可行性分析 2.1.3操作可行性分析 2.2 功能需求分析 2.2.1普通用户功能 2.2.2商家用户功能 2.2.3管理…

Qt:玩转QPainter序列六

前言 继续看源码。 正文 剩下的大部分都是画各种图形的函数&#xff0c;它们一般都有多个重载版本&#xff0c;我就不一 一介绍使用了&#xff0c;只挑其中的一部分使用一下。 在 QPainter 类中&#xff0c;这些方法涉及到绘图的各种功能&#xff0c;主要用于设置视图变换、…

chapter08-面向对象编程——(Object类详解)——day09

目录 319-运算符 320-查看Jdk源码 321-子类重写equals 322-equals课堂练习1 323-equals重写练习2 324-equals重写练习3 325-hashCode 326-toString 327-finalize 319-运算符 引用的都是同一个地址&#xff0c;所以返回true 320-查看Jdk源码 equals只能判断引用类型是…

线程同步学习

1、线程同步的定义 线程同步不是一起、相同&#xff0c;而是协调、协同的意思。 1)按预定的先后次序进行运行&#xff0c;如:您说完&#xff0c;我再说;线程A生成数据后交给线程B处理; 2)公共资源同一时刻只能被一个线程使用;共享数据在同一时刻只能被一个线程修改&#xff0c…