首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redis源码之常用数据结构和函数

Redis源码之常用数据结构和函数

作者头像
心平气和
发布2020-09-11 11:33:09
4030
发布2020-09-11 11:33:09
举报

上一篇 扩展Redis:增加Redis命令 讲了如何动手编写一个命令,但没有具体讲代码的细节,今天讲下Redis代码中的常用数据结构和函数,看完这篇文章希望大家自己能写一个helloworld的命令。

首先讲下环境,代码的版本还是3.2.11的。

一、数据结构

1、robj

robj是Redis源码中用的非常多的数据结构,包括我们输入的命令和参数在服务端都是以robj来表示的。

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */
    int refcount;
    void *ptr;
} robj;

其中比较重要的是type和ptr字段,type表示数据的类型,常用类型有:

#define OBJ_STRING 0
#define OBJ_LIST 1
#define OBJ_SET 2
#define OBJ_ZSET 3
#define OBJ_HASH 4

所有客户端输入过来的参数都是STRIGN类型,即我们输入命令:

set key value

则服务端会生成3个robj对象,每个的type都是 OBJ_STRING。

可以参考typeCommand的代码:

void typeCommand(client *c) {
    robj *o;
    char *type;

    o = lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH);
    if (o == NULL) {
        type = "none";
    } else {
        switch(o->type) {
        case OBJ_STRING: type = "string"; break;
        case OBJ_LIST: type = "list"; break;
        case OBJ_SET: type = "set"; break;
        case OBJ_ZSET: type = "zset"; break;
        case OBJ_HASH: type = "hash"; break;
        default: type = "unknown"; break;
        }
    }
    addReplyStatus(c,type);
}

还有一个重要的字段是ptr,这是一个void类型,即可以代表任何类型,如果是type为OBJ_STRING,则ptr是一个char*的指针。

另外就是encoding字段了,上面发现所有基本类型是字符串,如果要表示整型怎么办呢,就是通过encoding帮忙了,可以看下代码:

int isObjectRepresentableAsLongLong(robj *o, long long *llval) {
    serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);
    if (o->encoding == OBJ_ENCODING_INT) {
        if (llval) *llval = (long) o->ptr;
        return C_OK;
    } else {
        return string2ll(o->ptr,sdslen(o->ptr),llval) ? C_OK : C_ERR;
    }
}

2、client

client结构代表一个连接,当然一个从Redis也是一个连接,所以这个结构有从的一些信息:

/* With multiplexing we need to take per-client state.
 * Clients are taken in a linked list. */
typedef struct client {
    uint64_t id;            /* Client incremental unique ID. */
    int fd;                 /* Client socket. */
    redisDb *db;            /* Pointer to currently SELECTed DB. */
    int dictid;             /* ID of the currently SELECTed DB. */
    robj *name;             /* As set by CLIENT SETNAME. */
    sds querybuf;           /* Buffer we use to accumulate client queries. */
    size_t querybuf_peak;   /* Recent (100ms or more) peak of querybuf size. */
    int argc;               /* Num of arguments of current command. */
    robj **argv;            /* Arguments of current command. */
    struct redisCommand *cmd, *lastcmd;  /* Last command executed. */
    int reqtype;            /* Request protocol type: PROTO_REQ_* */
    int multibulklen;       /* Number of multi bulk arguments left to read. */
    long bulklen;           /* Length of bulk argument in multi bulk request. */
    list *reply;            /* List of reply objects to send to the client. */
    unsigned long long reply_bytes; /* Tot bytes of objects in reply list. */
    size_t sentlen;         /* Amount of bytes already sent in the current
                               buffer or object being sent. */
    time_t ctime;           /* Client creation time. */
    time_t lastinteraction; /* Time of the last interaction, used for timeout */
    time_t obuf_soft_limit_reached_time;
    int flags;              /* Client flags: CLIENT_* macros. */
    int authenticated;      /* When requirepass is non-NULL. */
    int replstate;          /* Replication state if this is a slave. */
    int repl_put_online_on_ack; /* Install slave write handler on ACK. */
    int repldbfd;           /* Replication DB file descriptor. */
    off_t repldboff;        /* Replication DB file offset. */
    off_t repldbsize;       /* Replication DB file size. */
    sds replpreamble;       /* Replication DB preamble. */
    long long reploff;      /* Replication offset if this is our master. */
    long long repl_ack_off; /* Replication ack offset, if this is a slave. */
    long long repl_ack_time;/* Replication ack time, if this is a slave. */
    long long psync_initial_offset; /* FULLRESYNC reply offset other slaves
                                       copying this slave output buffer
                                       should use. */
    char replrunid[CONFIG_RUN_ID_SIZE+1]; /* Master run id if is a master. */
    int slave_listening_port; /* As configured with: REPLCONF listening-port */
    char slave_ip[NET_IP_STR_LEN]; /* Optionally given by REPLCONF ip-address */
    int slave_capa;         /* Slave capabilities: SLAVE_CAPA_* bitwise OR. */
    multiState mstate;      /* MULTI/EXEC state */
    int btype;              /* Type of blocking op if CLIENT_BLOCKED. */
    blockingState bpop;     /* blocking state */
    long long woff;         /* Last write global replication offset. */
    list *watched_keys;     /* Keys WATCHED for MULTI/EXEC CAS */
    dict *pubsub_channels;  /* channels a client is interested in (SUBSCRIBE) */
    list *pubsub_patterns;  /* patterns a client is interested in (SUBSCRIBE) */
    sds peerid;             /* Cached peer ID. */

    /* Response buffer */
    int bufpos;
    char buf[PROTO_REPLY_CHUNK_BYTES];
} client;

整个结构体字段比较多,重点关注以下几块:

argc:参数个数

argv:具体一个个的参数

db:当前选择的数据库

argv[0]表示我们的命令,后面就是命令具体的参数了,如果我们输入命令:

set name ljh

则:

c->argv[0]表示set命令,type为OBJ_STRING,ptr是一个char*;

c->argv[1]表示参数name,type为OBJ_STRING,ptr是一个char*;

c->argv[2]表示参数ljh,type为OBJ_STRING,ptr是一个char*;

通过解析c->argv参数,我们就可以得到一个个的参数进行验证和处理了。

二、常用函数

1、addReplyError

一般表示执行遇到错误,返回客户端一个错误;

这个函数有2个参数,第一个是一个client的指针,第二个为错误字符串:

addReplyError(c,"increxpire command maxNum param must bigger or equal than 0");

2、addReply

向输出缓存区增加内容;

有2个参数,第一个也是一个client的指针,第二个是一个robj的指针:

addReply(c,shared.colon);
addReply(c, o);
addReply(c,shared.crlf);

上面是上一篇扩展命令里的代码,如果O的type为OBJ_STRIGN,并且encoding是OBJ_ENCODING_INT,值为5,这里表示向客户端输出以下内容:

:5\r\n

这里讲下Redis协议,响应这块的协议如下:

状态回复(status reply)的第一个字节是 “+”

错误回复(error reply)的第一个字节是 “-“

整数回复(integer reply)的第一个字节是 “:”

批量回复(bulk reply)的第一个字节是 “$”

多条批量回复(multi bulk reply)的第一个字节是 “*”

因为我们的incexpire返回一个整型数字,所以先是:,然后是数字,最后是表示结束的换行和回车。

3、lookupKey

相关的还有lookupKeyRead,lookupKeyWrite,这几个函数都是表示从某个库里查找相应的key,带写的函数会先判断是否过期;

第一个是redisDb结构,第二个是一个robj*,表示要查找哪个key;

当前连接的数据库可以通过 c->db来获取。

4、createStringObjectFromLongLong

这些是创建相应类型robj的命令,有的时候我们要根据一些处理结果返回字符串或数值给客户端,前面讲了大部分函数都接受robj作为参数,像添加内容到输入缓存区的addReply;

其它的命令有:

createStringObject:根据字符串创建robj

createStringObjectFromLongDouble:根据double创建robj

三、调试

大家看完上面代码想自己写个命令呢,按上面讲的基本上可以把环境跑起来,最后就是调试了,可以下个gdb,然后在相应的函数上打断点就可以了,常用命令如下:

break 函数名

print 变量名

上面两个命令应该是最常用的了,第一个是下断点,第二个是打印变量的值。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员升级之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档