Skip to content

Commit b770436

Browse files
authored
[common] document and test Redis DB isolation (alibaba#243)
1 parent a51f23f commit b770436

7 files changed

Lines changed: 140 additions & 8 deletions

File tree

docs/configuration.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,21 @@ Example:
1919
/home/admin/kv_cache_manager/bin/kv_cache_manager_bin \
2020
-c '/home/admin/kv_cache_manager/etc/default_server_config.conf' \
2121
-l '/home/admin/kv_cache_manager/etc/default_logger_config.conf' \
22-
-e 'kvcm.registry_storage.uri=redis://:foo@bar:6379?cluster_name=placeholder'
22+
-e 'kvcm.registry_storage.uri=redis://your_auth_token@redis-host:6379/?db=1&cluster_name=placeholder'
2323
```
2424

25+
### Redis URI
26+
27+
Registry、Meta 和 Coordination Redis 后端共用以下 URI 格式:
28+
29+
```TEXT
30+
redis://[auth_token@]host:port/?db=<non-negative-integer>[&param=value...]
31+
```
32+
33+
- `auth_token` 会作为 Redis `AUTH` 命令的单个参数传入;`auth=...` query 参数不会用于认证。
34+
- `db` 必须放在 query 参数中;未配置时使用 DB 0。`redis://host:port/1` 中的 `/1` 是 path,不能用于选择 DB。
35+
- `db > 0` 依赖 Redis 服务端支持 `SELECT`。Redis Cluster 不支持多逻辑 DB,此时应使用 DB 0,并通过 `cluster_name` 和 key 前缀隔离,或使用独立 Redis 实例。
36+
2537
## KVCacheManager Server Config
2638

2739
KVCM server可识别的配置参数列表如下。可通过配置文件、启动参数--env、系统环境变量进行配置。
@@ -35,10 +47,11 @@ CacheReclaimer 异步删除相关参数的生命周期语义见
3547
## 3. 通过环境变量配置,set_env(kvcm.service.rpc_port, 6381)
3648
3749
# 指定系统Registry自身数据存储位置
38-
# kvcm.registry_storage.uri=redis://127.0.0.1:6379?auth=123456
50+
# cluster_name必填;db必须通过query参数指定
51+
# kvcm.registry_storage.uri=redis://your_auth_token@127.0.0.1:6379/?db=1&cluster_name=kvcm_cluster
3952
4053
# 指定协调后端服务的URI(用于多节点选主、节点信息存储等)。Redis选主key按cluster_name隔离。
41-
# kvcm.coordination.uri=redis://127.0.0.1:6379?auth=123456&cluster_name=kvcm_app_0
54+
# kvcm.coordination.uri=redis://your_auth_token@127.0.0.1:6379/?db=2&cluster_name=kvcm_app_0
4255
# 旧配置 kvcm.distributed_lock.uri 仍可使用(向后兼容),但新配置优先
4356
4457
# 指定选主时当前节点使用的node_id(如果不指定会自动生成)
@@ -172,6 +185,9 @@ kvcm.event.event_publishers_configs
172185
"mutex_shard_num": 16,
173186
"batch_key_size": 16,
174187
"meta_storage_backend_config": { # 控制meta indexer的storage backend,可选local本地文件或者redis
188+
# Redis示例:
189+
# "storage_type": "redis",
190+
# "storage_uri": "redis://your_auth_token@redis-host:6379/?db=3&client_max_pool_size=16"
175191
"storage_type": "local",
176192
"storage_uri": ""
177193
},

docs/design/ha_leader_elector.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ KVCacheManager 支持多节点高可用(HA)部署模式。多个 KVCM 实例
5858
|---|---|---|
5959
| CoordinationMemoryBackend | `memory://` | 单进程测试 |
6060
| CoordinationFileBackend | `file:///path/to/dir` | 单机开发/测试 |
61-
| CoordinationRedisBackend | `redis://[password@]host:port[?params]` | 生产环境 |
61+
| CoordinationRedisBackend | `redis://[auth_token@]host:port/?db=<index>[&params]` | 生产环境 |
62+
63+
Redis DB 只能通过 query 参数 `db` 指定,未配置时使用 DB 0;URI path(例如 `/1`)不会选择 DB。`db > 0` 要求 Redis 服务端支持 `SELECT`,因此不适用于 Redis Cluster。
6264

6365
### 3.2 LeaderElector
6466

@@ -251,7 +253,7 @@ kvcm.leader_elector.loop_interval_ms=100
251253
**多节点 HA(Redis,生产):**
252254

253255
```
254-
kvcm.coordination.uri=redis://your_password@redis-host:6379?timeout_ms=5000&retry_count=3&cluster_name=kvcm_app_0
256+
kvcm.coordination.uri=redis://your_auth_token@redis-host:6379/?db=2&timeout_ms=5000&retry_count=3&cluster_name=kvcm_app_0
255257
kvcm.leader_elector.lease_ms=10000
256258
kvcm.leader_elector.loop_interval_ms=100
257259
kvcm.service.advertised_host=10.0.0.1

kv_cache_manager/config/coordination_backend.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class CoordinationBackend {
2424
*
2525
* @param standard_uri Standardized URI for configuring backend connection parameters
2626
* For local backend: file:///path/to/lock/dir
27-
* For Redis backend: redis://host:port/db
27+
* For Redis backend: redis://[auth_token@]host:port/?db=<index>[&param=value...]
2828
* @return ErrorCode error code
2929
* - EC_OK: Initialization successful
3030
* - EC_BADARGS: Invalid arguments (e.g., unsupported URI protocol)
@@ -141,4 +141,4 @@ class CoordinationBackend {
141141
virtual ErrorCode GetValue(const std::string &key, std::string &out_value) = 0;
142142
};
143143

144-
} // namespace kv_cache_manager
144+
} // namespace kv_cache_manager

kv_cache_manager/config/test/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ cc_test(
110110
],
111111
deps = [
112112
":coordination_backend_test_base",
113+
"//kv_cache_manager/common:redis_client_ext",
114+
"//kv_cache_manager/common:standard_uri",
113115
"//kv_cache_manager/common:unittest",
114116
"//kv_cache_manager/config:coordination_backend",
115117
],

kv_cache_manager/config/test/coordination_redis_backend_test.cc

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,4 +300,48 @@ TEST_F(CoordinationRedisDiffClusterNameTest, TestLeaderElectionKeyIsolation) {
300300
ASSERT_EQ(EC_OK, backend_cluster_b_->Unlock(leader_lock_key, "node_b"));
301301
}
302302

303-
} // namespace kv_cache_manager
303+
TEST(CoordinationRedisDbIsolationTest, SameKeysAreIsolatedByDb) {
304+
const std::string uri_prefix = "redis://test_redis_user:test_redis_password@localhost:6379/"
305+
"?timeout_ms=1000&retry_count=3&client_min_pool_size=1&client_max_pool_size=2";
306+
const std::string cluster_name = "coordination_db_isolation_test";
307+
const std::string uri_db0 = uri_prefix + "&db=0&cluster_name=" + cluster_name;
308+
const std::string uri_db1 = uri_prefix + "&db=1&cluster_name=" + cluster_name;
309+
const std::string key = "same_key";
310+
const std::string lock_key = "same_lock";
311+
312+
RedisClientExt cleanup_db0(StandardUri::FromUri(uri_db0));
313+
RedisClientExt cleanup_db1(StandardUri::FromUri(uri_db1));
314+
ASSERT_TRUE(cleanup_db0.Open());
315+
ASSERT_TRUE(cleanup_db1.Open());
316+
const std::string redis_key = "kvcm_" + cluster_name + "_kv:" + key;
317+
const std::string redis_lock_key = "kvcm_" + cluster_name + "_lock:" + lock_key;
318+
cleanup_db0.Del(redis_key);
319+
cleanup_db1.Del(redis_key);
320+
cleanup_db0.Del(redis_lock_key);
321+
cleanup_db1.Del(redis_lock_key);
322+
323+
auto backend_db0 = CoordinationBackendFactory::CreateAndInitCoordinationBackend(uri_db0);
324+
auto backend_db1 = CoordinationBackendFactory::CreateAndInitCoordinationBackend(uri_db1);
325+
ASSERT_NE(nullptr, backend_db0);
326+
ASSERT_NE(nullptr, backend_db1);
327+
328+
ASSERT_EQ(EC_OK, backend_db1->SetValue(key, "db1"));
329+
std::string value;
330+
EXPECT_EQ(EC_NOENT, backend_db0->GetValue(key, value));
331+
ASSERT_EQ(EC_OK, backend_db0->SetValue(key, "db0"));
332+
ASSERT_EQ(EC_OK, backend_db0->GetValue(key, value));
333+
EXPECT_EQ("db0", value);
334+
ASSERT_EQ(EC_OK, backend_db1->GetValue(key, value));
335+
EXPECT_EQ("db1", value);
336+
337+
// Locks use the same selected connection DB as coordination KV data.
338+
ASSERT_EQ(EC_OK, backend_db0->TryLock(lock_key, "holder_db0", 5000));
339+
ASSERT_EQ(EC_OK, backend_db1->TryLock(lock_key, "holder_db1", 5000));
340+
ASSERT_EQ(EC_OK, backend_db0->Unlock(lock_key, "holder_db0"));
341+
ASSERT_EQ(EC_OK, backend_db1->Unlock(lock_key, "holder_db1"));
342+
343+
cleanup_db0.Del(redis_key);
344+
cleanup_db1.Del(redis_key);
345+
}
346+
347+
} // namespace kv_cache_manager

kv_cache_manager/config/test/registry_manager_redis_backend_test.cc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "kv_cache_manager/config/instance_group.h"
99
#include "kv_cache_manager/config/instance_info.h"
1010
#include "kv_cache_manager/config/registry_manager.h"
11+
#include "kv_cache_manager/config/registry_storage_backend_factory.h"
1112
#include "kv_cache_manager/data_storage/data_storage_manager.h"
1213
#include "kv_cache_manager/data_storage/storage_config.h"
1314
#include "kv_cache_manager/metrics/metrics_registry.h"
@@ -99,6 +100,33 @@ TEST_F(RegistryManagerRedisBackendTest, TestInit) {
99100
ASSERT_FALSE(registry_manager_->Init());
100101
}
101102

103+
TEST_F(RegistryManagerRedisBackendTest, TestRedisDbIsolation) {
104+
const std::string uri_prefix =
105+
"redis://test_redis_user:test_redis_password@localhost:6379/?timeout_ms=1000&retry_count=3";
106+
const std::string cluster_name = "registry_db_isolation_test";
107+
const std::string key = "same_key";
108+
109+
auto backend_db0 =
110+
RegistryStorageBackendFactory::CreateAndInitStorageBackend(uri_prefix + "&db=0&cluster_name=" + cluster_name);
111+
auto backend_db1 =
112+
RegistryStorageBackendFactory::CreateAndInitStorageBackend(uri_prefix + "&db=1&cluster_name=" + cluster_name);
113+
ASSERT_NE(nullptr, backend_db0);
114+
ASSERT_NE(nullptr, backend_db1);
115+
116+
// Use the same cluster name and key so Redis DB is the only isolation boundary.
117+
backend_db0->Delete(key);
118+
backend_db1->Delete(key);
119+
const std::map<std::string, std::string> expected{{"field", "db1"}};
120+
ASSERT_EQ(EC_OK, backend_db1->Save(key, expected));
121+
122+
std::map<std::string, std::string> actual;
123+
EXPECT_EQ(EC_NOENT, backend_db0->Load(key, actual));
124+
ASSERT_EQ(EC_OK, backend_db1->Load(key, actual));
125+
EXPECT_EQ(expected, actual);
126+
127+
EXPECT_EQ(EC_OK, backend_db1->Delete(key));
128+
}
129+
102130
TEST_F(RegistryManagerRedisBackendTest, TestRecover) {
103131
// do init
104132
ASSERT_TRUE(InitRegistryManager());

kv_cache_manager/meta/test/meta_redis_backend_real_service_test.cc

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,46 @@ TEST_F(MetaRedisBackendRealServiceTest, TestOpenAndClose) {
7171
ASSERT_TRUE(meta_redis_backend_->client_pool_ == nullptr);
7272
}
7373

74+
TEST_F(MetaRedisBackendRealServiceTest, TestRedisDbIsolation) {
75+
auto make_config = [](int64_t db) {
76+
auto config = std::make_shared<MetaStorageBackendConfig>();
77+
config->SetStorageType(META_REDIS_BACKEND_TYPE_STR);
78+
config->SetStorageUri("redis://test_redis_user:test_redis_password@localhost:6379/"
79+
"?client_max_pool_size=2&db=" +
80+
std::to_string(db));
81+
return config;
82+
};
83+
84+
MetaRedisBackend backend_db0;
85+
MetaRedisBackend backend_db1;
86+
ASSERT_EQ(EC_OK, backend_db0.Init("test_redis_db_isolation", make_config(0)));
87+
ASSERT_EQ(EC_OK, backend_db1.Init("test_redis_db_isolation", make_config(1)));
88+
ASSERT_EQ(EC_OK, backend_db0.Open());
89+
ASSERT_EQ(EC_OK, backend_db1.Open());
90+
91+
// Use the same instance and cache key so Redis DB is the only isolation boundary.
92+
const KeyTypeVec keys{987654321};
93+
backend_db0.Delete(nullptr, keys);
94+
backend_db1.Delete(nullptr, keys);
95+
const FieldMapVec expected{{{"field", "db1"}}};
96+
ASSERT_EQ(std::vector<ErrorCode>{EC_OK},
97+
backend_db1.Put(nullptr, keys, CacheLocationMapVector(keys.size()), expected));
98+
99+
std::vector<bool> exists;
100+
ASSERT_EQ(std::vector<ErrorCode>{EC_OK}, backend_db0.Exists(nullptr, keys, exists));
101+
EXPECT_EQ(std::vector<bool>{false}, exists);
102+
ASSERT_EQ(std::vector<ErrorCode>{EC_OK}, backend_db1.Exists(nullptr, keys, exists));
103+
EXPECT_EQ(std::vector<bool>{true}, exists);
104+
105+
FieldMapVec actual;
106+
ASSERT_EQ(std::vector<ErrorCode>{EC_OK}, backend_db1.GetProperties(nullptr, keys, {"field"}, actual));
107+
EXPECT_EQ(expected, actual);
108+
109+
EXPECT_EQ(std::vector<ErrorCode>{EC_OK}, backend_db1.Delete(nullptr, keys));
110+
EXPECT_EQ(EC_OK, backend_db0.Close());
111+
EXPECT_EQ(EC_OK, backend_db1.Close());
112+
}
113+
74114
TEST_F(MetaRedisBackendRealServiceTest, TestMultiThreadSimple) {
75115
ASSERT_EQ(EC_OK, meta_redis_backend_->Init("test_multi_thread_simple", meta_storage_backend_config_));
76116
ASSERT_EQ(EC_OK, meta_redis_backend_->Open());

0 commit comments

Comments
 (0)