一、故障背景
环境信息:
操作系统:openEuler 22.03 LTS SP4(内核
5.10.0-216.12.3.0119.oe22003sp4.x86_64)CPU 架构:x86_64(TaiShan 200 机型也有 aarch64 版本,排查命令通用)
业务角色:MySQL 8.0 主库 + Redis 6.2 集群(6 节点)
硬件:TaiShan 200 / 256GB 内存 / NVMe SSD × 4
系统自带服务:A-Tune 已启用(
atuned服务 running,profile 为database)——本次故障的隐藏主角故障起因:DBA 周末按「CentOS 7 优化文档」一次性改了
fstab、sysctl.conf、limits.conf三个文件,周一重启后服务器进不了系统。
故障现象:
重启后卡在紧急模式(fstab 挂载失败)
紧急模式修复后能启动,但 MySQL 报
Too many open filesMySQL 启动后 Redis 报
Cannot assign requested address(端口耗尽)最诡异:
sysctl -p明明设置成功的参数,过几分钟自己变回去了整个集群连锁故障
三个配置文件互相独立但连环影响,还叠加了 openEuler 特有的A-Tune 自动调优回写,必须按「启动顺序 + 服务依赖」逐层修复。
二、排查与修复全过程
阶段 1:fstab 配置错误 → 系统无法启动
1.1 故障现象
重启后控制台输出:
[ OK ] Started Dispatch Password Requests to Console Directory Watch. [FAILED] Failed to mount /data. See 'systemctl status data.mount' for details. [DEPEND] Dependency failed for Local File Systems. [DEPEND] Dependency failed for MySQL 8.0 database server. ... Reloading system manager configuration Welcome to emergency mode! After logging in, type "journalctl -xb"... Give root password for maintenance: _
1.2 排查步骤
# ① 输入 root 密码进入紧急模式 shell,先确认系统版本(养成习惯,先定位发行版) cat /etc/os-release # 输出: # NAME="openEuler" # VERSION="22.03 LTS SP4" # ID="openEuler" # VERSION_ID="22.03" # PRETTY_NAME="openEuler 22.03 LTS SP4" # ANSI_COLOR="0;31" # ② 查看 systemd 挂载失败详情 systemctl status data.mount -l --no-pager # 关键输出: # ● data.mount - /data # Active: failed (Result: exit-code) # Process: 456 ExecMount=/usr/bin/mount /data (code=exited, status=32) # Aug 26 09:14:22 db01 mount[456]: mount: /data: wrong fs type, bad option, # bad superblock on /dev/mapper/vg_data-lv_data, missing codepage # or helper program, or other error. # ③ 查看 fstab 中 /data 行 grep "/data" /etc/fstab # 实际输出(DBA 添加): # /dev/mapper/vg_data-lv_data /data xfs defaults,noatime,nodiratime,discard 0 0 # ↑↑↑ 问题在这里 # ④ 确认实际文件系统类型 blkid /dev/mapper/vg_data-lv_data # 实际输出: # /dev/mapper/vg_data-lv_data: UUID="a1b2c3d4-..." TYPE="ext4" # ↑↑↑↑ 实际是 ext4! # ⑤ 为什么 DBA 会写 xfs?——因为 openEuler 22.03 安装器(Anaconda)默认根分区就是 xfs, # DBA 想当然认为所有分区都是 xfs,没核对这块手动建的 /data findmnt -no FSTYPE / # 输出:xfs ← 根分区确实是 xfs,误导了 DBA
1.3 根因分析
| 错误 | 原因 |
|---|---|
xfs写成了 ext4 盘的文件系统类型 | DBA 看到 openEuler 根分区默认 xfs,就以为/data也是 xfs,没跑blkid核对 |
discard选项 | 这块 NVMe 盘做了硬件 RAID(LSI 9460),RAID 卡未透传 UNMAP/TRIM,mount 时报 bad option |
📌openEuler 与 CentOS 差异提示:openEuler 22.03 根分区默认xfs(CentOS 7 也是 xfs,但 Ubuntu 默认 ext4);硬件 RAID 卡后面
discard基本都不可用,云盘/直通盘才有效。
1.4 修复
# ① 先用正确的类型手动挂载验证 mount -t ext4 /dev/mapper/vg_data-lv_data /data echo $? # 0 → 挂载成功 # ② 修正 fstab(备份后编辑) cp /etc/fstab /etc/fstab.bak.$(date +%F) # 修正后的行(用 UUID 更稳健): # UUID=a1b2c3d4-e5f6-7890-abcd-ef1234567890 /data ext4 defaults,noatime,nodiratime 0 2 # ③ 验证 fstab 语法(不验证直接重启会再次进紧急模式) findmnt --verify--verbose # 必须全部输出 "SUCCESS",不能有 "FATAL" 或 "ERROR" # ④ 重新挂载所有 fstab 项 mount -a && echo $? # 0 → 全部成功 # ⑤ 验证挂载选项生效 mount | grep /data # 输出应包含 rw,noatime,nodiratime
阶段 2:sysctl 配置错误 + A-Tune 回写(openEuler 特有坑)
2.1 故障现象
fstab 修复后系统正常启动,但:
Redis 报错:
Cannot assign requested address(端口耗尽)业务报错:
nf_conntrack: table full, dropping packet最诡异:
sysctl -p设置成功的参数,几分钟后自己变回去了
2.2 排查步骤
# ① 查看 Redis 报错 tail -50 /var/log/redis/redis-6379.log # 关键行: # # Error condition on socket for UPDATE: Cannot assign requested address # ↑ 可用端口耗尽的经典症状 # ② 查看当前端口范围与 conntrack sysctl net.ipv4.ip_local_port_range net.netfilter.nf_conntrack_max # 实际输出: # net.ipv4.ip_local_port_range = 32768 60999 ← 还是默认值 # sysctl: cannot stat /proc/sys/net/netfilter/nf_conntrack_max: No such file or directory # ↑↑↑ 模块没加载,路径不存在 # ③ 检查模块加载状态 lsmod | grep conntrack # 输出:(空)→ nf_conntrack 没加载! # ④ 查看 DBA 改的 sysctl 配置 cat /etc/sysctl.conf | grep -v "^#" | grep -v "^$" # DBA 添加的内容: # net.ipv4.ip_local_port_range = 1024 65535 ← 问题1:和 22/3306/6379 等服务端口冲突 # net.netfilter.nf_conntrack_max = 1048576 ← 问题2:模块没加载时此行报错被忽略 # net.core.somaxconn = 1024 # fs.file-max = 2000000 # ⑤ 手动执行看真实报错 sysctl -p2>&1 # 输出: # net.ipv4.ip_local_port_range = 1024 65535 # sysctl: cannot stat /proc/sys/net/netfilter/nf_conntrack_max: No such file or directory # net.core.somaxconn = 1024 # fs.file-max = 2000000 # ⑥ 加载模块后设置成功 modprobe nf_conntrack sysctl -p 2>&1 echo "nf_conntrack" > /etc/modules-load.d/conntrack.conf # 开机自动加载 # ⑦ 5 分钟后验证 —— 诡异现象出现了! sysctl net.core.somaxconn # 输出:net.core.somaxconn = 128 ← ❌ 又变回去了!! sysctl fs.file-max # 输出:fs.file-max = 98840 ← ❌ 也变回去了!!
2.3 深挖:为什么参数会「自己变回去」?
这是openEuler 独有的坑:A-Tune 自动调优服务会根据负载 profile 动态回写 sysctl 参数。
# ① 检查 A-Tune 服务状态 systemctl status atuned --no-pager # 输出: # ● atuned.service - A-Tune daemon # Active: active (running) since Mon 2026-08-26 09:20:11 CST; 2h ago # ← 在运行! # ② 查看 A-Tune 当前应用的 profile atune-adm list # 输出: # ++++++++++++++++ System Info ++++++++++++++++ # os_info: openEuler 22.03 LTS SP4 # kernel: 5.10.0-216.12.3.0119.oe22003sp4.x86_64 # platform: x86_64 # ++++++++++++++++++++++++++++++++++++++++++++ # ++++++++++++++++ Profile ++++++++++++++++++++ # active_profile : database ← ❗ 当前激活了 database profile! # ++++++++++++++++++++++++++++++++++++++++++++ # ③ 查看 database profile 都会改什么 atune-adm info database # 输出(节选,重点关注 sysctl 相关): # [sysctl] # vm.dirty_ratio = 20 # vm.dirty_background_ratio = 10 # vm.swappiness = 10 # kernel.sched_autogroup_enabled = 0 # net.core.somaxconn = 128 ← 💥 找到了!profile 里的 somaxconn=128 # fs.file-max = 98840 ← 💥 file-max 也被 profile 锁定 # net.ipv4.tcp_max_syn_backlog = 128 # # [disk] # dataMountPoint = queue/scheduler = mq-deadline # ← 连磁盘调度器都会被改! # ④ 查看 A-Tune 日志确认回写动作 journalctl -u atuned --since"1 hour ago"--no-pager | grep-iE"sysctl|apply|exec" | tail -20 # 输出(节选): # Aug 26 10:42:15 db01 atuned[1234]: [INFO] [engine/sysctl.go:120] Apply sysctl config for profile: database # Aug 26 10:42:15 db01 atuned[1234]: [INFO] [engine/sysctl.go:135] Set kernel param: net.core.somaxconn = 128 # Aug 26 10:42:15 db01 atuned[1234]: [INFO] [engine/sysctl.go:135] Set kernel param: fs.file-max = 98840 # ↑↑↑ 石锤:atuned 每 5 分钟(负载评估周期)把 profile 值写回去
2.4 修复:sysctl 正确配置 + 与 A-Tune 共存策略
# === 第一步:重写 /etc/sysctl.conf(针对 256GB 内存 + MySQL/Redis 场景) === cp /etc/sysctl.conf /etc/sysctl.conf.bak.$(date +%F) cat > /etc/sysctl.conf << 'EOF' # === 网络参数 === # 端口范围:避开 1-10000(特权端口 + 常见服务端口 22/80/443/3306/6379) net.ipv4.ip_local_port_range =10000 65535 # TCP 连接相关 net.core.somaxconn = 32768 net.ipv4.tcp_max_syn_backlog = 8192 net.ipv4.tcp_max_tw_buckets = 1048576 net.ipv4.tcp_fin_timeout = 15 net.ipv4.tcp_tw_reuse = 1 # 缓冲区(256GB 内存场景) net.core.rmem_max = 67108864 net.core.wmem_max = 67108864 net.ipv4.tcp_rmem = 4096 87380 67108864 net.ipv4.tcp_wmem = 4096 65536 67108864 # === conntrack(前提:modprobe nf_conntrack 已加载) === net.netfilter.nf_conntrack_max = 2621440 net.netfilter.nf_conntrack_tcp_timeout_established = 1800 net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30 # === 文件描述符 === fs.file-max = 2097152 # === 内存参数 === vm.swappiness =1 vm.overcommit_memory =1 # === INOTIFY(容器/多实例场景必备) === fs.inotify.max_user_instances =8192 fs.inotify.max_user_watches =1048576 EOF # === 第二步:处理 A-Tune 冲突(三选一,生产推荐方案 B) === # ── 方案 A:直接停用 A-Tune(不适合,会失去欧拉调优能力)── # systemctl disable --now atuned # ── 方案 B(推荐):自定义 profile,把 DBA 参数固化进 A-Tune ── # ① 基于 database profile 导出现有配置做模板 atune-adm info database > /tmp/database-profile.txt # ② 在 /etc/atk/conf.d/ 下创建自定义 profile(A-Tune 支持自定义 profile 目录) mkdir -p /etc/atk/conf.d cat > /etc/atk/conf.d/mysql-redis.yaml << 'EOF' mysql-redis: schedule_policy: performance sysctl: net.core.somaxconn: -32768 fs.file-max: -2097152 net.ipv4.ip_local_port_range: -"10000 65535" net.netfilter.nf_conntrack_max: -2621440 vm.swappiness: -1 vm.dirty_ratio: -10 vm.dirty_background_ratio: -3 disk: /dev/mapper/vg_data-lv_data: queue/read_ahead_kb: -1024 queue/scheduler: - mq-deadline EOF # ③ 加载并激活自定义 profile atune-adm list # 确认 mysql-redis 出现在 profile 列表 atune-adm profile mysql-redis # 激活 atune-adm list | grep active_profile # 输出:active_profile : mysql-redis ← 以后 A-Tune 回写的就是我们自己的值了 # ── 方案 C:只关掉 A-Tune 的定时回写,保留服务 ── # 编辑 /etc/ata/ata.ini,把 period 相关采集间隔调大或禁用 sysctl 引擎 # (细节随 A-Tune 版本变化,不如方案 B 可控) # === 第三步:验证长效性 === sysctl -p sleep 360 # 等过 A-Tune 的回写周期 sysctl net.core.somaxconn fs.file-max net.ipv4.ip_local_port_range # 输出: # net.core.somaxconn = 32768 ← ✅ 不再被改回 # fs.file-max = 2097152 ← ✅ # net.ipv4.ip_local_port_range = 10000 65535 ← ✅
2.5 openEuler sysctl 与 CentOS 的差异速查
| 差异点 | CentOS 7 | openEuler 22.03 |
|---|---|---|
| 内核版本 | 3.10 | 5.10(参数面更宽,如bpf、cgroup v2相关) |
| A-Tune | 无 | 有,且会动态回写 sysctl(最大坑点) |
| sysctl.d 加载顺序 | /usr/lib→/run→/etc | 相同,但自带/usr/lib/sysctl.d/内置文件更多 |
nf_conntrack | 手动 modprobe | 相同,需手动加载 + 写modules-load.d |
| 调优框架 | tuned | A-Tune(默认启用),tuned 也可用但双调优框架并存易冲突 |
⚠️重要:如果系统同时启用了
tuned和atuned,两者都会改内核参数。生产建议二选一,欧拉环境优先 A-Tune + 自定义 profile。
阶段 3:limits.conf 配置错误 → MySQLToo many open files
3.1 故障现象
sysctl 稳定后,MySQL 启动失败:
systemctl status mysqld --no-pager-l # 输出: # ● mysqld.service - MySQL 8.0 database server # Active: failed (Result: exit-code) # Aug 26 09:35:12 db01 mysqld[9876]: 2026-08-26T01:35:12.438678Z 0 [ERROR] # [MY-010191] Can't open dir '/data/mysql/', errno: 24, error: Too many open files
3.2 排查步骤
# ① 当前会话 fd 限制 ulimit -n # 输出:1024 # ② 查看 DBA 改的 limits.conf grep -v "^#" /etc/security/limits.conf | grep -v "^$" # DBA 添加: # * soft nofile 2000000 # * hard nofile 2000000 # * soft nproc 65535 # * hard nproc 65535 # mysql soft nofile 2000000 # mysql hard nofile 2000000 # ③ 新开 SSH 会话验证(limits.conf 只对新 PAM 会话生效) ssh root@db01 'ulimit -n' # 输出:2000000 ← ✅ SSH 会话生效了! # ④ 但 MySQL 是 systemd 启动的 —— 查看 systemd 服务的实际限制 systemctl show mysqld | grep-iE"^LimitNOFILE|^TasksMax" # 输出: # LimitNOFILE=1024 ← ❌ limits.conf 对 systemd 服务无效! # TasksMax=480 ← ❌ 还有进程数限制(systemd 默认 15% 的 pids.max)! # ⑤ 确认 MySQL 进程的真实限制(如果进程已拉起) MYSQL_PID=$(pgrep -x mysqld 2>/dev/null) [ -n"$MYSQL_PID" ] && grep-E"Max open files|Max processes" /proc/$MYSQL_PID/limits # Max open files 1024 1024 files # Max processes 480 480 processes
3.3 根因分析
| 层次 | 原因 |
|---|---|
| 直接原因 | systemd 启动的 mysqld 的LimitNOFILE默认 1024 |
| 配置原因 | DBA 只改了limits.conf(PAM 会话层),systemd 服务不读它 |
| openEuler 加重因素 | openEuler 22.03 的 systemdDefaultTasksMax默认 15%(本机 256GB/128核 → pids.max≈480),DBA 想改nproc 65535但 systemd 侧根本没放开 |
3.4 修复
# === 第一步:systemd override(核心修复) === # ① 创建 override systemctl edit mysqld # 在打开的编辑器中写入: # [Service] # LimitNOFILE=2000000 # LimitNPROC=65535 # TasksMax=infinity # LimitMEMLOCK=infinity # 或者直接写文件(等价): mkdir -p /etc/systemd/system/mysqld.service.d cat > /etc/systemd/system/mysqld.service.d/limits.conf << 'EOF' [Service] LimitNOFILE=2000000 LimitNPROC=65535 TasksMax=infinity LimitMEMLOCK=infinity EOF # ② 重载并重启 systemctl daemon-reload systemctl restart mysqld # ③ 验证 systemctl show mysqld | grep -E "^LimitNOFILE|^TasksMax" # LimitNOFILE=2000000 # TasksMax=infinity MYSQL_PID=$(pgrep -x mysqld) grep -E "Max open files|Max processes" /proc/$MYSQL_PID/limits # Max open files 2000000 2000000 files ✅ # Max processes 65535 65535 processes ✅
# === 第二步:修正 limits.conf(保持 SSH 会话层正确) === cat > /etc/security/limits.conf << 'EOF' # root root soft nofile 2000000 root hard nofile 2000000 root soft nproc 65535 root hard nproc 65535 # mysql mysql soft nofile 2000000 mysql hard nofile 2000000 mysql soft nproc 65535 mysql hard nproc 65535 # 通配(优先级最低,放最后) * soft nofile 1048576 * hard nofile 1048576 * soft nproc 65535 * hard nproc 65535 EOF # === 第三步:确认 PAM 加载了 pam_limits(openEuler 路径与 CentOS 略有不同) === grep -rn "pam_limits" /etc/pam.d/system-auth /etc/pam.d/postlogin 2>/dev/null # 必须输出: # /etc/pam.d/system-auth:session required pam_limits.so # /etc/pam.d/postlogin:session required pam_limits.so # 缺哪行补哪行: # echo 'session required pam_limits.so' >> /etc/pam.d/system-auth # === 第四步:检查 limits.d 目录有没有覆盖(openEuler 默认无 20-nproc.conf, # 但如果有人抄过 CentOS 的配置可能多出来) === ls -l /etc/security/limits.d/ # 如果存在 20-nproc.conf 且值较小(如 4096),通配符 * 会被它覆盖: # cat /etc/security/limits.d/20-nproc.conf → 建议删除或调大 # === 第五步:全机 systemd 服务统一兜底(可选,谨慎) === # /etc/systemd/system.conf 与 user.conf 中: # DefaultLimitNOFILE=1048576 # DefaultTasksMax=80% # 改完 systemctl daemon-reexec 生效(不影响运行中服务,新服务生效)
3.5 limits 排查速查表(openEuler 版)
| 错误现象 | 根因 | 修复 |
|---|---|---|
SSH 会话ulimit -n还是 1024 | limits.conf 只对新会话生效 | 新开 SSH 验证 |
| systemd 服务不生效 | 服务不读 limits.conf | systemctl edit <svc>写LimitNOFILE |
LimitNOFILE设了没生效 | 忘了daemon-reload | reload 后restart(restart 才重新 fork) |
nproc放开仍报进程数不够 | systemdTasksMax限制 | override 里TasksMax=infinity |
通配符*不生效 | limits.d/*.conf优先级更高 | 检查/etc/security/limits.d/ |
| Docker 容器内不生效 | 容器有独立 ulimit | docker run --ulimit nofile=... |
MySQL 里show variables like 'open_files_limit'仍 1024 | MySQL 自身变量没跟上 | my.cnf加open_files_limit=2000000,或直接依赖 systemd 值 |
三、三方联动验证脚本(openEuler 版)
#!/bin/bash # === openEuler 配置体检:fstab + sysctl + limits + A-Tune 四项全检 === echo "========================================" echo " 0. 系统信息" echo "========================================" grep PRETTY_NAME /etc/os-release uname -r systemctl is-active atuned && atune-adm list | grep active_profile echo "" echo "========================================" echo " 1. FSTAB 检查" echo "========================================" findmnt --verify--verbose2>&1 | grep-E"FATAL|ERROR" \ && echo"❌ fstab 有错误" || echo"✅ fstab 语法正确" mount -a -v --fake 2>&1 | grep -v "^$" | head -20 # 干跑一次全部挂载 echo "" echo "========================================" echo " 2. SYSCTL 检查" echo "========================================" declare -AEXPECTED=( ["net.ipv4.ip_local_port_range"]="1000065535" ["net.core.somaxconn"]="32768" ["net.netfilter.nf_conntrack_max"]="2621440" ["fs.file-max"]="2097152" ["vm.swappiness"]="1" ) lsmod | grep -q nf_conntrack && echo"✅ nf_conntrack 已加载" || echo"❌ nf_conntrack 未加载" for param in "${!EXPECTED[@]}"; do actual=$(sysctl -n "$param" 2>/dev/null || echo "NOT_FOUND") if [ "$actual"="${EXPECTED[$param]}" ]; then echo"✅ $param = $actual" else echo"❌ $param: expected=[${EXPECTED[$param]}] actual=[$actual]" fi done echo "" echo "========================================" echo " 3. A-TUNE 冲突检查(openEuler 特有)" echo "========================================" if systemctl is-active --quiet atuned; then PROFILE=$(atune-adm list 2>/dev/null | grep active_profile | awk -F: '{print $2}' | xargs) echo"⚠️ atuned 运行中,当前 profile: $PROFILE" # 检查 profile 是否包含与 /etc/sysctl.conf 冲突的键 atune-adm info "$PROFILE"2>/dev/null | grep-A50"\[sysctl\]" | \ grep-oE"^[a-z].*=" | sed's/ =//' | while read -r key; do ifgrep-q"^$key" /etc/sysctl.conf 2>/dev/null; then echo"⚠️ 冲突参数:$key 同时存在于 profile[$PROFILE] 和 /etc/sysctl.conf" fi done echo" 建议:把冲突参数固化进自定义 profile(见文档阶段2方案B)" else echo "✅ atuned 未运行,无回写冲突" fi echo "" echo "========================================" echo " 4. LIMITS 检查" echo "========================================" echo "当前会话: nofile=$(ulimit -n) nproc=$(ulimit -u)" for svc in mysqld redis redis-6379; do systemctl is-active --quiet"$svc"2>/dev/null || continue echo"[$svc] systemd 视角:" systemctl show "$svc" | grep-E"^LimitNOFILE|^LimitNPROC|^TasksMax" | \ awk'{printf " %s\n", $0}' PID=$(systemctl show -p MainPID --value "$svc") if [ "$PID" !="0" ] && [ -n"$PID" ]; then echo"[$svc] 进程(PID $PID)视角:" grep-E"Max open files|Max processes" /proc/$PID/limits | \ awk'{printf " %-16s soft=%-12s hard=%s\n", $1" "$2, $4, $5}' fi done echo "" echo "========================================" echo " 检查完成" echo "========================================"四、根因总结
| 配置文件 | 错误 | 后果 | 根因 |
|---|---|---|---|
/etc/fstab | xfs 写成 ext4 盘 + RAID 后 discard 失效 | 系统进紧急模式 | 没用blkid核对;没确认 RAID 卡是否透传 TRIM |
/etc/sysctl.conf | nf_conntrack 模块未加载 | conntrack 参数静默失败 | 没先modprobe+modules-load.d |
/etc/sysctl.conf | 端口范围含服务端口 | Redis 端口耗尽 | 没避开 1-10000 |
| A-Tune(openEuler 独有) | profile 定期回写默认值 | 参数「自己变回去」 | 手动 sysctl 与 A-Tune profile 冲突,未做 profile 固化 |
/etc/security/limits.conf | 只改了 PAM 层 | MySQL fd 限制 1024 | systemd 服务走LimitNOFILE/TasksMax,不读 limits.conf |
| 三个文件 | 一次性全改 | 连环故障难定位 | 违反「单变量迭代」原则 |
五、最佳实践 SOP(openEuler 版)
改前必备份:
cp file file.bak.$(date +%F);sysctl 类变更同时记录sysctl -a > /tmp/sysctl-before.txt先弄清发行版特性:openEuler 上改内核参数前必查
atune-adm list,冲突参数一律固化进自定义 profile,而不是裸改 sysctl.conf单变量迭代:一次只改一个文件,验证通过再改下一个
改后必验证 + 等一个回写周期:A-Tune 环境下
sysctl -p后至少等 10 分钟再复查一遍重启前干跑:
findmnt --verify、mount -a --fake、sysctl -p、systemctl daemon-reload全绿再 rebootsystemd 服务永远用 override:
systemctl edit <service>,同时关注LimitNOFILE / LimitNPROC / TasksMax三件套limits.conf 与 systemd 双轨都要改:SSH 会话靠 limits.conf,服务靠 unit override,缺一个都是坑。