7.5 KiB
7.5 KiB
服务器重启数据加载修复说明
问题描述
服务器重启后没有正确载入底层数据库中的数据,导致内存存储为空。
根本原因
在之前的实现中,服务器启动时只创建了空的 MemoryStore,但没有从数据库中加载已有的数据到内存中。这导致:
- 服务器重启后,内存中的数据丢失
- 查询操作无法找到已有数据
- 插入操作可能产生重复数据
修复方案
1. 添加 ListCollections 方法到数据库适配器
为所有数据库适配器实现了 ListCollections 方法,用于获取数据库中所有现有的表(集合):
文件修改:
internal/database/base.go- 添加基础方法声明internal/database/sqlite/adapter.go- SQLite 实现internal/database/postgres/adapter.go- PostgreSQL 实现internal/database/dm8/adapter.go- DM8 实现
SQLite 示例代码:
// ListCollections 获取所有集合(表)列表
func (a *SQLiteAdapter) ListCollections(ctx context.Context) ([]string, error) {
query := `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`
rows, err := a.GetDB().QueryContext(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()
var tables []string
for rows.Next() {
var table string
if err := rows.Scan(&table); err != nil {
return nil, err
}
tables = append(tables, table)
}
return tables, rows.Err()
}
2. 添加 Initialize 方法到 MemoryStore
在 internal/engine/memory_store.go 中添加了 Initialize 方法:
// Initialize 从数据库加载所有现有集合到内存
func (ms *MemoryStore) Initialize(ctx context.Context) error {
if ms.adapter == nil {
log.Println("[INFO] No database adapter, skipping initialization")
return nil
}
// 获取所有现有集合
tables, err := ms.adapter.ListCollections(ctx)
if err != nil {
// 如果 ListCollections 未实现,返回 nil(不加载)
if err.Error() == "not implemented" {
log.Println("[WARN] ListCollections not implemented, skipping initialization")
return nil
}
return fmt.Errorf("failed to list collections: %w", err)
}
log.Printf("[INFO] Found %d collections in database", len(tables))
// 逐个加载集合
loadedCount := 0
for _, tableName := range tables {
// 从数据库加载所有文档
docs, err := ms.adapter.FindAll(ctx, tableName)
if err != nil {
log.Printf("[WARN] Failed to load collection %s: %v", tableName, err)
continue
}
// 创建集合并加载文档
ms.mu.Lock()
coll := &Collection{
name: tableName,
documents: make(map[string]types.Document),
}
for _, doc := range docs {
coll.documents[doc.ID] = doc
}
ms.collections[tableName] = coll
ms.mu.Unlock()
loadedCount++
log.Printf("[DEBUG] Loaded collection %s with %d documents", tableName, len(docs))
}
log.Printf("[INFO] Successfully loaded %d collections from database", loadedCount)
return nil
}
3. 在服务器启动时调用初始化
修改 cmd/server/main.go,在创建内存存储后立即调用初始化:
// 创建内存存储
store := engine.NewMemoryStore(adapter)
// 从数据库加载现有数据到内存
log.Println("[INFO] Initializing memory store from database...")
if err := store.Initialize(ctx); err != nil {
log.Printf("[WARN] Failed to initialize memory store: %v", err)
// 不阻止启动,继续运行
}
// 创建 CRUD 处理器
crud := engine.NewCRUDHandler(store, adapter)
工作流程
服务器启动流程:
1. 连接数据库
↓
2. 创建 MemoryStore
↓
3. 【新增】调用 Initialize() 从数据库加载数据
↓
4. 创建 CRUDHandler
↓
5. 启动 HTTP/TCP 服务器
测试方法
快速测试(推荐)
cd /home/kingecg/code/gomog
./test_quick.sh
预期输出:
✅ 成功!服务器重启后正确加载了数据库中的数据
=== 测试结果:SUCCESS ===
详细测试
./test_reload_simple.sh
手动测试
- 启动服务器并插入数据
./bin/gomog -config config.yaml
# 在另一个终端插入数据
curl -X POST http://localhost:8080/api/v1/testdb/users/insert \
-H "Content-Type: application/json" \
-d '{"documents": [{"name": "Alice", "age": 30}]}'
- 验证数据已存入数据库
sqlite3 gomog.db "SELECT * FROM users;"
- 停止并重启服务器
# Ctrl+C 停止服务器
./bin/gomog -config config.yaml
- 查询数据(验证是否加载)
curl -X POST http://localhost:8080/api/v1/testdb/users/find \
-H "Content-Type: application/json" \
-d '{"filter": {}}'
应该能看到之前插入的数据。
日志输出示例
成功的初始化日志:
2026/03/14 22:00:00 [INFO] Connected to sqlite database
2026/03/14 22:00:00 [INFO] Initializing memory store from database...
2026/03/14 22:00:00 [INFO] Found 1 collections in database
2026/03/14 22:00:00 [DEBUG] Loaded collection users with 2 documents
2026/03/14 22:00:00 [INFO] Successfully loaded 1 collections from database
2026/03/14 22:00:00 Starting HTTP server on :8080
2026/03/14 22:00:00 Gomog server started successfully
关键技术细节
集合名称映射
由于 HTTP API 使用 dbName.collection 格式(如 testdb.users),而 SQLite 数据库中表名不带前缀(如 users),实现了智能名称映射:
1. Initialize 方法加载数据
// 从数据库加载时使用纯表名(例如:users)
ms.collections[tableName] = coll
2. GetCollection 方法支持两种查找方式
// 首先尝试完整名称(例如:testdb.users)
coll, exists := ms.collections[name]
if exists {
return coll, nil
}
// 如果找不到,尝试去掉数据库前缀(例如:users)
if idx := strings.Index(name, "."); idx > 0 {
tableName := name[idx+1:]
coll, exists = ms.collections[tableName]
if exists {
return coll, nil
}
}
这样确保了:
- 新插入的数据可以使用
testdb.users格式 - 重启后加载的数据也能通过
testdb.users查询到 - 向后兼容,不影响现有功能
容错处理
修复实现了多层容错机制:
- 无数据库适配器:如果未配置数据库,跳过初始化
- ListCollections 未实现:如果数据库不支持列出表,记录警告但不阻止启动
- 单个集合加载失败:记录错误但继续加载其他集合
- 初始化失败:记录错误但服务器继续运行(不会崩溃)
影响范围
- ✅ 服务器重启后数据自动恢复
- ✅ HTTP API 和 TCP 协议行为一致
- ✅ 支持 SQLite、PostgreSQL、DM8 三种数据库
- ✅ 向后兼容,不影响现有功能
- ✅ 优雅降级,初始化失败不影响服务器启动
相关文件清单
修改的文件
internal/engine/memory_store.go- 添加 Initialize 方法internal/database/base.go- 添加 ListCollections 基础方法internal/database/sqlite/adapter.go- SQLite 实现internal/database/postgres/adapter.go- PostgreSQL 实现internal/database/dm8/adapter.go- DM8 实现cmd/server/main.go- 启动时调用初始化
新增的文件
test_reload.sh- 自动化测试脚本RELOAD_FIX.md- 本文档
后续优化建议
- 增量加载:对于大数据量场景,可以考虑分页加载
- 懒加载:只在第一次访问集合时才从数据库加载
- 并发加载:并行加载多个集合以提高启动速度
- 加载进度监控:添加更详细的进度日志和指标