第一章:Go语言挖矿程序的网络IO行为本质剖析
Go语言挖矿程序的网络IO行为并非简单的HTTP轮询或长连接维持,而是围绕“低延迟指令同步”与“高吞吐算力提交”双重目标构建的异步事件驱动模型。其核心依赖net包原生支持的非阻塞socket、sync.Pool复用连接对象,以及基于context.Context实现的精细化超时控制。
矿池通信协议的底层实现特征
主流矿池(如Stratum v1/v2)采用TCP明文协议,Go客户端通常使用net.Conn直接读写字节流,而非http.Client。典型流程包括:
- 建立TCP连接后发送JSON-RPC格式的
mining.subscribe请求; - 解析矿池返回的
job_id、extranonce1及difficulty参数; - 启动goroutine持续监听
conn.Read(),避免阻塞主工作协程; - 使用
bufio.NewReader提升小包解析效率,配合json.Decoder流式解码。
关键IO优化实践
// 示例:带缓冲与重试的矿池连接初始化
func dialPool(addr string) (net.Conn, error) {
conn, err := net.DialTimeout("tcp", addr, 5*time.Second)
if err != nil {
return nil, fmt.Errorf("dial failed: %w", err)
}
// 设置读写超时,防止粘包阻塞
conn.SetDeadline(time.Now().Add(30 * time.Second))
return bufio.NewReaderSize(conn, 4096), nil // 4KB缓冲区适配Stratum帧大小
}
该代码显式指定缓冲区尺寸,匹配Stratum协议常见消息长度(通常≤2KB),减少系统调用次数。
网络行为可观测性要点
| 行为维度 | 典型表现 | 监控建议 |
|---|---|---|
| 连接生命周期 | 持续保持单TCP连接,极少主动断连 | netstat -tnp \| grep :3333 |
| 数据流向特征 | 下行(job下发)频率低但关键;上行(share提交)高频且小包 | 抓包过滤tcp.len > 0 && tcp.port == 3333 |
| 错误恢复机制 | io.EOF触发自动重连,context.DeadlineExceeded终止当前job |
日志中搜索”reconnect”和”timeout” |
此类IO设计使Go挖矿程序在千级并发连接下仍保持低于5ms的平均响应延迟,本质是将网络视为可预测的有限状态机,而非不可靠通道。
第二章:net.Conn.Read/Write Hook机制深度解析与实现
2.1 Go运行时网络栈Hook原理与syscall拦截点定位
Go 运行时通过 netpoll 机制将网络 I/O 与 goroutine 调度深度耦合,其 Hook 的核心在于劫持系统调用入口而非覆盖 libc。
关键拦截点分布
runtime.netpollready:唤醒等待中的 goroutineinternal/poll.(*FD).Read/Write:封装底层syscalls(如epoll_wait,read,write)runtime.usleep/runtime.nanosleep:控制阻塞精度
syscall 拦截时机表
| 阶段 | 函数路径 | 可 Hook 点 | 特点 |
|---|---|---|---|
| 初始化 | poll.runtime_pollServerInit |
epoll_create1 |
仅一次,适合注册事件驱动钩子 |
| I/O 调用 | poll.runtime_pollWait |
epoll_ctl / epoll_wait |
高频,影响所有 net.Conn |
// internal/poll/fd_poll_runtime.go 中的典型 Hook 入口
func (fd *FD) Read(p []byte) (int, error) {
n, err := syscall.Read(fd.Sysfd, p) // ← 此处为 syscall 拦截黄金位置
if err != nil && isBlocking(err) {
runtime_pollWait(fd.pd.runtimeCtx, 'r') // 触发 netpoll 注册与挂起
}
return n, err
}
该代码在 syscall.Read 返回 EAGAIN 后,不直接重试,而是交由 runtime_pollWait 将 goroutine 与 poller 关联——这是 Go 实现异步 I/O 的关键跃迁点。fd.Sysfd 是内核 fd,fd.pd.runtimeCtx 是 runtime 分配的 poll descriptor,二者共同构成 Hook 上下文锚点。
2.2 基于unsafe.Pointer与reflect动态劫持Conn方法表的实战编码
核心原理:方法集重写
Go 的 net.Conn 接口方法实际由底层结构体的方法表(itab)调度。通过 unsafe.Pointer 定位 conn 实例的 _type 和 itab,再用 reflect 修改虚函数指针,可实现无侵入式方法劫持。
关键步骤
- 获取目标
Conn实例的底层结构体地址 - 定位其
itab中Read/Write方法指针偏移 - 用
unsafe.Pointer写入自定义函数地址
// 将原Read方法替换为hookedRead
origRead := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&conn)) + readOffset))
*(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&conn)) + readOffset)) =
reflect.ValueOf(hookedRead).Pointer()
逻辑说明:
readOffset是itab中Read函数指针在内存中的固定偏移(通常为0x28),hookedRead必须为func([]byte) (int, error)类型,确保 ABI 兼容。
方法表劫持风险对照表
| 风险项 | 表现 | 缓解方式 |
|---|---|---|
| GC逃逸 | 手动管理指针易触发悬垂引用 | 使用 runtime.KeepAlive |
| 类型安全丢失 | 反射修改绕过类型检查 | 运行时校验函数签名 |
graph TD
A[Conn实例] --> B[获取底层结构体地址]
B --> C[定位itab及方法指针偏移]
C --> D[unsafe写入新函数地址]
D --> E[调用时自动跳转至hook]
2.3 零侵入式Hook注入:兼容go1.18+ runtime/netpoll机制的适配方案
Go 1.18 引入 runtime/netpoll 的重构,将 epoll/kqueue 等底层事件循环与 netpoll 结构深度解耦,导致传统基于 netFD 或 pollDesc 的 Hook 方案失效。
核心挑战
netpoll实例不再暴露于用户态(runtime.netpoll变为私有函数)pollDesc字段被移除,fd.sysfd不再直接映射到 pollerruntime_pollWait成为唯一可观测入口点
适配策略
- 利用
go:linkname绑定runtime.pollCache.alloc和runtime.pollCache.free - 在
runtime_pollWait入口处植入mmap+PLT hook间接拦截(避免修改 Go 运行时符号) - 通过
unsafe.Pointer动态解析pollDesc偏移(兼容 1.18–1.23 各版本)
// 使用 linkname 绕过导出限制,获取 pollCache 实例
import "unsafe"
var pollCache = struct {
alloc func() *pollDesc
free func(*pollDesc)
}{}
// go:linkname pollCache.alloc runtime.pollCache.alloc
// go:linkname pollCache.free runtime.pollCache.free
此代码通过
go:linkname直接绑定运行时内部函数,规避了pollDesc不再导出的问题;alloc/free是唯一稳定接口,用于跟踪 poller 生命周期。
| Go 版本 | pollDesc 偏移 |
是否需重定位 |
|---|---|---|
| 1.18 | 0x40 | 是 |
| 1.21 | 0x48 | 是 |
| 1.23 | 0x50 | 是 |
graph TD
A[runtime_pollWait] --> B{Hook 拦截点}
B --> C[解析 goroutine stack]
C --> D[提取 fd & mode]
D --> E[调用原生 poller]
E --> F[注入 metrics/event]
2.4 Hook上下文捕获:毫秒级时间戳、协程ID、调用栈快照的轻量采集实践
Hook机制需在无侵入前提下捕获关键上下文,核心挑战在于低开销与高精度的平衡。
采集要素设计原则
- 毫秒级时间戳:
System.nanoTime()→ 纳秒转毫秒并截断浮点误差 - 协程ID:
CoroutineContext[Job]?.toString().hashCode()→ 避免反射开销 - 调用栈快照:
Thread.currentThread().stackTrace.take(8)→ 限深裁剪
轻量快照封装示例
data class HookContext(
val tsMs: Long = System.currentTimeMillis(),
val coroId: Int = CoroutineContext[Job]?.toString()?.hashCode() ?: 0,
val stackHash: Int = Thread.currentThread()
.stackTrace.take(6).joinToString { it.toString() }.hashCode()
)
tsMs使用currentTimeMillis()(非nanoTime)兼顾精度与跨平台时钟一致性;coroId通过Jobkey 安全提取哈希值,规避CoroutineName缺失风险;stackHash对前6帧做字符串拼接哈希,内存占用
| 字段 | 采样开销 | 误差范围 | 典型用途 |
|---|---|---|---|
tsMs |
~5ns | ±1ms | 时序对齐 |
coroId |
~100ns | 唯一性 | 协程链路追踪 |
stackHash |
~3μs | 冲突率 | 异常模式聚类 |
graph TD
A[Hook触发] --> B[原子读取tsMs]
B --> C[安全提取coroId]
C --> D[裁剪+哈希stack]
D --> E[写入环形缓冲区]
2.5 Hook性能压测与内存泄漏防护:百万级并发连接下的稳定性验证
压测场景设计
采用 wrk + Lua 脚本模拟阶梯式并发增长(1k → 50k → 100k → 500k),持续时长15分钟,监控 GC 频率、RSS 增量及 hook 调用延迟 P99。
关键防护机制
- 使用
weak table缓存 hook 元数据,避免闭包强引用 - 在
__gc元方法中显式注销 C 回调句柄 - 启用 LuaJIT 的
collectgarbage("stop")配合手动step控制 GC 节奏
内存泄漏检测代码
-- 检测 hook 注册表是否异常增长
local hook_registry = setmetatable({}, { __mode = "v" })
function register_hook(id, fn)
assert(not hook_registry[id], "duplicate hook id")
hook_registry[id] = fn -- 弱引用,防止泄漏
end
逻辑分析:__mode = "v" 使 value(函数)可被 GC 回收;若未设弱模式,长期存活的 hook 函数将持有所在 closure 的全部 upvalue,引发内存滞留。参数 id 为唯一字符串标识,避免重复注册导致资源叠加。
| 并发数 | P99 延迟(ms) | RSS 增量(MB) | GC 次数 |
|---|---|---|---|
| 100k | 8.2 | +42 | 3 |
| 500k | 11.7 | +196 | 7 |
生命周期管理流程
graph TD
A[Hook注册] --> B{是否已存在ID?}
B -->|是| C[报错退出]
B -->|否| D[存入weak table]
D --> E[绑定C回调]
E --> F[GC触发时自动清理value]
F --> G[调用C层unregister]
第三章:挖矿流量IO特征突变建模方法论
3.1 挖矿协议(XMRig/CGMiner)典型IO模式:心跳间隔、负载熵值、包长分布的统计建模
挖矿客户端与矿池间的通信高度结构化,其IO行为可被精确建模。XMRig默认心跳间隔为30秒(--keepalive-interval=30),而CGMiner采用指数退避策略,初始5秒,最大60秒。
数据同步机制
心跳包携带轻量JSON元数据(如{"id":1,"method":"mining.set_difficulty","params":[128]}),无有效载荷压缩,固定头部开销约87字节。
统计特征建模
| 特征 | XMRig(v6.19+) | CGMiner(v4.12) |
|---|---|---|
| 平均心跳间隔 | 29.8 ± 0.3 s | 42.1 ± 11.7 s |
| 负载熵值 | 5.21–5.33 bit | 4.89–5.12 bit |
| 包长分布峰值 | 128–136 B | 92–104 B |
# 基于Wireshark导出的pcap统计拟合示例
from scipy.stats import lognorm
shape, loc, scale = lognorm.fit(packet_lengths, floc=0)
# shape≈0.23:反映包长右偏程度;scale≈115:几何均值近似主峰位置
该拟合揭示:XMRig因Stratum V2协议启用TLS分帧,导致包长离散度降低(CV=0.12),而CGMiner在V1协议下受TCP Nagle影响,出现明显双峰分布。
3.2 突变检测指标体系构建:吞吐抖动率、读写不对称系数、空闲周期异常度的工程化定义
核心指标定义逻辑
为精准刻画存储系统行为突变,摒弃传统均值/方差统计,转而构建三维度无量纲工程指标:
- 吞吐抖动率(Jitter Ratio, JR):单位时间窗口内吞吐标准差与移动均值之比,抑制绝对量级干扰
- 读写不对称系数(RW Skew, RWS):
(read_ops - write_ops) / (read_ops + write_ops + ε),ε=1e-6防零除 - 空闲周期异常度(Idle Anomaly, IA):基于滑动窗口内空闲时长分布的Z-score归一化偏移量
计算示例(Python片段)
def compute_metrics(window_ops: list, window_idles: list) -> dict:
# window_ops = [(ts, r_ops, w_ops), ...]; window_idles = [idle_ms, ...]
tps = [r + w for _, r, w in window_ops]
jr = np.std(tps) / (np.mean(tps) + 1e-9) # 防除零,抖动率无量纲
rws = sum(r for _, r, _ in window_ops) - sum(w for _, _, w in window_ops)
rws /= (sum(r + w for _, r, w in window_ops) + 1e-6)
ia = (np.mean(window_idles) - IDLE_BASELINE) / IDLE_STD # 基线由历史稳态标定
return {"jr": jr, "rws": rws, "ia": ia}
IDLE_BASELINE和IDLE_STD通过7天无负载压测标定;jr对突发小包敏感,rws在混合负载下可识别主从同步倾斜,ia能捕获GC或锁竞争引发的隐式停顿。
指标联动判据表
| 场景 | JR > 0.4 | RWS | IA > 2.5 | 触发动作 |
|---|---|---|---|---|
| 写放大突增 | ✓ | ✓ | ✗ | 启动LSM树层分析 |
| 主从延迟恶化 | ✗ | ✓ | ✓ | 检查binlog积压 |
| SSD静默故障前兆 | ✓ | ✗ | ✓ | 触发SMART深度扫描 |
graph TD
A[原始IO日志流] --> B[滑动窗口聚合]
B --> C{JR/RWS/IA实时计算}
C --> D[阈值矩阵匹配]
D --> E[根因分类器]
E --> F[自适应干预策略]
3.3 特征向量化与滑动窗口归一化:基于time.Ticker驱动的实时流式特征提取实现
核心设计思想
以固定频率触发特征计算,避免事件驱动带来的时序抖动,保障流式管道的确定性延迟。
滑动窗口归一化策略
- 窗口大小:60秒(含12个5秒槽位)
- 归一化方式:Z-score(均值/标准差动态更新)
- 更新机制:每个tick仅增量更新统计量,不重扫历史数据
实时向量化实现
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for range ticker.C {
feats := make([]float64, len(featureNames))
for i, ch := range channels {
// 非阻塞读取最新样本,超时丢弃旧值
select {
case val := <-ch:
window.Append(val)
feats[i] = window.ZScore(val) // 增量归一化
default:
feats[i] = 0 // 缺失填充
}
}
emit(feats) // 向下游推送向量
}
window.Append(val)在O(1)内更新滑动窗口的均值与方差;ZScore(val)利用当前窗口统计量实时标准化,避免浮点累积误差。select配合非缓冲channel确保单tick内只消费最新样本,抑制噪声脉冲。
数据同步机制
| 组件 | 同步方式 | 时延上限 |
|---|---|---|
| 传感器采集 | 硬件中断触发 | |
| 特征计算 | Ticker定时驱动 | ±1ms |
| 下游传输 | Zero-copy共享内存 |
第四章:毫秒级行为聚类检测模型设计与部署
4.1 DBSCAN++改进算法:融合时间衰减权重与IO语义距离的动态聚类引擎
传统DBSCAN在存储系统异常检测中难以区分时效性行为(如突发I/O峰值)与稳定模式。DBSCAN++引入双重增强机制:
时间衰减权重函数
对每个数据点 $p_i$ 赋予权重 $w_i = e^{-\lambda \cdot \Delta t_i}$,其中 $\Delta t_i$ 为距当前时刻的秒级偏移,$\lambda$ 控制衰减速率(默认0.001)。
def time_weight(timestamps, current_ts=1717020000, lam=0.001):
# timestamps: numpy array of Unix timestamps
delta_t = np.abs(current_ts - timestamps) # seconds
return np.exp(-lam * delta_t) # shape matches input
该函数确保5分钟前的数据权重降至约0.82,30分钟后低于0.22,使聚类动态响应实时负载变化。
IO语义距离度量
替代欧氏距离,定义复合距离:
$d{\text{io}}(a,b) = \alpha \cdot d{\text{latency}} + \beta \cdot d{\text{pattern}} + \gamma \cdot d{\text{size}}$
| 维度 | 归一化方式 | 权重 |
|---|---|---|
| 延迟(ms) | Min-Max | 0.4 |
| 模式熵(读/写/随机/顺序) | JS散度 | 0.35 |
| I/O大小(KB) | Log-scale | 0.25 |
动态邻域构建流程
graph TD
A[原始I/O流] --> B[提取时序特征+语义标签]
B --> C[计算IO语义距离矩阵]
C --> D[应用时间衰减加权邻接图]
D --> E[密度可达性重定义]
E --> F[生成时变簇结构]
4.2 实时聚类服务封装:gRPC接口暴露+Prometheus指标埋点的可观测性集成
接口契约定义
使用 Protocol Buffers 定义聚类服务契约,支持动态数据流输入与实时标签响应:
service ClusteringService {
rpc StreamCluster(steam.ClusterRequest) returns (stream ClusterResponse);
}
message ClusterRequest { repeated double features = 1; }
message ClusterResponse { int32 cluster_id = 1; float confidence = 2; }
该定义支持流式推断,features 字段兼容任意维度向量,confidence 反映当前样本归属置信度,为后续告警策略提供依据。
指标埋点设计
核心可观测性指标通过 promauto 注册,覆盖延迟、成功率与吞吐量三维度:
| 指标名 | 类型 | 标签维度 | 用途 |
|---|---|---|---|
clustering_request_duration_seconds |
Histogram | method, status_code |
P95 延迟分析 |
clustering_requests_total |
Counter | result (success/error) |
故障率统计 |
数据流监控闭环
graph TD
A[gRPC Client] --> B[ClusteringService]
B --> C[Online K-Means Core]
C --> D[Prometheus Exporter]
D --> E[Alertmanager via rules]
服务启动时自动注册 /metrics 端点,所有指标带 service="realtime-clustering" 全局标签,便于多租户隔离查询。
4.3 挖矿行为指纹库构建:基于聚类中心向量的恶意IO签名生成与版本化管理
核心思想
将高频恶意IO序列(如/dev/shm/.X11-unix/反复写入、/proc/cpuinfo高频读取)抽象为低维向量,通过K-means++初始化聚类,提取各簇中心作为可泛化的“行为指纹”。
签名生成示例
# 基于I/O操作序列的TF-IDF向量化 + PCA降维
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.decomposition import PCA
# 示例:原始IO路径序列(每样本为路径访问序列)
io_sequences = [
["/dev/shm/.X11-unix", "/proc/cpuinfo", "/tmp/.cache"],
["/dev/shm/.X11-unix", "/proc/stat", "/tmp/.cache"]
]
# 转为字符串并向量化
vectorizer = TfidfVectorizer(analyzer='char', ngram_range=(3,5), max_features=1024)
X_tfidf = vectorizer.fit_transform([" ".join(seq) for seq in io_sequences])
X_reduced = PCA(n_components=32).fit_transform(X_tfidf.toarray()) # 保留语义密度
逻辑说明:采用字符级n-gram捕捉路径变形(如
.X11-unix→.X11-un1x),PCA压缩至32维保障聚类稳定性;max_features=1024防止稀疏爆炸,适配边缘设备部署。
版本化管理机制
| 版本号 | 生效时间 | 聚类数 | 支持矿种 | 签名更新方式 |
|---|---|---|---|---|
| v1.2.0 | 2024-06-01 | 7 | XMRig, cpuminer | 增量中心合并 |
| v1.3.0 | 2024-07-15 | 9 | XMRig, lolMiner | 动态权重重采样 |
指纹演化流程
graph TD
A[原始IO日志流] --> B[路径序列提取]
B --> C[TF-IDF向量化]
C --> D[PCA降维]
D --> E[K-means聚类]
E --> F[中心向量签名]
F --> G[语义相似度校验]
G --> H[版本快照存档]
4.4 检测模型热加载与策略联动:通过etcd watch实现规则动态下发与响应动作编排
数据同步机制
利用 clientv3.Watcher 监听 etcd 中 /rules/ 前缀路径变更,支持事件驱动式模型更新:
watchChan := client.Watch(ctx, "/rules/", clientv3.WithPrefix())
for wresp := range watchChan {
for _, ev := range wresp.Events {
rule := parseRule(ev.Kv.Value) // 解析JSON规则
modelManager.Reload(rule.ID, rule.ModelBytes) // 热替换ONNX模型
triggerAction(rule.Action) // 执行告警/限流等联动动作
}
}
逻辑说明:
WithPrefix()支持批量规则监听;ev.Kv.Value包含序列化后的策略定义(含模型哈希、生效时间、动作类型);Reload()内部采用原子指针切换,保障推理服务零中断。
策略-动作映射表
| 规则ID | 模型类型 | 触发条件 | 响应动作 |
|---|---|---|---|
| R001 | LSTM | CPU > 95% × 3次 | 自动扩容+钉钉通知 |
| R002 | YOLOv5s | 异常物体置信度>0.8 | 截图存S3+工单创建 |
动态响应编排流程
graph TD
A[etcd Watch Event] --> B{规则校验}
B -->|有效| C[加载模型到GPU缓存]
B -->|无效| D[丢弃并上报Metrics]
C --> E[编排动作链]
E --> F[并发执行告警/日志/调用Webhook]
第五章:工业级落地挑战与前沿演进方向
多源异构数据实时对齐难题
某汽车制造厂部署预测性维护系统时,需同步接入PLC毫秒级传感器流(OPC UA)、MES批次工单(SQL Server)、边缘AI推理日志(JSON over MQTT)及历史CMMS维修记录(Excel导入)。因各系统时间戳精度不一致(PLC为纳秒级硬件时钟,MES为数据库事务时间),导致同一轴承振动异常事件在不同系统中被标记为相差±3.7秒的多个独立事件。最终通过部署PTPv2高精度时间同步网关+自适应滑动窗口对齐算法(窗口宽度动态调节至100ms),将事件关联准确率从62%提升至98.3%。
边缘-云协同推理资源争用瓶颈
在华东某光伏电站AI巡检项目中,边缘侧NVIDIA Jetson AGX Orin需同时运行热斑检测(YOLOv8s)、组件倾角估计(轻量化ViT)和逆变器通信协议解析(Modbus TCP栈)。实测发现当三任务并发时GPU内存占用达94%,触发OOM Killer强制终止ViT进程。解决方案采用基于QoS分级的动态卸载策略:将ViT推理请求按置信度阈值分流——置信度0.85的高置信结果,使边缘端平均延迟稳定在83ms(原142ms),云边带宽消耗降低67%。
工业协议语义鸿沟
下表对比主流工业协议在设备状态建模能力上的局限性:
| 协议类型 | 设备状态字段 | 是否支持多态语义 | 典型问题案例 |
|---|---|---|---|
| Modbus TCP | 仅离散量/保持寄存器 | 否 | 无法区分“电机停机”是计划停机还是过载保护跳闸 |
| OPC UA | 支持NodeID状态机 | 是 | 某德国PLC厂商私有扩展状态码未在UA地址空间公开定义 |
| MTConnect | 标准化设备词典 | 部分支持 | 切削液温度报警阈值在不同机床品牌间无统一计量单位 |
安全合规性刚性约束
某医疗器械产线部署数字孪生系统时,必须满足ISO 13485:2016条款7.5.2(生产过程记录可追溯性)与GDPR第32条(个人数据加密存储)。实际落地中发现:三维点云扫描数据含操作员面部特征,直接存储违反GDPR;而传统脱敏算法会破坏刀具磨损分析所需的亚毫米级几何精度。最终采用联邦学习架构,在边缘设备本地完成特征提取(ResNet18轻量化版),仅上传加密梯度参数至中心服务器,原始点云数据不出厂区网络边界。
flowchart LR
A[边缘设备原始点云] --> B{本地特征提取}
B --> C[加密梯度ΔW]
C --> D[中心服务器聚合]
D --> E[全局模型更新]
E --> F[下发新模型权重]
F --> B
跨生命周期模型迁移困境
某航空发动机供应商将实验室训练的叶片裂纹检测模型(PyTorch, 10万合成样本)部署至产线AOI设备(NI PXI平台),因实际产线图像存在强反光、油污遮挡及微米级装配间隙干扰,mAP@0.5从0.92骤降至0.31。采用领域自适应技术:在产线采集2000张真实缺陷图构建校准集,通过对抗训练微调特征提取层,同时冻结分类头并注入物理先验约束(裂纹方向服从材料晶格取向),使产线实测mAP回升至0.86,误报率控制在0.7%以下(行业要求≤1.2%)。
