key-value存储实现

一、项目简介

key-value存储其实是一个小型的redis,用户在客户端输入存储相关的指令发送给服务器端,服务器在接受到指令之后对指令做对应的操作。在整个过程中,涉及到客户端和服务器端使用什么样的网络模型,如epoll,io_uring,ntyco等等,服务器端对指令做操作涉及到的数据结构,如array,rbtree,hash等等。
在本项目中,网络层的组件选择了epoll,io_uring,存储数据结构选择了array,rbtree,hash。

二、项目流程图

在这里插入图片描述

三、网络

在网络具体实现中,把收发数据存放在不同的数组当中,下面是结构体定义:

struct conn_item{
    int fd;

    char rbuffer[BUFFER_LENGTH];
    char wbuffer[BUFFER_LENGTH];
    int rlen;
    int wlen;

    union 
    {
        RCALLBACK accept_callback;
        RCALLBACK recv_callback;
    }recv_t;

    RCALLBACK send_callback;
    
};

3.1、epoll实现

如果对epoll不熟悉,可以参考我的博文:高性能网络编程

下面是具体实现

#include "kvstore.h"

#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <pthread.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#include <sys/time.h>

#define PORT 8000
#define SERVER_ADR "127.0.0.1"
struct conn_item connlist[1048576] = {0};

int epfd = 0;

int init_serv(unsigned short port)
{
    int fd = socket(PF_INET, SOCK_STREAM, 0);

    struct sockaddr_in sever_adr;

    memset(&sever_adr, 0, sizeof(sever_adr));
    sever_adr.sin_family = AF_INET;
    sever_adr.sin_port = htons(port);
    sever_adr.sin_addr.s_addr = htonl(INADDR_ANY);

    if ((bind(fd, (const struct sockaddr *)&sever_adr, sizeof(sever_adr))) == -1)
    {
        perror("bind error");
        return -1;
    }

    if ((listen(fd, 10)) == -1)
    {
        perror("listen error");
        return -1;
    }

    return fd;
}

int set_event(int fd, int event, int flag)
{
    if (flag)
    {
        struct epoll_event ev;

        ev.data.fd = fd;
        ev.events = event;

        epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
    }
    else
    {
        struct epoll_event ev;

        ev.data.fd = fd;
        ev.events = event;

        epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
    }
}

int send_cb(int fd)
{
    char *buffer = connlist[fd].wbuffer;

    int idx = connlist[fd].wlen;

    int count = send(fd, buffer, idx, 0);

    set_event(fd, EPOLLIN, 0);

    memset(connlist[fd].wbuffer, 0, BUFFER_LENGTH);
    connlist[fd].wlen = 0;

    return count;
}

int recv_cb(int fd)
{

    char *buffer = connlist[fd].rbuffer;
    int idx = connlist[fd].rlen;

    int count = recv(fd, buffer, BUFFER_LENGTH, 0);
    if (count == 0)
    {
        printf("disconnect\n");
        epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
        close(fd);

        return -1;
    }

    connlist[fd].rlen = count;

    kvstore_request(&connlist[fd]);
    connlist[fd].wlen = strlen(connlist[fd].wbuffer);

    set_event(fd, EPOLLOUT, 0);

    memset(connlist[fd].rbuffer, 0, BUFFER_LENGTH);
    connlist[fd].rlen = 0;

    return count;
}

int accept_cb(int fd)
{
    struct sockaddr_in clnt_adr;

    memset(&clnt_adr, 0, sizeof(clnt_adr));
    socklen_t len = sizeof(clnt_adr);
    int connfd = accept(fd, (struct sockaddr *)&clnt_adr, &len);
    if (connfd < 0)
    {
        perror("accept error");
        return -1;
    }

    set_event(connfd, EPOLLIN, 1);

    connlist[connfd].fd = connfd;
    memset(connlist[connfd].rbuffer, 0, BUFFER_LENGTH);
    connlist[connfd].rlen = 0;
    memset(connlist[connfd].wbuffer, 0, BUFFER_LENGTH);
    connlist[connfd].wlen = 0;

    connlist[connfd].send_callback = send_cb;
    connlist[connfd].recv_t.recv_callback = recv_cb;

    return connfd;
}

int epoll_entry()
{

    int port_count = 20;
    unsigned short port = 2048;
    int i = 0;

    epfd = epoll_create(1);
    int sockfd;

    for (i = 0; i < port_count; i++)
    {
        sockfd = init_serv(port + i);
        connlist[sockfd].fd = sockfd;
        connlist[sockfd].recv_t.accept_callback = accept_cb;
        set_event(sockfd, EPOLLIN, 1);
    }

    struct epoll_event events[1024] = {0};

    while (1)
    {
        int nfds = epoll_wait(epfd, events, 1024, -1);
        if (nfds < 0)
        {
            perror("epoll wait error");
            return -1;
        }

        int i;
        for (i = 0; i < nfds; i++)
        {
            int connfd = events[i].data.fd;

            if (events[i].events & EPOLLIN)
            {

                int count = connlist[connfd].recv_t.recv_callback(connfd);
            }
            else if (events[i].events & EPOLLOUT)
            {

                int count = connlist[connfd].send_callback(connfd);
            }
        }
    }
}

其实,这部分代码也是针对epoll实现的一个改写,关键还是在于掌握epoll如何使用。项目只是把这些知识点组织起来。

3.2、io_uring实现

如果对io_uring不熟悉,可以参考我的博文:【C】io_uring原理与实战

下面是具体实现

#include "kvstore.h"
#include <unistd.h>
#include <liburing.h>
#include <netinet/in.h>

struct conn_info
{
    int fd;
    int event;
};

int init_server(unsigned short port)
{

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(struct sockaddr_in));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serveraddr.sin_port = htons(port);

    if (-1 == bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr)))
    {
        perror("bind");
        return -1;
    }

    listen(sockfd, 10);

    return sockfd;
}

#define ENTRIES_LENGTH 1024

#define EVENT_ACCEPT 0
#define EVENT_READ 1
#define EVENT_WRITE 2

struct conn_item connlist[1048576] = {0};

int set_event_accept(struct io_uring *ring, int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags)
{
    struct io_uring_sqe *sqe = io_uring_get_sqe(ring);

    struct conn_info accept_info;
    accept_info.fd = sockfd;
    accept_info.event = EVENT_ACCEPT;

    io_uring_prep_accept(sqe, sockfd, addr, addrlen, flags);
    memcpy(&sqe->user_data, &accept_info, sizeof(accept_info));
}

int set_event_recv(struct io_uring *ring, int sockfd, int flags)
{
    struct io_uring_sqe *sqe = io_uring_get_sqe(ring);

    struct conn_info accept_info;
    accept_info.fd = sockfd;
    accept_info.event = EVENT_READ;

    io_uring_prep_recv(sqe, sockfd, connlist[sockfd].rbuffer, BUFFER_LENGTH, flags);
    memcpy(&sqe->user_data, &accept_info, sizeof(accept_info));
}

int set_event_send(struct io_uring *ring, int sockfd, int flags)
{
    struct io_uring_sqe *sqe = io_uring_get_sqe(ring);

    struct conn_info accept_info;
    accept_info.fd = sockfd;
    accept_info.event = EVENT_WRITE;

    io_uring_prep_send(sqe, sockfd, connlist[sockfd].wbuffer, connlist[sockfd].wlen, flags);
    memcpy(&sqe->user_data, &accept_info, sizeof(accept_info));
}

int io_uring_entry()
{
    unsigned short port = 8001;
    int sockfd = init_server(port);

    struct io_uring_params params;
    memset(&params, 0, sizeof(params));

    struct io_uring ring;
    memset(&ring, 0, sizeof(ring));

    io_uring_queue_init_params(ENTRIES_LENGTH, &ring, &params);

    struct sockaddr_in clntadr;
    socklen_t len = sizeof(clntadr);

    set_event_accept(&ring, sockfd, (struct sockaddr *)&clntadr, &len, 0);

    char buffer[BUFFER_LENGTH] = {0};

    while (1)
    {
        io_uring_submit(&ring);

        struct io_uring_cqe *cqe;

        io_uring_wait_cqe(&ring, &cqe);

        struct io_uring_cqe *cqes[128];
        int nready = io_uring_peek_batch_cqe(&ring, cqes, 128);

        int i;
        for (i = 0; i < nready; i++)
        {
            struct io_uring_cqe *entry = cqes[i];
            struct conn_info result;

            memcpy(&result, &entry->user_data, sizeof(result));
            if (result.event == EVENT_ACCEPT)
            {
                set_event_accept(&ring, sockfd, (struct sockaddr *)&clntadr, &len, 0);
                int connfd = entry->res;

                connlist[connfd].fd = connfd;
                memset(connlist[connfd].rbuffer, 0, BUFFER_LENGTH);
                connlist[connfd].rlen = 0;
                memset(connlist[connfd].wbuffer, 0, BUFFER_LENGTH);
                connlist[connfd].wlen = 0;

                set_event_recv(&ring, connfd, 0);
            }
            else if (result.event == EVENT_READ)
            {

                kvstore_request(&connlist[result.fd]);
                connlist[result.fd].wlen = strlen(connlist[result.fd].wbuffer);
                // printf("wbuffer is %s\n",connlist[result.fd].wbuffer);
                int ret = entry->res;

                if (ret == 0)
                {
                    close(result.fd);
                }
                else if (ret > 0)
                {
                    memset(connlist[result.fd].rbuffer, 0, BUFFER_LENGTH);
                    connlist[result.fd].rlen = 0;
                    set_event_send(&ring, result.fd, 0);
                }
            }
            else if (result.event == EVENT_WRITE)
            {
                memset(connlist[result.fd].wbuffer, 0, BUFFER_LENGTH);
                connlist[result.fd].wlen = 0;
                set_event_recv(&ring, result.fd, 0);
            }
        }
        io_uring_cq_advance(&ring, nready);
    }
}

四、协议

协议的设置也是参照redis的一些命令格式,实现一些CRUD的操作,下面是具体的协议格式

    "SET",
    "GET",
    "DEL",
    "MOD",
    "COUNT",
    "RSET",
    "RGET",
    "RDEL",
    "RMOD",
    "RCOUNT",
    "HSET",
    "HGET",
    "HDEL",
    "HMOD",
    "HCOUNT"

其中R代表rbtree,H代表hash。
下面是关于如何实现协议的具体代码

int kvstore_spilt_token(char *msg, char **tokens)
{
    if (msg == NULL || tokens == NULL)
        return -1;

    int idx = 0;
    char *ch = strtok(msg, " ");
    while (ch != NULL)
    {
        tokens[idx++] = ch;
        ch = strtok(NULL, " ");
    }

    return idx;
}

int kvstore_parser_protocol(struct conn_item *item, char **tokens, int count)
{
    if (item == NULL || tokens[0] == NULL || count == 0)
        return -1;

    int cmd = KVS_CMD_START;

    for (cmd = KVS_CMD_START; cmd < KVS_CMD_SIZE; cmd++)
    {
        if (strcmp(commands[cmd], tokens[0]) == 0)
        {
            break;
        }
    }

    char *msg = item->wbuffer;
    char *key = tokens[1];
    char *value = tokens[2];
    memset(msg, 0, BUFFER_LENGTH);

    switch (cmd)
    {
#if ENABLE_ARRAY_KVENGINE
    // array
    case KVS_CMD_SET:
    {
     
        int ress = kvstore_array_set(key, value);
       
        if (!ress)
        {
            snprintf(msg, BUFFER_LENGTH, "SUCCESS");
        }
        else
        {
            snprintf(msg, BUFFER_LENGTH, "FAILED");
        }
        break;
    }

    case KVS_CMD_GET:

    {
      
        char *val = kvstore_array_get(key);
       
        if (val)
        {
            snprintf(msg, BUFFER_LENGTH, "%s", val);
        }
        else
        {
            snprintf(msg, BUFFER_LENGTH, "NO EXIST");
        }
        break;
    }
    case KVS_CMD_DEL:
    { // printf("del\n");
       
        int resd = kvstore_array_delete(key);
       
        if (resd < 0)
        { // server
            snprintf(msg, BUFFER_LENGTH, "%s", "ERROR");
        }
        else if (resd == 0)
        {
            snprintf(msg, BUFFER_LENGTH, "%s", "SUCCESS");
        }
        else
        {
            snprintf(msg, BUFFER_LENGTH, "NO EXIST");
        }

        break;
    }

    case KVS_CMD_MOD:
    { // printf("mod\n");
      
        int resm = kvstore_array_modify(key, value);
       
        if (resm < 0)
        { // server
            snprintf(msg, BUFFER_LENGTH, "%s", "ERROR");
        }
        else if (resm == 0)
        {
            snprintf(msg, BUFFER_LENGTH, "%s", "SUCCESS");
        }
        else
        {
            snprintf(msg, BUFFER_LENGTH, "NO EXIST");
        }

        break;
    }
    case KVS_CMD_COUNT:
    {
        int counta = kvstore_array_count();
        if (counta < 0)
        { // server
            snprintf(msg, BUFFER_LENGTH, "%s", "ERROR");
        }
        else
        {
            snprintf(msg, BUFFER_LENGTH, "%d", counta);
        }
        break;
    }
#endif

#if ENABLE_RBTREE_KVENGINE
    // rbtree
    case KVS_CMD_RSET:
    {

        int resr = kvstore_rbtree_set(key, value);
        if (!resr)
        {
            snprintf(msg, BUFFER_LENGTH, "SUCCESS");
        }
        else
        {
            snprintf(msg, BUFFER_LENGTH, "FAILED");
        }
        break;
    }
    case KVS_CMD_RGET:
    {
        
        char *valr = kvstore_rbtree_get(key);
        if (valr)
        {
            snprintf(msg, BUFFER_LENGTH, "%s", valr);
            
        }
        else
        {
            snprintf(msg, BUFFER_LENGTH, "NO EXIST");
        }
        break;
    }
    case KVS_CMD_RDEL:

    {
        int resrd = kvstore_rbtree_delete(key);
        if (resrd < 0)
        { // server
            snprintf(msg, BUFFER_LENGTH, "%s", "ERROR");
        }
        else if (resrd == 0)
        {
            snprintf(msg, BUFFER_LENGTH, "%s", "SUCCESS");
        }
        else
        {
            snprintf(msg, BUFFER_LENGTH, "NO EXIST");
        }

        break;
    }

    case KVS_CMD_RMOD:

    {
        
        int resrm = kvstore_rbtree_modify(key, value);
        if (resrm < 0)
        { // server
            snprintf(msg, BUFFER_LENGTH, "%s", "ERROR");
        }
        else if (resrm == 0)
        {
            snprintf(msg, BUFFER_LENGTH, "%s", "SUCCESS");
        }
        else
        {
            snprintf(msg, BUFFER_LENGTH, "NO EXIST");
        }

        break;
    }
    case KVS_CMD_RCOUNT:
    {
        int countr = kvstore_rbtree_count();
        if (countr < 0)
        { // server
            snprintf(msg, BUFFER_LENGTH, "%s", "ERROR");
        }
        else
        {
            snprintf(msg, BUFFER_LENGTH, "%d", countr);
        }
        break;
    }
#endif

#if ENABLE_HASH_KVENGINE
    case KVS_CMD_HSET:
    {

        int res = kvstore_hash_set(key, value);
        if (!res)
        {
            snprintf(msg, BUFFER_LENGTH, "SUCCESS");
        }
        else
        {
            snprintf(msg, BUFFER_LENGTH, "FAILED");
        }
        break;
    }
    // hash
    case KVS_CMD_HGET:
    {

        char *val = kvstore_hash_get(key);
        if (val)
        {
            snprintf(msg, BUFFER_LENGTH, "%s", val);
        }
        else
        {
            snprintf(msg, BUFFER_LENGTH, "NO EXIST");
        }

        break;
    }
    case KVS_CMD_HDEL:
    {

        int res = kvstore_hash_delete(key);
        if (res < 0)
        { // server
            snprintf(msg, BUFFER_LENGTH, "%s", "ERROR");
        }
        else if (res == 0)
        {
            snprintf(msg, BUFFER_LENGTH, "%s", "SUCCESS");
        }
        else
        {
            snprintf(msg, BUFFER_LENGTH, "NO EXIST");
        }

        break;
    }
    case KVS_CMD_HMOD:
    {

        int res = kvstore_hash_modify(key, value);
        if (res < 0)
        { // server
            snprintf(msg, BUFFER_LENGTH, "%s", "ERROR");
        }
        else if (res == 0)
        {
            snprintf(msg, BUFFER_LENGTH, "%s", "SUCCESS");
        }
        else
        {
            snprintf(msg, BUFFER_LENGTH, "NO EXIST");
        }

        break;
    }

    case KVS_CMD_HCOUNT:
    {
        int count = kvstore_hash_count();
        if (count < 0)
        { // server
            snprintf(msg, BUFFER_LENGTH, "%s", "ERROR");
        }
        else
        {
            snprintf(msg, BUFFER_LENGTH, "%d", count);
        }
        break;
    }
#endif
    default:
        break;
    }
}

int kvstore_request(struct conn_item *item)
{
    char *msg = item->rbuffer;
    char *tokens[KVSTORE_MAX_TOKENS];

    int count = kvstore_spilt_token(msg, tokens);

    kvstore_parser_protocol(item, tokens, count);
    return 0;
}

具体流程,服务器端通过网络获取到客户端传来的指令,将指令发送到kvstore_request,由kvstore_spilt_token对指令进行解析,由kvstore_parser_protocol执行具体的指令。

五、存储

5.1、array实现

Array其实就是数组,在数组中CRUD,下面是具体的实现
首先,定义一个结构体

struct kvs_array_item {
	char *key;
	char *value;
};

#define KVS_ARRAY_SIZE		1024

typedef struct array_s {
	struct kvs_array_item *array_table;
	int array_idx;
} array_t;

extern array_t Array;

然后,数组执行CRUD的具体实现

#include "kvstore.h"

array_t Array;

int kvstore_array_create(array_t *arr)
{
    if (!arr)
        return -1;

    arr->array_table = (struct kvs_array_item *)kvstore_malloc(KVS_ARRAY_SIZE * sizeof(struct kvs_array_item));
    if (!arr->array_table)
    {
        return -1;
    }

    memset(arr->array_table, 0, KVS_ARRAY_SIZE * sizeof(struct kvs_array_item));

    arr->array_idx = 0;
    return 0;
}

void kvstore_array_destory(array_t *arr)
{
    if (!arr)
        return;

    if (arr->array_table)
        kvstore_free(arr->array_table);
    return;
}

int kvs_array_set(array_t *arr, char *key, char *value)
{
    if (arr == NULL || key == NULL || value == NULL)
        return -1;
    if (arr->array_idx == KVS_ARRAY_SIZE)
        return -1;

    char *kcopy = kvstore_malloc(strlen(key)+1);
    if (kcopy == NULL)
        return -1;
    memset(kcopy,0,strlen(key)+1);
    strncpy(kcopy, key, strlen(key));

    char *vcopy = kvstore_malloc(strlen(value)+1);
    if (vcopy == NULL)
    {
        kvstore_free(kcopy);
        return -1;
    }
    memset(vcopy,0,strlen(value)+1);
    strncpy(vcopy, value, strlen(value)+1);

    int i = 0;
    for (i = 0; i < arr->array_idx; i++)
    {
        if (arr->array_table->key == NULL)
        {
            arr->array_table[i].key = kcopy;
            arr->array_table[i].value = vcopy;
            arr->array_idx++;

            return 0;
        }
    }

    if (i < KVS_ARRAY_SIZE && i == arr->array_idx)
    {
        arr->array_table[arr->array_idx].key = kcopy;
        arr->array_table[arr->array_idx].value = vcopy;
        arr->array_idx++;
    }

    return 0;
}

char *kvs_array_get(array_t *arr, char *key)
{
    if (arr == NULL)
    {
       
        return NULL;
    }
    int i;
    for (i = 0; i < arr->array_idx; i++)
    {
        if (arr->array_table[i].key == NULL)
        {
            
            return NULL;
        }
      
        if (strcmp(arr->array_table[i].key, key) == 0)
        {
            return arr->array_table[i].value;
        }
    }
  
    return NULL;
}

int kvs_array_delete(array_t *arr, char *key)
{
    int i;
    if (arr == NULL || key == NULL)
        return -1;

    for (i = 0; i < arr->array_idx; i++)
    {
        if (strcmp(arr->array_table[i].key, key) == 0)
        {
            kvstore_free(arr->array_table[i].value);
            arr->array_table[i].value = NULL;

            kvstore_free(arr->array_table[i].key);
            arr->array_table[i].key = NULL;

            arr->array_idx--;

            return 0;
        }
    }
    return i;
}

int kvs_array_modify(array_t *arr, char *key, char *value)
{

    int i = 0;
    if (arr == NULL || key == NULL || value == NULL)
        return -1;

    for (i = 0; i < arr->array_idx; i++)
    {

        if (strcmp(arr->array_table[i].key, key) == 0)
        {

            kvstore_free(arr->array_table[i].value);
            arr->array_table[i].value = NULL;

            char *vcopy = kvstore_malloc(strlen(value) + 1);
            strncpy(vcopy, value, strlen(value) + 1);

            arr->array_table[i].value = vcopy;

            return 0;
        }
    }

    return i;
}

int kvs_array_count(array_t *arr)
{
    if (!arr)
        return -1;

    return arr->array_idx;
}

5.2、rbtree实现

红黑树是一个比较难的内容,在实现具体操作前需要充分理解原理。如果对红黑树的原理并不熟悉的可以参考我的上一篇博文:一文速学—红黑树

在上一篇博文中讲解了红黑树实现原理,以及如何实现,在这里只需要对红黑树稍微改写一下就能够达成目的。下面是实现代码

具体实现前需要再头文件中做一些声明

typedef struct _rbtree rbtree_t;

extern rbtree_t Tree;

下面是具体实现


rbtree Tree;

int kvstore_rbtree_create(rbtree_t *tree)
{

    if (tree == NULL)
    {
        perror(" rbtree error");
        return -1;
    }
    memset(tree, 0, sizeof(rbtree));
    tree->nil = (rbtree_node *)malloc(sizeof(rbtree_node));
    tree->nil->key = malloc(1);
    *(tree->nil->key) = '\0';
    tree->root = tree->nil;
    tree->nil->color = BLACK;
    return 0;
}
void kvstore_rbtree_destory(rbtree_t *tree)
{
    if (!tree)
        return;

    if (tree->nil->key)
        kvstore_free(tree->nil->key);

    rbtree_node *node = tree->root;
    while (node != tree->nil)
    {
        node = rbtree_mini(tree, tree->root);
        if (node == tree->nil)
            break;

        node = rbtree_delete(tree, node);
        if (!node)
        {
            kvstore_free(node->key);
            kvstore_free(node->value);
            kvstore_free(node);
        }
    }
}
int kvs_rbtree_set(rbtree_t *tree, char *key, char *value)
{
    rbtree_node *node = (rbtree_node *)malloc(sizeof(rbtree_node));
    if (!node)
        return -1;

    node->key = kvstore_malloc(strlen(key) + 1);
    if (node->key == NULL)
    {
        kvstore_free(node);
        return -1;
    }
    memset(node->key, 0, strlen(key) + 1);
    strcpy(node->key, key);
    node->value = kvstore_malloc(strlen(value) + 1);
    if (node->value == NULL)
    {
        kvstore_free(node->key);
        kvstore_free(node);
        return -1;
    }
    memset(node->value, 0, strlen(value) + 1);
    strcpy(node->value, value);

    rbtree_insert(tree, node);

    tree->count++;

    return 0;
}

char *kvs_rbtree_get(rbtree_t *tree, char *key)
{
    rbtree_node *node = rbtree_search(tree, key);
    if (node == tree->nil)
    {
        return NULL;
    }

    return node->value;
}
int kvs_rbtree_delete(rbtree_t *tree, char *key)
{
    rbtree_node *node = rbtree_search(tree, key);
    if (node == tree->nil)
        return -1;

    rbtree_node *cur = rbtree_delete(tree, node);

    if (!cur)
    {
        kvstore_free(cur->key);
        kvstore_free(cur->value);
        kvstore_free(cur);
    }
    tree->count--;
    return 0;
}
int kvs_rbtree_modify(rbtree_t *tree, char *key, char *value)
{
    rbtree_node *node = rbtree_search(tree, key);
    
    if (node == tree->nil)
    {
        return -1;
    }

    char *temp = node->value;
    kvstore_free(temp);

    node->value = kvstore_malloc(strlen(value) + 1);
    if (node->value == NULL)
        return -1;
    strcpy(node->value, value);
    return 0;
}
int kvs_rbtree_count(rbtree_t *tree)
{
    return tree->count;
}


5.3、hash实现

哈希表其实不是一个很难理解的概念,网上有很多关于哈希表的介绍。哈希表是通过哈希函数定位元素在哈希表的哪个位置。哈希表还存在另一个问题,哈希冲突。

哈希冲突有多种解决方法,具体解决方法可以参考:哈希冲突
本文使用的是链式解决方法。

如果大家对于算法感兴趣可以阅读:Hello算法
里面实现了很多常见的算法,并且具有大多数编程语言实现的版本。

具体实现前需要再头文件中做一些声明

typedef struct hashtable_s hashtable_t;


extern hashtable_t Hash;

下面是具体代码实现

#include "kvstore.h"
#include <string.h>

#define MAX_KEY_LEN 128
#define MAX_VALUE_LEN 512

#define MAX_TABLE_SIZE 102400
#define ENABLE_POINTER_KEY 1

typedef struct hashnode_s
{
#if ENABLE_POINTER_KEY
    char *key;
    char *value;
#else
    char key[MAX_KEY_LEN];
    char value[MAX_VALUE_LEN];
#endif
    struct hashnode_s *next;

} hashnode_t;

typedef struct hashtable_s
{
    int count;
    int max_slots;

    hashnode_t **nodes;
} hashtable_t;

hashtable_t Hash;

static int _hash(char *key, int size)
{
    if (!key)
        return -1;

    int sum = 0;
    int i = 0;

    while (key[i] != 0)
    {
        sum += key[i];
        i++;
    }

    return sum % size;
}

hashnode_t *_create_node(char *key, char *value)
{
    hashnode_t *node = (hashnode_t *)kvstore_malloc(sizeof(hashnode_t));
    if (!node)
        return NULL;

#if ENABLE_POINTER_KEY
    node->key = kvstore_malloc(strlen(key) + 1);
    if (!node->key)
    {
        kvstore_free(node);
        return NULL;
    }
    strcpy(node->key, key);

    node->value = kvstore_malloc(strlen(value) + 1);
    if (!node->value)
    {
        kvstore_free(node->key);
        kvstore_free(node);
        return NULL;
    }
    strcpy(node->value, value);

#endif

    node->next = NULL;
    return node;
}

int put_kv_hashtable(hashtable_t *hash, char *key, char *value)
{
    if (!hash || !key || !value)
        return -1;

    int idx = _hash(key, MAX_TABLE_SIZE);

    hashnode_t *node = hash->nodes[idx];

    while (node != NULL)
    {
        if (strcmp(node->key, key) == 0)
        {
            return 1;
        }
        node = node->next;
    }

    hashnode_t *new_node = _create_node(key, value);
    new_node->next = hash->nodes[idx];
    hash->nodes[idx] = new_node;
    hash->count++;
    return 0;
}

char *get_kv_hashtable(hashtable_t *hash, char *key)
{
    if (!hash || !key)
        return NULL;

    int idx = _hash(key, MAX_TABLE_SIZE);

    hashnode_t *node = hash->nodes[idx];

    while (node != NULL)
    {
        if (strcmp(node->key, key) == 0)
        {
            return node->value;
        }
        node = node->next;
    }

    return NULL;
}

int init_hashtable(hashtable_t *hash)
{
    if (!hash)
        return -1;

    hash->nodes = (hashnode_t **)kvstore_malloc(sizeof(hashnode_t *) * MAX_TABLE_SIZE);
    if (!hash->nodes)
        return -1;

    hash->count = 0;
    hash->max_slots = MAX_TABLE_SIZE;

    return 0;
}

int delete_kv_hashtable(hashtable_t *hash, char *key)
{
    if (!hash || !key)
        return -2;

    int idx = _hash(key, MAX_TABLE_SIZE);
    hashnode_t *head = hash->nodes[idx];

    if (head == NULL)
        return -1;

    if (strcmp(head->key, key) == 0)
    {
        hashnode_t *tmp = head->next;
        hash->nodes[idx] = tmp;
#if ENABLE_POINTER_KEY
        if (head->key)
        {
            kvstore_free(head->key);
        }
        if (head->value)
        {
            kvstore_free(head->value);
        }
        kvstore_free(head);
#else
        free(head);
#endif

        hash->count--;

        return 0;
    }

    hashnode_t *cur=head;
    while (cur->next!=NULL)
    {
        if(strcmp(cur->next->key,key)==0) break;

        cur=cur->next;
    }

    if(cur->next==NULL){
        return-1;
    }

    hashnode_t *tmp=cur->next;
    cur->next=tmp->next;
#if ENABLE_POINTER_KEY
	if (tmp->key) {
		kvstore_free(tmp->key);
	}
	if (tmp->value) {
		kvstore_free(tmp->value);
	}
	kvstore_free(tmp);
#else
	free(tmp);
#endif
	hash->count --;

	return 0;
    
}

void dest_hashtable(hashtable_t *hash){
    if(!hash) return;

    int i=0;
    for(i=0;i<hash->max_slots;i++){
        hashnode_t* node=hash->nodes[i];

        while (node!=NULL)
        {
            hashnode_t *tmp=node;
            node=node->next;
            hash->nodes[i]=node;
            kvstore_free(tmp);
        }
        
    }
    kvstore_free(hash->nodes);
}

int count_kv_hashtable(hashtable_t *hash) {
	return hash->count;
}

int kvs_hash_count(hashtable_t *hash) {
	return hash->count;
}

int exist_kv_hashtable(hashtable_t *hash, char *key) {

	char *value = get_kv_hashtable(hash, key);
	if (value) return 1;
	else return 0;
	
}

int kvs_hash_modify(hashtable_t *hash, char *key, char *value) {
    if(!hash || !key || !value) return -1;

    int idx=_hash(key,MAX_TABLE_SIZE);
    hashnode_t *node=hash->nodes[idx];

    while(node!=NULL){
        if(strcmp(node->key,key)==0){
            kvstore_free(node->value);

            node->value=kvstore_malloc(strlen(value)+1);
            if(node->value){
                strcpy(node->value,value);
                return 0;
            }else{
                assert(0);
            }
        }
        node=node->next;
    }
    return -1;
}

int kvstore_hash_create(hashtable_t *hash)
{
    return init_hashtable(hash);
}

int kvs_hash_set(hashtable_t *hash, char *key, char *value)
{
    return put_kv_hashtable(hash, key, value);
}

char *kvs_hash_get(hashtable_t *hash, char *key)
{

    return get_kv_hashtable(hash, key);
}

int kvs_hash_delete(hashtable_t *hash, char *key)
{

    return delete_kv_hashtable(hash, key);
}

void kvstore_hash_destory(hashtable_t *hash)
{

    return dest_hashtable(hash);
}

六、测试

针对上面的框架我们可以制定下面几种测试样例:
在这里插入图片描述
针对上面的样例,使用下面的测试代码,进行10w次的CRUD测试


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <getopt.h>

#include <sys/socket.h>
#include <sys/time.h>

#include <arpa/inet.h>

#define MAX_MAS_LENGTH 512
#define TIME_SUB_MS(tv1, tv2) ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)

int connect_tcpserver(const char *ip, unsigned short port)
{
    int connfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in tcpserver_addr;
    memset(&tcpserver_addr, 0, sizeof(struct sockaddr_in));

    tcpserver_addr.sin_family = AF_INET;
    tcpserver_addr.sin_addr.s_addr = inet_addr(ip);
    tcpserver_addr.sin_port = htons(port);

    int ret = connect(connfd, (struct sockaddr *)&tcpserver_addr, sizeof(struct sockaddr_in));
    if (ret)
    {
        perror("connect");
        return -1;
    }

    return connfd;
}

int send_msg(int connfd, char *msg, int length)
{
    int res = send(connfd, msg, length, 0);
    if (res < 0)
    {
        perror("send error");
        exit(1);
    }
    return res;
}

int recv_msg(int connfd, char *msg, int length)
{

    int res = recv(connfd, msg, length, 0);
    if (res < 0)
    {
        perror("recv error");
        exit(1);
    }
    return res;
}

void equals(char *pattern, char *result, char *casename)
{

    if (strcmp(pattern, result) == 0)
    {
        // printf("==> PASS --> %s\n", casename);
    }
    else
    {
        printf("==> FAILED --> %s, '%s' != '%s'\n", casename, pattern, result);
    }
}

void test_case(int connfd, char *msg, char *pattern, char *casename)
{
    if (!msg || !pattern || !casename)
        return;

    send_msg(connfd, msg, strlen(msg));

    char result[MAX_MAS_LENGTH] = {0};
    recv_msg(connfd, result, MAX_MAS_LENGTH);

    equals(pattern, result, casename);
}

void array_testcase(int connfd)
{
    test_case(connfd, "SET Name King", "SUCCESS", "SETCase");
    test_case(connfd, "GET Name", "King", "GETCase");
    test_case(connfd, "MOD Name Darren", "SUCCESS", "MODCase");
    test_case(connfd, "GET Name", "Darren", "GETCase");
    test_case(connfd, "DEL Name", "SUCCESS", "DELCase");
    test_case(connfd, "GET Name", "NO EXIST", "GETCase");
}

void rbtree_testcase(int connfd)
{

    test_case(connfd, "RSET Name King", "SUCCESS", "SETCase");
    test_case(connfd, "RGET Name", "King", "GETCase");
    test_case(connfd, "RMOD Name Darren", "SUCCESS", "MODCase");
    test_case(connfd, "RGET Name", "Darren", "GETCase");
    test_case(connfd, "RDEL Name", "SUCCESS", "DELCase");
    test_case(connfd, "RGET Name", "NO EXIST", "GETCase");
}

void hash_testcase(int connfd)
{

    test_case(connfd, "HSET Name King", "SUCCESS", "HSETCase");
    test_case(connfd, "HGET Name", "King", "HGETCase");
    test_case(connfd, "HMOD Name Darren", "SUCCESS", "HMODCase");
    test_case(connfd, "HGET Name", "Darren", "HGETCase");
    test_case(connfd, "HDEL Name", "SUCCESS", "HDELCase");
    test_case(connfd, "HGET Name", "NO EXIST", "HGETCase");
}

void array_testcase_10w(int connfd)
{
    int count = 100000;
    int i = 0;
    while (i++ < count)
    {
        array_testcase(connfd);
    }
}

void rbtree_testcase_10w(int connfd)
{
    int count = 100000;
    int i = 0;
    while (i++ < count)
    {
        rbtree_testcase(connfd);
    }
}

void hash_testcase_10w(int connfd)
{
    int count = 100000;
    int i = 0;
    while (i++ < count)
    {
        hash_testcase(connfd);
    }
}

int main(int argc, char *argv[])
{

    int ret;
    char ip[16] = {0};
    int port = 0;
    int mode = 1;
    int opt;

    while ((opt = getopt(argc, argv, "s:p:m:?")) != -1)
    {
        switch (opt)
        {
        case 's':
            strcpy(ip, optarg);
            break;
        case 'p':
            port = atoi(optarg);
            break;
        case 'm':
            mode = atoi(optarg);
            break;
        default:
            return -1;
        }
    }
    int connfd = connect_tcpserver(ip, port);

    if (mode & 0x1)
    {
        struct timeval tv_begin;
        gettimeofday(&tv_begin, NULL);

        array_testcase_10w(connfd);

        struct timeval tv_end;
        gettimeofday(&tv_end, NULL);

        int time_used = TIME_SUB_MS(tv_end, tv_begin);

        printf("array testcase--> time_used: %d, qps: %d\n", time_used, 600000 * 1000 / time_used);
    }

    if (mode & 0x2)
    {
        struct timeval tv_begin;
        gettimeofday(&tv_begin, NULL);

        rbtree_testcase_10w(connfd);

        struct timeval tv_end;
        gettimeofday(&tv_end, NULL);

        int time_used = TIME_SUB_MS(tv_end, tv_begin);

        printf("rbtree testcase--> time_used: %d, qps: %d\n", time_used, 600000 * 1000 / time_used);
    }

    if (mode & 0x4)
    {
        struct timeval tv_begin;
        gettimeofday(&tv_begin, NULL);

        hash_testcase_10w(connfd);

        struct timeval tv_end;
        gettimeofday(&tv_end, NULL);

        int time_used = TIME_SUB_MS(tv_end, tv_begin);

        printf("hash testcase--> time_used: %d, qps: %d\n", time_used, 600000 * 1000 / time_used);
    }

    return 0;
}

测试结果如下:
在这里插入图片描述
整体来说,数据结构相同情况下,io_uring会比epoll的时间要更少,性能更优。数据结构自身对比,hash的qps最高,array的qps最低,说明在KV存储中hash的性能更好(这个结果也可能受限于测试样例,其他类型的测试样例可能是红黑树会优于哈希表,但两者都会优于array)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值