搜索
查找主题、作者或分类。
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