
从 mimalloc、traefik 和 htmx 出发,分别练习看见一次 malloc 落进哪个尺寸的桶、一个请求被哪条路由规则接手、一段响应换掉哪块 DOM,并给出今晚能跑通的最小路径。
page_reclaim_on_free 改回默认 0。3mi_usable_size 把请求的字节数换成实际拿到的字节数:ask 1 -> usable 8
ask 9 -> usable 16
ask 17 -> usable 32
ask 33 -> usable 48
ask 56 -> usable 64
ask 72 -> usable 80
ask 100 -> usable 112
ask 200 -> usable 224
ask 300 -> usable 320
ask 20000 -> usable 20480
ask 100000 -> usable 114688LD_PRELOAD: glibc mimalloc
反复申请释放 500 万次(8~512B) 39.2 ms 50.5 ms
同时持有 200 万块(32~256B) 166.6 ms 36.6 ms
8 线程 × 40 万块、交叉释放 569.6 ms 41.4 ms
8 线程 × 500 万次反复申请释放 132.9 ms 198.5 msMIMALLOC_SHOW_STATS=1 LD_PRELOAD=$PWD/libmimalloc.so ./bench 0 bin S 11 : 15.0 MiB 32.1 MiB 0 112 300.6 K
bin S 12 : 17.2 MiB 36.6 MiB 0 128 300.6 K
bin S 16 : 69.3 MiB 146.7 MiB 0 256 601.2 K
allocated : 312.4 MiB 1.6 GiB 4.0 KiB
pages : 4.8 K 4.8 K 1
abandoned : 4.7 K 4.7 K 0curl -L -o mimalloc.tar.gz https://github.com/microsoft/mimalloc/archive/v3.6.1.tar.gz
tar xzf mimalloc.tar.gz && cd mimalloc-3.6.1
gcc -O3 -fPIC -shared -DNDEBUG -DMI_MALLOC_OVERRIDE=1 -Iinclude \
-o libmimalloc.so src/static.c -pthread
LD_PRELOAD=$PWD/libmimalloc.so ls -lMI_MALLOC_OVERRIDE=1 这个宏必须给。不给也能编出一个 .so,只是里面没有 malloc 这个符号,LD_PRELOAD 什么都换不掉。MI_OVERRIDE=ON。4mi_usable_size 从 1 扫到 300,看哪几个数是分界点。扫一遍那 14 个数就忘不掉了。MIMALLOC_SHOW_STATS=1 跑一次,盯住 abandoned 和 reclaimf 两行。-DMI_SECURE=ON 再编一个版本,跑同一个基准,看慢多少。官方自述通常在 10% 量级。4Authorization: NTLM 或 Negotiate,凭据真假不论,Traefik 就给这条客户端连接单独开一份到后端的 transport。8WWW-Authenticate 之后才分配 transport,所以漏不到那几条线。v2 线同日发了 2.11.61。8golang.org/x/net 升到 v0.62.0,给入口点加了限制请求头条数的选项。7&&、||、! 和括号,匹配器有 Host、PathPrefix、Path、Header、Method、Query、ClientIP。10stripPrefix 改的是交给后端的路径本身。10http:
routers:
root:
rule: "Host(`who.test`)"
service: a
api:
rule: "Host(`who.test`) && PathPrefix(`/api`)"
service: a
api-v2:
rule: "Host(`who.test`) && PathPrefix(`/api/v2`)"
service: b
middlewares: ["strip"]
middlewares:
strip:
stripPrefix:
prefixes: ["/api/v2"]GET /hello -> {"backend":19101,"url":"/hello"}
GET /api/v1/x -> {"backend":19101,"url":"/api/v1/x"}
GET /api/v2/users -> {"backend":19102,"url":"/users"}
GET /hello 不带 Host -> 404 page not foundstrip 把 /api/v2 摘掉,后端收到 /users。第四个请求没匹配上任何路由,404 是 Traefik 自己回的。api-v2@file priority=41 rule_len=41 Host(`who.test`) && PathPrefix(`/api/v2`) -> b
api@file priority=38 rule_len=38 Host(`who.test`) && PathPrefix(`/api`) -> a
root@file priority=16 rule_len=16 Host(`who.test`) -> aapi 加一行 priority: 100,再请求一次 /api/v2/users:{"backend":19101,"method":"GET","url":"/api/v2/users"}strip 挂在 api-v2 上,而 api-v2 已经轮不到了。{"backend":19102,"url":"/users","x_forwarded_host":"who.test","x_from_proxy":"traefik-3.7.17"}RouterName=api-v2@file ServiceName=b@file ServiceAddr=127.0.0.1:19102
Duration p50 = 159.3 µs p90 = 206.9 µs
OriginDuration p50 = 139.1 µs p90 = 182.1 µs
Overhead p50 = 19.7 µs p90 = 25.2 µsOverhead 是 Traefik 自己那一段,中位 19.7 微秒。这三个字段在日志里是纳秒:换一个故意睡 50 毫秒的后端,Duration 报的是 54,539,858。curl -L -o traefik.tgz https://github.com/traefik/traefik/releases/download/v3.7.17/traefik_v3.7.17_linux_amd64.tar.gz
tar xzf traefik.tgztraefik.yml 只写入口点、文件 provider 和打开 API:entryPoints:
web:
address: ":18080"
traefik:
address: ":18081"
providers:
file:
filename: ./dynamic.yml
api:
insecure: true
accessLog:
format: jsondynamic.yml 用上面那一段,然后:node backend.js 19101 &
node backend.js 19102 &
./traefik --configfile traefik.yml &
curl -s -H "Host: who.test" http://127.0.0.1:18080/api/v2/users
curl -s http://127.0.0.1:18081/api/rawdata | python3 -m json.tool | head -40api.insecure 把 API 挂在默认叫 traefik 的入口点上。入口点不改名就得改它的端口,不然 8080 被占用的时候 API 起不来。priority: 100 注释掉,再看 /api/v2/users 落在哪个后端,规则长度那件事立刻明白。strip 从 api-v2 挪到 api 上,再请求 /api/v1/x,看后端收到的路径。RouterName、ServiceName、Overhead 三个字段。线上排查「这个请求到底走了哪条路由」靠的就是它们。priority。10<hx-partial> 回移到了 2.x 线。两条线在同一天动同一处,说明这是这一轮的重点。13hx-get、hx-post、hx-put、hx-patch、hx-delete 各对应一个方法。15hx-swap 决定替换哪块。 innerHTML 换内部、outerHTML 换自己、beforeend 追加到最后一个子节点后面、textContent 只换文本。15<hx-partial hx-target="#count"> 自带目标,跟主替换各走各的。15hx-target 现在只作用于自己,想让子元素沿上这条,得写 :inherited。15dist/htmx.js,浏览器是 Chromium 156 headless,用 Puppeteer 驱动。每次点击之前,在 document.body 上挂一个 MutationObserver。点之前 : 3 个 li,计数 3
点「加一件」(beforeend) : 4 个 li,计数 4,2 条变更记录,新增 2 个节点,删 1 个,响应 85 字节
点「整块换掉」(innerHTML) : 3 个 li 全换成新的,1 条变更记录,新增 3 个,删 4 个,响应 39 字节<li>,加一个 <hx-partial hx-target="#count">4</hx-partial>。hx-swap。beforeend 往列表尾巴塞一个新节点,那块 partial 顺手把计数改了,一共动了两处;innerHTML 把整块列表重画,三个 li 全删了重建。mkdir htmxtry && cd htmxtry && npm init -y && npm i htmx.org@4.0.1const http = require('http');
http.createServer((req, res) => {
if (req.url === '/') {
res.setHeader('content-type', 'text/html');
return res.end(`<!doctype html><meta charset="utf-8">
<script src="/htmx.js"></script>
<p>共 <span id="count">2</span> 件</p>
<ul id="list"><li>键盘</li><li>显示器</li></ul>
<button hx-post="/items" hx-target="#list" hx-swap="beforeend">加一件</button>`);
}
if (req.url === '/htmx.js') {
res.setHeader('content-type', 'text/javascript');
return res.end(require('fs').readFileSync('node_modules/htmx.org/dist/htmx.js'));
}
if (req.url === '/items') {
res.setHeader('content-type', 'text/html');
return res.end('<li>新物品</li><hx-partial hx-target="#count" hx-swap="innerHTML">3</hx-partial>');
}
}).listen(19201, '127.0.0.1');http://127.0.0.1:19201/,点一下按钮,列表和计数同时变。beforeend 改成 innerHTML,再数一次变更记录。响应一样,改动数量不一样。<hx-partial>,把计数从 2 改成 3 再看。hx-target 挪到父元素上,子按钮会失效;在父元素上补 :inherited 才恢复。这一条会让 2.x 的页面悄悄坏掉。hx-trigger="every 2s",看 Network 面板每两秒一个请求,响应还是几十字节。upgrade-check,三条旧属性报出来的原文是:[renamed-attr] hx-disable → rename to hx-ignore (hx-disable now means 'disable during request')
[inheritance] hx-target needs :inherited suffix (descendant on line 2 has hx-get)
[renamed-attr] hx-disabled-elt → rename to hx-disablehx-disable 现在的意思是「请求期间禁用」,原来的「不处理」改叫 hx-ignore;hx-disabled-elt 改叫 hx-disable。升级前先跑 upgrade-check。16dist/ext/ 里,按需引。| 想看见的决定点 | 仓库与本期信号 | 最小条件 | 第一条可观察路径 | 主要限制 |
|---|---|---|---|---|
| 几百字节落进哪个桶 | mimalloc · v3.6.1 北京时间 10 月 31 日 05:50 | gcc,一次编译 3.9 秒 | 用 MI_MALLOC_OVERRIDE 编出 .so,LD_PRELOAD 换掉 malloc | 单线程短生命周期场景 glibc 更快 |
| 一个请求交给哪个后端 | traefik · v3.7.17 北京时间 11 月 2 日 17:15 | 一个静态二进制,两个本地后端 | 三条路由,curl -H "Host: who.test" 打过去 | 默认优先级等于规则长度 |
| 一段响应换掉哪块 DOM | htmx · v4.0.1 北京时间 11 月 2 日 07:06 | 一个 npm 包,一个浏览器 | 同一个响应,beforeend 与 innerHTML 各点一次 | 服务器必须返回片段 |
hx-swap 不写就按 innerHTML 处理。hx-swap、写 priority、换分配器。改之前得先看见,而看见的成本都不高:一张尺寸表、一个 API 字段、一个 MutationObserver。github.com
github.com
github.com
github.com
microsoft.com
github.com
github.com
github.com
github.com
doc.traefik.io
github.com
github.com
github.com
github.com
github.com
github.com
github.com
This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.