这次我们来看一个很有意思的落地场景:在 Termux 里用 SQLite 搭一个 10 层 agent mesh,同时想办法不让手机被热节流拖死。
先说结论:这个方案的可行点在于,SQLite 作为多级 agent 之间的状态存储和任务队列,完全够用;真正的瓶颈往往不是数据库,而是手机 SoC 的散热和系统调度策略。所以这篇文章不会只讲“装个 SQLite 跑个脚本”,而是会分三块:第一,SQLite 在 agent mesh 里怎么设计表结构和任务流转;第二,Termux 环境下怎么启动、验证、调接口;第三,怎么通过监控温度、背压调度和小批量任务,尽量避免 thermal throttling。
文章会覆盖环境准备、数据库初始化、worker 示例、API 调用示例、批量任务脚本、热节流监控和常见问题排查。感兴趣的读者可以直接照做,最小闭环跑通后再往 10 层扩展。
1. 核心能力速览
先给一张规格速览表,方便快速判断这套体系适不适合你。
| 能力项 | 说明 |
|---|---|
| 项目类型 | Termux 环境下的多智能体网格(agent mesh)+ SQLite 状态存储 |
| 主要功能 | 多级 agent 任务流转、SQLite 持久化、HTTP API 提交任务、批量任务入队、热节流监控 |
| 推荐硬件 | 支持 Termux 的 Android 设备;内存建议 6GB 以上;高负载场景建议有散热条件或空调环境 |
| 显存需求 | 无 GPU 依赖,CPU 推理即可;如果 agent 内部再接本地模型,则需要按模型单独评估 |
| 支持平台 | Android + Termux;macOS/Linux 的类 Unix 环境也适用,需调整部分路径 |
| 启动方式 | 命令行启动;可选 Python 内置 HTTP Server 作为 API 服务 |
| 是否支持 API | 支持,可以基于 http.server 或 Flask 自行封装 |
| 是否支持批量任务 | 支持,SQLite 任务表天然适合批量入队和状态更新 |
| 数据库要求 | SQLite 3.31+,开启 WAL 模式,设置 busy_timeout,降低并发写锁冲突 |
| 适合场景 | 多 agent 流程编排、任务队列、本地自动化实验、边缘设备轻量调度、教学演示 |
从材料来看,这个方案的重点不是做一个高性能分布式任务系统,而是在手机这种弱设备上,用 SQLite 把“10 层 agent 网格”这件事跑通。因此,判断标准不是吞吐量,而是稳定性和可复现性。
2. 适用场景与使用边界
2.1 适合谁
这类方案比较适合下面几类人:
- 在手机或平板上折腾 Termux 的用户,想验证 SQLite 能不能承担多 agent 任务编排。
- 做 agent 流程实验的开发者,不需要引入 Redis、RabbitMQ 这类重量级中间件,想用 SQLite 快速落地一个任务队列。
- 希望在边缘设备上跑自动化流程的爱好者,比如定时采集、文本处理、数据清洗等轻任务。
- 想学习 agent mesh 概念的人,SQLite 的表结构设计本身就是很好的教学样例。
2.2 能解决什么问题
SQLite 在 agent mesh 里的价值很直接:用一个数据库文件,把 10 层 agent 的状态、任务、结果全部串起来。每一层 agent 不再需要单独维护自己的持久化文件,也不需要复杂的网络通信协议,直接读写同一张任务表即可。
这样设计的好处有三点:
- 状态可追溯:任务从 tier 1 流转到 tier 10,每一步都能在 tasks 表里看到当前层、状态和更新时间。
- 故障恢复简单:worker 挂掉后,任务停留在 processing 状态,重启后可以把超时任务重置回 pending。
- 批量任务友好:一次插入大量 task_key,然后逐个处理,天然支持批处理场景。
2.3 不适合什么场景
这套方案不适合高并发、强一致、大流量场景。SQLite 单写多读,虽然有 WAL 模式,但写入并发能力有限。10 层 agent mesh 如果每层有多个 worker 同时高频率更新任务表,写锁冲突会明显增加。更稳妥的做法是控制每层 worker 数量,或者在任务表中加批次号,让同一个批次尽量只由一个 worker 处理。
2.4 合规与安全边界
在 Termux 里做自动化实验时,需要特别注意使用边界:
- 只处理自己拥有或有合法授权的数据,不要采集、调用或爬取第三方未授权内容。
- 如果 agent 涉及调用外部 API,需要确认该 API 的调用条款和频率限制。
- 不要利用 Termux 做任何网络攻击、未授权访问、破解、渗透或绕过系统安全机制的操作。
- 涉及个人信息、人脸、声音、日志等敏感数据时,必须做脱敏处理,只在本地测试环境中使用。
- 在公共网络或共享设备上运行时,API 服务必须绑定 127.0.0.1,不要暴露到公网。
3. Termux 环境准备与前置条件
3.1 系统与版本要求
- Android 设备,建议 Android 10 及以上,保证 Termux 的存储和系统调用权限可用。
- Termux 版本建议从 F-Droid 官方渠道更新,避免旧版 dependency 不适配。
- 需要安装 packages:
sqlite、python、python-pip、git、curl。 - 如果后续要用 Python 的第三方库,再按需通过
pip安装。
3.2 检查基础环境
打开 Termux 后,先执行:
pkg update && pkg upgrade -y pkg install -y sqlite python python-pip git curl sqlite3 --version python --version看到 sqlite3 和 python 的版本输出,说明基础环境没问题。
如果需要把 Termux 的存储权限打开,执行:
termux-setup-storage弹出系统授权窗口后允许访问存储,后续建项目目录会放到~/storage或者普通家目录下。
3.3 项目目录规划
建议单独建目录,避免脚本和数据库文件散落:
mkdir -p ~/agent_mesh/{db,logs,workers} cd ~/agent_mesh目录结构预留好之后,数据库文件放db/,日志放logs/,Python worker 脚本放workers/。
4. 10 层 Agent Mesh + SQLite 架构设计
4.1 什么是 agent mesh
agent mesh 不是传统的主从架构,而是让多个 agent 节点按层级组织,每个节点可以独立处理任务,也可以把任务交给下一层继续处理。10 层意味着任务从输入层开始,依次经过多个处理层,最终输出结果。
每一层可以有不同的职责,比如:
- tier 1:任务接收、参数解析。
- tier 2:文本分类、意图识别。
- tier 3:工具调用、信息查询。
- tier 4:信息汇总、上下文拼接。
- tier 5:结果校验、格式修正。
- tier 6:内容生成、模板填充。
- tier 7:敏感信息过滤。
- tier 8:质量评分。
- tier 9:最终组装。
- tier 10:输出归档。
实际实现时不需要所有层都是复杂逻辑,可以先用简单规则函数代替,重点是把“层与层之间的任务流转”跑通。
4.2 SQLite 表结构设计
这里给出推荐的 3 张核心表:
- agents:记录每个 agent 节点的 ID、所属层级、当前状态、心跳时间。
- tasks:记录每个任务的当前层级、目标层级、状态、payload 和重试次数。
- agent_logs:记录每层 agent 的处理日志,方便追踪问题。
初始化 SQL:
CREATE TABLE IF NOT EXISTS agents ( id INTEGER PRIMARY KEY AUTOINCREMENT, tier INTEGER NOT NULL, node_id TEXT NOT NULL UNIQUE, status TEXT NOT NULL DEFAULT 'idle', last_heartbeat INTEGER, created_at INTEGER ); CREATE TABLE IF NOT EXISTS tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, task_key TEXT NOT NULL UNIQUE, current_tier INTEGER NOT NULL DEFAULT 1, target_tier INTEGER NOT NULL DEFAULT 10, status TEXT NOT NULL DEFAULT 'pending', payload TEXT, attempts INTEGER NOT NULL DEFAULT 0, created_at INTEGER, updated_at INTEGER ); CREATE INDEX IF NOT EXISTS idx_tasks_queue ON tasks(status, current_tier); CREATE TABLE IF NOT EXISTS agent_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, task_id INTEGER NOT NULL, tier INTEGER NOT NULL, node_id TEXT NOT NULL, log_message TEXT, created_at INTEGER );把这段 SQL 保存为db/init.sql,然后执行:
sqlite3 db/agent_mesh.db < db/init.sql执行完后可以验证:
sqlite3 db/agent_mesh.db ".tables"输出结果里应该包含agents、tasks、agent_logs三张表。
4.3 任务状态流转
任务状态建议用以下几类:
| 状态 | 含义 |
|---|---|
| pending | 等待当前层 worker 处理 |
| processing | 当前层 worker 正在处理 |
| done | 已到达目标层级,任务完成 |
| failed | 任务处理失败,等待重试或人工处理 |
每次 worker 取任务时,把 pending 改成 processing;处理完成后,如果还有下一层,就把 current_tier + 1,状态改回 pending;如果已经是 target_tier,直接改成 done。
这个状态机的核心逻辑很轻,但它是整个 agent mesh 的骨架。只要有这一套状态流转,10 层还是 20 层,差别只在于循环次数。
5. 安装部署与启动方式
5.1 数据库初始化
把上面db/init.sql写好之后,执行一次初始化。如果数据库已经存在,SQLite 会跳过已存在的表,所以重复执行是安全的。
5.2 Python worker 示例
在workers/目录下创建worker.py,实现任务获取、处理和流转:
import sqlite3 import time import json DB_PATH = "db/agent_mesh.db" TIER_ID = 1 def get_connection(): conn = sqlite3.connect(DB_PATH, timeout=30) conn.execute("PRAGMA journal_mode=WAL;") conn.execute("PRAGMA busy_timeout=30000;") conn.execute("PRAGMA synchronous=NORMAL;") return conn def fetch_next_task(tier): conn = get_connection() try: cur = conn.execute( """SELECT id, task_key, payload FROM tasks WHERE status='pending' AND current_tier=? ORDER BY created_at ASC LIMIT 1""", (tier,) ) row = cur.fetchone() if row: conn.execute( "UPDATE tasks SET status='processing', updated_at=? WHERE id=?", (int(time.time()), row[0]) ) conn.commit() return row finally: conn.close() def complete_task(task_id, result_payload): conn = get_connection() try: cur = conn.execute( "SELECT current_tier, target_tier FROM tasks WHERE id=?", (task_id,) ) row = cur.fetchone() if not row: return current_tier, target_tier = row next_tier = current_tier + 1 if next_tier > target_tier: new_status = "done" else: new_status = "pending" conn.execute( """UPDATE tasks SET status=?, current_tier=?, payload=?, updated_at=? WHERE id=?""", (new_status, next_tier, json.dumps(result_payload), int(time.time()), task_id) ) conn.commit() finally: conn.close() def process_payload(payload): # 这里是每一层 agent 的实际业务逻辑,按需替换 data = payload if isinstance(payload, dict) else {} data["processed_by_tier"] = TIER_ID return data def run_loop(): while True: task = fetch_next_task(TIER_ID) if not task: time.sleep(2) continue task_id, task_key, payload_str = task try: payload = json.loads(payload_str) if payload_str else {} result = process_payload(payload) complete_task(task_id, result) print(f"[{time.strftime('%H:%M:%S')}] task {task_key} done at tier {TIER_ID}") except Exception as exc: print(f"[ERROR] task {task_key} failed: {exc}") conn = get_connection() conn.execute( "UPDATE tasks SET attempts=attempts+1, status='failed', updated_at=? WHERE id=?", (int(time.time()), task_id) ) conn.commit() conn.close() time.sleep(1) if __name__ == "__main__": run_loop()启动 worker:
cd ~/agent_mesh python workers/worker.py5.3 插入测试任务
另开一个终端,插入一条测试数据:
sqlite3 db/agent_mesh.db "INSERT INTO tasks(task_key, current_tier, target_tier, status, payload, created_at, updated_at) VALUES('task_001', 1, 10, 'pending', '{\"text\":\"hello\"}', strftime('%s','now'), strftime('%s','now'));"worker 终端里如果打印了task task_001 done at tier 1,说明任务已经被拉起来。之后再插入几条任务,worker 会逐条处理,直到 current_tier 超过 target_tier,状态自动变为 done。
6. 功能测试与效果验证
6.1 测试目标
验证 5 件事:
- 任务能够被 worker 获取并处理。
- 任务能逐层递增到 target_tier。
- 任务到达目标层级后状态变成 done。
- 多个连续任务能被依次处理。
- 异常任务会被标记为 failed,不影响后续任务。
6.2 批量插入测试数据
执行以下命令插入 20 条任务:
for i in $(seq 1 20); do sqlite3 db/agent_mesh.db "INSERT OR IGNORE INTO tasks(task_key, current_tier, target_tier, status, payload, created_at, updated_at) VALUES('batch_$i', 1, 5, 'pending', '{\"index\": '$i'}', strftime('%s','now'), strftime('%s','now'));" done然后观察 worker 输出,正常情况下应该逐条处理。
6.3 查询任务状态
处理过程中可以查询任务状态:
sqlite3 -header -column db/agent_mesh.db "SELECT task_key, current_tier, target_tier, status, attempts FROM tasks ORDER BY id DESC LIMIT 10;"判断是否成功的标准:
status=done的任务数量符合预期。current_tier等于target_tier。- 没有大量
failed任务。 - 处理过程中没有出现
database is locked错误。
6.4 模拟失败场景
把一条任务的payload改成无法解析的内容,例如直接插入空字符串或者非法 JSON,然后观察 worker 是否会把任务标记为 failed:
sqlite3 db/agent_mesh.db "INSERT INTO tasks(task_key, current_tier, target_tier, status, payload, created_at, updated_at) VALUES('bad_task', 1, 3, 'pending', '{bad json', strftime('%s','now'), strftime('%s','now'));"预期结果:worker 输出 ERROR 日志,任务记录 attempts + 1,status 变为 failed。
7. 接口 API 与批量任务
7.1 使用 Python 内置 HTTP Server 暴露 API
Termux 环境里不一定需要装 Flask。Python 标准库的http.server足够支撑轻量接口测试。
在workers/目录下创建api_server.py:
from http.server import BaseHTTPRequestHandler, HTTPServer import json import sqlite3 import time DB_PATH = "db/agent_mesh.db" def init_db(): conn = sqlite3.connect(DB_PATH, timeout=30) conn.execute("PRAGMA journal_mode=WAL;") conn.execute("PRAGMA busy_timeout=30000;") conn.close() class Handler(BaseHTTPRequestHandler): def do_POST(self): if self.path != "/api/task": self.send_response(404) self.end_headers() return length = int(self.headers.get("Content-Length", 0)) body = json.loads(self.rfile.read(length)) task_key = body.get("task_key") target_tier = body.get("target_tier", 10) payload = body.get("payload", {}) if not task_key: self.send_response(400) self.end_headers() self.wfile.write(b"task_key is required") return conn = sqlite3.connect(DB_PATH, timeout=30) conn.execute("PRAGMA journal_mode=WAL;") conn.execute("PRAGMA busy_timeout=30000;") cur = conn.execute( """INSERT INTO tasks(task_key, current_tier, target_tier, status, payload, created_at, updated_at) VALUES(?,?,?,?,?,?,?)""", (task_key, 1, target_tier, "pending", json.dumps(payload, ensure_ascii=False), int(time.time()), int(time.time())) ) conn.commit() conn.close() self.send_response(200) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps({"task_id": cur.lastrowid}).encode()) def do_GET(self): if self.path == "/health": self.send_response(200) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(b"{\"status\":\"ok\"}") return self.send_response(404) self.end_headers() def log_message(self, format, *args): pass if __name__ == "__main__": init_db() server = HTTPServer(("127.0.0.1", 8745), Handler) print("API server running on http://127.0.0.1:8745") server.serve_forever()启动 API 服务:
cd ~/agent_mesh python workers/api_server.py7.2 用 curl 提交任务
curl -s -X POST http://127.0.0.1:8745/api/task \ -H "Content-Type: application/json" \ -d '{"task_key":"api_001","target_tier":10,"payload":{"text":"hello agent"}}'预期返回:
{"task_id": 21}7.3 用 Python requests 提交批量任务
如果安装了 requests,可以这样批量提交:
import requests import time url = "http://127.0.0.1:8745/api/task" for i in range(50): resp = requests.post(url, json={ "task_key": f"py_batch_{i}", "target_tier": 5, "payload": {"index": i} }, timeout=10) print(i, resp.json()) time.sleep(0.5)没有安装 requests 时,可以用标准库urllib代替,这里先给出 requests 示例,方便理解。
7.4 批量任务的调度策略
批量任务最怕两件事:数据库写锁集中和 CPU 持续高负载。建议在提交端做限速,比如每 0.5 秒或 1 秒提交一条;在 worker 端控制处理速率,处理完一条任务后 sleep 0.5 到 1 秒。这样既降低 SQLite 锁冲突概率,也减少瞬时发热。
8. 避免热节流:温度监控与背压调度
8.1 热节流发生的原理
移动设备没有桌面级散热条件,CPU 或 GPU 温度超过阈值后,系统会主动降频,这就是 thermal throttling。表现是:任务处理变慢、卡顿、后台进程被系统回收。如果只是简单跑一个 SQLite 写入脚本,没那么容易触发;但 10 层 agent mesh 如果开了多个 worker 持续跑,CPU 高负载会明显拉升温度。
8.2 读取 CPU 温度
在多数 Android 设备上,可以尝试读取热区温度接口:
cat /sys/class/thermal/thermal_zone0/temp输出通常是毫摄氏度,比如45000表示 45 摄氏度。
不同设备 thermal_zone 编号不同,有的叫thermal_zone1或thermal_zone10。建议多试几个路径,读取到有效值后记录下来。
8.3 Python 侧温度监控与冷却等待
在 worker 里增加温度判断,超过阈值就暂停任务处理:
import os import time import glob def read_cpu_temp(): # 遍历常见 thermal zone 路径,读取第一个有效温度 for path in glob.glob("/sys/class/thermal/thermal_zone*/temp"): try: with open(path, "r") as f: raw = int(f.read().strip()) return raw / 1000.0 except Exception: continue return 0.0 def wait_for_cooldown(max_temp=55.0): while True: temp = read_cpu_temp() if temp <= max_temp: return print(f"[thermal] temp {temp:.1f}C, waiting for cooldown...") time.sleep(5)然后在 run_loop 的 while 循环开头调用:
def run_loop(): while True: wait_for_cooldown(max_temp=55.0) task = fetch_next_task(TIER_ID) # 后续逻辑不变这里的max_temp阈值需要根据设备调整。身边有空调或者开了风扇时,可以把阈值调到 60;无散热环境建议调低到 50,避免前面触发系统级热节流。
8.4 进程优先级与系统限制
如果设备 root 可用,可以通过nice调整进程优先级,降低对前台系统进程的影响。没有 root 时,Termux 可以尝试用termux-wake-lock保持 CPU 唤醒:
termux-wake-lock处理完任务后释放:
termux-wake-unlock注意,保持唤醒状态会增加功耗,长时间运行时建议外接电源并放在散热良好的平面上。
8.5 控制并发 Worker 数量
如果数据库连接数过多,SQLite 写锁冲突会更明显。更稳妥的做法是:每个 tier 最多一个 worker 进程,全局最多 10 到 12 个 worker 进程。每次取任务前增加随机退避时间,避免多个 worker 同时查到同一条 pending 任务。
示例:
import random time.sleep(random.uniform(0.2, 1.0))8.6 优化 SQLite 写入参数
把数据库连接配置固定下来:
conn.execute("PRAGMA journal_mode=WAL;") conn.execute("PRAGMA busy_timeout=30000;") conn.execute("PRAGMA synchronous=NORMAL;") conn.execute("PRAGMA cache_size=-16000;")注意,synchronous=NORMAL在 WAL 模式下的崩溃恢复能力仍然可以接受,但比 FULL 模式更能降低写入等待。如果对数据安全性要求更高,可以保持synchronous=FULL。
9. 资源占用与性能观察
9.1 观察 CPU 和内存
在 Termux 里可以通过top查看进程资源占用:
top -n 1重点看 Python 进程的 CPU 百分比和 RES 内存。
9.2 观察数据库连接数
SQLite 不提供直接的系统连接数查询,但可以通过lsof查看打开 db 文件的进程数:
lsof 2>/dev/null | grep agent_mesh.db如果大量进程同时打开同一个文件,说明并发控制需要加强。
9.3 任务处理速率
通过查询 tasks 表里 done 状态的任务数量变化,估算速率:
sqlite3 db/agent_mesh.db "SELECT status, COUNT(*) FROM tasks GROUP BY status;"观察分钟级变化,如果速率突然下降,先看温度,再看数据库锁日志。
9.4 如何降低高负载
- 调小批量提交速率。
- 增加 worker 内部 sleep 时间。
- 关闭不必要的动画和后台 App。
- 使用充电器供电,降低系统功耗限制对性能的影响。
- 如果只是做接口验证,把 target_tier 改成 3 或 5,减少重复层数处理。
10. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| sqlite3 命令不存在 | 未安装 sqlite | 执行which sqlite3 | pkg install sqlite |
| Python 脚本找不到模块 | 依赖未安装或路径错误 | 检查python -c "import sqlite3" | 确认在项目根目录运行,或设置PYTHONPATH |
| 启动 API 后 127.0.0.1:8745 无法访问 | 端口被占用或服务未启动 | 执行netstat -tlnp查看端口 | 换端口或杀掉占用进程 |
| worker 报 database is locked | 写入并发过高 | 查看日志中的锁错误频率 | 增加 busy_timeout,开启 WAL,减少 worker 并发 |
| 任务长时间停在 processing | worker 崩溃或任务卡住 | 查询 tasks 表该任务的状态 | 写一个重启脚本,把超时 processing 任务重置为 pending |
| 设备明显发烫,任务变慢 | thermal throttling 触发 | 读取温度,观察 CPU 频率下降 | 增加冷却等待,降低任务密度 |
| 插入任务后 worker 没有反应 | 任务不在当前 tier | 检查 current_tier 和 worker 的 TIER_ID | 修改插入任务的 current_tier 或 worker 层级 |
| API 返回 400 缺少参数 | 请求体格式不对 | 检查 curl 或 requests 参数 | 确认 task_key 和 payload 字段名 |
| 数据库文件突然变大 | 日志表和任务表积累历史数据 | 查询各表行数 | 定期清理已完成的 tasks 和 agent_logs |
| Termux 后台被杀 | 系统内存回收 | 使用 termux-wake-lock | 保持前台/锁屏唤醒,或外接电源持续运行 |
额外提醒:如果在 API 服务里返回了 Windows 风格的换行符,或者代码从本地复制到 Termux 出现格式问题,可以先在编辑器里统一转换为 LF 换行,避免 Python 出现\r解释错误。
11. 最佳实践与使用建议
11.1 先跑最小闭环
第一次测试不要直接上 10 层。先把 target_tier 改成 2,插入一条任务,确认 worker 能把它从 tier 1 推到 tier 2 并变成 done。这个闭环跑通之后,再逐步放大到 5 层、10 层。
11.2 目录管理建议
推荐这样的目录结构:
~/agent_mesh/ ├── db/ │ ├── init.sql │ └── agent_mesh.db ├── logs/ │ └── worker.log ├── workers/ │ ├── worker.py │ └── api_server.py └── scripts/ └── submit_batch.py数据库文件和代码分开,日志单独存放,批量脚本单独管理,后续排查问题时能快速定位。
11.3 批量任务要加日志
不要让 worker 只是静默处理。至少在每个任务开始和结束时打印一行日志。如果批量任务数量大,建议把 stdout 重定向到日志文件:
python workers/worker.py >> logs/worker.log 2>&1 &11.4 失败重试策略
任务失败后直接标记 failed 是最简单的做法,但更稳妥的做法是:如果 attempts 小于 3,把任务重置回 pending 并让后续 worker 再试一次。示例:
UPDATE tasks SET status='pending', attempts=attempts+1, updated_at=strftime('%s','now') WHERE id=? AND attempts < 3;11.5 安全说明再次强调
如果未来要把这个 agent mesh 接到外部服务,务必确认每个调用都有授权;不要在这次实验里导入或处理非授权数据;API 服务绑定在 127.0.0.1,这是 Termux 环境里比较稳妥的做法。
到这里,整个方案的骨架、实现和常见问题都已经覆盖完整。最容易踩的坑是同时开太多 worker,导致 SQLite 写锁和发热一起出现。建议先把最少可用版本跑通,再加入温度监控和冷却等待。先去验证单条任务从 tier 1 走到 tier 10,再考虑扩大批量和处理速度,这是手机端 agent mesh 实验最稳的路径。