站保站

服务市场
  • 网站市场
  • 单机游戏
  • 平台大厅
  • 转让市场
  • 发卡市场
  • 广告市场
  • 下载市场
  • 收录市场
  • 本站平台
    平台客服
    微信Q群



    平台微博/weibo    平台微信/公众号    平台抖音/快手   
    曝光台    保障    地图   
    上传资源 快速赚钱
    站保站    登录      |  注册  |  

    只需一步,快速开始!

     找回密码   |   协议
    热门搜索: 网站开发 App报毒 挖矿源码 代办资质

    玩一把redis源码(一):为redis添加自己的列表类型

    • 时间:2019-01-22 09:44 编辑:老叶茶馆_ 来源: 阅读:4848
    • 扫一扫,手机访问
    摘要:

    导读

    作者:郭江伟,MySQL学员

    目测本文有续集哦,欢迎大家持续关注~

    2019年第一篇文档,为2019年做个良好的开端,本文档通过step by step的方式向读者展示如何为redis添加一个数据类型,阅读本文档后读者对redis源码的执行逻辑会有比较清晰的认识,并且可以深入理解redis 源码中关于链表数据结构的使用,写这篇文档作者获益良多,阅读系统软件源码的兴趣也大大提高。

    同时也再次感受到良好的基础是深入学习的前提。特别强调本文档仅用于学习,并非是要修改redis源码。 建议读者阅读本文档时实际动手敲一下代码,然后翻阅下redis源码,debug下redis

    本文档分为三大部分:

    • 环境介绍与效果演示

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

    • 代码实现

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

    环境介绍和效果演示

    环境介绍

    redis版本为5.0.3 64 bit
    操作系统版本为Ubuntu 18.10 64bit
    源码可以用gedit查看 gdb调试
    ide 可以用eclipse+CDT

    效果演示

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

    实现命令redis原生命令命令含义
    myrpushrpush从尾部插入链表
    mylrangelrange获取范围内值
    myrpoprpop从右侧弹出值
    myllenllen获取list长度
    mylsetlset设置某个节点值
    myinsertlinsert在指定位置插入值
    mylindexlindex获取指定位置值

    实现命令演示:

    1. 127.0.0.1:6379> myrpush mygjw gjw0(integer)
      1127.0.0.1:6379> myrpush mygjw gjw1
    2. (integer) 2
      127.0.0.1:6379> myrpush mygjw gjw2
    3. (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
    4. (integer) 2
      127.0.0.1:6379> mylset mygjw 0 gjw00
      OK127.0.0.1:6379> mylset mygjw 1 gjw01
    5. 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
    6. (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
    头文件内容如下:

    1. /*
    2. * mylineklist.h
    3. *
    4. *  Created on: Dec 29, 2018
    5. *      Author: gjw
    6. */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);

    在本例中并非所有的声明函数都已实现,仅仅实现了上面演示的几个命令,源文件内容如下:

    1. /*
    2. * mylinkedlist.c
    3. *
    4. *  Created on: Dec 29, 2018
    5. *      Author: gjw
    6. */
      #include <stdlib.h>
      #include <stdio.h>
      #include <string.h>
      #include "mylinkedlist.h"
    7. struct NODE {
    8. ADT data;
             int size;
    9. PNODE next;
    10. PNODE previous;
    11. } ;
    12. typedef struct NODE NODE;
    13. struct MylinkedListS {
    14. unsigned int len;
    15. PNODE head, tail;
    16. };
    17. MylinkedList* MyCreateList() {
    18. MylinkedList* list = (MylinkedList*) malloc(sizeof(MylinkedList));
    19. PNODE head = (NODE *) malloc(sizeof(NODE));
    20. 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;
    21. printf("init a list");
             return list;
    22. }
    23. void MyAppend(MYLINKEDLIST list, ADT data,int len) {
             if (list->head->next == NULL) {
    24. MyInsert(list, data, 0,len);
                     return;
    25. }
    26. printf("->");
    27. PNODE ctail = (NODE *) malloc(sizeof(NODE));
    28. ctail->data=(char *)malloc(len);
    29. memcpy(ctail->data,data,len);
    30. ctail->size=len;
    31. ctail->next=NULL;
    32. ctail->previous=list->tail->next;
             list->tail->next->next = ctail;
             list->tail->next = ctail;
             list->len = list->len + 1;
    33. }
      int MyInsert(MYLINKEDLIST list, ADT data, unsigned int pos,int len) {
      if ( pos > list->len) return 0;
    34. printf("insert success!\n");
    35. PNODE node = list->head;
      for (unsigned int i = 0; i < pos; i++) {
    36. node = node->next;
    37. }
    38. PNODE newNode = (NODE *) malloc(sizeof(NODE));
    39. newNode->data=(char *)malloc(len);
    40. memcpy(newNode->data,data,len);
    41. newNode->size=len;
    42. newNode->next = node->next;
    43. newNode->previous=node; if(node->next){
    44. newNode->next->previous=newNode;
    45. }
    46. node->next = newNode;
             list->tail->next=newNode;
             list->len = list->len + 1;
             return 1;
    47. }
    48. ADT  Myrpop(MYLINKEDLIST list,int * sz){
    49. PNODE tail=list->tail->next;
             if(list->len==0){
             return NULL;
    50. }
    51. ADT data=tail->data;
    52. *sz=tail->size; list->tail->next=tail->previous;
    53. tail->previous->next=NULL;
             list->len=list->len-1;
    54. free(tail);
             return data;
    55. }int MyLength(MYLINKEDLIST list) {
             return list->len;
    56. }
      int MyIsEmpty(MYLINKEDLIST list) {
             return !list->len;
    57. }
    58. void getData(PNODE n,char ** data,int * sz) {
    59. *data=n->data;
    60. *sz=n->size;
    61. }
    62. PNODE MyNext(PNODE node) {
             return node->next;
    63. }
    64. PNODE MyIndex(MYLINKEDLIST list,unsigned int index){
    65. PNODE n=list->head->next;  
             if(index>=list->len){  
                 return NULL;
    66.           }  
             for(int i=0;i<index;i++){
    67.   n=n->next;
    68.   }  
             return n;
    69. }
      int Mylset(MYLINKEDLIST list, ADT data, unsigned int pos,int len) {
    70. PNODE node=list->head->next;
      if(list->len==0){
    71. MyInsert(list,data,pos,len);
      return 1;
    72. }
      if(pos>=list->len){
      return 0;
    73. }  for(int i=0;i<pos;i++){
    74.  node=node->next;
    75.  }
    76.  realloc(node->data,len);
    77.  node->size=len;
    78.  memcpy(node->data,data,len);  
      return 1;
    79. }

    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,添加以下内容

    1. robj *createMylistObject(void) {
    2. MylinkedList*  l = MyCreateList(); //quicklist *l = quicklistCreate();
    3.    robj *o = createObject(MYOBJ_LIST,l);
    4.    o->encoding = OBJ_ENCODING_QUICKLIST;  
         return o;
    5. }

    4.修改 t_list.c文件,实现server.h中声明的命令处理函数

    1. //added by guojiangweivoid myrpushCommand(client *c){
    2.    int j, pushed = 0;    
      robj *lobj = lookupKeyWrite(c->db,c->argv[1]);    
      for (j = 2; j < c->argc; j++) {        
         if (!lobj) {
    3.            lobj = createMylistObject();          
                 dbAdd(c->db,c->argv[1],lobj);
    4.        }
    5.        MyAppend(lobj->ptr,c->argv[j]->ptr,sdslen(c->argv[j]->ptr));
    6.        pushed++;
    7.    }    
             addReplyLongLong(c, MyLength(lobj->ptr));    if (pushed) {
    8.        char *event = "rpush";        
             signalModifiedKey(c->db,c->argv[1]);      
             notifyKeyspaceEvent(1,event,c->argv[1],c->db->id);
    9.    }
    10.    server.dirty += pushed;
    11. }
    12. void mylrangeCommand(client *c){
    13.    robj *o;
    14.    long start, end, llen, rangelen;
    15.    char * data;
    16.    int  sz;    
          if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != C_OK) ||
    17.        (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != C_OK)) return;    
          if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
    18.         || checkType(c,o,MYOBJ_LIST)) return;    
         llen = MyLength(o->ptr);    /* convert negative indexes */
    19.    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.
    20.     * The range is empty when start > end or start >= length. */
    21.    if (start > end || start >= llen) {
    22.        addReply(c,shared.emptymultibulk);
    23.        return;
    24.    }    if (end >= llen) end = llen-1;
    25.    rangelen = (end-start)+1;    /* Return the result in form of a multi-bulk reply */
    26.    addReplyMultiBulkLen(c,rangelen);    
         if (o->encoding == OBJ_ENCODING_QUICKLIST) {
    27.     PNODE n = MyIndex(o->ptr, start);        
         while(rangelen--) {
    28.         getData( n,&data,&sz);
    29.         addReplyBulkCBuffer(c,data,sz);
    30.            n=MyNext(n);
    31.        }
    32.    } else {
    33.        serverPanic("List encoding is not QUICKLIST!");
    34.    }
    35. }
    36. void myrpopCommand(client *c){    
         robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk);
    37.    int sz;    
         if (o == NULL || checkType(c,o,MYOBJ_LIST)) return;    
         char * data=Myrpop(o->ptr,&sz);
    38.    robj *value = createStringObject(data,sz);
    39.    free(data);    if (value == NULL) {
    40.        addReply(c,shared.nullbulk);
    41.    } else {
    42.        char *event = "rpop";
    43.        addReplyBulk(c,value);
    44.        decrRefCount(value);      
             notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id);        
             if (listTypeLength(o) == 0) {
    45.            notifyKeyspaceEvent(NOTIFY_GENERIC,"del", c->argv[1],c->db->id);            
             dbDelete(c->db,c->argv[1]);
    46.        }        signalModifiedKey(c->db,c->argv[1]);
    47.        server.dirty++;
    48.    }
    49. }
    50. 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));
    51. }
    52. void mylsetCommand(client *c){    
             robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr);    
             if (o == NULL || checkType(c,o,MYOBJ_LIST)) return;
    53.        long index;  
              robj *value = c->argv[3];    
             if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != C_OK))
    54.        return;    
             if (o->encoding == OBJ_ENCODING_QUICKLIST) {
    55.     MYLINKEDLIST ql = o->ptr;        
             int replaced = Mylset(ql,value->ptr, index,sdslen(value->ptr));        
             if (!replaced) {
    56.            addReply(c,shared.outofrangeerr);
    57.        } else {
    58.            addReply(c,shared.ok);            
             signalModifiedKey(c->db,c->argv[1]);            
             notifyKeyspaceEvent(NOTIFY_LIST,"lset",c->argv[1],c->db->id);
    59.            server.dirty++;
    60.        }
    61.    } else {
    62.        serverPanic("Unknown list encoding");
    63.    }
    64. }
    65. void mylinsertCommand(client *c){
    66.    long index;
    67.    robj *subject;
    68.    int inserted = 0;    
             if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != C_OK)){
    69.     addReply(c,shared.syntaxerr);
    70.     return;
    71.    }    
             if ((subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
    72.        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]);
    73.        notifyKeyspaceEvent(NOTIFY_LIST,"linsert", c->argv[1],c->db->id);
    74.        server.dirty++;
    75.    } else {        /* Notify client of a failed insert */
    76.        addReply(c,shared.cnegone);
    77.        return;
    78.    }    addReplyLongLong(c,MyLength(subject->ptr));
    79. }
    80. void mylindexCommand(client *c){
    81. long index;
    82. 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))
    83.        return;
    84.    PNODE n=MyIndex(o->ptr,index);    
              if(n==NULL){
    85.     addReply(c,shared.nullbulk);
    86.     return;
    87.    }
    88.    char *data;
    89. getData( n,&data,&size);    
          if (o->encoding == OBJ_ENCODING_QUICKLIST) {          
           if (data) {
    90.               robj *value=createStringObject(data,size);
    91.               addReplyBulk(c,value);
    92.               decrRefCount(value);
    93.           } else {
    94.               addReply(c,shared.nullbulk);
    95.           }
    96.       } else {
    97.           serverPanic("Unknown list encoding");
    98.       }
    99. }

    5.修改server.c 文件,在变量struct redisCommand redisCommandTable[] 中添加自定义类型命令字符串与命令处理函数映射

    1. {"myrpush",myrpushCommand,-3,"wmF",0,NULL,1,1,1,0,0},
    2. {"mylrange",mylrangeCommand,4,"wmF",0,NULL,1,1,1,0,0},
    3. {"myrpop",myrpopCommand,2,"wmF",0,NULL,1,1,1,0,0},
    4. {"myllen",myllenCommand,2,"wmF",0,NULL,1,1,1,0,0},
    5. {"mylset",mylsetCommand,4,"wmF",0,NULL,1,1,1,0,0},
    6. {"mylinsert",mylinsertCommand,4,"wmF",0,NULL,1,1,1,0,0},
    7. {"mylindex",mylindexCommand,3,"r",0,NULL,1,1,1,0,0},

    另外还需要编辑下make文件,比较简单这里不单独列出。

    redis源码阅读

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


      

    640.gif?


    加入知数堂

    挑战40万+年薪!



    640?640?640?640?


    知数堂

    叶金荣与吴炳锡联合打造

    领跑IT精英培训

    行业资深专家强强联合,倾心定制

    MySQL实战/MySQL优化/MongoDB/

    Python/ SQL优化/Hadoop+ELK

    数门精品课程

    “阅读原文”可获更多正课试听视频

    密码:hg3h

    紧随技术发展趋势,定期优化培训教案

    融入大量生产案例,贴合企业一线需求

    社群陪伴学习,一次报名,可学1年

    DBA、开发工程师必修课

    上千位学员已华丽转身,薪资翻番,职位提升

    改变已悄然发生,你还在等什么?

    640.png?



    640?wx_fmt=gif 扫码加入QQ技术交流群

    MySQL 8.0|MGR研究院-ZST

    (QQ群号:650149401)    

    640?wx_fmt=jpeg

    • 全部评论(0)
    • 最新

    信息加载中,请等待

    微信客服(速回)

    微信客服(慢回)



    企业微信客服二维码
    联系我们
    平台客服: 平台QQ客服

    平台电话:400电话迁移中!

    平台邮箱:28292383@qq.com

    工作时间:周一至周五:早10:00 晚:18:00

    营业执照     网站ICP备案:鲁ICP备20027607号-1     鲁公网安备:37068702000078号     增值电信业务经营许可证、在线数据与交易处理业务许可证:鲁B2-20200681      © 2016-2024 站保站  https://www.zhanbaozhan.com/ 版权所有!      平台规范:   关于我们   广告合作   隐私条款   免责声明   法律声明   服务条款   网站地图   平台工单!