在之前的文章中,无论是令牌桶、漏桶还是自适应限流方式,总的来说都是服务端的单机限流方式。服务器端限流虽然可以帮助我们抵抗一定的压力,但是拒绝请求毕竟还是有代价的。如果我们原来的流量可以支持1wrps,加上流量限制可以在10wrps的情况下支持1wrps的有效请求,但是流量突然增加10倍到100wrps,那么服务应该挂了。所以,我们的可用性建设不仅仅是服务端的建设,一切都会好的。整个链路上的每个组件都必须做好自己的事情。今天我们就来看看客户端的限流措施。:保险丝。熔断器fuse[^2]如上图[^2]所示,熔断器有三种状态:闭合(closed):闭合状态下,不触发断路器保护,所有请求通过常开(open):当发生错误阈值被触发后,会进入开启状态。此时所有流量都会被节流,半开不跑:处于开状态一段时间后,会尝试释放一个流量,检测当前服务器是否OK。接收到新的流量,如果没有问题,就进入关闭状态,如果有问题,就回到打开状态案例是如何实现的先来看一个用例。首先,我们使用gin来启动一个服务器。这个服务器主要是前200ms的请求返回500,后面的请求返回200。funcserver(){e:=gin.Default()e.GET("/ping",func(ctx*gin.Context){iftime.Since(start)<201*time.Millisecond{ctx.String(http.StatusInternalServerError"pong")return}ctx.String(http.StatusOK,"pong")})e.Run(":8080")}然后配置hystrix,hystrix.ConfigureCommand(commandname,config)hystrix的配置是根据每个命令进行配置,我们在使用A命令的时候也需要传递,下面的配置是当我们的请求数大于等于10且错误率大于等于20%时,保险丝开关将被触发。熔断器打开500ms后进入半开状态。尝试把一些请求访问funcmain(){hystrix.ConfigureCommand("test",hystrix.CommandConfig{//执行命令的超时时间Timeout:10,//最大并发数MaxConcurrentRequests:100,//数量requestsinastatisticalwindowwithin10seconds//最多达到这个请求数后,判断是否打开熔断器RequestVolumeThreshold:10,//熔断器打开后//SleepWindow时间是控制多长时间尝试服务是否可用//单位为毫秒SleepWindow:500,//错误百分比//请求数大于等于RequestVolumeThreshold且错误率达到这个百分比,就会触发熔断ErrorPercentThreshold:20,})}然后我们用一个循环作为客户端代码,其中会请求20次,每次请求消耗100msfuncmain(){goserver()//这里是i的config代码:=0;i<20;i++{_=hystrix.Do("test",func()error{resp,_:=resty.New().R().Get("http://localhost:8080/ping")ifresp.IsError(){returnfmt.Errorf("errcode:%s",resp.Status())}returnnil},func(errerror)error{fmt.Println("fallbackerr:",err)returnerr})time.Sleep(100*time.Millisecond)}}所以我们执行的结果是前两个请求报500,请求10次后进入熔断发起,500ms也就是发送5次请求后,会重新请求服务器image-20210504164650024hystrix-go。核心实现的核心实现方法是AllowRequest,IsOpen判断当前是否处于熔断状态,allowSingleTest是看是否需要过一段时间再试func(circuit*CircuitBreaker)AllowRequest()bool{return!circuit.IsOpen()||circuit.allowSingleTest()}IsOpen首先检查当前是否开启,如果已经开启,直接返回即可,如果没有开启,则去判断请求次数是否满足要求,是否请求错误率过高。如果两者都满足,熔断器f将被打开unc(circuit*CircuitBreaker)IsOpen()bool{circuit.mutex.RLock()o:=circuit.forceOpen||circuit.opencircuit.mutex.RUnlock()ifo{returntrue}ifuint64(circuit.metrics.Requests().Sum(time.Now()))
