欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-08-20 15:34:32
导读作者:郭江伟,MySQL学员目测本文有续集哦,欢迎大家持续关注~2019年第一篇文档,为2019年做个良好的开端,本文档通过step by step的方式向读者展示如......

导读

作者:郭江伟,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原生命令 命令含义
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函数开始。

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


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

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


代码实现与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!\n"); 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。


  

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


加入知数堂

挑战40万+年薪!



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


知数堂

叶金荣与吴炳锡联合打造

领跑IT精英培训

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

MySQL实战/MySQL优化/MongoDB/

Python/ SQL优化/Hadoop+ELK

数门精品课程

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

密码:hg3h

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

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

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

DBA、开发工程师必修课

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

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

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



玩一把redis源码(一):为redis添加自己的列表类型扫码加入QQ技术交流群

MySQL 8.0|MGR研究院-ZST

(QQ群号:650149401)    

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

本文地址:https://blog.csdn.net/n88Lpo/article/details/86610287