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!\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添加自己的列表类型的更多相关文章
- 玩一把redis源码(一):为redis添加自己的列表类型
2019年第一篇文档,为2019年做个良好的开端,本文档通过step by step的方式向读者展示如何为redis添加一个数据类型,阅读本文档后读者对redis源码的执行逻辑会有比较清晰的认识,并且 ...
- 【redis源码阅读】redis对象
结构定义 在redis中,对象的数据结构定义如下: typedef struct redisObject { unsigned type:4; unsgined encoding:4; uns ...
- [Redis源码阅读]redis持久化
作为web开发的一员,相信大家的面试经历里少不了会遇到这个问题:redis是怎么做持久化的? 不急着给出答案,先停下来思考一下,然后再看看下面的介绍.希望看了这边文章后,你能够回答这个问题. 为什么需 ...
- Redis 源码学习之 Redis 事务Nosql
Redis事务提供了一种将多个命令请求打包,然后一次性.按照顺序地执行多个命令的机制,并且在事务执行的期间,服务器不会中断事务而去执行其他不在事务中的命令请求,它会把事务中所有的命令都执行完毕才会去执 ...
- Redis源码研究--字典
计划每天花1小时学习Redis 源码.在博客上做个记录. --------6月18日----------- redis的字典dict主要涉及几个数据结构, dictEntry:具体的k-v链表结点 d ...
- redis源码笔记(一) —— 从redis的启动到command的分发
本作品采用知识共享署名 4.0 国际许可协议进行许可.转载联系作者并保留声明头部与原文链接https://luzeshu.com/blog/redis1 本博客同步在http://www.cnblog ...
- [Redis源码阅读]sds字符串实现
初衷 从开始工作就开始使用Redis,也有一段时间了,但都只是停留在使用阶段,没有往更深的角度探索,每次想读源码都止步在阅读书籍上,因为看完书很快又忘了,这次逼自己先读代码.因为个人觉得写作需要阅读文 ...
- [Redis源码阅读]dict字典的实现
dict的用途 dict是一种用于保存键值对的抽象数据结构,在redis中使用非常广泛,比如数据库.哈希结构的底层. 当执行下面这个命令: > set msg "hello" ...
- Redis源码学习:Lua脚本
Redis源码学习:Lua脚本 1.Sublime Text配置 我是在Win7下,用Sublime Text + Cygwin开发的,配置方法请参考<Sublime Text 3下C/C++开 ...
随机推荐
- Android 圆形/圆角图片的方法
Android 圆形/圆角图片的方法 眼下网上有非常多圆角图片的实例,Github上也有一些成熟的项目.之前做项目,为了稳定高效都是选用Github上的项目直接用.但这样的结束也是Android开发必 ...
- JQuery图例
- 服务器端将复合json对象传回前端
前端接收后端传过来的JSON对象,对前端来说,传过来的确实就是JSON对象:但后端,类型则灵活得多,可以是IList<>等类型,当然也可以是newtonsoft的JObject类型.反正在 ...
- 【OI】Kruskal & ufs (克鲁斯卡与并查集)
Kruskal是有关于最小生成树的算法. 这个算法非常好理解,用一句话来概括就是: 从小到大找不同集合的边. 那么,具体是怎样的呢. 1.先把所有顶点初始化为一个连通分量. 2.从所有边中选择最小的( ...
- JMeter快捷键图标制作 去掉cmd命令窗口
使用jmeter时: 如果使用默认的jmeter.bat启动的话,会出现一个CMD命令窗口之后再会启动jmeter工作界面 直接启用ApacheJMeter.jar文件即可跳过CMD命令窗口启动jme ...
- 安装linux系统-CentOS-6.8-x86_64-minimal.iso
1: 2: 3:单击[Next]继续安装. 4:安装语言,选择[Chinese(Simplified)(中文(简体))]菜单,单击[Next]继续. 5:系统键盘,选择[美国英语式]菜单,单击[下一步 ...
- ExecuteNonQuery()的用法
ExecuteNonQuery()的用法 下面我们将详细讲解如何在Page_Load()中对数据库的增加.删除.修改,最后我们再来总结一下ExecuteNonQuery(),ExecuteScalar ...
- JfreeChart的使用1
JfreeChart的使用 来自: 克洛泽大地(DREG) 2008-07-03 14:35:11 先从网上找点介绍. 一.简介 WW 的发展使得基于因特网的应用程序不再局限于静态或者简单的动态内容提 ...
- 测试-Swagger:Swagger
ylbtech-测试-Swagger:Swagger The Best APIs are Built with Swagger Tools. Swagger 是一款RESTFUL接口的文档在线自动生成 ...
- 关于js-cookie使用出现兼容性问题以及js-cookie的如何使用
最近使用vue开发的项目,开发过程引入了js-cookie插件,在PC端以及移动端网页调试都没出现问题,但是打包成APP在安卓手机调试发现使用js-cookie保存的数据失效了,然后只能使用local ...