feat(db): 迁移 db 配置到 confs 统一配置服务
- 新增 confs/configs/db.ini 集中托管 db 服务配置(server/database/logs/confs) - dbv2.py 采用三层 fail-safe 加载模式:内置默认值 → confs(db.ini,优先)→ 环境变量覆盖(最高优先级,Docker 兼容) - _merge_ini() 白名单合并,仅覆盖已知 section/key,ConfigParser(interpolation=None) 禁用插值 - db 先于 confs 启动时连接失败静默 fail-safe,启动时打印配置来源 - db_tools.py 运维工具保持独立,直接读 DATABASE_URL 环境变量,不依赖 confs - 更新 confs/README.md 与 db/README.md 文档
This commit is contained in:
1 parent
c06eb19f64
commit
6ebd544619
4 files changed
+150
-15
No files matched your search
+54
-4
@@ -25,10 +25,60 @@
|
||||
./_run.sh # venv 中执行 python3 dbv2.py
|
||||
```
|
||||
|
||||
服务监听 `tcp://127.0.0.1:8005`。生产环境由 core(Nidus)按 init.txt 统一拉起,守护靠 systemd。
|
||||
服务监听 `tcp://0.0.0.0:8005`(默认)。生产环境由 core(Nidus)按 init.txt 统一拉起,守护靠 systemd。
|
||||
|
||||
**注意**:`execute` 路由依赖表已存在(通过反射获取结构)。建表见 `create_sqlite3.sql`(Users / Notes;Logs 表由服务启动时自建)。
|
||||
|
||||
## 配置管理
|
||||
|
||||
db 已接入 [confs](../confs/) 统一配置服务,采用三层 fail-safe 加载模式(与 static/dnfs 一致):
|
||||
|
||||
1. **内置默认值** — 硬编码在 `dbv2.py` 中,保证服务在任何环境都能启动
|
||||
2. **confs 统一配置服务**(优先)— 通过 pymsg RpcClient 从 `confs` 拉取 `db.ini`
|
||||
3. **环境变量覆盖**(最高优先级)— Docker/部署环境通过环境变量覆盖,保持向后兼容
|
||||
|
||||
### confs 配置文件(db.ini)
|
||||
|
||||
配置文件托管于 [confs/configs/db.ini](../confs/configs/db.ini):
|
||||
|
||||
```ini
|
||||
[server]
|
||||
host = 0.0.0.0
|
||||
port = 8005
|
||||
workers = 4
|
||||
|
||||
[database]
|
||||
url = sqlite:///database.db
|
||||
|
||||
[logs]
|
||||
retention_days = 30
|
||||
max_records = 1000000
|
||||
cleanup_interval = 3600
|
||||
|
||||
[confs]
|
||||
host = 127.0.0.1
|
||||
port = 8010
|
||||
```
|
||||
|
||||
### 环境变量(最高优先级)
|
||||
|
||||
| 环境变量 | 说明 | 对应 confs 配置项 |
|
||||
|---------|------|------------------|
|
||||
| `LISTEN_ADDR` | 监听地址 | `[server] host` |
|
||||
| `LISTEN_PORT` | 监听端口 | `[server] port` |
|
||||
| `WORKERS` | 回调并发线程数 | `[server] workers` |
|
||||
| `DATABASE_URL` | 数据库连接 URL | `[database] url` |
|
||||
| `CONFS_HOST` | confs 服务地址 | `[confs] host` |
|
||||
| `CONFS_PORT` | confs 服务端口 | `[confs] port` |
|
||||
|
||||
### 启动顺序注意
|
||||
|
||||
根据 micro_servers 的启动顺序:**db → confs → ai_review/...**,db 先于 confs 启动。因此:
|
||||
- db 启动时若 confs 尚未就绪,`_fetch_from_confs()` 连接失败会静默 fail-safe,使用内置默认值 + 环境变量正常启动
|
||||
- 启动时控制台会打印配置来源:`配置来源: confs 统一配置服务 (db.ini)` 或 `警告: confs 不可用,使用内置默认值`,最后 `start()` 前会再次打印最终来源
|
||||
- 独立运维工具 `db_tools.py`(backup/export/import/vacuum 等)仍直接读 `DATABASE_URL` 环境变量,不依赖 confs 服务就绪
|
||||
- Docker 环境通过 `docker-entrypoint.sh` 传入环境变量(见 [Dockerfile](Dockerfile)),保持与之前完全兼容,无需额外配置
|
||||
|
||||
## 协议与 API
|
||||
|
||||
### 传输协议(pymsg)
|
||||
@@ -232,10 +282,10 @@ python3 _view_db.sqlite3.py -s "SELECT service, COUNT(*) FROM Logs GROUP BY serv
|
||||
|
||||
## 切换数据库
|
||||
|
||||
只需修改环境变量 `DATABASE_URL`,并安装对应驱动:
|
||||
推荐方式:修改 confs 的 [db.ini](../confs/configs/db.ini) 中 `[database] url`,然后通过 confs `reload` 路由重载(或重启 db)。也可通过环境变量 `DATABASE_URL` 覆盖(Docker 部署时常用):
|
||||
|
||||
- SQLite(默认):`sqlite:///database.db`
|
||||
- PostgreSQL:`postgresql://user:pass@localhost/dbname`(需 `pip install psycopg2`)
|
||||
- SQLite(默认):`sqlite:///database.db`(相对路径)或 `sqlite:////app/data/database.db`(绝对路径,Docker 中使用)
|
||||
- PostgreSQL:`postgresql://user:pass@localhost/dbname`(需 `pip install psycopg2-binary`)
|
||||
- MySQL:`mysql+pymysql://user:pass@localhost/dbname`(需 `pip install pymysql`)
|
||||
|
||||
SQLAlchemy 会自动生成适配各数据库的 SQL 方言,无需修改接口调用。
|
||||
|
||||
+69
-9
@@ -27,6 +27,7 @@ dbv2 — 基于 pymsg 协议(MsgServer)的数据库服务。
|
||||
响应中的日期时间一律序列化为 ISO 8601(T 分隔)。
|
||||
"""
|
||||
|
||||
import configparser
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
@@ -47,9 +48,70 @@ import sys as _sys
|
||||
_sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
from pymsg import RpcServer # noqa: E402
|
||||
|
||||
# ── 数据库配置 ──────────────────────────────────────────────────────────
|
||||
# ── 配置加载(三层 fail-safe:内置默认值 → confs → 环境变量覆盖)──────
|
||||
# 加载顺序:内置默认值 → confs 统一配置服务(db.ini,优先);
|
||||
# confs 不可用时 fail-safe 使用默认值;环境变量优先级最高(Docker 兼容)。
|
||||
|
||||
DATABASE_URL = os.environ.get("DATABASE_URL", "sqlite:///database.db")
|
||||
_db_config = {
|
||||
'server': {'host': '0.0.0.0', 'port': '8005', 'workers': '4'},
|
||||
'database': {'url': 'sqlite:///database.db'},
|
||||
'logs': {
|
||||
'retention_days': '30',
|
||||
'max_records': '1000000',
|
||||
'cleanup_interval': '3600',
|
||||
},
|
||||
'confs': {'host': '127.0.0.1', 'port': '8010'},
|
||||
}
|
||||
|
||||
|
||||
def _merge_ini(text):
|
||||
"""把 ini 文本合并进 _db_config(仅覆盖已知的 section/key)。"""
|
||||
ini = configparser.ConfigParser(interpolation=None)
|
||||
ini.read_string(text)
|
||||
for section in _db_config:
|
||||
if ini.has_section(section):
|
||||
for key, value in ini[section].items():
|
||||
if key in _db_config[section]:
|
||||
_db_config[section][key] = value
|
||||
|
||||
|
||||
def _fetch_from_confs():
|
||||
"""从 confs 统一配置服务拉取 db.ini 全文,失败返回 None(fail-safe)。"""
|
||||
try:
|
||||
from pymsg import RpcClient
|
||||
host = os.environ.get('CONFS_HOST', _db_config['confs']['host'])
|
||||
port = int(os.environ.get('CONFS_PORT', _db_config['confs']['port']))
|
||||
data = RpcClient(host, port, 'db', timeout=3).call(
|
||||
'get', {'name': 'db.ini'})
|
||||
return data.get('content')
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
_confs_text = _fetch_from_confs()
|
||||
_config_source = 'built-in defaults'
|
||||
if _confs_text is not None:
|
||||
_merge_ini(_confs_text)
|
||||
_config_source = 'confs (db.ini)'
|
||||
print(f'[dbv2] 配置来源: confs 统一配置服务 (db.ini)')
|
||||
else:
|
||||
print(f'[dbv2] 警告: confs 不可用,使用内置默认值')
|
||||
|
||||
# 环境变量覆盖(最高优先级,保持 Docker 兼容)
|
||||
if os.environ.get('LISTEN_ADDR'):
|
||||
_db_config['server']['host'] = os.environ['LISTEN_ADDR']
|
||||
if os.environ.get('LISTEN_PORT'):
|
||||
_db_config['server']['port'] = os.environ['LISTEN_PORT']
|
||||
if os.environ.get('WORKERS'):
|
||||
_db_config['server']['workers'] = os.environ['WORKERS']
|
||||
if os.environ.get('DATABASE_URL'):
|
||||
_db_config['database']['url'] = os.environ['DATABASE_URL']
|
||||
|
||||
# 解析为最终常量
|
||||
DATABASE_URL = _db_config['database']['url']
|
||||
DEFAULT_RETENTION_DAYS = int(_db_config['logs']['retention_days'])
|
||||
DEFAULT_MAX_RECORDS = int(_db_config['logs']['max_records'])
|
||||
DEFAULT_CLEANUP_INTERVAL = int(_db_config['logs']['cleanup_interval'])
|
||||
|
||||
# ── SQLite 连接配置(针对 Docker/多线程环境优化)──────────────────────
|
||||
|
||||
@@ -76,10 +138,6 @@ def _configure_sqlite(dbapi_connection, connection_record):
|
||||
MAX_MESSAGE_LEN = 8192 # message 上限 8KB
|
||||
MAX_EXTRA_LEN = 4096 # extra 上限 4KB
|
||||
|
||||
DEFAULT_RETENTION_DAYS = 30
|
||||
DEFAULT_MAX_RECORDS = 1_000_000
|
||||
DEFAULT_CLEANUP_INTERVAL = 3600 # 秒
|
||||
|
||||
#: where/values 中的哨兵值:服务端当前 UTC 时间
|
||||
NOW_SENTINEL = "$now"
|
||||
|
||||
@@ -456,17 +514,19 @@ class DBServer:
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
default_addr = os.environ.get("LISTEN_ADDR", "0.0.0.0")
|
||||
default_port = int(os.environ.get("LISTEN_PORT", "8005"))
|
||||
default_addr = _db_config['server']['host']
|
||||
default_port = int(_db_config['server']['port'])
|
||||
default_workers = int(_db_config['server']['workers'])
|
||||
|
||||
parser = argparse.ArgumentParser(description="dbv2 — pymsg 数据库服务")
|
||||
parser.add_argument("--addr", default=default_addr, help="监听地址")
|
||||
parser.add_argument("--port", type=int, default=default_port, help="监听端口")
|
||||
parser.add_argument("--workers", type=int, default=4,
|
||||
parser.add_argument("--workers", type=int, default=default_workers,
|
||||
help="回调并发线程数(0=同步串行模式)")
|
||||
args = parser.parse_args()
|
||||
|
||||
server = DBServer(args.addr, args.port, workers=args.workers)
|
||||
print(f'[dbv2] 配置来源: {_config_source}')
|
||||
try:
|
||||
server.start()
|
||||
except KeyboardInterrupt:
|
||||
|
||||
Reference in new issue
Block a user