msgbartop
PHP语言, PHP扩展, Zend引擎相关的研究,技术,新闻分享 – 左手代码 右手诗
msgbarbottom

18 Mar 13 Yac (Yet Another Cache) – 无锁共享内存Cache

好久没有更新blog了, 这一年来的工作确实很忙….. anyway, 今天终于有新东西可以和大家分享.

这个idea来自一个很简单的想法, 以及目前所遇到的一个机会. 首先我们来谈谈这个机会.

在以前, 很多人都会选择使用APC, APC除了提供Opcode Cache以外, 还会提供一套User Data Cache(apc_store/apc_fetch), 所以对于很多有需求使用User Data Cache的同学, 使用APC, 就没什么问题.

然而, 最近Zend Optimizer Plus开源了, 测试表明, Zend O+在Opcode Cache方面, 因为做了Opcode Cache优化, 所以会比APC要高效, 再后来, PHP5.5已经把Zend O+作为了源代码的一部分. 会随着PHP一起发布.

这就有了个问题, 对于那些既要使用Zend O+的Opcode Cache, 又要使用APC的User Data Cache的同学, 怎么办呢?

开始的时候, 我只是给APC增加了一个开关apc.opcode_cache_enable, 这样一来, 用户就可以使用APC然而关闭opcode cache来达到这个目的, 但是APC的User Data Cache使用的存储机制是和Opcode Cache一样的, 这样的场景要求数据严格正确, 所以锁会比较多, 测试表明, APC的User Data Cache的效率和本地memcached几乎相当.

所以, 我想到了这个idea, 单独开发一个基于共享内存的, 高性能的User Data Cache, 来满足:

  • 1. 我就是想让PHP进程之间共享一些简单的数据
  • 2. 我希望非常高效的缓存一些页面结果

Okey, 那么叫什么呢? 呵呵, 考虑到我之前的Yaf, Yar, 那么自然就叫Yac啦, :)

言归正传, 谈谈这个无锁的共享内存Cache的设计思路, 首先, 这个设计基于如下几个经验假设:

  • 1. 对于一个应用来说, 同名的Cache键, 对应的Value, 大小几乎相当.
  • 2. 不同的键名的个数是有限的.
  • 3. Cache的读的次数, 远远大于写的次数.
  • 4. Cache不是数据库, 即使Cache失效也不会带来致命错误.
  • 5. 典型的应用场景类似于:
    <?php
        if (!($data = cache_fetch($key))) {
             /* cache不存在 */
             $data =  从接口/数据库取数据();
             cache_set($key, $data);
        }
    ?>
    

好, 基于这些假设, 我们来看看如何实现Yac, 首先Cache最常用的就是读了, 那么能不能做到读不加锁呢?

这个很容易, 不加锁的读, 拿到以后做数据校验, 如果校验成功, 增说明查询成功, 否则就认为查询失败, 这是一种常用的采用CPU来换锁的方法. 对于目前的服务器, 大部分都是多核的, 如果是加锁, 那么对CPU是一个极大的浪费.

那么, 与其让这些CPU空闲, 不如大家同时读, 大不了回来多做一个数据校验(Yac 采用的是crc校验)

okey, 读锁很容易解决, 但是写锁呢? 我们先来看看Yac的共享内存分配模型图:

Key空间在启动的时候就是确定大小的, 这个是基于上面的假设(2), 默认的(在64位Linux上), Yac会开辟32768个Key Slots, 也就是说你最多可用存储32768个不同的Cache值, 当然这个大小你可以通过yac.keys_memory_size来调整, 如果你设置yac.keys_memory_size为32M的话, 你就能得到262144个Key Slots.

Yac采用双散列法来解决Hash冲突, 首选的Hash函数是进来比较流行的 MurmurHash.

共享内存会被按照固定带下分为尽量多的小块, 默认是4M一小块, 然后Key的值会根据Key的Hash, 来选择到底在那一块内存上申请空间, 从而减少写的时候可能的冲突.

而对于大块内存分配的时候, 只需要操作一个segment->pos指针, 也就是只需要做一个加操作, 从而减少多个进程同时在同一块内存分配的时候可能出现的冲突.

那么, 万一真正的发生了冲突呢? 比如A进程申请了40字节, B进程申请了60字节, 但是Pos只增加了60字节. 这个时候有如下几种情况:

1. A写完了数据, 返回成功, 但是B进程又写完了数据返回成功, 最终B进程的Cache种上了, 而A进程的被踢出了.

2. B进程写完了数据, 返回成功, A进程又写完了数据返回成功, 最终A进程的Cache种上了, B进程的被踢出.

3. A进程写一半, B进程写一半, 然后A进程又写一半, B进程又写一半, 都返回成功, 但最终, 缓存都失效.

可见, 最严重的错误, 就是A和B的缓存都失效, 但是Yac不会把错误数据返回给用户, 当下一次来查询Cache的时候, 因为存在crc校验, 所以都miss.

那么, 考虑到上面的假设(3), (4), (5), okey, Not a big deal, 对吧? 没关系, 错就错吧, 呵呵

那么, 当内存写满了呢? 再看上面的内存分配图, 注意到红色的部分没有?

当一个新的Key到来的时候, Yac会尝试查找合适的Key Slot, 如果找到同名的Key, 那么紧接着判断原来Key的Value内存大小, 考虑到假设(1), 并且Yac在分配内存的时候, 会有意的多给一些内存. 所以, 很大的概率上, 你不需要重新分配内存, 只需要再原有的内存基础上写入新数据即可.

那么万一原有的内存不够呢? 那就分配呗.

这个时候, 假设内存已经分配完了, Yac就会在选定的内存快上, 重置pos, 从头开始分配, 注意到上图的红色部分, 就是新写入的数据, 而黄色部分就是因为写入的新数据, 导致Cache失效的部分. 也就是说, 并不会导致大量的Cache失效.

那么, 万一Key Slots不够了呢?

Yac会从目地Key Slots开始, 根据Hash路径, 选取5个Keys slot, 根据LRU, 踢出一个.

那么, 这样的Cache, 性能到底怎么样呢? 我做了一个和APC对比的简单测试(ab -n 10000 -c 50), 测试脚本如下:

Yac:

<?php

$yac = new Yac();

for ($i = 0; $i<1000; $i++) {
    $key =  "xxx" . rand(1, 10000);
    $value = str_repeat("x", rand(1, 10000));

    if (!$yac->set($key, $value)) {
        var_dump("write " . $i);
    }

    if ($value != ($new = $yac->get($key))) {
        var_dump("read " . $i);
    }
}

var_dump($i);

APC:


<?php

for ($i = 0; $i<1000; $i++) {
    $key =  "xxx" . rand(1, 10000);
    $value = str_repeat("x", rand(1, 10000));

    if (!apc_store($key, $value)) {
        var_dump("write " . $i);
    }

    if ($value != ($new = apc_fetch($key))) {
        var_dump("read " . $i);
    }
}

var_dump($i);

最终的结果:
Yac

Write errors:           0
Total transferred:      597358 bytes
HTML transferred:       368358 bytes
Requests per second:    359.69 [#/sec] (mean)
Time per request:       139.010 [ms] (mean)
Time per request:       2.780 [ms] (mean, across all concurrent requests)
Transfer rate:          209.83 [Kbytes/sec] received

APC的:

Write errors:           0
Total transferred:      7050591 bytes
HTML transferred:       6828577 bytes
Requests per second:    46.79 [#/sec] (mean)
Time per request:       1068.502 [ms] (mean)
Time per request:       21.370 [ms] (mean, across all concurrent requests)
Transfer rate:          322.20 [Kbytes/sec] received

好了, 主要的思想就是这些, 接下来说说Yac的限制吧:

1. key的长度最大不能超过48个字符. (我想这个应该是能满足大家的需求的, 如果你非要用长Key, 可以MD5以后再存)

2. Value的最大长度不能超过64M, 压缩后的长度不能超过1M.

3. 当内存不够的时候, Yac会有比较明显的踢出率, 所以如果要使用Yac, 那么尽量多给点内存吧.

感谢@cydu @cunsheng @rodin, @猫咪的万岁爷_宋Q等同学给的建议.

最后, Yac的代码已经上传到了github: Yac, 不过目前还是完善阶段, 还不支持Windows, 我会继续完善, 有兴趣的同学可以抢先试用, 更加感谢如果能帮忙找找bug, 做做优化. thanks :)


分享到:



Related Posts:

Tags: , , , , , , , , ,

49 Responses to “Yac (Yet Another Cache) – 无锁共享内存Cache”

  1. Dnbntravel.Com |

    There are several methods of making salvia extract. At times, there are company appointed distributors and
    companies also who sell their clothes and accessories at the wholesale or discounted prices.
    It depends how much you value your sight,
    if you want to have a long and illustrious time racing around the track or pulling
    45ft heel clickers, you need to be able to see
    where you are going to land.

    Also visit my web page Credit Report Free,
    Dnbntravel.Com,

  2. 项目常用软件及工具 | 学习笔记 |

    [...] 原来php加速软件一般用的是zend optimizer这个.现在5.3以后的版本就要使用opcache这个了.还有一些并发用的软件yar也推荐使用,这是一款RPC framework.这几款软件全部出自鸟哥之手.除外还有yac这个缓存框架,这个与apc这类的加速软件差不多. [...]

  3. webpage |

    Permanent installations in vehicles gave way to the portable Bag Phones, built with a cigarette lighter plug.
    You can then go through the hyperlink to observe the entire
    is a result of the Wikipedia site. To my mind the idea
    of woman president is something speculative, going past the generally accepted rules and also at the very best be subject to the theoretical and philosophical comprehension.

  4. luckgo |

    能不能增加类似于apc_bin_loadfile的方法呢?

  5. MagentoEye |

    请问:兼容apache 2.4的新版APC什么时候可以发布?

  6. mark |

    弱弱问一句 有没有帮助文档

  7. Ce sera peutêtre the cas pour arab-speaking labour sitcom fofolpar |

    [...] lui Avant child Nous iron au Vélodrome sans appréhension en somme united nations authority Nouveauté encore chez Line6 united nations matin you can rewire haze chevy go indication key plans suv tahoe le langage Ces [...]

  8. linux_chen |

    鸟哥:
    我在我PC机上压测了,有个疑问为什么我压的结果写比读还快呢?

  9. testdown |

    如果 Yac::get() 无法通过 &$cas 参数获得 CAS 编号,那么当获取一个键时,它返回一个 false。那这个 false 代表不存在此键还是该键其值就是 false 呢?

  10. testdown |

    为什么我使用 Yac::get() 获取不到 cas 的值呢?

    phpcode:

    $yac = new Yac(‘DEV:’);
    $yac->flush();
    $yac->set(‘A’, ‘A’);

    $cas = 0;

    $yac->get(‘A’, $cas);
    echo $cas; // 0

    $yac->get(['A'], $cas);
    echo $cas; // 0

  11. testdown |

    bug:

    $yac = new Yac(‘DEV:’);
    echo $yac->set(‘A’, 10000, 1);
    echo $yac->set(‘A’, 10000, 1); // false
    echo $yac->set(‘A’, ’10000′, 1); // true
    echo $yac->set(‘A’, 10000, 1); // true

    把 ttl 参数去掉没问题。

  12. testdown |

    add(‘key’, ‘value’);
    echo $yac->delete(‘key’); // 输出 true
    echo $yac->delete(‘key’); // 还输出 true
    ?>

    第二次删除还输出 true,就是这么设计的吗?

  13. testdown |

    关注此项目。

  14. ahuo |

    yac 安装好了 用了下 速度蛮快的 期待鸟哥出个详细的手册啊

  15. ahuo |

    安装好之后 重启了php 没有看到
    var_dump(class_exists(‘Yac’)) 还是没有啊 这个是啥情况啊?

  16. Andy |

    Thanks very much

  17. rayban |

    大神啊 原来APC 是您弄出来了, 自从驾了APC感觉php快了挺多的

  18. 鸚鵡 |

    另外想請教一個問題

    key的长度最大不能超过48个字符

    在 utf8 編碼的情況下

    每個中文字會佔用幾個字符?

  19. 鸚鵡 |

    您好

    更新後,已經不會在發生了
    感謝您的協助

  20. laruence |

    @鸚鵡 请试用最新的snapshot, https://github.com/laruence/yac

    另外, 也可以在github上新建一个issue, 附上你的测试脚本, thanks

  21. 鸚鵡 |

    您好

    我下載了 yac 並安裝使用
    但是我察覺到一個現象

    我的伺服器是 fedora 14,並安裝 cacti
    當我使用 yac 時,系統定時執行 cacti 的 poller.php 時
    程序會無法正常結束,並出現錯誤訊息: Segmentation fault

    /var/log/message 中會出現下面的訊息
    kernel: [1242737.739304] php[14321]: segfault at 7f96b2f136b6 ip 00007f96b2f136b6 sp 00007fff9d9b53b0 error 14 in libgpg-error.so.0.7.0[7f96b3276000+a3000]
    kernel: [1242737.801953] php[14322]: segfault at 7fa134d686b6 ip 00007fa134d686b6 sp 00007fff66eedb90 error 14 in libgpg-error.so.0.7.0[7fa1350cb000+a3000]
    kernel: [1242737.855070] php[14323]: segfault at 7f8eaf5ba6b6 ip 00007f8eaf5ba6b6 sp 00007fff4d4c7e60 error 14 in libgpg-error.so.0.7.0[7f8eaf91d000+a3000]

    若需要提供更多的資訊,請告訴我

  22. Yac (Yet Another Cache) – 无锁共享内存Cache | 午后小憩 |

    [...] 本文地址: http://www.laruence.com/2013/03/18/2846.html [...]

  23. 雪候鸟 |

    @四不象 恩, 对于你说的场景确实是有一定的概率会这样, 因为我们无锁, 所以就是last win, 不过, 对于Yac解决的场景来说, 比如我们的场景是, Cache用作用户信息缓存, 或者页面输出缓存, 因为都是以UID作为key的一部分, 一个用户会话同事也不会有多个请求产生, 所以不需要这个写锁.

    我后续可以考虑加个选项: 是否开启写锁, 来满足你假设的这种场景.(当然, 这种场景也不一定就一定会热点, 三个前提, 1. 请求繁忙, 2. 请求处理时间快, 3. 配置文件比较大) ,

    谢谢

  24. 四不象 |

    这样写入时很容易形成热点吧。比如下面情景:网站全局配置信息保存在缓存里,当配置文件更新缓存失效时,可能会有几百个请求同时重新将新的配置信息写入缓存。那样配置信息成功写入的可能性就大大降低了。
    既然缓存的使用情景设定为读多写少,那就写的时候加锁,读的时候不加锁,如果数据校验错误,尝试二次读取,读取的时候加锁

  25. wenson smith |

    佩服的我六体投地~

  26. php |

    关注好久了,终于更新了,这下又有新东西可以学习了,呵呵,谢谢分享哦

  27. Anonymous |

    终于有更新了 鸟哥

  28. justdoit |

    等了好久,终于更新了!

  29. cys.tony |

    反正不需要保证数据可靠性,怎么快怎么来吧

  30. Kyli |

    终于又有更新了,

  31. Christopher Jones |

    One other user-data cache project for PHP 5.5 is https://github.com/krakjoe/apcu

  32. goosman.lei |

    是好久没见鸟哥维护blog了….
    先fork了改天抽空看看鸟哥新作…学习学习..

  33. hliang |

    更新了源码,好了。

  34. hliang |

    为啥make的时候给我报了一个“/yac/storage/yac_storage.c:38:5: 错误: 与‘yac_storage_startup’类型冲突”呢。

  35. darasion |

    又见 yet another … 哈。。。

  36. hilojack |

    linuxsir 又有福了

  37. Cary |

    鸟哥威武! 学习下~~~

  38. 徐长龙 |

    多线程并发写是否会出问题?

  39. hkshadow |

    循环利用……为了避免此类缓存无法写入和提出的情况下,还得加大内存,来满足内存的吃紧,但一般来说,这够了,以上本人挫见,见谅。

  40. pysche |

    请教一下:

    1. apc. opcode_cache_enable这个配置项已经支持了么?文档及phpinfo里面都没有看到
    2. 如果同时安装apc和optimizerplus,opcache部分由谁处理?

  41. 徐长龙 |

    鸟哥是不是说你对读取以前需要“锁”的内容作了一次hash保存在读取区

    每次读取后对读取内容进行hash,以此来判断读取的数据完整性?
    如果是错误的那么重复读取一次?
    可以这么理解吗?

  42. Actrace |

    没用过APC,,,这样的设计性能肯定比memcached好,但是可以考虑在内存中建立一个简单的文件系统来实现可调整的内存空间,K/V模式限制了灵活性,虽然性能不错。但是我想如果追求性能的话,应该不会采用PHP来写。我可能宁愿实用tmpfs来共享进程数据。

  43. bide |

    请问下黄色部分cache为什么会失效?

  44. M2 |

    阅,先了解了。 鸟哥 v5

  45. easy |

    牛逼~

  46. liexusong |

    我觉得这个应用使用自旋锁会比较好, 因为读取内存不会花费很多时间.

  47. 花生 |

    支持鸟哥!!
    前排占座!!
    广告位招租!!

  48. shaukei |

    广告位招租

  49. FtMan |

    赞,鸟哥V5

Leave a Reply

*