Golang中间隔速度错误问题排查
Golang中间隔速度错误问题排查 我的系统被设计为能够在任何定义的毫秒数下运行查询。如果设置为100毫秒,它能正常运行(每秒10次),同样在200毫秒时也能正常运行(每秒5次)。然而,如果我想要以170毫秒为例运行,它会默认为200毫秒。我推测这可能是因为它只处理整数,因此在下面的计算中忽略了任何小数部分。关于如何解决这个问题,有什么建议吗?
config.Dac.RequestRateMs = 200
type DacConfig struct {
MaxConnections int
RequestRateMs int // 以毫秒为单位
HostPort string
}
type DacClient struct {
Connection net.Conn
Config DacConfig
RateLimiter ratelimit.Limiter
}
func NewDacClient(config Config) DacClient {
return DacClient{
nil,
config.Dac,
ratelimit.New(1000/config.Dac.RequestRateMs, ratelimit.WithoutSlack),
}
}
更多关于Golang中间隔速度错误问题排查的实战教程也可以访问 https://www.itying.com/category-94-b0.html
如果你不计算速率而是直接在代码中写入,会发生什么?就像这样:
ratelimit.New(167, ratelimit.WithoutSlack),
使用那个例子,查询速度大约是5毫秒,而不是167毫秒。如果我将167增加到167000(以防它难以确定毫秒),结果相同,运行得太快了!
抱歉,为了澄清一下,我查询的是150毫秒的间隔,也就是每秒6.66次,它无法处理这个频率。使用整数间隔似乎没问题,比如100毫秒间隔、200毫秒间隔等。
尝试过但很遗憾没有成功,它只能让我在100到200毫秒之间发送请求,但并非完全按照定义的时间,例如设置150毫秒时,实际仍然是每167毫秒发送一次。
系统通过定期发送来运行,而不是在一个设定的时间内全部发送然后休眠,所以应该每150毫秒发送一次。
以下是相关的库等
import (
"bufio"
"fmt"
"net"
"strings"
"time"
"go.uber.org/zap"
"go.uber.org/ratelimit"
)
不幸的是,我的开发人员似乎使用了大量第三方应用程序,这在每个环节都带来了问题。
该函数的接口接收 int 类型参数,而 int 没有小数部分。你可以每秒处理6个事件或7个事件,但不能处理6.66个。
让我再重复一遍,速率限制器的作用不是指定间隔,而是指定每个时间单位内的最大事件数量。或者每个时间单位内的最大传输字节数,或者每个时间单位内任何事物的最大数量。
速率限制器的本质并不要求将事件均匀分布在时间单位内,它也可以突发处理然后暂停,并且仍然能正确完成其工作。
我的假设是 go.uber.org/ratelimit,因为它符合接口要求。
它是一个漏桶算法…
与任何速率限制器一样,它并非设计用于每隔 X 秒运行某些操作,而是为了确保某些操作每秒运行的次数不超过 Y 次。
如果某人想要每隔 X 秒运行一个命令,应该在一个 goroutine 中使用无限循环,并从 time.Ticker 接收信号,至少这是我的看法。
我猜这可能是因为它只处理整数,所以会忽略下面计算中的任何小数部分。
确实如此。请使用浮点数:
package main
import (
"fmt"
)
func main() {
rate1 := 1000 / 170
fmt.Println(rate1)
rate2 := 1000.0 / 170.0
fmt.Println(rate2)
}
输出:
5
5.882352941176471
谢谢,我试过了,但使用你的例子时,它仍然以200毫秒的间隔进行查询。如果我把它改成150.0,它就以167毫秒运行了!!!
config.Dac.RequestRateMs = 150.0
type DacConfig struct {
MaxConnections int
RequestRateMs int // 单位:毫秒
HostPort string
}
type DacClient struct {
Connection net.Conn
Config DacConfig
RateLimiter ratelimit.Limiter
}
func NewDacClient(config Config) DacClient {
return DacClient{
nil,
config.Dac,
ratelimit.New(1000.0/config.Dac.RequestRateMs, ratelimit.WithoutSlack),
}
}
不幸的是,我的开发人员似乎使用了大量第三方应用程序,这在每个环节都带来了问题。
使用第三方库本身没有问题。特别是对于 Uber 的那些库,我有很好的使用体验。我真的很喜欢用 zap 进行日志记录,并且我认为其他库也经过了实战检验。
不过,需要正确地使用它们。
另外,当配置为每秒 167 个事件时,5 毫秒的间隔看起来大致是正确的,并且处于舍入误差范围内,特别是因为 ratelimit.New() 的第一个参数是 int 类型。可能还有一些事件之间的间隔是 6 毫秒。这是为了调整令牌数量,以便能够达到每秒 167 次的目标。
从速率限制器的角度来看,甚至可以在最初的 150 毫秒内完成所有这 167 个请求,然后暂停 850 毫秒(尽管,这是那些实现得非常糟糕的速率限制器才会有的行为)。
所以,如果你想要一个基于间隔的、接近精确速率的方案,请使用 time.Ticker,正如上面已经提到的那样。
问题出在整数除法上。在Go语言中,当两个整数相除时,结果会被截断为整数,小数部分会被丢弃。在你的代码中,1000/config.Dac.RequestRateMs 当 RequestRateMs 为170时,计算结果是 1000/170 = 5.882...,但整数除法会得到 5,这导致速率限制器被设置为每秒5个请求(即200毫秒间隔),而不是预期的每秒约5.88个请求(170毫秒间隔)。
要解决这个问题,你需要确保使用浮点数进行计算,或者调整速率限制器的创建方式。以下是两种解决方案:
方案1:使用浮点数创建速率限制器
修改 NewDacClient 函数,将计算转换为浮点数,然后取整到最接近的整数(如果需要整数)。但注意 ratelimit.New 的第一个参数是 int 类型,所以最终需要转换回整数。你可以使用 math.Round 来四舍五入。
import "math"
func NewDacClient(config Config) DacClient {
rate := math.Round(1000.0 / float64(config.Dac.RequestRateMs))
return DacClient{
nil,
config.Dac,
ratelimit.New(int(rate), ratelimit.WithoutSlack),
}
}
方案2:调整速率限制器以支持更精确的间隔
如果速率限制器库支持,考虑使用一个可以处理非整数速率的实现。但根据你使用的 ratelimit 库(看起来是 github.com/juju/ratelimit 或类似库),它可能只接受整数速率。在这种情况下,方案1是直接的方法。
示例代码修正
假设你使用的是 golang.org/x/time/rate,它支持浮点数速率。但根据你的代码,你似乎在使用自定义的 ratelimit 包。如果无法更改库,这里提供一个使用 golang.org/x/time/rate 的示例,因为它更灵活且标准。
首先,导入 golang.org/x/time/rate:
import "golang.org/x/time/rate"
然后修改 DacClient 结构体和构造函数:
type DacClient struct {
Connection net.Conn
Config DacConfig
RateLimiter *rate.Limiter
}
func NewDacClient(config Config) DacClient {
// 计算每秒的请求数:1000毫秒 / RequestRateMs
requestsPerSecond := float64(1000) / float64(config.Dac.RequestRateMs)
limiter := rate.NewLimiter(rate.Limit(requestsPerSecond), 1) // burst 设置为1
return DacClient{
nil,
config.Dac,
limiter,
}
}
这样,当 RequestRateMs 为170时,requestsPerSecond 约为5.882,速率限制器会以大约170毫秒的间隔允许请求。使用 golang.org/x/time/rate 可以更精确地控制速率,因为它基于令牌桶算法,支持浮点数速率。
总结
问题根源是整数除法导致精度丢失。通过使用浮点数计算并选择合适的速率限制器库,可以解决这个问题。如果坚持使用当前的 ratelimit 库,方案1是可行的;如果允许切换库,方案2使用 golang.org/x/time/rate 会更精确和标准。根据你的系统需求选择合适的方法。

