`
lingqi1818
  • 浏览: 253375 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

memcached源代码分析

阅读更多

 

 

目录

一.         概述... 3

二.         服务器实现... 3

三.         Memcached协议... 5

四.         数据存储... 8

五.         参考文献... 9

 


 

一.概述

 

本文主要对memcached服务器代码进行分析,这里对各种客户端的实现不做阐述。

原生的memcached是一款基于内存的缓存软件,存储的方式基于k/v.

 

 

二.服务器实现

 

Memcached的服务器主要依赖libevent+多线程模型来实现核心逻辑。

为了更好的了解memcached的实现,我们首先需要知道下什么是libevent,它是用来干什么的。

 

Libevent是一个轻量级的开源高性能网络库, Libevent 有几个显著的亮点:

事件驱动(event-driven),高性能;

轻量级,专注于网络,不如ACE那么臃肿庞大;

源代码相当精炼、易读;

跨平台,支持WindowsLinux*BSDMac Os

支持多种I/O多路复用技术, epollpolldev/pollselectkqueue等;

支持I/O,定时器和信号等事件;

注册事件优先级;

Libevent已经被广泛的应用,作为底层的网络库;比如memcachedVomitNylonNetchat等等。

 

说了那么多,我们来看一个实际的例子:

int main(int argc, char **argv)

{

...

    ev_init();

 

    /* Setup listening socket */

 

    event_set(&ev_accept, listen_fd, EV_READ|EV_PERSIST, on_accept, NULL);

    event_add(&ev_accept, NULL);

 

    /* Start the event loop. */

    event_dispatch();

}

 

以上的例子构建了一个简单服务器。

假如listen_fd有数据可读,那么就会调用on_accept方法。

ev_init用于初始化一个事件环境

event_setevent_add方法分别用于设置和添加事件,event_dispatch则启动当前线程事件监听的主循环。

 

了解了这个之后,我们再来看一下memcached的多线程服务器模型。

 

 



  

说明:

Mthread:程序启动的主线程

Cthread:用于处理连接请求的分线程

eb:libeventevent_base指针。

Cq:连接队列,每个分线程都拥有一个连接队列。

 

从图中可以看到,整个服务器处理流程分为以下几个步骤:

1.       主线程建立新的连接,并把连接句柄交给请求队列。

2.       分线程从队列中取出连接数据,并进行处理。

 

这个地方memcached做了一个优化,因为加入一直没有数据进来,那么cthread就会一直空跑耗费性能,所以这边由管道来实现。

代码如下:

创建管道:

int fds[2];

        if (pipe(fds)) {

            perror("Can't create notify pipe");

            exit(1);

        }

 

        threads[i].notify_receive_fd = fds[0];

        threads[i].notify_send_fd = fds[1];

 

 

 

线程事件中的逻辑:

  if (read(fd, buf, 1) != 1)//假如没有数据则一直阻塞

        if (settings.verbose > 0)

            fprintf(stderr, "Can't read from libevent pipe\n");

 

 

 

三.Memcached协议

 

Memcached目前支持2种协议类型,asciibinary,假如指定协商方式的话,由请求的第一位决定。

if ((unsigned char)c->rbuf[0] == (unsigned char)PROTOCOL_BINARY_REQ) {

            c->protocol = binary_prot;

        } else {

            c->protocol = ascii_prot;

        }

 

1.4.5之前的版本仅对ascii协议完全支持,binary的目前还未完全实现。

 

下面对协议的处理流程,以及协议的格式做一下阐述。

命令格式:

<command name> <key> <flags> <exptime> <bytes>\r\n

 

- <command name> set, add, 或者 repalce                                   

  • set 意思是储存此数据
  • add 意思是储存此数据,只在服务器**保留此键值的数据时
  • replace意思是储存此数据,只在服务器**保留此键值的数据时

- <key> 是接下来的客户端所要求储存的数据的键值

- <flags> 是在取回内容时,与数据和发送块一同保存服务器上的任意16位无符号整形(用十进制来书写)。客户端可以用它作为位域来存储一些特定的信息;它对服务器是不透明的。

- <exptime> 是终止时间。如果为0,该项永不过期(虽然它可能被删除,以便为其他缓存项目腾出位置)。如果非0Unix时间戳或当前时刻的秒偏移),到达终止时间后,客户端无法再获得这项内容。

- <bytes> 是随后的数据区块的字节长度,不包括用于分野的“\r\n”。它可以是0(这时后面跟随一个空的数据区块)。

 

在这一行以后,客户端发送数据区块。

- <data block> 是大段的8位数据,其长度由前面的命令行中的<bytes>指定。

发送命令行和数据区块以后,客户端等待回复,可能的回复如下:

表明成功.

表明数据没有被存储,但不是因为发生错误。这通常意味着add replace命令的条件不成立,或者,项目已经位列删除队列(参考后文的“delete”命令)。

 

整个命令的解析过程是由对conn的状态转变来维护的,conn的状态有以下几种:

enum conn_states {

    conn_listening,  /**< the socket which listens for connections */

    conn_new_cmd,    /**< Prepare connection for next command */

    conn_waiting,    /**< waiting for a readable socket */

    conn_read,       /**< reading in a command line */

    conn_parse_cmd,  /**< try to parse a command from the input buffer */

    conn_write,      /**< writing out a simple response */

    conn_nread,      /**< reading in a fixed number of bytes */

    conn_swallow,    /**< swallowing unnecessary bytes w/o storing */

    conn_closing,    /**< closing this connection */

    conn_mwrite,     /**< writing out many items sequentially */

conn_max_state   /**< Max state value (used for assertion) */

 

下图以插入数据为例来说明状态的变化:

 



  

整个处理过程又一个循环+switch逻辑组成:

while (!stop) {

 

        switch(c->state) {

        case conn_listening:

            addrlen = sizeof(addr);

            if ((sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen)) == -1) {

                if (errno == EAGAIN || errno == EWOULDBLOCK) {

                    /* these are transient, so don't log anything */

                    stop = true;

                } else if (errno == EMFILE) {

                    if (settings.verbose > 0)

                        fprintf(stderr, "Too many open connections\n");

                    accept_new_conns(false);

                    stop = true;

                } else {

                    perror("accept()");

                    stop = true;

                }

                break;

            }

            if ((flags = fcntl(sfd, F_GETFL, 0)) < 0 ||

                fcntl(sfd, F_SETFL, flags | O_NONBLOCK) < 0) {

                perror("setting O_NONBLOCK");

                close(sfd);

                break;

            }

 

            dispatch_conn_new(sfd, conn_new_cmd, EV_READ | EV_PERSIST,

                                     DATA_BUFFER_SIZE, tcp_transport);

            stop = true;

            break;

 

        case conn_waiting:

            if (!update_event(c, EV_READ | EV_PERSIST)) {

                if (settings.verbose > 0)

                    fprintf(stderr, "Couldn't update event\n");

                conn_set_state(c, conn_closing);

                break;

            }

 

            conn_set_state(c, conn_read);

            stop = true;

            break;

 

        case conn_read:

            res = IS_UDP(c->transport) ? try_read_udp(c) : try_read_network(c);

 

            switch (res) {

            case READ_NO_DATA_RECEIVED:

                conn_set_state(c, conn_waiting);

                break;

            case READ_DATA_RECEIVED:

                conn_set_state(c, conn_parse_cmd);

                break;

            case READ_ERROR:

                conn_set_state(c, conn_closing);

                break;

            case READ_MEMORY_ERROR: /* Failed to allocate more memory */

                /* State already set by try_read_network */

                break;

            }

            break;

 

 

 

 

四.数据存储

 

首先我们看图说话:

 



  

Memcached的内存由一块一块的slabclass组成的。

每个slabclass格式化为200slab.

每个数据item按照大小匹配存放到slab中。Slab的大小按照factor发生递增。

 

我们可以看一下它的初始化方法:

if (prealloc) {

        /* Allocate everything in a big chunk with malloc */

        mem_base = malloc(mem_limit);

        if (mem_base != NULL) {

 

            mem_current = mem_base;

            mem_avail = mem_limit;

        } else {

            fprintf(stderr, "Warning: Failed to allocate requested memory in"

                    " one large chunk.\nWill allocate in smaller chunks\n");

        }

    }

 

    memset(slabclass, 0, sizeof(slabclass));

 

    while (++i < POWER_LARGEST && size <= settings.item_size_max / factor) {

        /* Make sure items are always n-byte aligned */

        if (size % CHUNK_ALIGN_BYTES)

            size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);

 

        slabclass[i].size = size;

        slabclass[i].perslab = settings.item_size_max / slabclass[i].size;

        size *= factor;

        if (settings.verbose > 1) {

            fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",

                    i, slabclass[i].size, slabclass[i].perslab);

        }

    }

 

    power_largest = i;

    slabclass[power_largest].size = settings.item_size_max;

    slabclass[power_largest].perslab = 1;

    if (settings.verbose > 1) {

        fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",

                i, slabclass[i].size, slabclass[i].perslab);

}

 

 

 

五.参考文献

http://memcached.org/

http://www.ibm.com/developerworks/cn/aix/library/au-libev/index.html?ca=drs-

 

 

 

  • 大小: 68.6 KB
  • 大小: 41.2 KB
  • 大小: 27.8 KB
分享到:
评论
3 楼 lingqi1818 2011-07-20  
mtnt2008 写道

觉得很多的地方都没有讲。比如:当内存用完时,memcache采用什么样的策略?memcache处理hash冲突的解决办法?怎么知道数据的过期?memcache的CAP实现?

哈哈,这个只是在内部邮件组里的一个分享,只是讲讲原理,细节太多不好表述。。
2 楼 mtnt2008 2011-07-20  

觉得很多的地方都没有讲。比如:当内存用完时,memcache采用什么样的策略?memcache处理hash冲突的解决办法?怎么知道数据的过期?memcache的CAP实现?
1 楼 diecui1202 2011-06-17  
不错,有空学习下。

PS:代码格式要是能调整一下就更好看了。

相关推荐

    Memcached源码分析之内存管理

    Memcached源码分析之内存管理Memcached源码分析之内存管理

    Memcached 源码剖析笔记

    memcached 源码剖析笔记和源码。 Memcached 是一个自由、源码开放、高性能、分布式内存对象缓存系统,目的在于过减轻数据库负载来使动态 Web 应用程序提速。

    memcache源代码分析

    在进行memcached源代码分析时,我们需要关注以下几个核心知识点: 1. **网络通信**:memcached 使用libevent库处理网络事件,这是一个非阻塞I/O模型,能够高效地处理大量的并发连接。libevent通过监听套接字,当有...

    memcached源代码下载.rar

    **memcached源代码详解** `memcached`是一个高性能的分布式内存对象缓存系统,它用于在动态系统中减少数据库负载,提升网站性能。这个压缩包包含的是`memcached`的源代码,允许开发者深入理解其内部工作原理,并...

    神级memcached源代码分析文档_1.4.0代码分析

    《深入解析Memcached源代码——基于1.4.0版本》 Memcached,这个被广泛应用于众多知名互联网公司的分布式内存对象缓存系统,其源代码分析对于提升系统性能和理解其实现原理至关重要。本文将围绕以下几个核心部分...

    memcached源码

    **memcached源码分析** `memcached`是一个高性能、分布式内存对象缓存系统,用于在动态系统中减少数据库负载,提升应用性能。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高了网站的响应速度。本文...

    memcached1.4.5源代码

    **memcached1.4.5源代码分析** `memcached`是一种高性能、分布式内存对象缓存系统,用于在Web应用程序中存储数据,以减少数据库负载。它的设计目标是减轻数据库的负担,通过缓存经常访问的数据来提高网络应用的响应...

    Memcached源码剖析笔记

    通过对Memcached源码的深入分析,我们不仅理解了其核心功能是如何实现的,还掌握了如何更高效地使用它来提升Web应用的性能。了解这些内部细节对于优化应用程序的缓存策略以及故障排查具有重要意义。

    memcached工具类源码

    3. **源码结构分析** 解压后的"memcached工具类源码"可能包含以下部分: - **Memcached客户端连接器**:初始化和管理到Memcached服务器的连接,如建立Socket连接,处理心跳机制,保持会话活跃。 - **Key-Value...

    memcached全面剖析.pdf

    - **安装memcached**: 可以通过包管理器(如apt-get或yum)安装,也可以从源码编译安装。 - **启动memcached**: 启动命令通常为`memcached -m [memory] -p [port] -u [username] -l [ip_address]`,其中[memory]表示...

    最新版Memcached for windows + 源码

    4. **Memcached与源码分析** - 源码阅读可以帮助开发者理解Memcached的内存管理、数据结构、网络通信等核心部分,为自定义扩展或优化提供基础。 - Memcached的内存管理采用了slab分配器,将内存划分为不同的slabs...

    memcached java源码(performance分支)

    **Memcached Java源码分析——Performance分支** Memcached是一款高性能的分布式内存对象缓存系统,广泛应用于Web应用中,用于缓解数据库的负载。在Java环境中,我们常常使用Java客户端库来与Memcached服务器进行...

    memcached C++ 客户端 源码

    标题"memcached C++ 客户端 源码"表明了这是一个关于使用C++编写的memcached客户端的源代码库。memcached是一款高性能、分布式的内存对象缓存系统,常用于减轻数据库负载,提高Web应用性能。C++客户端则为开发者提供...

    memcached 源码生成的chm文档

    为了便于分析memcached的源码,使用doxygen生成了这个文档

    易语言源码易语言Memcached协议客户端模块源码.rar

    在这个"易语言源码易语言Memcached协议客户端模块源码.rar"压缩包中,我们找到了一个易语言实现的Memcached协议客户端模块的源代码。Memcached是一种高性能、分布式内存对象缓存系统,常用于减轻数据库负载,提高...

    Spring memcached 源码

    Spring Memcached 是一个用于在Spring应用中集成Memcached缓存服务的框架。Memcached是一种分布式内存对象缓存系统,常用于提高网站数据读取...通过源码分析,我们可以了解其工作原理,并根据实际需求进行调整和优化。

    Memcached源码剖析笔记.pdf

    安装Memcached通常涉及下载源代码,编译并安装二进制文件。在Unix-like系统上,这通常包括`./configure`, `make`, 和 `make install`等步骤。在某些操作系统上,还可以通过包管理器(如apt-get或brew)进行安装。 ...

Global site tag (gtag.js) - Google Analytics