首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

Google BBR拥塞控制算法背后的数学解释 | 深度

  • 24-03-05 05:00
  • 2583
  • 12839
blog.csdn.net

640?wx_fmt=jpeg

参加 2019 Python开发者日,请扫码咨询 ↑↑↑


作者 | 赵亚

转载自CSDN网站


杭州待了一段时间,回到深圳过国庆假期,无奈温州皮鞋?厂老板过节要回温州和上海,不在深圳,也就没有见着,非常遗憾!


国庆节当天,就写这个了。


我原本可能会在想国庆节的凌晨到大清早写点什么呢,现在不用想了,就写BBR拥塞控制算法背后的数学吧,这个事情我是在杭州回深圳的路上突然找到了最终结果,我必须把它记录下来。其实在找到这个结果之前,很久很久,我就在思考这个问题了。


背景和动机


2016年大概十月中下旬,同事推荐了一个视频:


Making Linux TCP Fast:

https://www.youtube.com/watch?v=hIl_zXzU3DA


跟随视频后的链接netdevconf的链接,还有一些slides和paper可以看:

https://netdevconf.org/1.2/session.html?yuchung-cheng


我也是那时,或者更早些一点,大概九月份的时候,接触了Google的BBR算法,应该算是国内第一批次的了,随后的一段相当长的时间,我对该算法进行了相对深入的剖析以及思考,从解释Paper,分析源码,设计到优化,反正不知花了多少周五的通宵。


随着后续这个BBR算法的逐渐普及,加入讨论的人也越来越多了,从最初的如何用起来到后面的各路大神的各路神技,可谓热闹非凡,我当时讲,TCP被你们玩坏掉了,BBR也难逃劫难…


和CUBIC背后那精湛简介的数学收敛模型不同,BBR是基于测量的一个算法,它甚至没有一个数学上的解释以证明 为什么这样做就是最好的,以至于,很多人开始盲改,最终的效果和自己的预期,当然是大相径庭。


我一直在思考BBR背后的数学,我总觉得能用数学公式表达的东西才是真正确定的,所以我希望在我长时间思考后,能有一个数学上的解释,来解释BBR为什么是高效率的,为什么只能这样做。


这几天,我感觉成功了一点,所以不敢独享这回报,写此文以分享。


插曲


先来个插曲。


很多人看到BBR不排队的特征后,第一想到的就是不排队甚好,但稍微在缓存队列里堆点数据包也不错,不然怎么能赢了那些不守规矩的流呢? 于是乎就出现了各类所谓的 BBR优化,无一例外地都是把Reno/CUBIC那一套算法的 精髓 照搬到BBR,于是BBR就被玩坏了!


我也干过这种事,后来我跟BBR的作者Neal Cardwell交流,他告诉我 这增加了算法的复杂性,并且破坏了BBR的根本。


我退出了BBR优化队伍,我也不玩了,我潜下心希望能从数学上证明BBR不排队就是最优的,只要排队就不行,一点队列也不行。本文写作前一天,我得了一些结论。记录这些结论并记录我是怎么想的,就是本文的主要内容。


温州皮鞋厂老板促使我开了场,让我第一次用数学来描述BBR算法。


当时是要计算一下为什么BBR在Startup阶段的gain是640?wx_fmt=png,详情看这里吧:


TCP BBR Startup gain计算总结和Startup失速问题:https://blog.csdn.net/dog250/article/details/80780346


现在,正文开始。

正文

先说一下我的思路,由于我自从中学开始就是一个深度的数形结合控,希望能把一切都画在一个坐标系里,然后无非就是找最高点,最低点,找规律这些了,所谓求极值,展示特征无非也就是那些惯用的方法, 求导,积分,数列展开这些,所以对于BBR算法,我依然循着这样的思路。


现在要做的,就是怎么把BBR的行为画在一个坐标系里。如果这一步做到了的话,我相信以我20年的经验顺水推舟事情就一定能成。


其实,BBR最初的slides和paper中,不断展示的图示是下面这个:640?wx_fmt=jpeg
然后,我仔细观察了这两个坐标系,分别是BW(其实是Deliveryrate) vs Inflight以及RTT vs Inflight,都有Infligh。其实,这两张图是用于展示BBR特征的,它只说了What,并没有解释Why,实际上,难道Inflight不应该是计算出来的 吗?如果说我能根据另一个坐标系的曲线进行一系列计算,最后推导出Inflight的值必须是那个值才能达到某种最优的效果,那就解释了Why。


就是这个思路。让我们一步一步去做。

既然我们把Inflight当成了一个结果而不是原因,为了找这个原因,我们合并两个坐标系,消去公共的Inflight,那么我们就可以得到RTT vs BW的曲线了!

为了数学上表述的方便,后面统一一下符号:


‍‍‍BW:w
RTT:
t

临界的Inflight:fc


下图是消去f的方法:


首先给出图像的方程表示:

640?wx_fmt=png

640?wx_fmt=png
很简单,方程组联立就可以消去变量f。

我们可以看到,最终w和t
的取值范围就是那条开向第二象限的折线,现在的问题是,在这条折线中,P(w,t)在哪里是最优的,而此时的Inflight值就是最适合灌进网络中的数据包的数量,换句话说,它反映了Cwnd应该取什么值。

问题转化为了 如何度量所谓的最优 。

一般工业界和经济学领域都会采用 ***产出/成本***的比值来衡量一个系统是不是优秀,换句话说就是 最优的系统是用最少的代价换取最高的收益。对于我们目前的模型而言,用语言来表达,即最少的时间传输最多的数据包。

因此,很显然,我给出列的比值,最为衡量系统是否最优的度量:

640?wx_fmt=png

问题变成了 P(w,t)取在哪里,上述的比值E最大?

肉眼看得出来,w取最大值640?wx_fmt=png

t取最小值640?wx_fmt=jpeg即可,即取点640?wx_fmt=png

所谓的最优的E值就是1,而此时,Inflight的值就是所谓的

640?wx_fmt=png

这确实说明了Inflight取值为临界的恰好要使得队列增加的fc
时,系统的效能真的是最优的。

640?wx_fmt=png

如果你将P(w,t)在允许的定义域值域稍微偏离Pe点,你会发现E值均会减少,这反映在TCP数据传输上,就是:

  1. 要么带宽没有用满,没有达到640?wx_fmt=png;

  2. 要么带宽超额了,数据排队了,数据到达的太快,因此延迟增加了。


无论发生了上述的什么情况,显然都不是什么好事。

这算完成了任务吗?证明了BBR是最优的。看起来算是吧…

如果这样就算是一个BBR背后完备的数学模型,这篇文章一个多月前就能写出了。。。

我们发现,上面的推导基于一个明显的事实,即用肉眼看,之所以可以用肉眼观测出最值,那是因为Yuchung Cheng和Neal Cardwell在描述BBR算法时,简化了模型,基于简化的模型,才给出了那两幅BW vs Inflight和RTT vs Inflight的坐标图示,在这两个图示中,所有的线均为直线。

所谓的简化模型,即假设数据包是间隔均匀匀速到达的,数据包也是匀速经过网络节点设备的。但是在实际上,这并不是事实,真实的情况画在坐标系里并不是直线,而是曲线,类似下面的样子:
640?wx_fmt=png
此时如果我们依然要求解640?wx_fmt=png的极大值,很显然要求这条曲线函数t针对w的导数,这下麻烦有点大!我怎么能知道带宽w和传输时间t之间的关系呢?显然,必须有二者之间的关系,才能求导啊!

怎么办?这件事让我搞了一个多月时间!

我能理解数据包的到达其实是柏松到达,被服务时间符合指数分布特征,但是我并没有就着这个思路思考下去(不然我一定能想到排队论),而是希望能先有一个通用的解。

起初,我是反着求解,我假设传输时间t已经可以用带宽w来表示,即
t=f(w)

然后,就会有:

640?wx_fmt=png

进一步按照求导法则针对w求导,推导如下:

640?wx_fmt=png

若求E的极大值,那么它一定出现在曲线的拐点处,其导数一定为0,因此:

640?wx_fmt=png

这便是最终的关系式,虽然我不知道t如何用w来表示,但这个关系是存在的,这是个普遍适用的关系。

我为得到这个关系式兴奋了一个周末,非常有成就感,虽然它现在看起来很简单,但在当时,这个想法真的显得来得太晚!

我是从初中开始就喜欢摆置坐标系的,一直到大学,几乎任何数学题,我都能采用数形结合的方案一题多解,某种程度上,我老婆就是当时我如此炫技追到的…最近几年,我用同样的方式设计了iptables的优化方案,DxR的优化方案…不多的几次失败包括Bloom Filter的形象化展示。

所以面对上述的表达式子,依然可以任意把玩。式子两边同时除以w:

640?wx_fmt=png

看看左边右边分别是什么?

  1. 左边:f(w)上任意一点到原点的直线的斜率;

  2. 右边:曲线f(w)上任意一点切线的斜率。

两个斜率一致的时候,w取值计算的E是最优的,即曲线f(w)上任意一点P和原点之间的直线与该点的切线共线! 的时候,该点P的w和t的取值为最优!

形象地看,我们可以通过 操作 来获取最优的点,即:
从经过原点的t=0这条直线开始,任其绕着原点逆时针旋转,第一次切到曲线f(w)的那个点,就是最优点。

很简单,是吧。

现在已经定性地把问题解决了,无论曲线t=f(w)长什么样子,我们可以从上述的操作步骤中获取最优的P点,进而求出Inflight值,最终确定Cwnd。

然而,这并不能定量地分析,如果要定量地分析,则必须要有t=f(w)的表达式。

这个表达式何来?我花了好长时间也没有思路。

再仔细看看带宽和RTT之间的关系,在固定的带宽下,RTT受到Inflight的影响,然而Inflight正是我们要求解的,这貌似进入了一个循环,消除Inflight变量并无法确定二者的关系,这便是陷入了两难!

…

我略过了好多文字,这些文字描述了大概将近一个月的见闻,然后我就想到了 排队论, 排队论, 排队论, 排队论, 排队论!

排队论基于概率理论给出精确的逗留时长和到达率以及服务率之间的关系。

排队论是一个复杂的理论,能写几大部头都写不完,我自己也只是略懂一二,并不是专门研究这个的,所以本着解决手头当前问题的广度优先原则,下面我依据排队论的一些结论性的公式进行推导,关于这个公式的推导,我会在本文后面给出一个附录。

我们依然根据最简单的情况建立模型,即经典的M/M/1排队模型下的场景,在该场景下,先设以下的变量:

  • 到达率:λ

  • 服务率:μ

  • 系统负荷水平:ρ

  • 用户停留时间:Ws

  • Ws

然后,有一些用到的定义以及公式,我们根据这些公式来进行后续的关于BBR最优化的证明。需要声明的是,M/M/1排队模型是一个简化的模型,并不代表现网中运行BBR算法的TCP传输发包特征就一定符合这个模型,但这是一个很好的开始。

这些结论性的定义和公式如下:

  • 系统负荷水平

640?wx_fmt=png


  • 用户停留时间(包括排队时间和被服务的时间)


640?wx_fmt=png


  • 当前系统中用户数(包括排队的和正在被服务的)


640?wx_fmt=png

  • 排队等待时间

640?wx_fmt=png

  • ...

有了这些,便可以建立BBR的模型了。

在一个带宽固定的网络中,我们假设带宽为C(基于这种固定带宽的假设,实际上这是M/D/1排队模型,但是推导过程并无伤大雅!),而我们发送的带宽可以理解为 到达率, 即λ,此时我们可以把Ws等效于RTT,因此RTT和带宽的关系则为:

640?wx_fmt=png

根据我们上面的那个最优解公式:

640?wx_fmt=png

带入则有:

640?wx_fmt=png

而这里的640?wx_fmt=png则可以对f(λ)简单求导:

640?wx_fmt=png

代入则有了结论:

640?wx_fmt=png

最终,我们发现了最优点P,即:

λ(C−λ

640?wx_fmt=png

这个结论有什么用呢?它意味着什么呢?

我们再来看M/M/1模型中的另外一个公式,即计算当前系统的用户数:

640?wx_fmt=png

这意味着系统中只有一个用户,这意味着没有排队!!这意味着最优的情况,即E最大的时候,系统是没有队列堆积的!这个时候,我们来计算一下BDP:

640?wx_fmt=png

正解于天下!这也是我在这方面工作的一个里程碑,现在总结一下就是:

在M/M/1排队模型的假设下,BBR拥塞控制算法是效能E最优的。

然而,M/M/1模型只是一个开始,事实上,基于端到端的TCP协议,在链路上会经过非常多的中间节点,即,至少起码的,我们也要在M/M/k排队模型上再次证明上述结论的正确性才行。

而这是简单的。我们假设一个端到端的链路上有k个中间节点,那么每一个节点都遵循M/M/1排队模型的法则,综合起来就是M/M/k了,我们假设这些节点的处理能力并不相同,并假设其中有一个能力最弱的节点m,其处理能力μm,在排队论模型中,只要有排队现象,就会增加时延而降低效能E,所以为了不排队每一个节点的到达率均要满足:640?wx_fmt=png,所以最终的系统中的用户数是要小于k的,这就是说,系统的吞吐是由最弱的节点决定的这个典型的漏桶理论。

我们来看BBR的名称,Bottleneck Bandwidth and RTT,其中以 Bottleneck Bandwidth 为M/M/k排队模型的依据保证了RTT不会因为排队引入的延迟而增加。

换句话说,BBR拥塞控制算法告诉你,别发太多包,超过Bottleneck Bandwidth的限额,你多发了也过不去,还平添时延,因为已经偏离了最优的操作点!

算法是OK的,代表了一种正确的方法,退一步,至少可以说是一种引向正确方法的趋势。然而在实现上,它的根基在于测量的准确性。算法实现的具体实施过程中,TCP协议固有的不可测量性是最大的掣肘,而这是TCP的固有缺陷所导致,永远也无法被解决!

那么QUIC的实现呢?我准备花大力气测测看。

在很早之前介绍BBR算法的文章中,我提到了带宽和RTT互为正交 的概念:
Google’s BBR拥塞控制算法模型解析:

https://blog.csdn.net/dog250/article/details/52895080

现在我们可以基于本文上述的关于最优化的结论再来理解这个正交。

先从固定的D/D/1排队模型看,我们再看BW vs RTT图:
640?wx_fmt=png


我们可以看到最优点Pe处,带宽和RTT的乘积正好是矩形的面积,而它就是BDP,无论你在折线上怎么移动P点,你均无法获得更大的矩形面积,往左移动,面积减少,这意味着不够,效能当然低,往上移动,面积不再增加,这说明多发数据包也没用,并不能增加有效BDP。

好完美的既视感!貌似打消了任何企图通过多发包来提升性能的念头,然而突然发现这只是理想D/D/1排队模型下的结论。

稍微真实点的M/M/1排队模型下,是这样子的:640?wx_fmt=png
你会发现,在最优化点附近,还是可以通过比较小的RTT增加的代价,获得更多有效BDP的。这似乎给了人们稍微增加一点Cwnd以理由和意义。黑暗压下来的时候,总是留下一丝的光亮,没有办法。

通过相对严格的数学推导,我们发现BBR算法在BW vs RTT坐标系的曲线上的操作点确实是最优的,然而我们又从同样一张图的M/M/1表述中发现了一点可以利用的Trick…

不管怎么说,不想排队是为了自己,然而制造排队则是毁了大家,你能控制的,仅仅是自己不排队,这是个博弈,答案却非常简单!

如此简单的数学推导,展示了事实,那么,为什么路由器和交换机还要设计队列缓存呢?

从商业的角度,如今的存储设备越来越便宜,更多的缓存可以换取更多的 不丢包指标,极低的代价换一个噱头。

从工程的角度来看,队列不仅仅是为了缓存多出来的数据包,更多的是一种主动式的管理设施,比如流量整形,按照不同的产品进行限速,优先级管理等等。

最后,从数学上看,假设数据包到达行为是泊松到达,其到达率期望是λ,那么为了获得最佳的效能,其服务率必然是2λ,但是这些都是统计分布,到达率的期望只是一个均值,在可计算的概率下,到达率完全可以达到3λ,4λ…为了吸收这些统计峰值,则必须设计队列缓存。

现在的问题是转发节点设备上的缓存要设计多大?有了BW vs RTT这张图,这是可以计算出来的!

我们依然假设这个模型是简单的M/M/1排队模型。我将D/D/1模型和M/M/1模型放在一张图里解释:

640?wx_fmt=png
虚线和橘黄色真实的实线围成部分的面积就是节点需要的缓存的大小(事实上要稍微大一点,毕竟这里的RTT也是均值),因为它在泊松到达的正常波动范围内。

此外根据本文上面的推论,当λ=μ/2的时候,系统的效能E最大,在上图中,这也是可以解释的:
640?wx_fmt=png

其中蓝线围成的区域面积为最佳的BDP,这个是和D/D/1模型不同的。

由于泊松到达的统计效应造成了不可避免的排队和空转,且二者不能抵消,因此必须设计缓存队列,曲线相对t=1往上下凸了,因此从原点出发相切于t=f(λ)的切线肯定在λ=μ的左边,这意味着相对于D/D/1模型,M/M/1模型在其它条件都相同的情况下,损失了一点Inflight!这是统计的不确定性带来的不可消除的代价!

所以说,在非主动管理的意义上,队列的作用是什么?队列的作用是,平滑泊松到达的统计特性引发的突发。突发总是存在的,为了能容忍这些突发, 而不至于丢包。

从示意图上看,即便是BBR,也是无法100%避免排队的,这是泊松到达的统计特征决定的,即便单一服务节点的服务率μ是到达率λ的1000倍(而不是计算出来的最优值2倍),也会有小概率的突发会造成轻微排队。故而,队列算是一个基础设施,必不可少,但你要明白,队列是用来干嘛的!

然而,还有一个话题,与统计突发概率相等的对称的现象就是系统空转,而空转是无法弥补的,没有任务到达,系统确实什么也做不了。因此,引入主动队列管理是必要的,上一时间周期来不及处理的任务通过缓存队列推迟到下一个时间周期,填补空转期,这方面,我觉得带突发的令牌桶这个设计,简单又直接。

宏观背景下的BBR

1980年代的拥塞崩溃导致了1980年代的拥塞控制机制的出炉,某种意义上这属于见招拆招的策略,针对1980年代的拥塞,提出了1980年代的拥塞控制算法,即ss,ssthresh,congestion avoid这些。

说实话,这些机制完美适应了1980年代的网络特征,低带宽,浅缓存队列,美好持续到了2000年代。

随后互联网大爆发,多媒体应用特别是图片,音视频类的应用促使带宽必须猛增,而摩尔定律促使存储设施趋于廉价而路由器队列缓存猛增,这便是BBR诞生的背景。换句话说,1980年代的CC已经不适用了,2010年代需要另外的一次见招拆招。

如果说上一次1980年代的CC旨在收敛,那么这一次BBR则旨在效能E最大化,这里的E就是本文上面大量篇幅描述的那个E,至少我个人是这么认为的,这也和BBR的初衷提高带宽利用率相一致!

本文该结束了,国庆节假期第一天我实在是想出去逛逛,但是又能去哪儿呢?

刚到杭州的时候,有次出去逛,和技术网友聊天,说到了数据库和网络。我是不懂数据库的,一点都不懂,但是我知道,数据库和网络是两个同样极为重要的领域,我做了一个比喻。

网络是道路的话,数据库就是家,道路是要快速通过,越快越好,不要逗留,而家则待的越久越幸福。可是如今呢?城市道路是越来越堵,基本成了条状停车场,而人们在家的时间则是越来越短,到底发生了什么?这是不是和网络节点转发设备的队列缓存越来越大相一致呢?

然而,这是错误的!

我就不说Kafka了,那真是一个优秀且正确的设计。本文真的要结束了,附录中,我会给出关于M/M/1排队模型中一些结论的简单数学推导。


附录:关于排队论一些典型结论的推导

排队理论是一个很成熟的理论,介绍它的资料可谓汗牛充栋,如果想简单了解一下概念,那么Wiki总是一个好地方:

https://zh.wikipedia.org/zh-hans/等候理論

强调一句, 排队理论是现代分组交换网络的根基,记住,它是根基,如果没有这个理论作为支撑,基于 统计复用 构建的分组交换网的可行性都无法得到验证!!

即便它如此重要,也并不能阻碍它的不为人知。

本附录,我仅仅给出一个基于简单通用的M/M/1排队模型而构建的系统中,稳定状态下用户数量的推导,旨在开个场。

我们假设一个单一的服务节点,它的队列容量是无限的,那么它可能存在于下面状态中的一种:

  • 没有用户排队

  • 有1个用户排队

  • 有2个用户排队

  • 有3个用户排队

  • …

状态n表示有n个用户在系统中。

上述的状态根据到到达率λ和服务率μ来相互切换,比如当前系统排队n个用户,那么在λ的时间间隔到达一个用户,系统随即切换到n+1个用户排队的状态,画成一个链,即:

640?wx_fmt=png

这个系统如果是可用的,那么它就一定会进入一种所谓的稳定状态,稳定状态的特征是,进入单一状态n的概率等于离开该状态的概率,即:

640?wx_fmt=png

这就是平衡方程:

640?wx_fmt=png

上面的式子是核心中的核心,因为如果系统未达到一个稳定状态,那么系统将可能朝着两个方向发生雪崩:

  1. 朝着左边雪崩:用户和任务将会被清空,资源浪费;

  2. 朝着右边雪崩:系统完全过载而崩溃,系统不再可用。


所以一个可用的系统,它是平衡的,我们基于上述的平衡方程做递推,可得解:

640?wx_fmt=png

其中640?wx_fmt=png,因此上面的解等价于:

640?wx_fmt=png

现在让我们计算系统中的用户数,包括排队的用户数和正在被服务的用户数。

记系统用户总量为N,一个显然的道理就是,系统中的用户总量等于各个状态与之概率的求和:640?wx_fmt=jpeg动用微积分吧640?wx_fmt=png

这就是结论!

其实,根据上述的状态转换平衡方程,以及泊松到达,马尔可夫模型,所有的排队论结论性的公式都可以推导出来,也是非常简单的,除了概率论之外,几乎没有任何前置知识,然而,它的结果却可以解释那么多好玩的事情,真的是非常棒。

关于排队论的应用之广泛,其实怎么说都不为过,我在今年6月份写过一篇关于排队spinlock优化的文章:

从CPU cache一致性的角度看Linux spinlock的不可伸缩性(non-scalable) :https://blog.csdn.net/dog250/article/details/80589442

里面提到的马尔可夫链式模型就是排队论的一个典型,非常好玩。此外,我们日常生活中的各种等待,开车时的各种拥堵,以及像某大型互联网公司的晋级晋等,都可以用排队理论进行建模和分析解释。

浙江温州皮鞋湿。
湿鞋皮州温江浙。

作者简介:赵亚,软件工程师、网络工程师。长期专注于网络底层技术,擅长网络协议的设计和优化,在Linux网络协议栈优化领域有丰富的经验,平时喜欢和同道中人互通有无。目前涉及的主要领域是IPv6技术和TCP传输优化。业余时间喜欢读书和思考,尤其对历史感兴趣。

原文链接:

https://blog.csdn.net/dog250/article/details/82892267

(本文为 AI科技大本营转载文章,转载请联系原作者)

◆

征稿

◆

640?wx_fmt=png

推荐阅读:

  • GitHub超全机器学习工程师成长路线图,开源两日收获3700+Star!

  • 吴恩达最新斯坦福课程《深度学习》全部视频已送达,请签收!

  • 用Python玩人脸合成,你也能有一张明星脸(附代码)

  • 微软开源Python静态类型检查器:Pyright

  • 雷军回应输 10 亿背后真相:世界正在青睐不务正业的人

  • 微服务与单体架构:IT变革中企业及个体如何自处?

  • “别傻了,你不需要区块链!”

  • 腾讯服务器崩溃!

  • 曝光!月薪5万的程序员面试题:73%人都做错,你敢试吗?


640?wx_fmt=png

❤点击“阅读原文”,查看历史精彩文章。

‍‍

注:本文转载自blog.csdn.net的AI科技大本营的文章"https://blog.csdn.net/dQCFKyQDXYm3F8rB0/article/details/88839202"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top