Redis源码分析06——通信协议

943 阅读5分钟

Redis通信协议

Redis基于RESP(Redis Serialization Protocol)协议来完成客户端和服务端通信。RESP本质是一种文本协议,实现简单、易于解析。

1590470451400

啥?你不信?那好我们来抓包看一下吧,以单条命令为例子set name bigold ex 1。

12
#由于我的客户端和服务端在同一台机器上,因此协议栈做了优化,直接走本地回环接口tcpdump -i lo port 6379 -Ann

1590471869535

细心的大佬可能发现,哎?不对呀。为啥我在客户端不是这样的?

1234567891011
[0 root@xiaxuefei ~]# redis-cli127.0.0.1:6379> set a aOK127.0.0.1:6379> set a(error) ERR wrong number of arguments for 'set' command127.0.0.1:6379> mset a a b  bOK127.0.0.1:6379> set name bigold ex 1OK127.0.0.1:6379> mset n1 n1 n2 n2OK

那是因为客户端对其进行转化,才显示出来的,见redis-cli.c文件:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
static sds cliFormatReplyTTY(redisReply *r, char *prefix) {    sds out = sdsempty();    switch (r->type) {    case REDIS_REPLY_ERROR:        out = sdscatprintf(out,"(error) %s\n", r->str);    break;    case REDIS_REPLY_STATUS:        out = sdscat(out,r->str);        out = sdscat(out,"\n");    break;    case REDIS_REPLY_INTEGER:        out = sdscatprintf(out,"(integer) %lld\n",r->integer);    break;    case REDIS_REPLY_STRING:        /* If you are producing output for the standard output we want        * a more interesting output with quoted characters and so forth */        out = sdscatrepr(out,r->str,r->len);        out = sdscat(out,"\n");    break;    case REDIS_REPLY_NIL:        out = sdscat(out,"(nil)\n");    break;    case REDIS_REPLY_ARRAY:        if (r->elements == 0) {            out = sdscat(out,"(empty list or set)\n");        } else {            unsigned int i, idxlen = 0;            char _prefixlen[16];            char _prefixfmt[16];            sds _prefix;            sds tmp;            /* Calculate chars needed to represent the largest index */            i = r->elements;            do {                idxlen++;                i /= 10;            } while(i);            /* Prefix for nested multi bulks should grow with idxlen+2 spaces */            memset(_prefixlen,' ',idxlen+2);            _prefixlen[idxlen+2] = '\0';            _prefix = sdscat(sdsnew(prefix),_prefixlen);            /* Setup prefix format for every entry */            snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%ud) ",idxlen);            for (i = 0; i < r->elements; i++) {                /* Don't use the prefix for the first element, as the parent                 * caller already prepended the index number. */                out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1);                /* Format the multi bulk entry */                tmp = cliFormatReplyTTY(r->element[i],_prefix);                out = sdscatlen(out,tmp,sdslen(tmp));                sdsfree(tmp);            }            sdsfree(_prefix);        }    break;    default:        fprintf(stderr,"Unknown reply type: %d\n", r->type);        exit(1);    }    return out;}

这里我们可以使用nc命令,来代替redis-cli命令行

12345
 nc 127.0.0.1 6379set a a+OKget a b-ERR wrong number of arguments for 'get' command
  • 其他说明

    • 常见的错误
    12345678910111213141516171819
    #define REDIS_ERR -1#define REDIS_OK 0/* When an error occurs, the err flag in a context is set to hold the type of * error that occurred. REDIS_ERR_IO means there was an I/O error and you * should use the "errno" variable to find out what is wrong. * For other values, the "errstr" field will hold a description. */#define REDIS_ERR_IO 1 /* Error in read or write */#define REDIS_ERR_EOF 3 /* End of file */#define REDIS_ERR_PROTOCOL 4 /* Protocol error */#define REDIS_ERR_OOM 5 /* Out of memory */#define REDIS_ERR_OTHER 2 /* Everything else... */#define REDIS_REPLY_STRING 1#define REDIS_REPLY_ARRAY 2#define REDIS_REPLY_INTEGER 3#define REDIS_REPLY_NIL 4#define REDIS_REPLY_STATUS 5#define REDIS_REPLY_ERROR 6
    • 字符串错误信息——共享对象
    12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
    void createSharedObjects(void) {    int j;    shared.crlf = createObject(OBJ_STRING,sdsnew("\r\n"));    shared.ok = createObject(OBJ_STRING,sdsnew("+OK\r\n"));    shared.err = createObject(OBJ_STRING,sdsnew("-ERR\r\n"));    shared.emptybulk = createObject(OBJ_STRING,sdsnew("$0\r\n\r\n"));    shared.czero = createObject(OBJ_STRING,sdsnew(":0\r\n"));    shared.cone = createObject(OBJ_STRING,sdsnew(":1\r\n"));    shared.cnegone = createObject(OBJ_STRING,sdsnew(":-1\r\n"));    shared.nullbulk = createObject(OBJ_STRING,sdsnew("$-1\r\n"));    shared.nullmultibulk = createObject(OBJ_STRING,sdsnew("*-1\r\n"));    shared.emptymultibulk = createObject(OBJ_STRING,sdsnew("*0\r\n"));    shared.pong = createObject(OBJ_STRING,sdsnew("+PONG\r\n"));    shared.queued = createObject(OBJ_STRING,sdsnew("+QUEUED\r\n"));    shared.emptyscan = createObject(OBJ_STRING,sdsnew("*2\r\n$1\r\n0\r\n*0\r\n"));    shared.wrongtypeerr = createObject(OBJ_STRING,sdsnew(        "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"));    shared.nokeyerr = createObject(OBJ_STRING,sdsnew(        "-ERR no such key\r\n"));    shared.syntaxerr = createObject(OBJ_STRING,sdsnew(        "-ERR syntax error\r\n"));    shared.sameobjecterr = createObject(OBJ_STRING,sdsnew(        "-ERR source and destination objects are the same\r\n"));    shared.outofrangeerr = createObject(OBJ_STRING,sdsnew(        "-ERR index out of range\r\n"));    shared.noscripterr = createObject(OBJ_STRING,sdsnew(        "-NOSCRIPT No matching script. Please use EVAL.\r\n"));    shared.loadingerr = createObject(OBJ_STRING,sdsnew(        "-LOADING Redis is loading the dataset in memory\r\n"));    shared.slowscripterr = createObject(OBJ_STRING,sdsnew(        "-BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.\r\n"));    shared.masterdownerr = createObject(OBJ_STRING,sdsnew(        "-MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no'.\r\n"));    shared.bgsaveerr = createObject(OBJ_STRING,sdsnew(        "-MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.\r\n"));    shared.roslaveerr = createObject(OBJ_STRING,sdsnew(        "-READONLY You can't write against a read only replica.\r\n"));    shared.noautherr = createObject(OBJ_STRING,sdsnew(        "-NOAUTH Authentication required.\r\n"));    shared.oomerr = createObject(OBJ_STRING,sdsnew(        "-OOM command not allowed when used memory > 'maxmemory'.\r\n"));    shared.execaborterr = createObject(OBJ_STRING,sdsnew(        "-EXECABORT Transaction discarded because of previous errors.\r\n"));    shared.noreplicaserr = createObject(OBJ_STRING,sdsnew(        "-NOREPLICAS Not enough good replicas to write.\r\n"));    shared.busykeyerr = createObject(OBJ_STRING,sdsnew(        "-BUSYKEY Target key name already exists.\r\n"));    shared.space = createObject(OBJ_STRING,sdsnew(" "));    shared.colon = createObject(OBJ_STRING,sdsnew(":"));    shared.plus = createObject(OBJ_STRING,sdsnew("+"));    for (j = 0; j < PROTO_SHARED_SELECT_CMDS; j++) {        char dictid_str[64];        int dictid_len;        dictid_len = ll2string(dictid_str,sizeof(dictid_str),j);        shared.select[j] = createObject(OBJ_STRING,            sdscatprintf(sdsempty(),                "*2\r\n$6\r\nSELECT\r\n$%d\r\n%s\r\n",                dictid_len, dictid_str));    } ....}

Redis命令对象

Redis的命令使用的是redisCommand数据结构来管理的.

数据结构
1234567891011121314151617
typedef void redisCommandProc(client *c);typedef int *redisGetKeysProc(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);struct redisCommand {    char *name;    redisCommandProc *proc;    int arity;    char *sflags; /* Flags as string representation, one char per flag. */    int flags;    /* The actual flags, obtained from the 'sflags' field. */    /* Use a function to determine keys arguments in a command line.     * Used for Redis Cluster redirect. */    redisGetKeysProc *getkeys_proc;    /* What keys should be loaded in background when calling this command? */    int firstkey; /* The first argument that's a key (0 = no keys) */    int lastkey;  /* The last argument that's a key */    int keystep;  /* The step between first and last key */    long long microseconds, calls;};

1590475287034

针对sflag标识,在这里引用《Redis设计与实现》中的一张图

1590474274504

flag记录的是flag值与sflag进行位运算的结果,见populateCommandTable函数

123456789101112131415161718192021222324
for (j = 0; j < numcommands; j++) {        struct redisCommand *c = redisCommandTable+j;        char *f = c->sflags;        int retval1, retval2;        while(*f != '\0') {            switch(*f) {            case 'w': c->flags |= CMD_WRITE; break;            case 'r': c->flags |= CMD_READONLY; break;            case 'm': c->flags |= CMD_DENYOOM; break;            case 'a': c->flags |= CMD_ADMIN; break;            case 'p': c->flags |= CMD_PUBSUB; break;            case 's': c->flags |= CMD_NOSCRIPT; break;            case 'R': c->flags |= CMD_RANDOM; break;            case 'S': c->flags |= CMD_SORT_FOR_SCRIPT; break;            case 'l': c->flags |= CMD_LOADING; break;            case 't': c->flags |= CMD_STALE; break;            case 'M': c->flags |= CMD_SKIP_MONITOR; break;            case 'k': c->flags |= CMD_ASKING; break;            case 'F': c->flags |= CMD_FAST; break;            default: serverPanic("Unsupported command flag"); break;            }            f++;        }

具体命令太多,只给出如下几个:

12345678910111213141516
struct redisCommand redisCommandTable[] = {    {"module",moduleCommand,-2,"as",0,NULL,0,0,0,0,0},    {"get",getCommand,2,"rF",0,NULL,1,1,1,0,0},    {"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},    {"setnx",setnxCommand,3,"wmF",0,NULL,1,1,1,0,0},    {"setex",setexCommand,4,"wm",0,NULL,1,1,1,0,0},    {"psetex",psetexCommand,4,"wm",0,NULL,1,1,1,0,0},    {"append",appendCommand,3,"wm",0,NULL,1,1,1,0,0},    {"strlen",strlenCommand,2,"rF",0,NULL,1,1,1,0,0},    {"del",delCommand,-2,"w",0,NULL,1,-1,1,0,0},    {"unlink",unlinkCommand,-2,"wF",0,NULL,1,-1,1,0,0},    {"exists",existsCommand,-2,"rF",0,NULL,1,-1,1,0,0},    {"setbit",setbitCommand,4,"wm",0,NULL,1,1,1,0,0},    {"getbit",getbitCommand,3,"rF",0,NULL,1,1,1,0,0},}

以set为例{“set”,setCommand,-3,”wm”,0,NULL,1,1,1,0,0}

1590475224693