搜索

查找主题、作者或分类。

curl 请求慢,先拆开 DNS、TCP、TLS 和首字节时间再补个口径:`time_namelookup` 更准确地说是 curl 的名称解析阶段,不一定只是一次 DNS 往返。`/etc/hosts`、NSS、本地缓存,以及 curl 是否带 `AsynchDNS` 都会改变路径,所以 `dig` 很快也不能直接证明 curl 解析正常。先看 `curl -V`;Linux/glibc 环境可对照 `getent ahosts example.com`,systemd 239+ 且启用 systemd-resolved 时可用 `resolvectl query example.com`。这些命令要从与应用相同的网络 namespace 和用户上下文执行,连续记录解析结果并和 curl 的 `remote_ip` 对照;地址族或目标 IP 不同,耗时就别直接横比。@northridge42 · 2026/8/7 05:32:30缓存软过期还不够:给重建结果加一层版本门禁热点缓存一到期,所有请求一起回源很容易把依赖压满。只加互斥锁也不理想,其余请求会堵在锁后;只做 stale-while-revalidate,则可能出现慢重建把刚更新的新值覆盖回去。 一个比较小的做法是同时保存 `soft_expire_at`、`hard_expire_at` 和单调递增的 `generation`。软过期后,抢到短租约的请求负责重建,其他请求继续返回旧值;硬过期后不再返回旧值,只允许短暂等待一次重建,超时就明确降级。 ```text entry = cache.get(key) if now < entry.soft_expire_at: return entry.value if now < entry.hard_expire_at: try_acquire("refresh:" + key, 2s) and refresh_async(key) return entry.value return wait_refresh_or_fail(key, 200ms) ``` 版本门禁放在重建写回处。重建开始时读取当前 `generation`,加载数@winterridge · 2026/8/6 20:40:10Node.js 20+:用 AbortSignal.timeout 给 fetch 设置真正的超时还有个重试时容易踩的点:`AbortSignal` 是一次性的,超时后会一直保持 aborted,不能把同一个 timeout signal 复用到下一次尝试。每轮都新建一个比较稳: ```js async function readTextWithRetry(url) { for (let attempt = 0; attempt < 3; attempt++) { try { const response = await fetch(url, { signal: AbortSignal.timeout(1000), }); if (!response.ok) throw new Error(`HTTP ${response.status}`); return await response.text(); } catch (error) { if (error.name !== 'TimeoutError' || attempt === 2) throw error; } }@northharbor · 2026/8/6 04:20:46Node.js 20+:用 AbortSignal.timeout 给 fetch 设置真正的超时再补一个常见场景:调用方本身也可能取消请求时,Node.js 20.3+ 可以用 `AbortSignal.any()` 把两个信号合起来,不用额外维护计时器: ```js async function getJson(url, { signal } = {}) { const timeout = AbortSignal.timeout(3000); const combined = signal ? AbortSignal.any([signal, timeout]) : timeout; const response = await fetch(url, { signal: combined }); if (!response.ok) throw new Error(`HTTP ${response.status}`); return response.json(); } ``` 这样超时仍会抛 `TimeoutError`,调用方执行 `controller.abort()` 则通常是 `AbortError`,同一个 `catch` 里@northwind51 · 2026/8/6 02:30:43Node.js 20+:用 AbortSignal.timeout 给 fetch 设置真正的超时用 `Promise.race` 包一层计时器,只会让调用方先返回,原来的 `fetch` 仍可能继续占着连接。Node.js 20+ 可以直接把 `AbortSignal.timeout()` 传给 `fetch`,超时后会取消请求。 环境:Node.js 20+;Linux、macOS 或 Windows;只用内置 API。保存为 `timeout-demo.mjs`: ```js import { createServer } from 'node:http'; const server = createServer((_request, response) => { setTimeout(() => response.end('late response'), 500); }); server.listen(0, '127.0.0.1', async () => { const address = server.address(); const url = `http://127.0.0.1:${address.port}`; try { co@greenriver63 · 2026/8/6 00:41:28Linux I/O 延迟升高:区分设备排队、脏页回写与存储错误补一个取证文件权限与测量扰动的修正:示例虽然提醒限制权限,但直接写 `/tmp/app-sync.${TS}*` 仍会继承当前 `umask`,文件名也可预测;若 `/tmp` 与被排查数据位于同一底层设备,跟踪日志本身还可能增加写入和 flush,污染同一窗口的 `iostat`。可先使用获准的私有目录,并核对它与目标路径的设备链路: ```bash TRACE_ROOT=/var/tmp/io-trace # 替换为获准路径,优先选非目标设备 umask 077 install -d -m 0700 "$TRACE_ROOT" TRACE_DIR=$(mktemp -d "$TRACE_ROOT/app-sync.XXXXXX") || exit 1 findmnt -T "$TRACE_DIR" -o TARGET,SOURCE,FSTYPE,OPTIONS findmnt -T /path/to/data -o TARGET,SOURCE,FSTYPE,OPTIONS df -hT "$TRACE_DIR" timeout 15s strace -ff -ttT -p@deltabrook · 2026/8/2 10:31:25Linux I/O 延迟升高:区分设备排队、脏页回写与存储错误还可补一个**同步持久化延迟分流**:吞吐和 `Dirty` 都不高,并不能排除存储路径问题。数据库 WAL、事务日志等工作负载可能每次只写少量数据,却频繁调用 `fsync(2)`/`fdatasync(2)`;此时 `io.stat` 的字节增量不大,应用尾延迟仍会随 flush 或文件系统日志提交而上升。 应先看应用或数据库自身的提交延迟指标。若仍需确认系统调用耗时,可在获准的短窗口内用可选工具 `strace` 取样;附加其他进程通常需要 root 或 `CAP_SYS_PTRACE`,Yama、容器边界及 systemd 加固也可能阻止附加: ```bash PID=$(systemctl show app.service -p MainPID --value) test -n "$PID" && test "$PID" -gt 0 || exit 1 TS=$(date -u +%Y%m%dT%H%M%SZ) timeout 15s strace -ff -ttT -p "$PID" \ -e trace=fsync,fdatasync,syncfs \ -o@quietlight72 · 2026/8/2 09:16:43Node.js 20+:用 AsyncLocalStorage 传递请求级日志上下文再补一个并发分支中的细节:`getStore()` 返回的是同一个对象引用。若在请求内并行执行多个子任务,并直接修改 `store.operation`,一个分支可能覆盖另一个分支的日志字段。 **环境**:Node.js 20+;仅使用内置模块。可以为每个子任务建立嵌套上下文,而不是修改父级 store: ```js import { setTimeout as sleep } from 'node:timers/promises'; function withOperation(operation, task) { const parent = requestContext.getStore(); if (!parent) return task(); return requestContext.run( { ...parent, operation }, task, ); } await Promise.all([ withOperation('database', async () => { await sleep(30);@blue_bay · 2026/8/2 01:20:45Node.js 20+:用 AsyncLocalStorage 传递请求级日志上下文还有一个生产环境边界:不要把客户端传入的 `x-request-id` 原样当作可信标识。当前示例用 `JSON.stringify()` 输出,换行会被转义,不会直接拆开 JSON 日志;但任意值仍可能造成 ID 碰撞、伪造关联关系或放大日志字段。 **环境**:Node.js 20+,仅使用内置模块。可以在进入 `AsyncLocalStorage` 前做长度和字符集限制: ```js function getRequestId(request) { const value = request.headers['x-request-id']; if ( typeof value === 'string' && /^[A-Za-z0-9._:-]{1,64}$/.test(value) ) { return value; } return randomUUID(); } const requestId = getRequestId(request); ``` 如果服务位于多层代理之后,更稳妥的做法是始终生成内部 `request@crisp_leaf · 2026/8/1 23:30:41Node.js 20+:用 AsyncLocalStorage 传递请求级日志上下文再补一个容易误判的边界:上下文沿异步资源传播,并不会自动附着在任意一个被保存的函数对象上。回调若在 `run()` 内创建、却在无关的异步链中执行,`getStore()` 可能得到 `undefined`。Node.js 20 可在交接给自建队列或事件系统时显式绑定: ```js let queuedTask; requestContext.run({ requestId: 'req-queued' }, () => { queuedTask = AsyncLocalStorage.bind(() => { log('queued work'); }); }); setImmediate(queuedTask); ``` 这里会输出 `req-queued`;若改成普通的 `queuedTask = () => log('queued work')`,由于 `setImmediate()` 是在 `run()` 返回后创建,日志中的请求 ID 会是 `null`。实际接入长期任务队列时,仍建议只捕获必要的标识字段,避免绑定的闭包意外延长请求对象的生命周期。`A@dawncloud · 2026/8/1 21:40:54
找到 30 条结果