memcached源码分析(五):数据保存及内存管理

http://www.luochunhui.com/memcached%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%EF%BC%88%E4%BA%94%EF%BC%89%EF%BC%9A%E6%95%B0%E6%8D%AE%E4%BF%9D%E5%AD%98%E5%8F%8A%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/

 

接下来,看看memcached的内存分配方法。

按照memcached的set方法来说明,memcached在保存一条数据时做了哪一些操作,其内存是如何操作的。

  • 先列出telnet指令,包括三个操作,连接memcached,保存一个数据,mykey=”abc”;最后使用stats slabs输出其slab信息。接下来,分析这三操作来看看,memcached内部是如何工作的,内存是怎么管理的
    <span style="font-size:18px;">$ <strong>telnet 127.0.0.1 11211</strong>
    Trying 127.0.0.1...
    Connected to 127.0.0.1.
    Escape character is '^]'.
    <strong>set mykey 0 3600 3</strong>
    abc
    STORED
    <strong>stats slabs</strong>
    STAT 1:chunk_size 80
    STAT 1:chunks_per_page 13107
    STAT 1:total_pages 1
    STAT 1:total_chunks 13107
    STAT 1:used_chunks 1
    STAT 1:free_chunks 0
    STAT 1:free_chunks_end 13106
    STAT 1:mem_requested 57
    STAT 1:get_hits 1
    STAT 1:cmd_set 1
    STAT 1:delete_hits 0
    STAT 1:incr_hits 0
    STAT 1:decr_hits 0
    STAT 1:cas_hits 0
    STAT 1:cas_badval 0
    STAT active_slabs 1
    STAT total_malloced 1048560
    END
    </span>
  • telnet连接操作。 在客户端进行连接之前tenlet连接前,服务器已经在监听相应端口了,而且创建了一个线程池,某些线程处于conn_listening状态,等待处理客户端连接。
    客户端发起telnet 127.0.0.1 11211请求后,memcached将从连接池中拿出一个连接,或者新建一个连接,来响应请求。连接成功后,其线程状态变为conn_new_cmd。等待客户端输入命令
  • set操作。
    1. 客户端输入set操作:
      <span style="font-size:18px;">set mykey 0 3600<span style="color:#ff0000;"><strong> 3</strong>
      </span></span>
    2. 敲击确定之后,本行信息将发送给服务端,服务器触发conn_read状态。在读取这一行数据完毕之后,进入conn_parse_cmd状态。
    3. 服务器在conn_parse_cmd状态机中调用try_read_command()方法尝试对这一行进行此法分析。在process_command函数中,有详细的此法分析等流程。他检测到,本命令为set操作,且需要保存的长度为3.
    4. 虽然目前尚不知道需要保存的数据的值,但数据长度已经知道了,因此,调用item_alloc进行内存分配。
    5. 先加cache_lock锁。该锁是set/add/replace操作的互斥锁。即保证在同一个时刻,只能有一个写操作。
    6. 并调用do_item_alloc,给需要保存的数据申请内存。申请内存的计算公式:
      <span style="font-size:18px;">/**
       * 计算存储数据需要的内存大小,包括头信息等。
       *
       * key     - The key
       * nkey    - key的长度
       * flags   - key标志位
       * nbytes  - 包括\r\n在内的数据长度
       * suffix  - 一段由(flags, size)拼接起来的字符串,
       * nsuffix - suffix的长度.
       *
       * 返回值的长度(原注释中写的是头的长度,但根据源码分析得到,这个大小是包含数据在内的。)
       */
      ntotal = item_make_header(nkey + 1, flags, nbytes, suffix, &nsuffix);
             = sizeof(item) + (nkey + 1) + *nsuffix + nbytes;
      *nsuffix = strlen(" %d %d\r\n", flags, nbytes - 2 )
      </span>

      通过两个例子来说明:
      1)建立一个key为a,保存一个1位长度的值,它将申请占用43个字节。

      <span style="font-size:18px;">set a 0 3600 1
      nkey的长度为1,flags为0,nbyte长度为3 = 1+2(有\r\n), item结构体的长度为32(item定义可以参考memcached.h文件)
      ntotal = item_make_header(1 + 1, 0, 3, suffix, &nsuffix)
             = 32 + (1+1) + 6【strlen(" 0 1\r\n")】 + 3
             = 43
      </span>

      2)建立一个key为a1234567890,保存一个80位长度的值。它将申请占用159个字节。

      <span style="font-size:18px;">set a1234567890 0 3600 105
      key的长度为11,flags为0,nbyte长度为105+2(有\r\n), item结构体的长度为32
      ntotal = 32 + (11+1) + 8【strlen(" 0 105\r\n")】 + 107 = 159; 
      </span>
    7. 获得需要的内存大小之后,通过slabs_clsid函数去找能够容下这个长度的slab id。
    8. 在这个slab尾部中快速的查找,看看是否有剩余的空间,或者尾部的50个item中有没有否过期的数据。如果找到,则使用这个item的内存空间。
    9. 如果在3.9中没有找到可以替换的item,则do_slabs_alloc,向系统申请一个新的slab,每个slab大小为1M。
    10. 若该slab尾部或slab用完了,则申请一个新的slab。slab大小为chunk size * perslab, 大约占用1M内存。若申请成功,则使用该slab的第一块空间
    11. 若内存申请不成功,比如memcached已经超过了设置的最大内存了。则在当前的slab的后50条数据中,使用一定的算法(最近最少使用算法)淘汰一个数据,并占用其空间片。至此,do_item_alloc方法结束。解锁cache_lock。
    12. 空间申请成功之后,将本item的值存入。memcpy(ITEM_data(new_it), buf, res);
    13. 因此将状态切换至conn_nread。等待用户再输入三个字符。
    14. 客户端输入”abc”换行,服务器conn_nread接收到值后,逐级调用函数complete_nread() -> complete_nread_ascii(),在检查完其输入(abc)长度是否与要求的长度(6)是否吻合之后,进入store_item(it, comm, c)函数存储该数据。其中参数it存储了需要保存的信息,comm围需要执行的命令,即set,c为当前连接信息。
    15. 进入do_store_item(),使用do_item_get()检查这个key是否存在,如果存在,记录该老数据的地址为old_it。最后保存数据,并将其建立hash关联。如果有老数据,则删除
    16. 至此,全部保存工作完成。将保存状态输出给客户端,并重置其状态位为conn_new_cmd

在整个过程中,发现到两个问题。
1. 在memcached内部,在做set,add, replace操作时会加cache_lock,这个所的级别很高,将影响整个memcache在同一个时刻只能写一个些操作。这个可以保证他的高性能么?还是我的理解有误?很怀疑这一个结论。
2. 在set过程中,memcached是为新值分配空间,再删除老空间。而不是直接使用老空间的内存。这使得保存逻辑更为简单,但很多情况下,一个key所对应的值长度是比较固定的,特别是在存储数字的时候。如果修改这里的逻辑,先测试老空间的大小能够容纳新空间,并尝试使用老空间的值。这样可以减少后期的内存分配申请流程,特别是过期及淘汰检查等等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值