Files
blackbird/memdb/src/memdb.cpp
T
qing a833cd0d5f fix(memdb): expire semantics + Boost 1.83 portability + backlog & test harness
- expire 改为 update+refresh_ttl:键不存在返回 false,与 del/update 约定一致
- _require_string 适配 Boost 1.83(boost::json::string 已非 std::string)
- 新增 --backlog(默认 128,libmsg 默认 10 短连接突刺易丢连接)
- Makefile 补 HOST/PORT 默认值,test-memdb 用 PID 文件替代失效的 kill %1
- 补测试:复制构造/源销毁后副本有效/refresh 延长 TTL/expire 不存在键
2026-08-17 14:29:41 +00:00

202 lines
6.8 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <us/RpcServer.hpp>
#include <us/memdb.hpp>
#include <boost/program_options.hpp>
#include <atomic>
#include <csignal>
#include <cstdint>
#include <iostream>
#include <mutex>
#include <stdexcept>
#include <string>
#include <thread>
namespace json = boost::json;
namespace po = boost::program_options;
struct ServerConfig {
std::string listen_host = "127.0.0.1";
int listen_port = 8011;
int workers = 4;
int max_time = 600; // 默认 TTL 10 分钟
int backlog = 128; // 短连接并发突刺时防丢连接(libmsg 默认仅 10)
};
static std::atomic<bool> g_running{true};
static void signal_handler(int) {
g_running = false;
}
class MemdbServer {
private:
ServerConfig __cfg;
Memdb<std::string, std::string> __db;
pmc::net::RpcServer __rpc;
std::mutex __mtx;
static std::string _require_string(const json::object& req, const std::string& field) {
auto it = req.find(field);
if (it == req.end()) {
throw std::runtime_error("missing required field: " + field);
}
if (!it->value().is_string()) {
throw std::runtime_error("field '" + field + "' must be a string");
}
const auto& s = it->value().as_string();
return std::string(s.data(), s.size());
}
public:
explicit MemdbServer(const ServerConfig& cfg)
: __cfg(cfg)
, __db(cfg.max_time)
, __rpc(cfg.listen_host, cfg.listen_port,
{.workers = cfg.workers, .listen_backlog = cfg.backlog})
{
_register_routes();
}
void start() { __rpc.start(); }
void stop() { __rpc.stop(); }
private:
void _register_routes() {
// ---- 健康检查 ----
__rpc.add_route("ping", [](const json::object&) -> json::value {
return "pong";
});
// ---- set: 插入键值对 ----
__rpc.add_route("set", [this](const json::object& req) -> json::value {
const auto& key = _require_string(req, "key");
const auto& val = _require_string(req, "value");
std::lock_guard<std::mutex> lock(__mtx);
return __db.insert(key, val);
});
// ---- get: 查询键 ----
__rpc.add_route("get", [this](const json::object& req) -> json::value {
const auto& key = _require_string(req, "key");
std::lock_guard<std::mutex> lock(__mtx);
auto result = __db.search(key);
if (result.has_value()) {
return json::string(*result);
}
return nullptr;
});
// ---- del: 删除键 ----
__rpc.add_route("del", [this](const json::object& req) -> json::value {
const auto& key = _require_string(req, "key");
std::lock_guard<std::mutex> lock(__mtx);
return __db.remove(key);
});
// ---- update: 更新键值(可选刷新 TTL)----
__rpc.add_route("update", [this](const json::object& req) -> json::value {
const auto& key = _require_string(req, "key");
const auto& val = _require_string(req, "value");
bool refresh_ttl = false;
if (auto it = req.find("refresh_ttl"); it != req.end() && it->value().is_bool()) {
refresh_ttl = it->value().as_bool();
}
std::lock_guard<std::mutex> lock(__mtx);
return __db.update(key, val, refresh_ttl);
});
// ---- exists: 检查键是否存在 ----
__rpc.add_route("exists", [this](const json::object& req) -> json::value {
const auto& key = _require_string(req, "key");
std::lock_guard<std::mutex> lock(__mtx);
return __db.search(key).has_value();
});
// ---- size: 当前条目数 ----
__rpc.add_route("size", [this](const json::object&) -> json::value {
std::lock_guard<std::mutex> lock(__mtx);
return static_cast<int64_t>(__db.size());
});
// ---- show: 导出所有条目 ----
__rpc.add_route("show", [this](const json::object&) -> json::value {
std::lock_guard<std::mutex> lock(__mtx);
json::array result;
for (const auto& col : __db.get_db()) {
json::object entry;
entry["key"] = json::string(col.key);
entry["value"] = json::string(col.value);
result.push_back(std::move(entry));
}
return result;
});
// ---- expire: 更新值并重置 TTL(仅已有键;等价于 update + refresh_ttl)----
__rpc.add_route("expire", [this](const json::object& req) -> json::value {
const auto& key = _require_string(req, "key");
const auto& val = _require_string(req, "value");
std::lock_guard<std::mutex> lock(__mtx);
return __db.update(key, val, true);
});
}
};
int main(int argc, char** argv) {
std::signal(SIGINT, signal_handler);
std::signal(SIGTERM, signal_handler);
ServerConfig cfg;
po::options_description desc("memdb options");
desc.add_options()
("help,h", "display help")
("instance", po::value<std::string>()->default_value("memdb"), "instance name")
("log-level", po::value<std::string>()->default_value("info"), "log level")
("host", po::value<std::string>()->default_value("127.0.0.1"), "listen host")
("port", po::value<int>()->default_value(8011), "listen port")
("workers", po::value<int>()->default_value(4), "worker threads")
("max-time", po::value<int>()->default_value(600), "default TTL (seconds)")
("backlog", po::value<int>()->default_value(128), "listen() backlog");
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
if (vm.count("help")) {
std::cout << desc << std::endl;
return 0;
}
cfg.listen_host = vm["host"].as<std::string>();
cfg.listen_port = vm["port"].as<int>();
cfg.workers = vm["workers"].as<int>();
cfg.max_time = vm["max-time"].as<int>();
cfg.backlog = vm["backlog"].as<int>();
auto instance = vm["instance"].as<std::string>();
auto log_level = vm["log-level"].as<std::string>();
MemdbServer server(cfg);
std::cout << "[memdb:" << instance << "] log-level=" << log_level
<< " listening on " << cfg.listen_host
<< ":" << cfg.listen_port << std::endl;
std::thread server_thread([&server]() {
server.start();
});
while (g_running) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::cout << "\n[memdb:" << instance << "] shutting down..." << std::endl;
server.stop();
if (server_thread.joinable()) {
server_thread.join();
}
return 0;
}