第一章:Go本地持久化监控体系的核心价值与设计哲学
在分布式系统日益复杂的今天,远程监控服务常面临网络抖动、中心节点单点故障、高延迟采样等固有瓶颈。Go语言凭借其轻量协程、零依赖二进制分发和卓越的I/O性能,天然适配构建嵌入式、自治型的本地持久化监控体系——它不依赖外部数据库或消息中间件,将指标采集、聚合、落盘与查询能力内聚于单一进程,实现毫秒级响应与强一致性保障。
为什么需要本地持久化而非纯内存监控
- 内存监控在进程崩溃或重启后丢失全部历史数据,无法支持故障回溯与趋势分析
- 外部存储引入网络开销与运维复杂度,违背边缘节点“自治可信”原则
- 本地持久化(如WAL+LSM结构)兼顾写入吞吐与查询效率,典型场景下QPS超10万/秒且P99延迟
核心设计信条
信任本地磁盘的可靠性,而非网络;优先保障写入不丢,再优化读取体验;用结构化日志替代时序数据库schema约束;让监控自身具备可观测性——即监控系统也要被监控。
快速启动一个带持久化的Go监控实例
以下代码使用开源库 github.com/prometheus/client_golang/prometheus 配合 github.com/mattn/go-sqlite3 实现指标自动落盘:
package main
import (
"log"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// 定义可持久化指标(通过Prometheus Pushgateway非推荐;此处采用SQLite定期快照)
var (
httpRequestsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests.",
},
[]string{"method", "status"},
)
)
func main() {
// 启动HTTP监控端点
go func() {
log.Println("Starting metrics server on :2112")
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(":2112", nil))
}()
// 模拟业务请求计数
for range time.Tick(500 * time.Millisecond) {
httpRequestsTotal.WithLabelValues("GET", "200").Inc()
}
}
该示例虽未直接写磁盘,但为扩展持久化预留了Hook接口:可通过 prometheus.Gatherers 获取指标快照,并用 sqlite3.Exec("INSERT INTO metrics ...") 每30秒批量落盘。关键在于——持久化逻辑与业务逻辑解耦,由独立goroutine异步执行,绝不阻塞主监控通路。
第二章:关键延迟指标的采集与告警实现
2.1 write latency 的内核级观测原理与 Go syscall 调用实践
write latency 指从用户态 write() 系统调用返回到对应数据真正落盘(或被内核确认持久化)的时间差,其可观测性依赖于内核 I/O 路径的关键钩子点。
数据同步机制
Linux 内核通过 io_uring、block_rq_insert tracepoint 及 writeback 子系统暴露写路径延迟。/sys/block/*/stat 提供基础队列统计,但缺乏 per-I/O 精度。
Go syscall 实践
// 使用 syscall.Syscall 调用 write 并记录 TSC 时间戳
start := rdtsc() // 读取时间戳计数器(需特权或 CAP_SYS_RAWIO)
_, err := syscall.Write(int(fd), buf)
end := rdtsc()
latencyCycles := end - start
rdtsc() 返回 CPU 周期数,需结合 cpuid 序列化避免乱序执行干扰;fd 必须为已打开的 O_DIRECT 文件描述符以绕过页缓存,使测量更贴近真实磁盘延迟。
| 指标 | 含义 | 典型值(NVMe) |
|---|---|---|
write() 返回延迟 |
用户态返回耗时 | |
bio_queue 到 blk_mq_dispatch |
内核块层排队延迟 | 500 ns–5 μs |
rq_issue 到 rq_complete |
设备实际处理延迟 | 10–100 μs |
graph TD
A[Go write syscall] --> B[copy_to_iter in vfs_write]
B --> C[submit_bio with REQ_OP_WRITE]
C --> D[blk_mq_sched_insert_request]
D --> E[NVMe driver sqe submission]
E --> F[PCIe TLP → SSD controller]
2.2 fsync latency 的精确测量:从 runtime.nanotime 到 io/fs 同步上下文追踪
数据同步机制
fsync 延迟受内核 I/O 调度、存储介质(如 NVMe vs HDD)及文件系统日志模式共同影响。单纯调用 time.Now() 无法捕获内核态阻塞耗时,需穿透到系统调用边界。
精确时间戳采集
start := runtime.nanotime() // 纳秒级单调时钟,不受系统时间调整影响
err := file.Sync() // 触发底层 fsync(2)
end := runtime.nanotime()
latencyNs := end - start
runtime.nanotime() 绕过 time.Time 的系统时钟校准开销,避免 NTP 跳变干扰;file.Sync() 对应 fsync(fd) 系统调用,其延迟包含用户态到内核态切换、日志刷盘、设备确认三阶段。
同步上下文追踪路径
| 阶段 | 典型耗时(NVMe) | 关键依赖 |
|---|---|---|
| syscall entry | ~50 ns | CPU 频率、seccomp 过滤器 |
| journal commit | ~10–50 μs | ext4/xfs 日志模式(ordered/writeback) |
| device flush | ~100–500 μs | 存储控制器缓存策略、FLUSH_CACHE 命令 |
graph TD
A[Go app: file.Sync()] --> B[syscall: fsync]
B --> C[Kernel VFS layer]
C --> D[Filesystem journal commit]
D --> E[Block layer queue]
E --> F[Storage device flush]
2.3 fdatasync 与 sync_file_range 的语义差异及 Go 封装策略
数据同步机制
fdatasync(2) 仅刷写文件数据与必需的元数据(如 mtime、ctime),跳过非关键项(如 atime、目录项);而 sync_file_range(2) 支持偏移量+长度粒度控制,且可指定行为标志(如 SYNC_FILE_RANGE_WRITE 仅提交到页缓存,不强制落盘)。
语义对比表
| 特性 | fdatasync | sync_file_range |
|---|---|---|
| 同步范围 | 整个文件 | 指定字节区间 |
| 元数据同步 | 必需元数据(是) | 不同步元数据(否) |
| 阻塞行为 | 同步阻塞至落盘完成 | 可异步(取决于 flags) |
Go 封装策略
// syscall.RawSyscall6(SYS_fdatasync, uintptr(fd), 0, 0, 0, 0, 0)
// syscall.Syscall6(SYS_sync_file_range, uintptr(fd), off, len, uintptr(flags), 0, 0)
fdatasync 封装为无参同步操作;sync_file_range 需显式传入 off, n, flags,适配流式写入场景。底层均使用 syscall 直接调用,规避 os.File.Sync() 的过度保守语义。
2.4 延迟分布建模:使用 histogram 库构建 P50/P95/P99 实时热力图
延迟热力图需在毫秒级分辨率下捕获分位数动态变化。histogram 库(如 Rust 的 hdrhistogram 或 Go 的 prometheus/client_golang 中的 HistogramVec)提供无锁、低内存开销的滑动窗口直方图能力。
核心数据结构选择
- 支持指数桶(exponential buckets)或预设桶(custom buckets)
- 内置线程安全写入与原子快照
- 支持纳秒级时间戳对齐
实时分位数提取示例(Rust + hdrhistogram)
use hdrhistogram::Histogram;
let mut hist = Histogram::<u64>::new_with_max(10_000_000, 3).unwrap(); // max=10s, sigfig=3
hist.record(127_456).unwrap(); // 127.456ms
hist.record(892_100).unwrap();
println!("P50: {}μs", hist.value_at_percentile(50.0)); // → 127456
println!("P95: {}μs", hist.value_at_percentile(95.0)); // → 892100
new_with_max(10_000_000, 3)表示最大值 10 秒(10⁷ μs),精度为 3 位有效数字,自动划分约 1200 个动态桶;value_at_percentile采用插值法,避免离散桶导致的跳变。
热力图维度设计
| X轴(时间) | Y轴(延迟区间) | 颜色强度 |
|---|---|---|
| 每10秒切片 | 1ms–1s 对数分桶 | P99 值归一化 |
graph TD
A[HTTP 请求延迟] --> B[纳秒级采样]
B --> C[写入线程本地 Histogram]
C --> D[每5s 全局 merge + 快照]
D --> E[计算 P50/P95/P99]
E --> F[推送至热力图后端]
2.5 基于滑动时间窗口的延迟突增检测与动态阈值告警机制
传统固定阈值告警在流量波动场景下误报率高。本机制采用 1分钟滑动窗口(步长10秒) 实时统计P99端到端延迟,并基于指数加权移动平均(EWMA)动态更新基线。
核心计算逻辑
# alpha=0.3: 平衡响应速度与稳定性
ewma_delay = alpha * current_p99 + (1 - alpha) * prev_ewma
dynamic_threshold = ewma_delay * 1.8 # 自适应放大系数
该公式使阈值随历史趋势缓慢漂移,避免瞬时毛刺触发告警;1.8 系数经A/B测试验证,在保留敏感度的同时降低37%误报。
检测流程
graph TD A[采集每10s延迟样本] –> B[滑窗聚合P99] B –> C[EWMA平滑基线] C –> D[实时比对突增] D –> E[连续3次超阈值则告警]
| 维度 | 静态阈值 | 动态窗口 |
|---|---|---|
| 日均误报次数 | 24 | 6 |
| 突增检出延迟 | ≤90s | ≤25s |
第三章:文件系统异常行为的可观测性建设
3.1 fsync 失败率的归因分析:EIO、ENOSPC、EROFS 等错误码的 Go 错误分类实践
数据同步机制
fsync 是保障持久化的关键系统调用,其失败常暴露底层存储异常。Go 中 file.Sync() 返回 *os.PathError,需解析 Err 字段的底层 syscall.Errno。
常见错误码语义映射
| 错误码 | 含义 | 运维响应优先级 |
|---|---|---|
EIO |
设备I/O错误(磁盘故障) | 紧急 |
ENOSPC |
文件系统空间耗尽 | 高 |
EROFS |
只读文件系统写入 | 中 |
错误分类实践代码
func classifyFsyncErr(err error) string {
if pe, ok := err.(*os.PathError); ok {
if se, ok := pe.Err.(syscall.Errno); ok {
switch se {
case syscall.EIO: return "io_failure"
case syscall.ENOSPC: return "disk_full"
case syscall.EROFS: return "readonly_fs"
}
}
}
return "unknown"
}
该函数提取 *os.PathError 的 Err 字段并断言为 syscall.Errno,避免 errors.Is() 在跨平台时对非标准错误码匹配失效;各 case 分支对应可监控、可告警的业务语义标签。
归因流程
graph TD
A[fsync 返回 error] --> B{是否 *os.PathError?}
B -->|是| C[提取 syscall.Errno]
B -->|否| D[归为 unknown]
C --> E[EIO/ENOSPC/EROFS 匹配]
E --> F[输出结构化归因标签]
3.2 文件描述符泄漏与 open() 调用频次监控:netFD 与 runtime/pprof 的协同诊断
Go 运行时将底层 socket 封装为 netFD,其生命周期与 file.descriptor 强绑定。若 Close() 被遗漏或 panic 中断 defer 链,fd 即泄漏。
netFD 的 fd 归属追踪
// 在 net.conn 实现中,fd 来自:
fd, err := syscall.Open("/dev/null", syscall.O_RDONLY, 0)
if err != nil {
return nil, err
}
// netFD{pfd: &poll.FD{Sysfd: fd}} —— Sysfd 即真实 fd 值
Sysfd 字段是 fd 泄漏的根源点;runtime/pprof 的 goroutine 和 heap profile 可间接暴露未关闭连接,但需结合 fd 指标交叉验证。
监控组合策略
- 启用
runtime/pprof的block和traceprofile 捕获阻塞型 I/O; - 通过
/proc/self/fd/统计实时 fd 数量变化趋势; - 使用
pprof.Lookup("goroutine").WriteTo(w, 1)提取含net.(*conn).Read的栈帧。
| 指标源 | 采样开销 | 定位精度 | 适用场景 |
|---|---|---|---|
/proc/self/fd |
极低 | 进程级 | 快速发现 fd 持续增长 |
runtime/pprof |
中(trace) | goroutine 级 | 定位未 Close 的调用路径 |
graph TD
A[open() 频次突增] --> B{是否伴随 fd 数持续上升?}
B -->|是| C[检查 netFD.Close() 调用链]
B -->|否| D[关注 syscall.Open 误用]
C --> E[分析 pprof trace 中 netFD.Read/Write 栈]
3.3 page cache 压力指标采集:/proc/sys/vm/swappiness 与 Go 内存映射行为联动分析
Linux 的 swappiness 参数(默认值60)直接调控内核在内存紧张时回收 page cache 与 swap 匿名页的倾向性,而 Go 运行时的 mmap 行为(如 runtime.sysMap 分配堆内存)会动态引入大量匿名映射页,加剧 page cache 竞争。
数据同步机制
Go 在 GODEBUG=madvdontneed=1 下对新分配的 mmap 区域调用 MADV_DONTNEED,主动释放物理页——但该操作会绕过 page cache,间接提升 swappiness 触发阈值。
# 查看当前压力策略
cat /proc/sys/vm/swappiness # 输出:60
# 临时调低以保 page cache
echo 10 | sudo tee /proc/sys/vm/swappiness
此命令将 swappiness 从默认60降至10,使内核更倾向回收匿名页而非 page cache,缓解 Go 高频 mmap 对文件缓存的挤占。参数范围0–100,0表示仅在 OOM 时 swap,100表示积极交换。
关键影响维度
| 维度 | swappiness=10 | swappiness=80 |
|---|---|---|
| page cache 保留率 | 高(优先丢弃匿名页) | 低(倾向回收 file-backed 页) |
| Go mmap 后延迟 | 更稳定(cache 不易抖动) | 波动增大(fsync 可能阻塞) |
// Go 中触发 mmap 的典型路径(简化)
func sysAlloc(n uintptr) unsafe.Pointer {
p := mmap(nil, n, protRead|protWrite, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)
madvise(p, n, MADV_DONTNEED) // 取决于 GODEBUG 设置
return p
}
mmap(MAP_ANONYMOUS)创建不可写入磁盘的匿名页,其生命周期完全依赖 swap;MADV_DONTNEED通知内核立即释放对应物理页,但若 swappiness 过高,后续 page cache 回收可能因内存碎片化而变慢。
第四章:存储介质健康度与持久化可靠性增强
4.1 ext4/xfs 日志状态监控:通过 ioctl 获取 journal 提交延迟与回滚事件
Linux 内核为 ext4 和 XFS 提供了统一的 FS_IOC_GETFSMAP 与专用 EXT4_IOC_GETSTATE / XFS_IOC_GETSTATE 接口,用于实时采集日志子系统运行指标。
数据同步机制
日志提交延迟(ms)和回滚事件计数由内核在 journal_t 或 xlog_t 结构中维护,需通过 ioctl(fd, EXT4_IOC_GETSTATE, &state) 原子读取。
struct ext4_state_info state = {0};
if (ioctl(fs_fd, EXT4_IOC_GETSTATE, &state) == 0) {
printf("commit_delay_us: %u\n", state.s_journal_commit_ms);
printf("rollback_count: %u\n", state.s_journal_rollback_cnt);
}
s_journal_commit_ms是最近一次 journal 提交耗时(微秒级采样均值),s_journal_rollback_cnt为自挂载以来异常回滚次数。需以O_RDONLY打开挂载点根目录 fd 并具备CAP_SYS_ADMIN权限。
关键字段语义对照
| 字段名 | ext4 含义 | XFS 等效字段 |
|---|---|---|
journal_commit_ms |
最近提交延迟(μs) | xlog_grant_head_wait |
journal_rollback_cnt |
强制回滚触发次数 | xlog_force_rollover |
graph TD
A[用户空间调用 ioctl] --> B{内核校验权限与 fs 类型}
B -->|ext4| C[读取 sb->s_es->s_journal_state]
B -->|xfs| D[读取 mp->m_log->l_state]
C & D --> E[拷贝结构体至用户态]
4.2 O_DIRECT 与 buffered I/O 混合场景下的 write amplification 量化方法
在混合 I/O 路径中,O_DIRECT 写入绕过页缓存,而 buffered write 触发脏页回写,二者并发易引发重复落盘。
数据同步机制
当应用调用 write()(buffered)后紧接 fsync(),内核需刷脏页;若同时有 O_DIRECT 写同一文件区域,ext4 可能触发 jbd2 日志重放+数据块双写,造成放大。
量化公式
定义写放大系数(WAF)为:
WAF = (实际写入磁盘的字节数) / (应用层请求写入的逻辑字节数)
实测工具链
# 使用 iostat + blktrace 分离 direct/buffered 路径
blktrace -d /dev/sdb -o trace -w 30 &
dd if=/dev/zero of=testfile bs=4k count=1000 oflag=direct
dd if=/dev/zero of=testfile bs=4k count=1000 conv=notrunc
sync
oflag=direct:强制 O_DIRECT;conv=notrunc触发 buffered overwriteblktrace可标记Q(queued)、D(issued)、C(completed)事件,区分路径来源
| 事件类型 | O_DIRECT 贡献 | Buffered I/O 贡献 | 共同触发 |
|---|---|---|---|
| 数据块写入 | ✓ | ✓ | ✗ |
| 日志元数据写 | ✗ | ✓ | ✓(journal replay) |
| 页缓存回写 | ✗ | ✓ | ✓(脏页驱逐) |
graph TD
A[App write syscall] --> B{flags & O_DIRECT?}
B -->|Yes| C[Direct to disk<br>skip page cache]
B -->|No| D[Copy to page cache<br>mark dirty]
D --> E[Background flush or fsync]
C & E --> F[Ext4 journal commit]
F --> G[Data block + journal block write<br>→ WAF ≥ 2.0]
4.3 持久化确认链路完整性验证:从 os.Write() → page cache → block layer → device queue 的 Go 层埋点设计
为精确观测 I/O 路径的持久化语义落地,需在关键跃迁点注入轻量级埋点:
数据同步机制
使用 runtime.SetFinalizer 关联 *os.File 与自定义 ioTrace 结构体,在 Write() 返回前记录 write_start_ns;通过 mmap 映射 /proc/self/io(仅 Linux)周期采样 rchar/wchar/syscr/syscw,交叉验证内核态写入进度。
埋点位置与语义对齐
| 埋点层 | 触发时机 | 可信度标识 |
|---|---|---|
os.Write() |
用户调用后、系统调用前 | user_write_enter |
| Page Cache | fsync() 或 msync() 时触发 |
pagecache_flush_start |
| Block Layer | blk_mq_submit_bio() hook |
bio_queued(eBPF) |
// 在 Write 方法包装中注入时间戳与上下文快照
func (t *TracedWriter) Write(p []byte) (n int, err error) {
start := time.Now().UnixNano()
t.span.Record("write_enter", start) // 埋点:用户态入口
n, err = t.w.Write(p)
t.span.Record("write_return", time.Now().UnixNano()) // 埋点:返回时刻
return
}
该代码在 Write() 入口与出口各打一个纳秒级时间戳,配合 context.WithValue 注入 request ID,确保跨 goroutine 的链路可追溯;span.Record 采用无锁环形缓冲写入,避免影响 I/O 吞吐。
内核态协同验证
graph TD
A[os.Write] --> B[Page Cache]
B --> C[Block Layer]
C --> D[Device Queue]
D --> E[Physical Media]
style A fill:#4CAF50,stroke:#388E3C
style B fill:#2196F3,stroke:#1976D2
style C fill:#FF9800,stroke:#EF6C00
style D fill:#9C27B0,stroke:#7B1FA2
4.4 WAL 日志刷盘成功率与 checkpoint 阻塞时长双维度告警模型
数据同步机制
PostgreSQL 依赖 WAL 持久化保障崩溃恢复能力,而 wal_writer_delay 和 checkpoint_timeout 共同影响刷盘行为与检查点触发节奏。
告警阈值设计
需联合监控两个关键指标:
- WAL 刷盘成功率(
pg_stat_bgwriter.buffers_written / (buffers_written + buffers_alloc)) - Checkpoint 平均阻塞时长(
pg_stat_bgwriter.checkpoint_write_time / pg_stat_bgwriter.checkpoints_timed)
| 指标 | 安全阈值 | 危险阈值 | 触发动作 |
|---|---|---|---|
| WAL 刷盘成功率 | ≥99.5% | 触发高优先级告警 | |
| Checkpoint 阻塞时长 | ≤200ms | >800ms | 关联 IO 负载分析 |
-- 实时计算双维指标(每分钟采集)
SELECT
ROUND(100.0 * (bw.buffers_written::NUMERIC / NULLIF(bw.buffers_written + bw.buffers_alloc, 0)), 2) AS wal_flush_success_rate,
ROUND((bw.checkpoint_write_time::NUMERIC / NULLIF(bw.checkpoints_timed, 0)) / 1000.0, 2) AS avg_checkpoint_block_ms
FROM pg_stat_bgwriter bw;
逻辑说明:
buffers_alloc表示未成功刷盘而被迫分配新页的次数,是刷盘失败的间接证据;checkpoint_write_time单位为毫秒,除以checkpoints_timed得平均写入耗时,再 /1000 转为秒级便于告警策略对齐。
决策联动流程
graph TD
A[采集指标] --> B{WAL成功率<98%?}
B -->|Yes| C[启动IO队列深度分析]
B -->|No| D{Checkpoint阻塞>800ms?}
D -->|Yes| E[触发slow-checkpoint告警并标记wal_sync_delay]
D -->|No| F[正常]
第五章:从单机监控到可演进持久化可观测平台的演进路径
单机时代:cAdvisor + Prometheus + Grafana 的轻量闭环
早期某电商订单服务集群仅含12台物理机,运维团队采用 cAdvisor 采集容器指标,Prometheus 每30秒拉取一次 /metrics 端点,Grafana 配置9个固定看板(如 CPU 使用率热力图、HTTP 5xx 错误率趋势)。所有数据存储在本地 --storage.tsdb.path=/data/prometheus,保留窗口设为7天。当磁盘写满导致 TSDB 崩溃后,团队手动执行 promtool tsdb analyze 定位到大量重复 label 的 http_request_duration_seconds_bucket 时间序列,通过 relabel_configs 过滤掉无业务意义的 instance="10.2.3.4:8080" 中的端口后,时间序列数下降62%。
分布式瓶颈:联邦架构失效与远程写入改造
随着微服务拆分为47个独立部署单元,原单体 Prometheus 实例内存飙升至32GB且查询延迟超8s。尝试 Prometheus Federation 后发现跨集群聚合引入2.3s网络抖动,且 /federate 接口无法按 service 标签动态分片。最终切换为 Prometheus Remote Write + VictoriaMetrics 集群:配置 remote_write 将指标按 job 标签分发至3个 VM 节点,启用 vm_promscrape 自动发现 Kubernetes Endpoints,并通过 vmalert 实现跨集群 SLO 计算——例如将支付服务的 P99 延迟阈值(≤800ms)与风控服务的 P99(≤300ms)联合告警。
持久化升级:时序+日志+链路的统一存储层
2023年Q3上线混合可观测平台,核心组件如下表所示:
| 数据类型 | 存储引擎 | 写入吞吐 | 查询延迟(P95) | 关键优化措施 |
|---|---|---|---|---|
| 指标 | VictoriaMetrics | 12M samples/s | 180ms | 启用 -search.latencyOffset=15s 缓解时钟漂移 |
| 日志 | Loki (v2.8) | 45K lines/s | 2.1s | 使用 periodic schema + boltdb-shipper |
| 链路 | Tempo (v2.2) | 8K traces/s | 3.4s | 启用 compactor 自动合并 block |
可演进设计:插件化采样与策略编排引擎
为应对未来新增的 eBPF 性能剖析数据,平台内置策略引擎支持 YAML 规则动态加载。例如以下采样策略定义:
policy: "high-cardinality-reduction"
match:
metric: "process_open_fds"
labels: ["pod", "namespace"]
action:
type: "head-sampling"
ratio: 0.1
fallback: "drop"
该策略经 CRD 注册后,OpenTelemetry Collector 的 filterprocessor 自动重载配置,无需重启进程。上线首周即降低 FD 指标存储开销 78%,同时保留关键异常 pod 的全量数据用于根因分析。
生产验证:双十一大促期间的弹性扩容实录
2023年双十一大促峰值期间,平台自动触发横向扩展:VictoriaMetrics 从3节点扩至9节点(基于 vm_cache_size_bytes 指标触发),Loki 的 ingester 组件根据 loki_ingester_streams_created_total 每分钟增长速率启动新副本。整个过程耗时47秒,期间所有 Grafana 看板保持 100% 可用性,告警规则评估延迟稳定在 120ms±15ms 区间内。
