文|孙珩珂
上海交通大学
本文1987字 阅览10 分钟
01 优化背景
此前 Dragonfly 的 P2P 下载选用静态限流战略,相关装备项在dfget.yaml
装备文件中:
# 下载服务选项。
download:
# 总下载限速。
totalRateLimit: 1024Mi
# 单个使命下载限速。 perPeerRateLimit: 512Mi
其中perPeerRateLimit
为单个使命设置流量上限,totalRateLimit
为单个节点的一切使命设置流量上限。
静态限流战略的抱负情况是:perPeerRateLimit
设置为20M ,totalRateLimit
设置为 100M ,且该节点现在运转了 5 个或更多的 P2P 下载使命,这种情况下能够确保一切使命总带宽不会超过 100M ,且带宽会被有效运用。
这种限流战略的缺陷是:若perPeerRateLimit
设置为 20M ,totalRateLimit
设置为 100M ,而且当时该节点只运转了一个下载使命,那么该使命的最大下载速度为 20M ,和最大带宽 100M 比较,浪费了 80% 的带宽。
因此,为了最大限度地运用带宽,需求运用动态限流来确保使命数量少时能能充分运用总带宽,而使命数量多时也能公正分配带宽。终究,咱们设计出一套依据上下文进行动态限流的算法,其中上下文指各使命在曩昔一秒内运用的带宽,此外,算法还考虑到了使命数量、使命剩下巨细、使命保底带宽等要素,功能比较本来的静态限流算法有显著提高。
02 相关代码分析
perPeerRateLimit
装备项终究赋值给peerTaskConductor
的pt.limiter
,由peerTaskConductor
的DownloadPiece()
函数里进行限速,pt.waitLimit()
进行实际限流工作,底层调用 Go 自带的限流函数WaitN()
。
TotalRateLimit
装备项则在创立Daemon
时被赋值给pieceManager
的pm.limiter
,在pieceManager
的DownloadPiece()
和processPieceFromSource()
函数中用到的pm.limiter
,而这两个函数都会由peerTaskConductor
调用,也就是说 P2P 下载会先进行总限速,之后再进行每个使命独自限速。
依据以上分析,Dragonfly 进行使命限速的逻辑为,每个peer task(peerTaskConductor
)会有独自的限速perPeerRateLimit
,一起pieceManager
会有TotalRateLimit
的总限速,以此达到单使命独自限流,一起约束一切使命总带宽的作用。
03 优化计划
为了处理此前静态限流算法总带宽运用率欠安的缺陷,需求将其改善为动态限流算法,即总带宽限速仍恒定,但每个使命的独自带宽限速需求依据上下文适度、定时调整,已达到最大化运用总带宽、一起相对公正分配带宽的目的。
在经过数个改版后,终究咱们确认了依据上下文进行限流的 sampling traffic shaper 动态限流算法。详细计划为,每个使命的单使命限流交由TrafficShaper
组建进行统一管理,TrafficShaper
保护当时正在运转的一切使命,而且定时(每秒)更新这些使命的带宽。
详细来说,上下文指每个使命在上一秒运用的带宽、每个使命的剩下巨细、使命数量、使命保底带宽(不能低于pieceSize
)等要素,TrafficShaper
会依据这些上下文公正地、效率最大化地为每个使命分配其下一秒的带宽(详细分配计划详见下一小节),实现动态限流的作用。
04 优化实现
定义TrafficShaper
接口如下:
//TrafficShaperallocatesbandwidthforrunningtasksdynamically
type TrafficShaper interface {
// Start starts the TrafficShaper
Start()
// Stop stops the TrafficShaper
Stop()
// AddTask starts managing the new task
AddTask(taskID string, ptc *peerTaskConductor)
// RemoveTask removes completed task
RemoveTask(taskID string)
// Record records task's used bandwidth
Record(taskID string, n int)
// GetBandwidth gets the total download bandwidth in the past second
GetBandwidth() int64
}
该接口有两种实现,第一种是samplingTrafficShaper
即基于上下文的 traffic shaper ,第二种是plainTrafficShaper
只记载带宽运用情况,除此之外不做任何动态限流工作,用于和samplingTrafficShaper
比照功能提高。
一起,将相关装备项修改为如下内容:
# 下载服务选项。
download:
# 总下载限速。
totalRateLimit: 1024Mi
# 单个使命下载限速。
perPeerRateLimit: 512Mi
# traffic shaper类型,有sampling和plain两种可选 trafficShaperType: sampling
Traffic shaper 的详细运转逻辑为,由peerTaskManager
保护trafficShaper
,在创立peerTaskManager
时,依据装备初始化trafficShaper
,而且调用Start()
函数,发动trafficShaper
,详细来说,新建time.NewTicker
,跨度为 1 秒,也即每秒trafficShaper
都会调用updateLimit()
函数以动态更新一切使命的带宽限流。
updateLimit()
函数会遍历一切运转中的使命,得出每个使命上一秒耗费的带宽以及一切使命耗费的总带宽,随后依据使命上一秒运用的带宽、使命剩下巨细等要素,按比例分配带宽,详细来说首要依据上一秒该使命运用带宽以及该使命剩下巨细的最大值确认下一秒该使命带宽,接着一切使命带宽依据总带宽按比例缩放,得到下一秒的真实带宽;一起需求确保每个使命的带宽不低于该使命的pieceSize
,以免出现继续饥饿状况。
在peerTaskManager
的getOrCreatePeerTaskConductor()
函数中,若新建使命,需求带宽,那么调用AddTask()
更新一切使命的带宽,即按照已有使命的平均使命分配带宽,然后再依据总带宽上限将一切使命的带宽等比例进行缩放;依据平均带宽分配新使命带宽的优势为,避免了已经有一个使命占满了一切带宽,有新使命进来时,带宽会被压缩到很小 **的情况;一起,不是平均分配带宽,而是按需等比例分配,能够确保带宽需求量大的使命仍然带宽最多。在peerTaskManager
的PeerTaskDone()
函数中,使命完成,不再占用带宽,调用RemoveTask()
按比例扩大一切使命的带宽。
最终,peerTaskManager
停止时,调用Stop
函数,停止运转 traffic shaper 。
05 优化成果
测试 traffic shaper 比较原有的静态限流战略在单个使命、多个使命并发、多个使命交织等多种情况下的功能提高,测试成果如下:
注:若不特别注明,单使命限流为4KB/s,总限流为10KB/s
能够看到, traffic shaper 在单使命、多使命不相交、单使命低带宽等情况下比较静态限流战略功能提高显着,为 24%~59% 。在多个使命并发、多个使命交织等情况下和静态限流战略功能相当。综上,试验证明 sampling traffic shaper 能很好地处理使命数量较少时总带宽被大量浪费的情况,一起在使命数量较多以及其他复杂情况时依旧能确保和静态限流算法持平的作用。
PR 链接(已兼并): github.com/dragonflyos…
本周推荐阅览
Dragonfly 基于 P2P 的文件和镜像分发体系
深入 HTTP/3(2)|不那么 Boring 的 SSL
Go 代码城市上云——KusionStack 实践
MOSN 反向通道详解