2026/08/31

tc (traffic control) in linux

tc(traffic control)是 Linux 裡用來做網路流量控制(QoS, Quality of Service)的工具。可針對網路卡,進行限速(rate limiting)、排程(traffic scheduling)、優先權控制(priority)、模擬網路狀況(延遲、丟包、抖動)、封包分類與整形(shaping)的網路封包控制。主要用途是用來模擬網際網路的封包狀況。

流量控制方式

  • shaping

    當流量被shaping限制後,它的傳輸速率就被控制在某個值以下。限制值可以遠小於有效頻寬,這樣可以平滑突發的資料流量,使網路更爲穩定。shaping(限制)只適用於向外的流量。shaping 只能用在 egress 對外的資料輸出。

  • scheduling

    通過調度封包的傳輸,可以在頻寬範圍內,按照優先等級分配頻寬。scheduling 只能用在 egress 對外的資料輸出。

  • policing

    shaping用於處理 egress 對外的流量,而 policing 用於處理 ingress 接收到的流量。

  • dropping

    如果流量超過某個設定的頻寬,就丟棄資料,可用在 egress 及 ingress。

主要元件

  1. qdisc (Queueing Discipline)

    佇列規則,決定封包排隊的送出規則。當kernel需要通過某個網絡port發送資料,它都需要按照爲這個port配置的qdisc排隊規則,把資料加入queue。然後kernel會儘可能地從qdisc裏面取出資料,交給網絡適配器驅動模塊。最簡單的qdisc是 pfifo,它不對進入的封包做任何的處理,採用先入先出的方式通過隊列。不過,它會暫存網絡接口短時間內無法處理的封包。

    常見的 qdisc:

    • pfifo_fast(預設)

    • htb(常用做限速)

    • fq_codel(改善 bufferbloat)

    • netem(模擬網路)

  2. class

    在 qdisc 底下建立不同流量分類,用來分配不同頻寬。不同的類別中可以包含更深入的QDisc(排隊規則),通過這些細分的QDisc還可以爲進入的隊列的封包排隊。通過設置各種類別封包的離隊次序,QDisc可以設定網路數據流量的優先等級。

  3. filter, policy 過濾器

    用來把封包分類到不同 class,決定它們按照何種QDisc進入隊列。分類的方法可以有多種,使用fileter(過濾器)就是其中之一。使用filter(過濾器)分類時,kernel會呼叫附屬於這個 class 的所有過濾器,直到回傳一個判決。如果沒有判決返回,就作進一步的處理,而處理方式和QDISC有關。filter(過濾器)是放在QDisc內部。

指令

tc [OPTIONS] OBJECT COMMAND

常見 OBJECT:

  • qdisc

  • class

  • filter

指令內可使用的資料單位

  • 頻寬或流量單位

    kbps
    mbps
    kbit
    mbit
    bps
  • 資料單位

    kb
    mb
    mbit
    kbit
    b
  • 時間

    s/sec/secs
    ms/msec/msecs
    us/usec/usecs

options

-s, -stats, -statistics: output more statistics about packet usage.
-d, -details: output more detailed information about rates and cell sizes.
-r, -raw: output raw hex values for handles.
-p, -pretty: decode filter offset and mask values to equivalent filter commands based on TCP/IP.
-iec: print rates in IEC units (ie. 1K = 1024).

Examples

tc 通常是控制 egress 的流量

查看設定

tc -s qdisc show dev eth0
tc class show dev eth0
tc filter show dev eth0
# tc -s qdisc show dev enp1s0
qdisc mq 0: root
 Sent 220264 bytes 431 pkt (dropped 0, overlimits 0 requeues 2)
 backlog 0b 0p requeues 2
qdisc fq_codel 0: parent :4 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
 Sent 20103 bytes 107 pkt (dropped 0, overlimits 0 requeues 0)
 backlog 0b 0p requeues 0
  maxpacket 0 drop_overlimit 0 new_flow_count 0 ecn_mark 0
  new_flows_len 0 old_flows_len 0
qdisc fq_codel 0: parent :3 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
 Sent 43974 bytes 148 pkt (dropped 0, overlimits 0 requeues 0)
 backlog 0b 0p requeues 0
  maxpacket 0 drop_overlimit 0 new_flow_count 0 ecn_mark 0
  new_flows_len 0 old_flows_len 0
qdisc fq_codel 0: parent :2 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
 Sent 146267 bytes 124 pkt (dropped 0, overlimits 0 requeues 2)
 backlog 0b 0p requeues 2
  maxpacket 8925 drop_overlimit 0 new_flow_count 1 ecn_mark 0
  new_flows_len 0 old_flows_len 0
qdisc fq_codel 0: parent :1 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
 Sent 9920 bytes 52 pkt (dropped 0, overlimits 0 requeues 0)
 backlog 0b 0p requeues 0
  maxpacket 0 drop_overlimit 0 new_flow_count 0 ecn_mark 0
  new_flows_len 0 old_flows_len 0
# tc -d qdisc show dev enp1s0
qdisc mq 0: root
qdisc fq_codel 0: parent :4 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
qdisc fq_codel 0: parent :3 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
qdisc fq_codel 0: parent :2 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
qdisc fq_codel 0: parent :1 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
  • qdisc mq 0: root

    根節點 mq : multi-queue

    代表你的硬體網路卡有多個傳輸佇列(在這個例子中是 4 個,分別對應 parent :1:4)。通常發生在多核心 CPU 的伺服器上,系統會將網路流量分散到不同的 CPU 核心處理,以提升效能

  • fq_codel (Fair Queuing Controlled Delay)

    這是目前 Linux 系統非常推薦的排隊規則,它由兩個部分組成

    • FQ (Fair Queuing):確保每個「連線」(Flow)都能公平分配頻寬。例如,即使有人在下載大檔案,你的網頁瀏覽或視訊通話依然能順暢通過。

    • CoDel (Controlled Delay):這是一個智慧演算法,當它發現緩衝區(Buffer)塞車太久時,會主動丟棄封包或標記 ECN,強制降低延遲,解決「緩衝區膨脹」問題。

  • 以其中一個 fq_codel 為例:

    • limit 10240p: 佇列中最多可以存放 10,240 個封包。

    • flows 1024: 它最多可以同時追蹤並隔離 1024 個不同的連線。

    • target 5ms / interval 100ms: 這是 CoDel 的核心參數。它會嘗試將延遲控制在 5ms 以內;如果延遲超過這個數值持續 100ms,它就會開始干預流量。

    • ecn: 支援顯式擁塞通知。當網路塞車時,它會通知發送端減速,而不是直接丟棄封包。

  • -s 統計

    Sent 146267 bytes 124 pkt (dropped 0, overlimits 0 requeues 2)

    • Sent: 該佇列目前傳送的資料量與封包數。

    • dropped 0: 這是一個好現象。 代表目前沒有封包因為塞車而被強制丟棄。

    • requeues 2: 封包重新排隊的次數。通常發生在硬體驅動程式暫時無法處理封包時(例如網路卡發送視窗滿了),這在正常運作下是偶爾會發生的。

    • new_flow_count 1: 代表自從統計開始以來,有一個「新連線」被偵測到。fq_codel 會優先處理新連線(互動式流量),以保證反應速度。

刪除設定

清除所有設定

tc qdisc del dev eth0 root

備份/還原完整設定

# 備份
tc -dump qdisc show dev eth0 > tc-backup.txt
tc -dump class show dev eth0 >> tc-backup.txt
tc -dump filter show dev eth0 >> tc-backup.txt

# 還原前先清空
tc qdisc del dev eth0 root
tc qdisc del dev eth0 ingress
tc -batch tc-backup.txt

完整備份所有網卡

tc -dump qdisc show > tc-all.txt
tc -dump class show >> tc-all.txt
tc -dump filter show >> tc-all.txt

# 還原
tc -batch tc-all.txt

限制整個網卡頻寬

限制 eth0 最大 1Mbps

tc qdisc add dev eth0 root tbf rate 1mbit burst 32kbit latency 400ms
  • tbf = Token Bucket Filter(簡單限速)

  • rate = 頻寬

  • burst = 突發流量

  • latency = 最大延遲

使用 HTB 做分流

# 建立 root qdisc
tc qdisc add dev eth0 root handle 1: htb default 30

# 建立 class 做頻寬分配
tc class add dev eth0 parent 1: classid 1:1 htb rate 10mbit
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5mbit
tc class add dev eth0 parent 1:1 classid 1:20 htb rate 2mbit
1: (root)
 └── 1:1 (10mbit)
      ├── 1:10 (5mbit)
      └── 1:20 (2mbit)

依 IP 分流

ex

192.168.1.100 → 5Mbps
其他 → 2Mbps
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
match ip dst 192.168.1.100 flowid 1:10

模擬網路延遲

增加 100ms 延遲

tc qdisc add dev eth0 root netem delay 100ms

模擬丟包

10% 封包丟失

tc qdisc add dev eth0 root netem loss 10%

延遲 + 抖動 + 丟包

tc qdisc add dev eth0 root netem delay 100ms 20ms loss 5%
  • 100ms = 基本延遲

  • 20ms = 抖動(jitter)

  • loss 5% = 丟包

控制 ingress

tc qdisc add dev eth0 handle ffff: ingress

ingress shaping ifb

tc 原生只能控制 egress(送出流量) ,要控制 ingress(進來的流量),就要「轉送」到 ifb 再做 shaping

eth0 (ingress)
   ↓ redirect
ifb0 (egress)
   ↓ tc 控制

限制下載速度(ingress 10Mbps)

# 載入 ifb 模組
modprobe ifb

# 指定數量
modprobe ifb numifbs=1

# 啟用 ifb0
ip link add ifb0 type ifb
ip link set dev ifb0 up

# 在 eth0 加 ingress qdisc
tc qdisc add dev eth0 handle ffff: ingress

# 把 ingress 流量導到 ifb0
# mirred = mirror/redirect
tc filter add dev eth0 parent ffff: protocol ip u32 \
match u32 0 0 action mirred egress redirect dev ifb0

# 在 ifb0 上做限速
tc qdisc add dev ifb0 root tbf rate 10mbit burst 32k latency 400ms

# show
tc -s qdisc show dev ifb0

clear

tc qdisc del dev eth0 ingress
tc qdisc del dev ifb0 root
ip link delete ifb0

不同 IP 不同下載速度

ex

192.168.1.100 → 5 Mbps
其他 → 2 Mbps

用 HTB

tc qdisc add dev ifb0 root handle 1: htb default 20

tc class add dev ifb0 parent 1: classid 1:1 htb rate 10mbit

tc class add dev ifb0 parent 1:1 classid 1:10 htb rate 5mbit
tc class add dev ifb0 parent 1:1 classid 1:20 htb rate 2mbit

# filter 分流
# ingress → ifb 後.  source IP 才是外部來源 IP
tc filter add dev ifb0 protocol ip parent 1: prio 1 u32 \
match ip src 192.168.1.100 flowid 1:10

note: ingress 只能有一個 qdisc

References

程式扎記: [Linux 文章收集] TC 入門 (traffic control)

TC (Traffic Control) - HackMD

2026/08/24

Java Multimap

一般基本的 Map 是唯一的 Key,搭配一個 Value 的資料結構,實務上,有時候會遇到,一個 Key,搭配對應的 Value,在 Value 是有多種可能的,例如在公司部門分類下,每個部門都有各自的員工。

這種資料結構,用標準 JDK 提供的 Map 搭配 ArrayList 就可以完成,但還有其他幾個封裝比較好的函式庫,有提供這種資料結構的類別實作。

  • Apache Commons Collecitons 的 MultiValuedMap

  • Guava 的 Multimap

  • Spring Framework 的 MultiValueMap

如果要測試這幾種實作方式,要先引用函式庫

    <dependencies>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>33.5.0-jre</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>7.0.6</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.5.0</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

Java 實例

import com.google.common.collect.Multimap;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.util.*;

public class ArrayListMultimap {
    public void main(String[] args) {
        ArrayListMultimap.jdk();
        ArrayListMultimap.apacheCommons();
        ArrayListMultimap.guava();
        ArrayListMultimap.spring();
    }

    public static void addEmployee(Map<String, List<String>> map, String department, String employee) {

        if (!map.containsKey(department)) {
            map.put(department, new ArrayList<>());
        }

        map.get(department).add(employee);
    }

    public static void jdk() {

        Map<String, List<String>> departmentEmployees = new HashMap<>();

        addEmployee(departmentEmployees, "RD", "Thomas");
        addEmployee(departmentEmployees, "RD", "James");
        addEmployee(departmentEmployees, "MA", "Emma");
        addEmployee(departmentEmployees, "MA", "Dave");
        addEmployee(departmentEmployees, "HR", "Carol");

        System.out.println("=== JDK HashMap + List<String> ===");
        System.out.println(departmentEmployees);
    }
    public static void apacheCommons() {
        MultiValuedMap<String, String> departmentEmployees = new ArrayListValuedHashMap<>();

        departmentEmployees.put("RD", "Thomas");
        departmentEmployees.put("RD", "James");
        departmentEmployees.put("MA", "Emma");
        departmentEmployees.put("MA", "Dave");
        departmentEmployees.put("HR", "Carol");

        System.out.println("=== Apache Commons ArrayListValuedHashMap ===");
        System.out.println(departmentEmployees);
    }
    public static void guava() {
        List<String> rdlist = Arrays.asList("Thomas", "James");
        List<String> malist = Arrays.asList("Emma", "Dave");
        List<String> hrlist = Arrays.asList("Carol");

        Multimap<String, List<String>> listMultimap = com.google.common.collect.ArrayListMultimap.create();
        listMultimap.put("RD", rdlist);
        listMultimap.put("MA", malist);
        listMultimap.put("HR", hrlist);

        System.out.println("=== guava ArrayListMultimap ===");
        System.out.println(listMultimap);
    }

    public static void spring() {

        MultiValueMap<String, String> departmentEmployees = new LinkedMultiValueMap<>();

        departmentEmployees.add("RD", "Thomas");
        departmentEmployees.add("RD", "James");
        departmentEmployees.add("MA", "Emma");
        departmentEmployees.add("MA", "Dave");
        departmentEmployees.add("HR", "Carol");

        System.out.println("=== Spring LinkedMultiValueMap ===");
        System.out.println(departmentEmployees);
    }
}

執行結果

=== JDK HashMap + List<String> ===
{RD=[Thomas, James], MA=[Emma, Dave], HR=[Carol]}
=== Apache Commons ArrayListValuedHashMap ===
{RD=[Thomas, James], MA=[Emma, Dave], HR=[Carol]}
=== guava ArrayListMultimap ===
{RD=[[Thomas, James]], MA=[[Emma, Dave]], HR=[[Carol]]}
=== Spring LinkedMultiValueMap ===
{RD=[Thomas, James], MA=[Emma, Dave], HR=[Carol]}

2026/08/17

erlang 的 == 與 =:=

erlang 的相等比較有兩種 == 與 =:= 。同樣都是相等,但比較的嚴格等級不同。

==

這是等值比較,如果比較的是整數跟浮點數,兩種不同的資料型別,在比較之前,會自動做型別轉換,然後再根據數值進行比較。

> 1 == 1.0.
true
> 1 == 1.
true

=:=

全等比較,除了數值要一樣,資料型別也必須要一樣。

> 1 =:= 1.0.
false
> 1 =:= 1.
true

pattern matching

erlang 的 pattern matching 行為比較接近 =:=

假設有這樣的一個 function

check_num(1) -> integer;
check_num(1.0) -> float.

在呼叫 chech_num(1.0) 的時候,會進入第二個 clause,不會進入第一個。因為 1 跟 1.0 是不同的資料型別

/= 與 =/=

不相等,也一樣有兩種寫法

  • /=:對應於 ==。若值在算術上不相等,則為 true

  • =/=:對應於 =:=。若值或型別任一不同,則為 true

>  1 /= 1.0.
false
> 1 =/= 1.0.
true

Term Ordering

在 erlang 的所有資料型別,都能夠直接比較大小,這是因為在處理集合的排序時,能夠得到一個固定的結果。資料型別的比較順序如下

number < atom < reference < fun < port < pid < tuple < map < list < bitstring

sample

> 999999 < apple.
true
> Mixed = [apple, 10, {test, 1}, [1,2,3], 5.5, "hello"].
[apple,10,{test,1},[1,2,3],5.5,"hello"]
> lists:sort(Mixed).
[5.5,10,apple,{test,1},[1,2,3],"hello"]
% 順序:數字(5.5, 10) < atom(apple) < tuple({test,1}) < list([1,2,3], "hello")

Summary

要確保資料比對正確,大部分的狀況都是要寫成 =:=,僅有少部分,遇到了整數跟浮點數必須混合比較時,才需要用到 ==。