diff options
| author | Zheng Chao <[email protected]> | 2022-11-30 10:50:46 +0800 |
|---|---|---|
| committer | Zheng Chao <[email protected]> | 2022-11-30 10:50:46 +0800 |
| commit | bf2d2e184f381c8f82a38cbc84b0cbd688413a35 (patch) | |
| tree | 476a7d8fed5139fc082b3e5df9ba7e377c23d9cc | |
| parent | e19de4ab3592690e3d0a54447b088ad38aa48683 (diff) | |
:sparkles: Support `KEYSLOT` command.
| -rw-r--r-- | docs/commands.md | 18 | ||||
| -rw-r--r-- | docs/design.md | 2 | ||||
| -rw-r--r-- | src/inc_internal/swarmkv_keyspace.h | 2 | ||||
| -rw-r--r-- | src/swarmkv.c | 4 | ||||
| -rw-r--r-- | src/swarmkv_keyspace.c | 9 | ||||
| -rw-r--r-- | test/swarmkv_gtest.cpp | 75 | ||||
| -rw-r--r-- | tools/swarmkv_cli.c | 38 |
7 files changed, 88 insertions, 60 deletions
diff --git a/docs/commands.md b/docs/commands.md index a89b9c6..45483da 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -80,6 +80,22 @@ Return - 1 if the timeout was removed. - 0 if key does not exist or does not have an associated timeout. + +### KEYSLOT + +Syntax + +``` +KEYSLOT key +``` + +Returns an integer identifying the hash slot the specified key hashes to. This command is mainly useful for debugging and testing. + +Return + +- Integer reply: The hash slot number. + + ## String and Integer ### GET @@ -524,6 +540,7 @@ The pattern is exactly same as Redis https://redis.io/commands/keys/ . | EXPIRE | key seconds | Integer reply, specifically: 1 if the timeout was set. 0 if the timeout was not set. e.g. key doesn't exist | | TTL | key | Integer reply: TTL in seconds, in case of error: -2 if the key does not exist; -1 if the key exists but has no associated expire. | | PERSIST | key | Integer reply, specifically:<br/><br/>1 if the timeout was removed.<br/>0 if key does not exist or does not have an associated timeout. | +| KEYSLOT | key | Integer reply: The hash slot the specified key hashes to. | | SADD | key member [member ...] | Integer reply: the number of elements that were added to the set, not including all the elements already present in the set. | | SREM | key member [member ...] | Integer reply: the number of members that were removed from the set, not including non existing members. The key will NOT be deleted if all members are removed. If key does not exist, it is treated as an empty set and this command returns 0. | | SCARD | key | Integer reply: the cardinality (number of elements) of the set, or 0 if key does not exist. | @@ -547,7 +564,6 @@ The pattern is exactly same as Redis https://redis.io/commands/keys/ . | CRDT PUSH | key blob | Simple string reply: OK if PUSH was executed correctly. | | CRDT PULL | key | Blog reply: a blob of state-based CRDT | | CLUSTER KEYS | pattern | Array reply: list of keys matching pattern. | -| CLUSTER KEYSLOT | keyslot | Integer reply: The hash slot the specified key hashes to. | | CLUSTER NODES | | | | CLUSTER SLOTS | | | | CLUSTER ADDNODE | IP:port | | diff --git a/docs/design.md b/docs/design.md index 7b1d701..c0b1d1a 100644 --- a/docs/design.md +++ b/docs/design.md @@ -78,7 +78,7 @@ Except for node in dry run mode, cluster nodes will watch leadership changes and ### Hash tags -Like [Redis Cluster](https://redis.io/docs/reference/cluster-spec/), SwarmKV implements hash tags, which can be used to ensure that multiple keys are allocated in the same hash slot. If the key contains a "{...}" pattern only the substring between `{` and `}` is hashed in order to obtain the hash slot. However since it is possible that there are multiple occurrences of `{` or `}` the algorithm is well specified by the following rules: +Like [Redis Cluster](https://redis.io/docs/reference/cluster-spec/), SwarmKV implements hash tags, which can be used to ensure that multiple keys are allocated in the same hash slot for managed by the same key owner. Note that the hash tags does not guarrantee objects are managed by the same object owner. If the key contains a "{...}" pattern only the substring between `{` and `}` is hashed in order to obtain the hash slot. However since it is possible that there are multiple occurrences of `{` or `}` the algorithm is well specified by the following rules: - IF the key contains a `{` character. - AND IF there is a `}` character to the right of `{`. diff --git a/src/inc_internal/swarmkv_keyspace.h b/src/inc_internal/swarmkv_keyspace.h index ee4282e..8208920 100644 --- a/src/inc_internal/swarmkv_keyspace.h +++ b/src/inc_internal/swarmkv_keyspace.h @@ -21,7 +21,7 @@ void swarmkv_keyspace_info(struct swarmkv_module *mod_keyspace, struct keyspace_ void swarmkv_keyspace_set_exec_cmd_func(struct swarmkv_module *mod_keyspace, exec_cmd_func *func, void *func_handle); void swarmkv_keyspace_set_monitor_handle(struct swarmkv_module *mod_keyspace, struct swarmkv_module *mod_monitor); - +enum cmd_exec_result keyslot_command(struct swarmkv_module *mod_keyspace, const struct swarmkv_cmd *cmd, const node_t *accessing_node, struct swarmkv_reply **reply); enum cmd_exec_result keyspace_rlist_command(struct swarmkv_module *mod_keyspace, const struct swarmkv_cmd *cmd, const node_t *accessing_node, struct swarmkv_reply **reply); enum cmd_exec_result keyspace_radd_command(struct swarmkv_module *mod_keyspace, const struct swarmkv_cmd *cmd, const node_t *accessing_node, struct swarmkv_reply **reply); enum cmd_exec_result keyspace_xradd_command(struct swarmkv_module *mod_keyspace, const struct swarmkv_cmd *cmd, const node_t *accessing_node, struct swarmkv_reply **reply); diff --git a/src/swarmkv.c b/src/swarmkv.c index 5fadad7..d955684 100644 --- a/src/swarmkv.c +++ b/src/swarmkv.c @@ -896,6 +896,9 @@ void command_spec_init(struct swarmkv *db) command_register(&(db->command_table), "TYPE", "key", 1, 1, CMD_KEY_RW, REPLY_STR_NONE, AUTO_ROUTE, type_command, db->mod_store); + command_register(&(db->command_table), "KEYSLOT", "key", + 1, 1, CMD_KEY_RO, REPLY_ERROR, AUTO_ROUTE, + keyslot_command, db->mod_keyspace); /* Set commands */ command_register(&(db->command_table), "SADD", "key member [member ...]", @@ -1011,6 +1014,7 @@ void command_spec_init(struct swarmkv *db) 1, 2, CMD_KEY_RO, REPLY_NA, AUTO_ROUTE, keyspace_rdel_command, db->mod_keyspace); + /* low-level keyspace reorgnization commands */ command_register(&(db->command_table), "KEYSPACE SETSLOT", "<slot> IMPORTING|MIGRATING|NODE|STABLE IP:port", 2, -1, CMD_KEY_NA, REPLY_NA, NOT_AUTO_ROUTE, diff --git a/src/swarmkv_keyspace.c b/src/swarmkv_keyspace.c index 66c90b5..3199451 100644 --- a/src/swarmkv_keyspace.c +++ b/src/swarmkv_keyspace.c @@ -1292,7 +1292,14 @@ void swarmkv_keyspace_info(struct swarmkv_module *mod_keyspace, struct keyspace_ pthread_mutex_unlock(&slot_rt->mutex); } } - +enum cmd_exec_result keyslot_command(struct swarmkv_module *mod_keyspace, const struct swarmkv_cmd *cmd, const node_t *accessing_node, struct swarmkv_reply **reply) +{ + /* KEYSLOT key*/ + char *key=cmd->argv[1]; + int slot_id=key_hash_slot(key, strlen(key)); + *reply=swarmkv_reply_new_integer(slot_id); + return FINISHED; +} enum cmd_exec_result keyspace_setslot_command(struct swarmkv_module *mod_keyspace, const struct swarmkv_cmd *cmd, const node_t *accessing_node, struct swarmkv_reply **reply) { /* KEYSPACE SETSLOT <slot> IMPORTING|MIGRATING|NODE|STABLE IP:port */ diff --git a/test/swarmkv_gtest.cpp b/test/swarmkv_gtest.cpp index 086e20f..f8d2a1d 100644 --- a/test/swarmkv_gtest.cpp +++ b/test/swarmkv_gtest.cpp @@ -56,14 +56,14 @@ protected: // Some expensive resource shared by all tests. static struct log_handle *logger; - static struct swarmkv* db; + static struct swarmkv *db; }; struct swarmkv *SwarmkvBasicTest::db; struct log_handle *SwarmkvBasicTest::logger; TEST_F(SwarmkvBasicTest, TypeString) { struct cmd_exec_arg *arg=NULL; - struct swarmkv* db=SwarmkvBasicTest::db; + struct swarmkv *db=SwarmkvBasicTest::db; const char* key="name"; const char* val="zhangsan"; int exec_successful=0; @@ -87,7 +87,7 @@ TEST_F(SwarmkvBasicTest, TypeString) } TEST_F(SwarmkvBasicTest, TypeInteger) { - struct swarmkv* db=SwarmkvBasicTest::db; + struct swarmkv *db=SwarmkvBasicTest::db; const char *key="int1"; struct swarmkv_reply * reply=NULL; @@ -123,7 +123,7 @@ TEST_F(SwarmkvBasicTest, TypeInteger) TEST_F(SwarmkvBasicTest, GenericDEL) { struct cmd_exec_arg *arg=NULL; - struct swarmkv* db=SwarmkvBasicTest::db; + struct swarmkv *db=SwarmkvBasicTest::db; const char* key="name2"; const char* val="zhangsan"; int exec_successful=0; @@ -145,7 +145,7 @@ TEST_F(SwarmkvBasicTest, GenericDEL) } TEST_F(SwarmkvBasicTest, GenericTYPE) { - struct swarmkv* db=SwarmkvBasicTest::db; + struct swarmkv *db=SwarmkvBasicTest::db; struct swarmkv_reply *reply=NULL; //TYPE string @@ -218,7 +218,7 @@ TEST_F(SwarmkvBasicTest, GenericTYPE) TEST_F(SwarmkvBasicTest, TypeSet) { struct cmd_exec_arg *arg=NULL; - struct swarmkv* db=SwarmkvBasicTest::db; + struct swarmkv *db=SwarmkvBasicTest::db; const char* key="Jack's friends"; const char* member[]={"zhangsan", "lisi", "王二麻子", "Tom", "مرحبا"}; size_t member_len[32]; @@ -403,7 +403,7 @@ TEST_F(SwarmkvBasicTest, TypeHash) TEST_F(SwarmkvBasicTest, EXPIRE_TTL) { struct cmd_exec_arg *arg=NULL; - struct swarmkv* db=SwarmkvBasicTest::db; + struct swarmkv *db=SwarmkvBasicTest::db; const char* key="quarantine"; const char* val="wuhan-江夏-如家"; int exec_successful=0; @@ -458,7 +458,24 @@ TEST_F(SwarmkvBasicTest, EXPIRE_TTL) cmd_exec_arg_free(arg); arg=NULL; } - +TEST_F(SwarmkvBasicTest, HashTags) +{ + struct swarmkv *db=SwarmkvBasicTest::db; + struct swarmkv_reply *reply=NULL; + const char *keys[]={"{user1000}.following", "{user1000}.followers", "{user1000}.{user1001}.watched"}; + int slot_id=-1; + for(size_t i=0; i<sizeof(keys)/sizeof(const char *); i++) + { + reply=swarmkv_command(db, "keyslot %s", keys[i]); + EXPECT_EQ(reply->type, SWARMKV_REPLY_INTEGER); + if(slot_id>=0) + { + EXPECT_EQ(reply->integer, slot_id); + } + slot_id=reply->integer; + swarmkv_reply_free(reply); + } +} class SwarmkvTwoNodes : public testing::Test { @@ -514,8 +531,8 @@ struct swarmkv* SwarmkvTwoNodes::db2; TEST_F(SwarmkvTwoNodes, SET_GET) { struct cmd_exec_arg *arg=NULL; - struct swarmkv* db1=SwarmkvTwoNodes::db1; - struct swarmkv* db2=SwarmkvTwoNodes::db2; + struct swarmkv *db1=SwarmkvTwoNodes::db1; + struct swarmkv *db2=SwarmkvTwoNodes::db2; const char *key="id001"; const char *val1="zhangsan", *val2="lisi"; int exec_successful=0; @@ -554,8 +571,8 @@ TEST_F(SwarmkvTwoNodes, SET_GET) TEST_F(SwarmkvTwoNodes, SET1kString) { struct cmd_exec_arg *arg=NULL; - struct swarmkv* db1=SwarmkvTwoNodes::db1; - struct swarmkv* db2=SwarmkvTwoNodes::db2; + struct swarmkv *db1=SwarmkvTwoNodes::db1; + struct swarmkv *db2=SwarmkvTwoNodes::db2; struct swarmkv* tmp_db=NULL; const char *key_prefix="test-1k-string"; const char *val_prefix="value-xx"; @@ -596,8 +613,8 @@ TEST_F(SwarmkvTwoNodes, SET1kString) TEST_F(SwarmkvTwoNodes, INCRYBY1kInteger) { struct cmd_exec_arg *arg=NULL; - struct swarmkv* db1=SwarmkvTwoNodes::db1; - struct swarmkv* db2=SwarmkvTwoNodes::db2; + struct swarmkv *db1=SwarmkvTwoNodes::db1; + struct swarmkv *db2=SwarmkvTwoNodes::db2; struct swarmkv* tmp_db=NULL; const char *key_prefix="incrby-1k-integer"; char key[128]="", val[128]=""; @@ -654,8 +671,8 @@ TEST_F(SwarmkvTwoNodes, INCRYBY1kInteger) TEST_F(SwarmkvTwoNodes, DEL) { struct cmd_exec_arg *arg=NULL; - struct swarmkv* db1=SwarmkvTwoNodes::db1; - struct swarmkv* db2=SwarmkvTwoNodes::db2; + struct swarmkv *db1=SwarmkvTwoNodes::db1; + struct swarmkv *db2=SwarmkvTwoNodes::db2; const char* key="id002"; const char* val="to-be-deleted"; int exec_successful=0; @@ -699,8 +716,8 @@ TEST_F(SwarmkvTwoNodes, DEL) TEST_F(SwarmkvTwoNodes, INCRBY) { struct cmd_exec_arg *arg=NULL; - struct swarmkv* db1=SwarmkvTwoNodes::db1; - struct swarmkv* db2=SwarmkvTwoNodes::db2; + struct swarmkv *db1=SwarmkvTwoNodes::db1; + struct swarmkv *db2=SwarmkvTwoNodes::db2; const char* key="id003"; long long val=10000; int exec_successful=0; @@ -734,8 +751,8 @@ TEST_F(SwarmkvTwoNodes, INCRBY) TEST_F(SwarmkvTwoNodes, EXPIRE) { struct cmd_exec_arg *arg=NULL; - struct swarmkv* db1=SwarmkvTwoNodes::db1; - struct swarmkv* db2=SwarmkvTwoNodes::db2; + struct swarmkv *db1=SwarmkvTwoNodes::db1; + struct swarmkv *db2=SwarmkvTwoNodes::db2; struct swarmkv* tmp_db=NULL; char key[128]="", val[128]=""; int exec_successful=0, i=0, round=128, n_success=0; @@ -828,8 +845,8 @@ TEST_F(SwarmkvTwoNodes, EXPIRE) TEST_F(SwarmkvTwoNodes, TTL) { struct cmd_exec_arg *arg=NULL; - struct swarmkv* db1=SwarmkvTwoNodes::db1; - struct swarmkv* db2=SwarmkvTwoNodes::db2; + struct swarmkv *db1=SwarmkvTwoNodes::db1; + struct swarmkv *db2=SwarmkvTwoNodes::db2; const char *key="ttl-key-001"; const char *value="hello-world"; @@ -874,8 +891,8 @@ TEST_F(SwarmkvTwoNodes, TTL) TEST_F(SwarmkvTwoNodes, PERSIST) { struct cmd_exec_arg *arg=NULL; - struct swarmkv* db1=SwarmkvTwoNodes::db1; - struct swarmkv* db2=SwarmkvTwoNodes::db2; + struct swarmkv *db1=SwarmkvTwoNodes::db1; + struct swarmkv *db2=SwarmkvTwoNodes::db2; const char *key="persit-key-001"; const char *value="hello-world"; @@ -938,8 +955,8 @@ TEST_F(SwarmkvTwoNodes, PERSIST) TEST_F(SwarmkvTwoNodes, FromLocalReplica) { struct cmd_exec_arg *arg=NULL; - struct swarmkv* db1=SwarmkvTwoNodes::db1; - struct swarmkv* db2=SwarmkvTwoNodes::db2; + struct swarmkv *db1=SwarmkvTwoNodes::db1; + struct swarmkv *db2=SwarmkvTwoNodes::db2; const char* key="id004"; int exec_successful=0; arg=cmd_exec_arg_new(); @@ -991,7 +1008,7 @@ TEST_F(SwarmkvTwoNodes, FromLocalReplica) TEST_F(SwarmkvTwoNodes, TypeSet) { struct cmd_exec_arg *arg=NULL; - struct swarmkv* db[2]; + struct swarmkv *db[2]; db[0]=SwarmkvTwoNodes::db1; db[1]=SwarmkvTwoNodes::db2; const char *key="myset001"; @@ -1069,7 +1086,7 @@ TEST_F(SwarmkvTwoNodes, TypeSet) TEST_F(SwarmkvTwoNodes, TypeTokenBucket) { struct cmd_exec_arg *arg=NULL; - struct swarmkv* db[2]; + struct swarmkv *db[2]; db[0]=SwarmkvTwoNodes::db1; db[1]=SwarmkvTwoNodes::db2; const char *key="tb-192.168.0.1"; @@ -1162,7 +1179,7 @@ TEST_F(SwarmkvTwoNodes, TypeTokenBucket) } TEST_F(SwarmkvTwoNodes, TypeHash) { - struct swarmkv* db[2]; + struct swarmkv *db[2]; db[0]=SwarmkvTwoNodes::db1; db[1]=SwarmkvTwoNodes::db2; diff --git a/tools/swarmkv_cli.c b/tools/swarmkv_cli.c index 9f73640..8e75dfe 100644 --- a/tools/swarmkv_cli.c +++ b/tools/swarmkv_cli.c @@ -135,7 +135,7 @@ static int consul_kv_init_block(char *cluster_name, char *argv[], size_t argc) sdsfree(slot_json); return ret; } -struct swarmkv_reply *cluster_create_command(struct swarmkv* db, char *argv[], size_t argc) +struct swarmkv_reply *cluster_create_command(struct swarmkv *db, char *argv[], size_t argc) { int ret=0; struct swarmkv_reply *reply=NULL; @@ -156,7 +156,7 @@ struct swarmkv_reply *cluster_create_command(struct swarmkv* db, char *argv[], s return reply; } -struct swarmkv_reply *cluster_nodes_command(struct swarmkv* db, char *argv[], size_t argc) +struct swarmkv_reply *cluster_nodes_command(struct swarmkv *db, char *argv[], size_t argc) { struct swarmkv_reply *reply=NULL; int ret=0, no_leader=1; @@ -211,7 +211,7 @@ struct swarmkv_reply *cluster_nodes_command(struct swarmkv* db, char *argv[], si } return reply; } -struct swarmkv_reply *cluster_slots_command(struct swarmkv* db, char *argv[], size_t argc) +struct swarmkv_reply *cluster_slots_command(struct swarmkv *db, char *argv[], size_t argc) { struct swarmkv_reply *reply=NULL; char url[SWARMKV_URL_MAX]=""; @@ -265,7 +265,7 @@ int is_node_active(struct swarmkv_reply *nodes_reply, node_t *node) } return node_is_active; } -struct swarmkv_reply *cluster_addkeyowner_command(struct swarmkv* db, char *argv[], size_t argc) +struct swarmkv_reply *cluster_addkeyowner_command(struct swarmkv *db, char *argv[], size_t argc) { size_t n_new_node=argc-2; node_t new_nodes[n_new_node]; @@ -498,22 +498,7 @@ error_out: return reply; } -struct swarmkv_reply *cluster_keyslot_command(struct swarmkv* db, char *argv[], size_t argc) -{ - /*KEYSPACE KEYSLOT key*/ - struct swarmkv_reply *reply=NULL; - if(argc <= 2) - { - reply = swarmkv_reply_new_error(error_read_slot_table_failed); - return reply; - } - - char* key=argv[2]; - int slot_id=key_hash_slot(key, strlen(key)); - reply=swarmkv_reply_new_integer(slot_id); - return reply; -} -struct swarmkv_reply *cluster_keys_command(struct swarmkv* db, char *argv[], size_t argc) +struct swarmkv_reply *cluster_keys_command(struct swarmkv *db, char *argv[], size_t argc) { struct swarmkv_reply *nodes_reply=NULL, *keyspace_keys_reply=NULL, *reply=NULL; size_t i=0; @@ -545,7 +530,7 @@ struct swarmkv_reply *cluster_keys_command(struct swarmkv* db, char *argv[], siz } return reply; } -struct swarmkv_reply *cluster_info_command(struct swarmkv* db, char *argv[], size_t argc) +struct swarmkv_reply *cluster_info_command(struct swarmkv *db, char *argv[], size_t argc) { struct swarmkv_reply *nodes_reply=NULL, *per_node_info_reply=NULL; struct swarmkv_reply *reply; @@ -643,7 +628,7 @@ void exec_cmd_generic_callback(const struct swarmkv_reply* reply, void * cb_arg) return; } -void exec_cmd_crdt_join(struct swarmkv* db, struct cluster_sanity_ctx *ctx, struct replica_list *keyspace_list, struct replica_node *crdt_addr, char *key) +void exec_cmd_crdt_join(struct swarmkv *db, struct cluster_sanity_ctx *ctx, struct replica_list *keyspace_list, struct replica_node *crdt_addr, char *key) { struct replica_node *tmp=NULL, *keyspace_addr=NULL; @@ -730,7 +715,7 @@ void destroy_relica_list_hash(struct cluster_sanity_ctx *ctx) FREE(&ctx); } -struct swarmkv_reply *cluster_sanity_command(struct swarmkv* db, char *argv[], size_t argc) +struct swarmkv_reply *cluster_sanity_command(struct swarmkv *db, char *argv[], size_t argc) { int need_fix=0; int timed_wait_rv; @@ -923,7 +908,7 @@ finish: return reply; } -struct swarmkv_reply *attach_command(struct swarmkv* db, char *argv[], size_t argc) +struct swarmkv_reply *attach_command(struct swarmkv *db, char *argv[], size_t argc) { struct swarmkv_reply *reply=NULL; if(argc<2) @@ -947,7 +932,7 @@ struct swarmkv_reply *attach_command(struct swarmkv* db, char *argv[], size_t ar return reply; } -struct swarmkv_reply *detach_command(struct swarmkv* db, char *argv[], size_t argc) +struct swarmkv_reply *detach_command(struct swarmkv *db, char *argv[], size_t argc) { struct swarmkv_reply *reply=NULL; g_runtime.is_attaching=0; @@ -956,7 +941,7 @@ struct swarmkv_reply *detach_command(struct swarmkv* db, char *argv[], size_t ar return reply; } -typedef struct swarmkv_reply *cluster_command_func(struct swarmkv* db, char *argv[], size_t argc); +typedef struct swarmkv_reply *cluster_command_func(struct swarmkv *db, char *argv[], size_t argc); struct cluster_cmd_spec { @@ -967,7 +952,6 @@ struct cluster_cmd_spec struct cluster_cmd_spec cluster_cmds[]={ {"CLUSTER KEYS", "pattern", cluster_keys_command}, {"CLUSTER INFO", "[section]", cluster_info_command}, - {"CLUSTER KEYSLOT", "key", cluster_keyslot_command}, {"CLUSTER NODES", "[breif | verbose]", cluster_nodes_command}, {"CLUSTER SLOTS", "", cluster_slots_command}, {"CLUSTER ADDKEYOWNER", "IP:port [IP:port ...]", cluster_addkeyowner_command}, |
