zoukankan      html  css  js  c++  java
  • redis源码(一):为redis添加自己的列表类型

    本文档分为三大部分:

    环境介绍与效果演示

    redis接收命令到返回数据的执行逻辑

    代码实现

    文档的重点和难点在第三部分,完全阅读本文档需要读者具备基本的c语言和数据结构知识。

    环境介绍和效果演示
    环境介绍
    redis版本为5.0.3 64 bit
    操作系统版本为Ubuntu 18.10 64bit
    源码可以用gedit查看 gdb调试
    ide 可以用eclipse+CDT

    效果演示
    本案例实现了一个链表,对应redis的list数据类型,对链表的操作实现了插入、设置某个节点的值、新建节点、获取一定范围内的节点、获取列表长度等.下面表格列出具体实现的命令与对应的redis原生命令

    实现命令 redis原生命令 命令含义
    myrpush rpush 从尾部插入链表
    mylrange lrange 获取范围内值
    myrpop rpop 从右侧弹出值
    myllen llen 获取list长度
    mylset lset 设置某个节点值
    myinsert linsert 在指定位置插入值
    mylindex lindex 获取指定位置值
    实现命令演示:

    127.0.0.1:6379> myrpush mygjw gjw0(integer)
    1127.0.0.1:6379> myrpush mygjw gjw1
    (integer) 2
    127.0.0.1:6379> myrpush mygjw gjw2
    (integer) 3
    127.0.0.1:6379> mylrange 0 -1
    (error) ERR wrong number of arguments for 'mylrange' command
    127.0.0.1:6379> mylrange mygjw 0 -1
    1) "gjw0"
    2) "gjw1"
    3) "gjw2"
    127.0.0.1:6379> myrpop mygjw
    "gjw2"
    127.0.0.1:6379> mylrange mygjw 0 -1
    1) "gjw0"
    2) "gjw1"
    127.0.0.1:6379> myllen mygjw
    (integer) 2
    127.0.0.1:6379> mylset mygjw 0 gjw00
    OK127.0.0.1:6379> mylset mygjw 1 gjw01
    OK127.0.0.1:6379> mylrange mygjw 0 -1
    1) "gjw00"
    2) "gjw01"
    127.0.0.1:6379> mylinsert mygjw 0 gjw0
    (integer) 3
    127.0.0.1:6379> mylinsert mygjw 1 gjw1
    (integer) 4
    127.0.0.1:6379> mylrange mygjw 0 -1
    1) "gjw0"
    2) "gjw1"
    3) "gjw00"
    4) "gjw01"
    127.0.0.1:6379> mylindex mygjw 0
    "gjw0"
    127.0.0.1:6379> mylindex mygjw 1
    "gjw1"
    redis接收命令到返回数据的执行逻辑
    此部分只选择与本文档相关部分做简单介绍,实际命令执行涉及到保存文件、主备同步、集群分发等等会复杂很多。下面从server.c文件的processCommand函数开始。

    640?wx_fmt=png

    下面以myrpush命令执行的时序图来说明具体调用的函数及其所在的文件,其他命令执行方式都类似.图中方框中字符串是文件名,所有关于list类型的命令处理函数都在文件t_list.c中,其他类型的命令处理函数需要找找对应的源文件,redis命令非常规范,找起来不难。

    640?wx_fmt=png

    代码实现与redis源码阅读

    代码实现
    1.添加自己的源文件和头文件
    mylinkedlist.h、mylinkedlist.h
    mylist类型采用双向链表数据结构,数据域采用了哑型指针,方便插入任何类型数据,为了方便代码仅仅实现了char型,node 结构体有个size属性用来存储数据域占用的字节数。

    此部分比redis源码实现简单很多,但是如果能看懂这部分代码,也应该可以比较容易的看懂redis源码。redis中list类型数据域是ziplist,默认情况下数据域最大8k字节,ziplist有自己的数据结构,这里列出redis 列表类型处理的相关源文件:quicklist.h、quicklist.c、ziplist.h、ziplist.c
    头文件内容如下:

    /*
    * mylineklist.h
    *
    * Created on: Dec 29, 2018
    * Author: gjw
    */typedef void * ADT;
    typedef const void * CADT;
    typedef int (*LIDESTROY) (ADT e);
    typedef void (*LITRVAVEL) (ADT e);
    struct NODE;
    typedef struct NODE * PNODE;
    typedef struct MylinkedListS MylinkedList;
    typedef MylinkedList * MYLINKEDLIST;
    MYLINKEDLIST MyCreateList();
    void MyAppend(MYLINKEDLIST list,ADT data,int len);
    int MyInsert(MYLINKEDLIST list,ADT data,unsigned int pos,int len);
    void MyDelete(MYLINKEDLIST list,unsigned int pos,LIDESTROY destroy);
    ADT Myrpop(MYLINKEDLIST list,int * sz);
    int Mylset(MYLINKEDLIST list, ADT data, unsigned int pos,int len);
    void MyClear(MYLINKEDLIST list,LIDESTROY destroy);
    int MyLength(MYLINKEDLIST list);
    PNODE MyIndex(MYLINKEDLIST list,unsigned int index);
    void MyTraverse(MYLINKEDLIST list,LITRVAVEL litravel);
    int MyIsEmpty(MYLINKEDLIST list);
    PNODE MyNext(PNODE node);
    void getData(PNODE n,char ** data,int * sz);
    在本例中并非所有的声明函数都已实现,仅仅实现了上面演示的几个命令,源文件内容如下:

    /*
    * mylinkedlist.c
    *
    * Created on: Dec 29, 2018
    * Author: gjw
    */
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include "mylinkedlist.h"
    struct NODE {
    ADT data;
    int size;
    PNODE next;
    PNODE previous;
    } ;
    typedef struct NODE NODE;
    struct MylinkedListS {
    unsigned int len;
    PNODE head, tail;
    };

    MylinkedList* MyCreateList() {
    MylinkedList* list = (MylinkedList*) malloc(sizeof(MylinkedList));
    PNODE head = (NODE *) malloc(sizeof(NODE));
    PNODE tail = (NODE *) malloc(sizeof(NODE));
    list->head = head;
    list->tail = tail;
    list->tail->next=NULL;
    list->tail->previous=NULL;
    list->head->next=NULL;
    list->head->previous=NULL;
    list->len=0;
    printf("init a list");
    return list;
    }
    void MyAppend(MYLINKEDLIST list, ADT data,int len) {
    if (list->head->next == NULL) {
    MyInsert(list, data, 0,len);
    return;
    }
    printf("->");
    PNODE ctail = (NODE *) malloc(sizeof(NODE));
    ctail->data=(char *)malloc(len);
    memcpy(ctail->data,data,len);
    ctail->size=len;
    ctail->next=NULL;
    ctail->previous=list->tail->next;
    list->tail->next->next = ctail;
    list->tail->next = ctail;
    list->len = list->len + 1;
    }
    int MyInsert(MYLINKEDLIST list, ADT data, unsigned int pos,int len) {
    if ( pos > list->len) return 0;
    printf("insert success! ");
    PNODE node = list->head;
    for (unsigned int i = 0; i < pos; i++) {
    node = node->next;
    }
    PNODE newNode = (NODE *) malloc(sizeof(NODE));
    newNode->data=(char *)malloc(len);
    memcpy(newNode->data,data,len);
    newNode->size=len;
    newNode->next = node->next;
    newNode->previous=node; if(node->next){
    newNode->next->previous=newNode;
    }
    node->next = newNode;
    list->tail->next=newNode;
    list->len = list->len + 1;
    return 1;
    }

    ADT Myrpop(MYLINKEDLIST list,int * sz){
    PNODE tail=list->tail->next;
    if(list->len==0){
    return NULL;
    }
    ADT data=tail->data;
    *sz=tail->size; list->tail->next=tail->previous;
    tail->previous->next=NULL;
    list->len=list->len-1;
    free(tail);
    return data;
    }int MyLength(MYLINKEDLIST list) {
    return list->len;
    }
    int MyIsEmpty(MYLINKEDLIST list) {
    return !list->len;
    }
    void getData(PNODE n,char ** data,int * sz) {
    *data=n->data;
    *sz=n->size;
    }

    PNODE MyNext(PNODE node) {
    return node->next;
    }
    PNODE MyIndex(MYLINKEDLIST list,unsigned int index){
    PNODE n=list->head->next;
    if(index>=list->len){
    return NULL;
    }
    for(int i=0;i<index;i++){
    n=n->next;
    }
    return n;
    }
    int Mylset(MYLINKEDLIST list, ADT data, unsigned int pos,int len) {
    PNODE node=list->head->next;
    if(list->len==0){
    MyInsert(list,data,pos,len);
    return 1;
    }
    if(pos>=list->len){
    return 0;
    } for(int i=0;i<pos;i++){
    node=node->next;
    }
    realloc(node->data,len);
    node->size=len;
    memcpy(node->data,data,len);
    return 1;
    }
    2.修改server.h主要是声明自己的类型,添加自定义类型的命令处理函数,加入自定义头文件,添加以下内容 :

    server.h
    #include "mylinkedlist.h" /* mylist ,guojiagnwei add */
    #define MYOBJ_LIST 11 /* MYList object. */
    void myrpushCommand(client *c);
    void mylrangeCommand(client *c);
    void myrpopCommand(client *c);
    void myllenCommand(client *c);
    void mylsetCommand(client *c);
    void mylinsertCommand(client *c);
    void mylindexCommand(client *c);
    robj *createMylistObject(void);
    3.修改object.c 此文件主要用来操作redis object,添加以下内容

    robj *createMylistObject(void) {
    MylinkedList* l = MyCreateList(); //quicklist *l = quicklistCreate();
    robj *o = createObject(MYOBJ_LIST,l);
    o->encoding = OBJ_ENCODING_QUICKLIST;
    return o;
    }
    4.修改 t_list.c文件,实现server.h中声明的命令处理函数

    //added by guojiangweivoid myrpushCommand(client *c){
    int j, pushed = 0;
    robj *lobj = lookupKeyWrite(c->db,c->argv[1]);
    for (j = 2; j < c->argc; j++) {
    if (!lobj) {
    lobj = createMylistObject();
    dbAdd(c->db,c->argv[1],lobj);
    }
    MyAppend(lobj->ptr,c->argv[j]->ptr,sdslen(c->argv[j]->ptr));
    pushed++;
    }
    addReplyLongLong(c, MyLength(lobj->ptr)); if (pushed) {
    char *event = "rpush";
    signalModifiedKey(c->db,c->argv[1]);
    notifyKeyspaceEvent(1,event,c->argv[1],c->db->id);
    }
    server.dirty += pushed;
    }
    void mylrangeCommand(client *c){
    robj *o;
    long start, end, llen, rangelen;
    char * data;
    int sz;
    if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != C_OK) ||
    (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != C_OK)) return;
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
    || checkType(c,o,MYOBJ_LIST)) return;
    llen = MyLength(o->ptr); /* convert negative indexes */
    if (start < 0) start = llen+start;
    if (end < 0) end = llen+end;
    if (start < 0) start = 0;
    /* Invariant: start >= 0, so this test will be true when end < 0.
    * The range is empty when start > end or start >= length. */
    if (start > end || start >= llen) {
    addReply(c,shared.emptymultibulk);
    return;
    } if (end >= llen) end = llen-1;
    rangelen = (end-start)+1; /* Return the result in form of a multi-bulk reply */
    addReplyMultiBulkLen(c,rangelen);
    if (o->encoding == OBJ_ENCODING_QUICKLIST) {
    PNODE n = MyIndex(o->ptr, start);
    while(rangelen--) {
    getData( n,&data,&sz);
    addReplyBulkCBuffer(c,data,sz);
    n=MyNext(n);
    }
    } else {
    serverPanic("List encoding is not QUICKLIST!");
    }

    }
    void myrpopCommand(client *c){
    robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk);
    int sz;
    if (o == NULL || checkType(c,o,MYOBJ_LIST)) return;
    char * data=Myrpop(o->ptr,&sz);
    robj *value = createStringObject(data,sz);
    free(data); if (value == NULL) {
    addReply(c,shared.nullbulk);
    } else {
    char *event = "rpop";
    addReplyBulk(c,value);
    decrRefCount(value);
    notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id);
    if (listTypeLength(o) == 0) {
    notifyKeyspaceEvent(NOTIFY_GENERIC,"del", c->argv[1],c->db->id);
    dbDelete(c->db,c->argv[1]);
    } signalModifiedKey(c->db,c->argv[1]);
    server.dirty++;
    }
    }
    void myllenCommand(client *c){
    robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.czero);
    if (o == NULL || checkType(c,o,MYOBJ_LIST)) return;
    addReplyLongLong(c,MyLength(o->ptr));
    }
    void mylsetCommand(client *c){
    robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr);
    if (o == NULL || checkType(c,o,MYOBJ_LIST)) return;
    long index;
    robj *value = c->argv[3];
    if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != C_OK))
    return;
    if (o->encoding == OBJ_ENCODING_QUICKLIST) {
    MYLINKEDLIST ql = o->ptr;
    int replaced = Mylset(ql,value->ptr, index,sdslen(value->ptr));
    if (!replaced) {
    addReply(c,shared.outofrangeerr);
    } else {
    addReply(c,shared.ok);
    signalModifiedKey(c->db,c->argv[1]);
    notifyKeyspaceEvent(NOTIFY_LIST,"lset",c->argv[1],c->db->id);
    server.dirty++;
    }
    } else {
    serverPanic("Unknown list encoding");
    }

    }
    void mylinsertCommand(client *c){
    long index;
    robj *subject;
    int inserted = 0;
    if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != C_OK)){
    addReply(c,shared.syntaxerr);
    return;
    }
    if ((subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
    checkType(c,subject,MYOBJ_LIST)) return;
    inserted=MyInsert(subject->ptr,c->argv[3]->ptr,index,sdslen(c->argv[3]->ptr));
    if (inserted) {
    signalModifiedKey(c->db,c->argv[1]);
    notifyKeyspaceEvent(NOTIFY_LIST,"linsert", c->argv[1],c->db->id);
    server.dirty++;
    } else { /* Notify client of a failed insert */
    addReply(c,shared.cnegone);
    return;
    } addReplyLongLong(c,MyLength(subject->ptr));
    }
    void mylindexCommand(client *c){
    long index;
    int size;
    robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr);
    if (o == NULL || checkType(c,o,MYOBJ_LIST)) return;
    if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != C_OK))
    return;
    PNODE n=MyIndex(o->ptr,index);
    if(n==NULL){
    addReply(c,shared.nullbulk);
    return;
    }

    char *data;
    getData( n,&data,&size);
    if (o->encoding == OBJ_ENCODING_QUICKLIST) {
    if (data) {
    robj *value=createStringObject(data,size);
    addReplyBulk(c,value);
    decrRefCount(value);
    } else {
    addReply(c,shared.nullbulk);
    }
    } else {
    serverPanic("Unknown list encoding");
    }
    }
    5.修改server.c 文件,在变量struct redisCommand redisCommandTable[] 中添加自定义类型命令字符串与命令处理函数映射

    {"myrpush",myrpushCommand,-3,"wmF",0,NULL,1,1,1,0,0},
    {"mylrange",mylrangeCommand,4,"wmF",0,NULL,1,1,1,0,0},
    {"myrpop",myrpopCommand,2,"wmF",0,NULL,1,1,1,0,0},
    {"myllen",myllenCommand,2,"wmF",0,NULL,1,1,1,0,0},
    {"mylset",mylsetCommand,4,"wmF",0,NULL,1,1,1,0,0},
    {"mylinsert",mylinsertCommand,4,"wmF",0,NULL,1,1,1,0,0},
    {"mylindex",mylindexCommand,3,"r",0,NULL,1,1,1,0,0},
    另外还需要编辑下make文件,比较简单这里不单独列出。

    redis源码阅读
    代码实现部分列出了需要修改的文件和修改的内容,redis源码设计的非常好,命名非常规范,如果读者按照文档所列进行了操作,对redis 源码结构和程序逻辑应该有了一个大致的认识,作者本来打算详细写下redis list类型的源码实现,但是想了想,水平有限,很难做到严谨与通俗,推荐大家看看《redis 设计与实现》这本书吧。个人觉得看源码最主要的是自己修改和不断的debug。

  • 相关阅读:
    20165334 第十三周选做
    20165334 实验五 网络编程与安全
    ### 20165334 —— 第十二周MySort(选作)
    2018-2019-1 20165227 20165228 20165237 实验一 开发环境的熟悉
    2018-2019-1 20165228 《信息安全系统设计基础》第四周学习总结
    2018-2019-1 20165228《信息安全系统设计基础》第三周课上测试
    2018-2019-1 20165228 《信息安全系统设计基础》第三周学习总结
    2018-2019-1 20165228 《信息安全系统设计基础》缓冲区溢出漏洞实验报告
    2018-2019-1 20165228 《信息安全系统设计基础》第二周学习总结
    2018-2019-1 20165228 苏祚堃《信息安全系统设计基础》第一周学习总结
  • 原文地址:https://www.cnblogs.com/hyhy904/p/10992435.html
Copyright © 2011-2022 走看看