tc网络流控详解及常用队列

  • TC是什么?
  • tc的组成有哪些?
  • 队列、类别、过滤器
  • 可分类别队列
  • 简单的无类别队列(pfifo_fast)
  • 简单的无类队列(TBF)
  • 无类别随机公平队列(SFQ)
  • 可分队列中的数据流向
  • PRIO队列
  • 著名的CBQ队列
  • 常用简单的过滤命令
  • HTB(Hierarchical Token Bucket, 分层的令牌桶)
  • 一个复杂的实例
  • IMQ(Intermediate queueing device,中介队列设备)

TC是什么?

Linux内核内置了一个Traffic Control框架,可以实现流量限速,流量整形,策略应用(丢弃,NAT等)。和 TC框架比较相似的是Netfilter框架,但是二者却又有很大的不同。
TC是linux自带的模块,在内核2.4.18以上。

Netfilter:网络协议栈上内核路径上过滤数据包,检测要得出一个结果动作:
丢弃,接受,等等。Netfilter框架关注的是只需针对一个数据包得出一个结果即可。

TC:是对数据包或者数据流提供一种服务,比如限速,整形,等等。TC框架关注的是如何执行而不是仅仅想要得到一个要执行的动作。

总结:Netfilter框架关键做什么,而TC框架关注怎么做。

tc的组成有哪些?

队列、类别、过滤器

队列(qdisc):
    流量控制的一个基本概念是队列(Qdisc),每个网卡都与一个队列(Qdisc)相联系,每当内核需要将报文分组从网卡发送出去,都会首先将该报文分组添加到该网卡所配置的队列中,由该队列决定报文分组的发送顺序。因此可以说,所有的流量控制都发生在队列中。
    有些队列的功能是非常简单的,它们对报文分组实行先来先走的策略。有些队列则功能复杂,会将不同的报文分组进行排队、分类,并根据不同的原则,以不同的顺序发送队列中的报文分组。为实现这样的功能,这些复杂的队列需要使用不同的过滤器(Filter)来把报文分组分成不同的类别(Class)。这里把这些复杂的队列称为可分类(Classiful)的队列。因此,类别(Class)和过滤器(Filter)也是流量控制的另外两个重要的基本概念。

可分类别队列

实例:

简单的无类别队列(pfifo_fast)

    这个队列有三个频道(0.1.2),FIFO应用于每一个频道,并且:如果0频道有数据发送就不会处理1频道的数据,1和2频道关系也是这样。

参数
priomap:数据包优先权。根据一个数据包中的TOS字节进行分类。
tos看上去是这样子的。
| 0 1 2 | 3 4 5 6 | 7
| 优先权 | TOS | MBZ
TOS字段4个bit定义如下:
1000 8 最小延时(md)
0100 4 最大throughput(mt)
0010 2 最大可靠性(mr)
0001 1 最小成本(mmc)
0000 0 正常服务
因为在这个4bit之后还有一位MBZ,所以tos的字段的实际值是上述的2倍

流控中默认的pfifo权限是这样的1,2,2,2,1,2,0,0,1,1,1,1,1,1,1,1 对应下表。

上述表意思:例如15表示一个数据包要求最小成本、最大可靠性、最大吞吐量、和最小延时的发送出去。

下表来自RFC1349,告诉你应用程序是如何设置他们的QOS:

使用iptables修改程序的QOS
    例如修改telnet的QOS为md:iptables -A PREROUTING -t mangle -p tcp --dport telnet -j TOS --set-tos Minimize-Delay

简单的无类队列(TBF)

    令牌桶过滤器(TBF,Token Bucket Filter)是一个简单的队列规则:只允许以不超过事先设定的速率到来的数据包通过,但可能允许短暂突发流量超过设定值。
    TBF很精确,对于网络和处理器的影响都很小。所以如果您想对一个网卡限速,它应该是最好选择!
    TBF的实现在于一个缓冲器(桶),该缓冲器(桶)不断地被一些叫做”令牌”的虚拟数据以特定速率(token rate)填充着。桶最重要的参数就是它的大小,也就是它能够存储令牌的数量。
这个算法关联到两个流上——令牌流和数据流,得出3种情景:
    1.数据流以等于令牌流的速率到达TBF。这种情况下,每个到来的数据包都能对应一个令牌,然后无延迟地通过队列。
    2.数据流以小于令牌流的速度到达TBF。通过队列的数据包只消耗了一部分令牌,剩下的令牌会在桶里积累下来,直到桶被装满。剩下的令牌可以在需要以高于令牌流速率发送数据流的时候消耗掉,这种情况下会发生突发传输。
    3.数据流以大于令牌流的速率到达TBF。这意味着桶里的令牌很快就会被耗尽。导致TBF中断一段时间,称为”越限”(overlimit)。如果数据包持续到来,将发生丢包。
配置参数:
limit latency
    limit确定最多有多少数据(字节数)在队列中等待可用令牌
    latency确定一个包在TBF中等待传输的最长时间,计算桶大小,速率,峰值速率。
burst buffer maxburst
    桶的大小,以字节计,指定最多可以有多少令牌能够即刻被用,
10兆bit/s的速率至少需要10k字节的缓冲区才可以达到期望的速率。
rate
    速度操纵杆,类似limits

例:
    tc qdisc add dev DEV root tbf rate 220kbit latency 50ms burst 1540

无类别随机公平队列(SFQ)

    SFQ(Stochastic Fairness Queueing,随机公平队列)简单实现公平队列算法。它的精确性不如其它的方法,但是它在实现高度公平的同时,需要的计算量却很少。
    SFQ的关键词是”会话”或“流”,主要针对一个TCP会话或者UDP流。流量被分成相当多数量的FIFO队列中,每个队列对应一个会话。数据按照简单轮转的方式发送,每个会话都按顺序得到发送机会。

参数
    perturb: 多少秒后重新配置一次散列算法。如果取消设置,散列算法将永远不会重新配置(不建议这样做)。10秒应该是一个合适的值。
    quantum: 一个流至少要传输多少字节后才切换到下一个队列。缺省设置为一个最大包的长度(MTU的大小)。不要设置这个数值低于MTU!

例:
     tc qdisc add dev ppp0 root sfq perturb 10

可分队列中的数据流向

    一旦有数据包进入一个分类队列中,它就得被送到一个类中,也就是需要分类,对数据包进行分类的工具是过滤器,分类器是从队列内部的调用。
    过滤器一般会返回一个决定,队列规定就根据这样的一个决定将数据包送入相应的类中进行排队。每一个子类可以再次使用他们的过滤器进行进一步分类,直到不需要进行分类时候,数据包才进入包含队列规定中排队。

队列规定家族:根、句柄、兄弟、父辈
    每一个网卡都有一个根队列规定模式是pfifo_fast,每一个队列都指定一个句柄,以便以后的配置语句能够引用队列规定。
    队列的规定的句柄有两个部分:一个主号码一个和次号码一个,一般情况下习惯吧根队列规定称为“1:”等价于“1:0”队列规定的次号码永远是0
    类的主号码必须与他们父类的主号码一致。

一个典型的分层关系:

数据包可能按照1:–>1:1–>12:–>12:2
数据包现在应该处于12:2下属的某一个队列中,上述图中树的每一个节点都附带一个过滤器,用来选择下一步进入那个分支,这样也比较直观,也可以允许这样子,1:–>12:2(也就是说根所带的过滤器要求把数据包直接交给12:2)

PRIO队列

    PRIO队列规定并不进行整形,仅仅根据你配置的过滤器把流量进一步细分。也可以认为PRIO队列是pfifo_fast的一种衍生物,区别在于每一个频道是一个单独的类。当一个数据包进入PRIO队列中,会根据过滤器选择一个类,缺省的是3种类别。当一个数据包需要出队列时候,首先处理:1类,当没有小号类中数据包发送时候才可以发送大号类的数据包

PRIO参数
    bands创建的频道的个数,每一个频道就是一个类。
    priomap如果不提供任何过滤器,PRIO队列会规定参考TC_PRIO的优先级来决定如何给数据包入队。

实例:


大量数据使用30:,交互数据使用20:或者10:
tc qdisc add dev DEV root handle 1:prio
#这个命令创建了3个子类: 1:1,1:2,1:3
tc qdisc add dev DEV parent 1:1 handle 10: sfq
tc qdisc add dev DEV parent 1:2 handle 20: tbf rate 20kbit buffer 1600 limit 3000
tc qdisc add dev DEV parent 1:3 handle 30: sfq

著名的CBQ队列

    CBQ是最复杂、最琐碎、最难以理解、最刁钻的队列规则。这并不是因为其作者设计不称职,而是因为CBQ算法本身的不精确,而且与Linux的内在机制不协调造成的。
    CBQ就采用了它一个近似值——来自硬件层的两个传输请求之间的毫秒数来代替它。这个参数可以近似地表现这个链路的繁忙程度。这样也不能得到正确的理论,比如某些网卡,总线设置,非物理网卡都会影响闲置时间。
    有效闲置时间的测量使用EWMA测出一个结果,链路的真实值减去测量值,得出的一个结果是“avgidle”最佳的链路是0,负数表示链路阻塞验证,应停止发包成为overlimit,相反,一个闲置的链路应该有很大的avgidle值,如果时间过长,会造成链路允许非常大的带宽通过,我们使用maxidle来限制avgidle的值不能太大。
参数:
    bandwidth:网卡的物理带宽,用来计算闲置时间。
    avpkt:平均包的大小,单位是字节。
    cell:用于设置时间力度,通常设置为8,必须是2的整数次幂。
    mpu:最小包大小。
    weight:偏袒数量。当其中一个类要求的带宽高于其他的类,那它每次就会比其他类处理weight数量的数据。
    prio:优先级参数。当prio值较低时,只要有数据就必须先服务,其他类要延后处理。
    allot:当一个类发送数据时,它发送的数据量的基值。
    maxburst:决定了计算maxidle所使用的数据包的个数。在avgidle跌落到0之前,maxburst的数据包可以突发传输出去。值越高,越能容纳突发传输。
    miniburst:当发生越限时,cbq禁止发包。一段时间后,突发性传输miniburst个数据包。值越大,整形越精确,一定程度上会有越多的突发传输。
    bounded/borrow:bounded表示不会向其兄弟类借入带宽。borrow与bounded相反。
    rate:期望中的传输速率。也就是“速度操纵杆”!

决定链路的共享和借用的CBQ参数
    除了纯粹地对某种数据流进行限速之外,CBQ还可以指定哪些类可以向其它哪些类借用或者出借一部分带宽。
Isolated/Sharing (isolated字面意思:独立,单独的)
    凡是使用“isolated”选项配置的类,就不会向其兄弟类借出带宽。如果你的链路上同时存在着不友好的人,你就可以使用这个选项。
    选项“sharing”是“isolated”的反义选项。
Bounded/Borrow (bounded字面意思:受限制的,有限的;borrow=借入)
    一个类也可以用“bounded”选项配置,意味着它不会向其兄弟类借入带宽。选项“borrow”是“bounded”的反义选项。
    一个典型的情况就是你的一个链路上有多个客户都设置成了“isolated”和“bounded”,那就是说他们都被限制在其要求的速率之下,且互相之间不会借用带宽(就是我们常说的带宽独享)。在这样的一个类的内部的子类之间是可以互相借用带宽的。
    其它CBQ参数:split和defmap (split:分离、分裂)(不做解释)

实例:

这个配置把WEB服务器的流量控制为5mbit、SMTP流量控制在3mbit上。而且二者一共不得超过6mbit,互相之间允许借用带宽。我们的网卡是100Mbps的。

#tc qdisc add dev eth0 root handle 1:0 cbq bandwidth 100Mbit avpkt 1000 cell 8
#tc class add dev eth0 parent 1:0 classid 1:1 cbq bandwidth 100Mbit rate 6Mbit weight 0.6Mbit prio 8 allot 1514 cell 8 maxburst 20 avpkt 1000 bounded

    这部分按惯例设置了根为1:0,并且绑定了类1:1。也就是说整个带宽不能超过6Mbps。
#tc class add dev eth0 parent 1:1 classid 1:3 cbq bandwidth 100Mbit rate 5Mbit weight 0.5Mbit prio 5 allot 1514 cell 8 maxburst 20 avpkt 1000
#tc class add dev eth0 parent 1:1 classid 1:4 cbq bandwidth 100Mbit rate 3Mbit weight 0.3Mbit prio 5 allot 1514 cell 8 maxburst 20 avpkt 1000

    我们建立了2个类。注意我们如何根据带宽来调整weight参数的。两个类都没有配置成“bounded”,但它们都连接到了类1:1上,而1:1设置了 “bounded”。所以两个类的总带宽不会超过6Mbps。别忘了,同一个CBQ下面的子类的主号码都必须与CBQ自己的号码相一致!
#tc qdisc add dev eth0 parent 1:3 handle 30: sfq
#tc qdisc add dev eth0 parent 1:4 handle 40: sfq
    缺省情况下,两个类都有一个FIFO队列规则。但是我们把它换成SFQ队列,以保证每个数据流都公平对待。
#tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 match ip sport 80 0xffff flowid 1:3
#tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 match ip sport 25 0xffff flowid 1:4
    这些命令规则了根上的过滤器,保证数据流被送到正确的队列规则中去。

常用简单的过滤命令

这里列出的绝大多数命令都根据这个命令改编而来:
#tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 这些是所谓的“u32”匹配,可以匹配数据包的任意部分。
根据源/目的地址: 源地址段 match ip src 1.2.3.0/24
                目的地址段 match ip dst 4.3.2.0/24
根据源/目的端口: 源 match ip sport 80 0xffff
                 目的 match ip dport 80 0xffff
根据IP协议(tcp, udp, icmp, gre, ipsec)使用/etc/protocols所指定的数字。
            比如: icmp是 match ip protocol 1 0xff
根据fwmark:
#tc filter add dev eth1 protocol ip parent 1:0 prio 1handle 6 fw flowid 1:1
注意,这不是一个u32匹配!
你可以象这样给数据包打标记:
#iptables -A PREROUTING -t mangle -i eth0 -j MARK --set-mark 6
按TOS字段选择交互和最小延迟的数据流:
#tc filter add dev ppp0 parent 1:0 protocol ip prio 10 u32 match iptos 0x10 0xff flowid 1:4

HTB(Hierarchical Token Bucket, 分层的令牌桶)

实例:将以HTB队列为主,结合需求来讲述TC的使用。假设eth0出口有100mbit/s的带宽, 分配给WWW 、E-mail和Telnet三种数据流量, 其中分配给WWW的带宽为40Mbit/s,分配给Email的带宽为40Mbit/s, 分配给Telnet的带宽为20Mbit/S。
首先,需要为网卡eth0配置一个HTB队列,使用下列命令:
#tc qdisc add dev eth0 root handle 1:htb default 11
     这里,命令中的"add 表示要添加,"dev eth0 表示要操作的网卡为eth0。"root 表示为网卡eth0添加的是一个根队列。"handle 1: 表示队列的句柄为1:。"htb 表示要添加的队列为HTB队列。命令最后的"default 11 是htb特有的队列参数,意思是所有未分类的流量都将分配给类别1:11。

为根队列创建相应的类别:
#tc class add dev eth0 parent 1: classid 1:11 htb rate 40mbit ceil 40mbit
#tc class add dev eth0 parent 1: classid 1:12 htb rate 40mbit ceil 40mbit
#tc class add dev eth0 parent 1: cllassid 1:13 htb rate 20mbit ceil 20mbit
命令中,"parent 1:"表示类别的父亲为根队列1:。"classid1:11"表示创建一个标识为1:11的类别,"rate 40mbit"表示系统将为该类别确保带宽40mbit,“ceil 40mbit”,表示该类别的最高可占用带宽为40mbit。

由于需要将WWW、E-mail、Telnet三种流量分配到三个类别,即上述1:11、1:12和1:13,因此,需要创建三个过滤器,如下面的三个命令:
#tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dport 80 0xffff flowid 1:11
#tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dport 25 0xffff flowid 1:12
#tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dport 23 oxffff flowid 1:13
这里,"protocol ip"表示该过滤器应该检查报文分组的协议字段。“pr[o 1” 表示它们对报文处理的优先级是相同的,对于不同优先级的过滤器, 系统将按照从小到大的优先级。

一个复杂的实例

上述中三种数据流(www、Email、Telnet)之间是互相排斥的。当某个数据流的流量没有达到配额时,其剩余的带宽并不能被其他两个数据流所借用。在这里将涉及如何使不同的数据流可以共享一定的带宽。
    首先需要用到HTB的一个特性, 即对于一个类别中的所有子类别,它们将共享该父类别所拥有的带宽,同时,又可以使得各个子类别申请的各自带宽得到保证。这也就是说,当某个数据流的实际使用带宽没有达到其配额时, 其剩余的带宽可以借给其他的数据流。而在借出的过程中,如果本数据流的数据量增大,则借出的带宽部分将收回, 以保证本数据流的带宽配额。
    下面考虑这样的需求, 同样是三个数据流WWW、E-mail和Telnet, 其中的Telnet独立分配20Mbit/s的带宽。另一方面,WWW 和SMTP各自分配40Mbit/s。同时,它们又是共享的关系, 即它们可以互相借用带宽。


#tc qdisc add dev eth0 root handle 1: htb default 21
#tc class add dev eth0 partent 1: classid 1:1 htb rate 20mbit ceil 20mbit
#tc class add dev eth0 parent 1: classid 1:2 htb rate 80mbit ceil 80mbit
#tc class add dev eth0 parent 1:2 classid 1:21 htb rate 40mbit ceil 80mbit
#tc class add dev eth0 parent 1:2 classid 1:22 htb rate 40mbit ceil 80mbit
#tc filter add dev eth0 protocol parent 1:0 prio 1 u32 match ip dport 80 0xffff flowid 1:21
#tc filter add dev eth0 protocol parent 1:0 prio 1 u32 match ip dport 25 0xffff flowid 1:22
#tc filter add dev eth0 protocol parent 1:0 prio 1 u32 match ip dport 23 0xffff flowid 1:1
这里为根队列1创建两个根类别,即1:1和1:2,其中1:1对应Telnet数据流,1:2对应80Mbit的数据流。然后,在1:2中,创建两个子类别1:21和1:22,分别对应WWW和E-mail数据流。由于类别1:21和1:22是类别1:2的子类别,因此他们可以共享分配的80Mbit带宽。同时,又确保当需要时,自己的带宽至少有40Mbit。

从这个例子可以看出,利用HTB中类别和子类别的包含关系,可以构建更加复杂的多层次类别树,从而实现的更加灵活的带宽共享和独占模式,达到企业级的带宽管理目的。

IMQ(Intermediate queueing device,中介队列设备)

    中介队列不是一个队列规定,但它的使用与队列规定是紧密相连的。就linux而言,队列是附加在网卡上的,出现了两个局限:
1.只能进行出口整形,(虽然也存在入口队列,但是上面实现的分类的队列规定可能性非常小)
2.一个队列规定只能处理一块网卡的流量,无法设置全局的限速。

IMQ就是解决了上述的两个局限,简单的说,你可以往队列中放任何东西。被打了特定的数据包在netfilter的BF_IP_PRE_ROUTING和NF_IP_POST_ROUTING的两个钩子函数被拦截,并被发送到一个队列中,该队列附加在一个IMQ的设备上,对数据包打标签可以用到iptables一种处理方法。
配置范例:
首先想到的是入口整形,以便配置得到高保证的带宽,就像配置其他网卡一样:
#tc qdisc add dev imq0 root handle 1: htb default 20
#tc class add dev imq0 parent 1: classid 1:1 htb rate 2mbit burst 15k
#tc class add dev imq0 parent 1:1 classid 1:10 htb rate 1mbit
#tc class add dev imq0 parent 1:1 classid 1:20 htb rate 1mbit

#tc qdisc add dev imq0 parent 1:10 handle 10: pfifo
#tc qdisc add dev imq0 parent 1:20 handle 20: sfq

#tc filter add dev imq0 parent 10:0 protocol ip prio 1 u32 match ip dst ip flowid 1:10
在这个列子中,使用32 匹配目的ip,然后被打上标记的数据包被送到imq0中排队。
iptables -t mangle -A PERROUTING -i eth0 -j IMQ --todev 0
ip link set imq0 up

tc网络流控详解及常用队列相关推荐

  1. 二叉堆详解实现优先级队列

    二叉堆详解实现优先级队列 文章目录 二叉堆详解实现优先级队列 一.二叉堆概览 二.优先级队列概览 三.实现 swim 和 sink 四.实现 delMax 和 insert 五.最后总结 二叉堆(Bi ...

  2. [转帖]Ipvsadm参数详解(常用命令)

    Ipvsadm参数详解(常用命令) 2013年11月29日 12:41:40 怀素1980 阅读数:15901 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn ...

  3. 说明使用tc编程的一般步骤 c语言,TC编程手册详解-完整版.doc

    TC编程手册详解-完整版 TC编程手册详解-完整版 本文是TC的第一部分,主要介绍一些TC相关的基础知识,并着重针对循环.变量等TC基本概念中的易混的淆部分加以辨析,即是一份编程初学者的指南,也可算作 ...

  4. Android多点触控详解

    本文转载自GcsSloop的 安卓自定义View进阶-多点触控详解 的文章 Android 多点触控详解,在前面的几篇文章中我们大致了解了 Android 中的事件处理流程和一些简单的处理方案,本次带 ...

  5. linux parted分区教程,分区工具parted的详解及常用分区使用方法

    分区工具parted的详解及常用分区使用方法 一. parted的用途及说明 概括使用说明: parted用于对磁盘(或RAID磁盘)进行分区及管理,与fdisk分区工具相比,支持2TB以上的磁盘分区 ...

  6. 【Lucene】分词器详解,常用的分词器,IKANalyzer

    [Lucene]分词器详解,常用的分词器,IKANalyzer 1. 分词器详解 1.1 分词器的作用 1.2 分词器API 1.2.1 示例 1.2.2 Analyzer 1.2.3 createC ...

  7. php循环取redis队列,详解Redis和队列

    下面由Redis教程栏目给大家详解Redis和队列,希望对需要的朋友有所帮助! 概要 Redis不仅可作为缓存服务器,还可用作消息队列.它的列表类型天生支持用作消息队列.如下图所示: 由于Redis的 ...

  8. MaxCompute SQL函数详解 ODPS SQL函数详解---之常用数学运算相关函数

    MaxCompute SQL函数详解 ODPS SQL函数详解---之常用数学运算相关函数 MaxCompute/ODPS SQL常用数学运算相关函数 ABS函数-计算绝对值 sql:select A ...

  9. java 消息队列详解_Java消息队列-Spring整合ActiveMq的详解

    本篇文章主要介绍了详解Java消息队列-Spring整合ActiveMq ,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 1.概述 首先和大家一起回顾一下Java 消息服 ...

最新文章

  1. java 实体属性个数_?Java中比较实用实体转换工具介绍
  2. [Luogu P2014]选课 (树形DP)
  3. 阿里云oss数据迁移到腾讯云cos 方法二(强烈推荐)
  4. H264编码之GOP含义
  5. linux内核配置失败,petalinux配置内核出现下面错误
  6. 设计模式研究(二)-Singleton
  7. 十年的老代码,你敢动?
  8. java类似goto_原来java中也有类似goto语句的标签啊--java label标签
  9. myisam表锁及锁粒度调节
  10. 【渝粤教育】广东开放大学 Photoshop 图像处理 形成性考核 (24)
  11. 华为荣耀电脑第三方linux,【第三方Linux版】荣耀MagicBook Pro 16.1英寸全面屏如何?某东入手评测...
  12. 引领PCB行业变革 捷配开启免费打样新时代
  13. html怎么移动到vue,vue自定义指令之拖动页面的元素
  14. mysql安装服务和安装中常见问题install/Remove of the Service Denied与net start mysql服务启动失败解决方法
  15. 前端怎样让图片缩小像素值不失真_纹理优化:不仅仅是一张图片那么简单
  16. Android:ViewFlipper、幻灯片
  17. python gui下载进度条_对python GUI实现完美进度条的示例详解
  18. 客户端软件的结构思考(一)
  19. 数据结构-栈操作-用链表实现栈基本操作
  20. DroidCam通过数据线调用手机摄像头的方法一

热门文章

  1. 期货穿仓是什么意思(期货交易穿仓)
  2. 从产品角度来聊ToC的数据和ToB的数据
  3. 最小二乘法与最小一乘法
  4. 网易云音乐评论爬取。
  5. MariaDB的安装步骤
  6. Git mvn 命令
  7. 如何将知网下载的caj文件转换为pdf文件
  8. rewrite地址转换
  9. r语言 面板数据回归_面板数据估计的R实现
  10. 亚马逊图书销量前五十分析