`
forenroll
  • 浏览: 12120 次
  • 性别: Icon_minigender_1
  • 来自: 西安
社区版块
存档分类
最新评论

Redis源碼概览

 
阅读更多

本文翻译自:A quick look at the Redis source code

 

       

       这七年以来,主要是在写Java和Scala代码,我的C语言技能都退化了。事实上,它可能已经完全没有了。除了偶尔会用来hack,大学毕业以后我基本上都没有用到C了。大家都说,阅读他人的代码是非常好的学习方法,特别是代码库的作者是专家或者它质量有很高的评价时。因此,我准备阅读一个这样的代码库:Redis。



 

       Redis是一个用ANSI C 编写的开源数据结构服务器。“数据结构服务器”只是对灵巧的key-value存储服务的另外一种称谓。你不仅仅可以存储简单的字符串,还可以存储包括hash(或者map,甚至dicts),list,set,sorted set。我们在Top10 中大量应用了Redis,大部分为了根据用户搜索的日期和酒店的空房情况和价格建立索引。我发现Redis的代码非常容易读懂,甚至是对于像我这样的新手。代码写的很整洁,并且代码量相对较小(4.5万行左右),大部分都是单线程的,依赖也很少。所有的依赖都跟源代码放在一起了,这中做法让编译它变得非常简单:clone它的库,然后输入make即可。

       我决定通过为它增加一条命令来深入代码。而这简单的事情可以让我知道Redis怎么处理一条命令并调度响应它。命令rand,接收一个整型值作为max,并随机返回0到max(不包含max)之间的一个整数。这不是使用键值存储的思路,但是实现它将会很有启发性。而我也肯定不会提交一个pull request。

       免责声明:如我之前所说,我绝对不是一个C语言的专家,因此这里所有的代码和其解释都符合这个条款。而且,我链接了Redis的一个不稳定分支,所以它是不稳定的。如果你自己去获取Redis源码,用你喜欢的编辑器来查看时,你将发现更多本文的不同,特别是如果你编译并运行时会发现不同。

       命令表在src/redis.c文件的靠顶部的位置。它是一个数组,数组的元素类型是redisCommand结构体。redisCommand是在src/redis.h中定义的。在redisCommandTable的上方有一块比较详细的注释,对它的每一个field做了解释。下面是get命令的定义:

 

{"get",getCommand,2,"r",0,NULL,1,1,1,0,0},
       第一个field是命令的名字“get”。第二个field是一个函数指针,指向这个命令的具体实现(你可以查看实现细节t_string.c)。

 

       第三个field是命令的参数数量限制(命令接收的参数个数)。指定这个,意味着在调用函数指针之前,查找和执行命令的代码可以做一个预先验证。这种做法减少了在每个命令函数必须的错误处理代码。参数的个数算上了命令名字本身,所以它只接受两个参数:它自己的名字,key的名字(我们要获取它的值)。

       第四个field,被设为"r",用来指明这个命令是只读的,不能修改这个key的value或状态。有一大堆的字母标志,你都可以用在这个位置。而且在附近的注释块中,每个字母标志都有详细的解释。紧跟这个field的field总是被设置为0,后面会用来计算。它只是第四个field的字符串包含信息的位掩码。

       第六个field是NULL,因为它只有在你要用复杂的逻辑去告诉Redis哪个参数才是真正的key的时候才需要。一个key指向一个存储在Redis中的值的引用,对应简单的参数,例如我们的max参数。这种机制,允许Redis在调用命令的实现之前,提取key的值(并且校验key是否存在)。如果这个field被设置了值,那么它将会是一个函数指针,指向的函数会返回一个参数索引的整型数组(db.c中的zunionInterGetKeys是一个示例)。在get命令(其他大部分命令)的场景下,这个数组的信息传达的信息跟后面三个field的一样。get命令只有一个参数,而它就是key。因此,第一个参数(key)在位置1上,最后一个参数(也是key)在位置1上,从第一个参数到最后一个参数的增量也是1(译者注:源码注释是:intkeystep;/* The step between first and last key */)。

        redisCommand最后两个field是命令的度量项,由Redis来设置,并且总是初始化为0。

        在命令表的底部加上我们的命令:

 

{"rand",randCommand,2,"rRl",0,NULL,0,0,0,0,0}
       命令的名字是“rand”,randCommand指向实现的指针(还未实现),它接收2个参数(命令名字和max)。至于标志,它是只读的(r),返回随机的,不确定的输出(R),而且它可以在Redis还在加载数据的时候使用(l)。它没有关键参数。

 

        下一步是在src/redis.h中增加randCommand的函数原型。Redis命令的函数接收一个参数,一个redisClient的结构体,作为命令的参数同时也用来向实际的客户端发送响应。

 

void randCommand(redisClient *c);
         这个原型应该放在src/redis.h中与其他所有命令的原型一起。搜索下面的一行:

 

 

/* Commands prototypes */
         这将帮你找到正确的位置

 

         我们在src/redis.c中加一个空实现:

 

void randCommand(redisClient *c) {
 
}
 
         我将它加在了infoCommand定义的旁边。现在,我们执行make命令。

 

 

make
        然后,启动我们刚刚编译成共的Redis服务(如果你已经有一个Redis服务在本地运行,你应该停掉它):

 

 

> src/redis-server
        接着我们在另外的终端中运行Redis客户端,并试着运行我们的命令:

 

 

>redis-cli
        首先,我们试一试我们的异常处理:

 

redis 127.0.0.1:6379> rand
(error) ERR wrong number of arguments for 'rand' command
        很好,参数数量限制检查是正常的。这一次我们指定一个参数:

 

redis 127.0.0.1:6379> rand 1
        Redis卡住了。这正是我预期的,因为我在randCommand函数中没有任何响应。将服务停掉,我们接着回去看代码。
        我们想返回一个整数,因此我在代码里翻找例子,最后在src/t_zset.c中找到了zcardCommand。这个命令用addReplyLongLong来向客户端返回一个64位(long long)的整数。我们也试一下:
void randCommand(redisClient *c) {
    addReplyLongLong(c,3);
}
         然后,我们在make一次,并测试命令:
redis 127.0.0.1:6379> rand 1
(integer) 3
 
redis 127.0.0.1:6379> rand 2
(integer) 3
 
redis 127.0.0.1:6379> rand 3
(integer) 3
        好吧,结果不是太随机,但这只是个开始。我们从命令里获取参数max,并返回一个由max限制的随机数:
void randCommand(redisClient *c) {
    long max;
 
    if (getLongFromObjectOrReply(c,c->argv[1],&max,NULL) != REDIS_OK)
        return;
 
    addReplyLongLong(c,random() % max);
}

       尽管Redis在整个代码库中都用原始类型和C型字符串,但它同时也拥有自己的以更通用的方式存在的内部对象系统,用来表示字符串,长整型和更复杂的类型。一个利用这种类型的例子就是:每个命令的参数。每一个命令的参数都作为一个Redis对象被存在redisClient实例c的field,数组argv里。(译注:在源码src/redis.c里面redisClient是一个结构体,argv是一个redisObject指针的指针)。在src/t_string.c里面有一个从Redis对象获取长整型的例子:getrangeCommand,它调用了src/object.c中的getLongFromObjectOrReply函数。

        getLongFromObjectOrReply函数接收一个redisClient实例作参数,并检查它的第二个参数是否是一个长整型,如果是则将第二个参数的指针赋给第三个参数(这个参数是一个指针类型),并且返回REDIS_OK。如果第二个参数不是长整型(或溢出了),函数返回REDIS_ERR。这个方法的美丽之处在于:如果我们从我们的randCommand函数得到的返回值是REDIS_ERR,所有必须的错误响应已经被发送给客户端了。我们再试一下我们的命令:
redis 127.0.0.1:6379> rand 10
(integer) 9
redis 127.0.0.1:6379> rand notanumber
(error) ERR value is not an integer or out of range
redis 127.0.0.1:6379> rand 10
(integer) 3
redis 127.0.0.1:6379> rand 10
(integer) 1
redis 127.0.0.1:6379> rand 100
(integer) 43
redis 127.0.0.1:6379> rand 100
(integer) 55
redis 127.0.0.1:6379> rand 100
(integer) 86
          看起来不错!rand看起来是一个没有多少意义的命令,但是从实现它的过程中学到很多关于Redis的东西,我希望你跟着做下来也同样学到很多。请在评论里告诉我这篇文章里是否明显的错误。我也很高兴知道这篇文章对你很有用或者你很喜欢它。我考虑写一些类似的东西,关于Redis或者其他的开源的代码库。

       

 

  • 大小: 23 KB
分享到:
评论

相关推荐

    redis源码日志

    ### Redis源码日志知识点概览 #### 0.1 源码日志 - **定义**: 本文档作为作者记录Redis源码学习过程的日志。 - **目的**: 分享作者在研究Redis源码过程中的心得与体会,旨在帮助读者更好地理解Redis的工作原理。 ...

    05_linux环境安装redis.pdf

    可以使用示例命令来下载对应版本的Redis源码:`wget ***`。 - 解压缩下载的tar.gz格式包:`tar -xvf 文件名.tar.gz`。 - 进入解压后的目录并执行编译命令:`make`。 - 安装Redis,可以指定安装目录:`make ...

    2020最新版Redis架构全套视频教程课件

    - **编译安装**:解压Redis源码包后,进入`src`目录执行`make`命令进行编译安装。 - **启动服务**:根据需要选择前台或后台模式启动Redis服务,使用`redis-server redis.conf`命令启动。 4. **Redis的数据类型与...

    Redis Cookbook.pdf

    ### Redis Cookbook 知识点概览 #### 一、Redis简介与应用场景 **知识点1:何时使用Redis** Redis是一款开源的内存数据结构存储系统,它可以用作数据库、缓存和消息中间件。根据《Redis Cookbook》中的介绍,在...

    Redis启动过程详解

    #### 二、启动过程概览 Redis启动过程主要包括以下几个阶段: 1. **初始化阶段**:此阶段主要负责加载必要的库、配置默认的服务器参数以及处理特殊选项。 2. **配置处理**:根据提供的命令行参数或配置文件调整...

    传智redis笔记

    ### 传智Redis笔记知识点概览 #### 一、NoSQL简介 NoSQL数据库是一种非关系型数据库,其设计初衷是为了处理大规模数据集,并能在分布式环境中提供高性能和高可用性。与传统的关系型数据库不同,NoSQL数据库通常不...

    centOS下搭建redis

    - **说明**: 上述命令首先通过 `wget` 命令从 Redis 官方网站下载最新的稳定版本的源码压缩包,然后使用 `tar` 命令进行解压,并进入解压后的目录。 ##### 2. 安装必要的依赖库 - **命令**: ```bash yum install...

    redis学习笔记

    ### Redis 学习笔记知识点概览 #### 一、Redis 简介及特性 - **Redis**(Remote Dictionary Server)是一种开源的、基于内存的数据结构存储系统,它支持多种数据结构,如字符串(strings)、散列(hashes)、列表...

    JavaWeb之Linux与Redis

    **注解的作用范围** 可分为三种:源码阶段、编译期间和运行期间。这由注解的保留策略决定。 **自定义注解格式** 包括了注解的数据类型,常见的包括基本数据类型(四类八种)、字节码类型(`Class`)、注解类型、...

    redis用法详细解释.doc

    安装 Redis 非常简单,首先从官方网站下载最新版本的 Redis 源码包(redis-X.Y.Z.tar.gz),解压之后进入到 redis-X.Y.Z 文件夹,运行 `make` 命令即可完成编译安装。成功编译后,会在 src 目录下生成一系列可执行...

    实体售卡商城系统源码联通移动电信卡销售源码流量卡商城源码免登录.txt

    #### 一、概览 在当前数字化时代背景下,各类线上服务逐渐成为主流趋势。实体售卡商城系统源码作为一种特定类型的电商平台解决方案,旨在为运营商提供一种便捷高效的卡片销售途径。此类系统通常具备自动化处理订单...

    PHP实例开发源码——590文学网.zip

    9. **缓存技术**:为了提高性能,源码可能采用了缓存技术,如文件缓存或内存缓存(如Redis、Memcached),来存储频繁访问的数据,减少数据库的负载。 10. **安全性**:源码应该包含了防止SQL注入、XSS攻击的安全...

    twitter_hito-源码.rar

    《深入剖析Twitter_hito源码》 Twitter作为一个全球知名的社交网络平台,其背后的代码实现一直备受开发者关注。本文将基于“twitter_...当然,真正的源码分析还需要结合实际代码进行,这里只是提供了一个初步的概览。

    java版商城源码下载-mall:购物中心

    java版商城源码下载 Mall 商城 该项目为 的学习项目。 部署完成进度 整合SpringBoot + MyBatis Swagger-UI 实现在线API文档 整合Redis实现缓存 SpringSecurity和JWT实现认证和授权 SpringTask实现定时任务 Elastic...

    RuleApp1.4.0文章社区客户端源码

    #### 一、概览 **RuleApp 1.4.0**是一款专为构建文章社区而设计的客户端源码,支持多平台(Android、iOS、H5、小程序)部署,并集成了丰富的功能模块与特色功能。该版本在原有基础上进行了大幅度升级,新增了私聊...

    springboot旧物回收管理系统(源码+数据库)221713

    标题 "springboot旧物回收管理系统(源码+数据库)221713" 提供了一个基于Spring Boot的旧物回收管理系统的项目概览。这个系统涵盖了管理端和用户端的功能,旨在提供全面的旧物回收管理解决方案。下面将详细讨论涉及...

    dubbo源码解读与实战.doc

    **1.2 架构概览** - **Registry(注册中心)**:负责服务地址的注册与查找。服务的Provider和Consumer只在启动时与注册中心交互。当Provider出现故障时,注册中心会及时通知Consumer。 - **Provider(服务提供者)*...

    基于springboot的在线团购系统-源码

    【概览】 SpringBoot作为一款由Pivotal团队开发的轻量级Java框架,已经广泛应用于各种Web应用程序的开发,尤其是微服务架构。本在线团购系统的实现充分利用了SpringBoot的优势,简化了配置,提高了开发效率。通过...

Global site tag (gtag.js) - Google Analytics