美文网首页
一次基于CI的Redis性能问题定位

一次基于CI的Redis性能问题定位

作者: hellojammyPlus | 来源:发表于2018-07-23 14:42 被阅读86次

    点击访问原文
    您还可以加入全栈技术交流群(QQ群号:254842154)


    使用PHP的CI 3.0框架已经有一段时间了,它内置的类库虽然比较少,但是基本的都有,例如Cache模块,它提供了几种最常用的快速缓存的封装,包括apc、memcached和redis等。其中redis是比较常用的缓存,用于存储程序中经常用到并且不会频繁变化的数据。用法:

    //加载类库
    $this->load->driver('cache', array('adapter' => 'redis'));
    //存字符串
    $this->cache->save('key_str_hello', 'value_hello', 3600);
    //存数组
    $this->cache->save('key_array_hello', array('a' => 123), 3600);
    //取
    $data = $this->cache->get('key_hello');
    //删除
    $this->cache->delete('key_hello');
    

    问题描述

    在一次接口性能测试中,发现取数据时即使命中缓存,耗时也会比较长,基本在200-300毫秒左右。按常理隐约感觉缓存可能出现了问题。

    问题定位

    问题的关键是找出耗时的方法或代码片段。检查了跟业务相关的逻辑,没有耗时的操作。由于调用了比较多的CI库函数,假如要层层深入跟踪代码,可能会比较难定位。于是想到了<a href='http://pecl.php.net/package/xhprof' target='_blank'>xhprof</a>,定位性能问题的利器,大家可以自行查资料如何使用。

    于是在测试环境装了xhprof,并在待测试的方法中开启了xhprof的数据收集。得到了一个详细的函数调用路径及耗时,如下图:

    image

    可知,此次请求耗时341ms左右,从每个方法的耗时中不难发现,其中有一个Redis::sMembers方法耗时289ms之多,占到了将近85%时间。继续深入查看,发现Redis::get函数耗时19.4%占用了大部分时间。

    image

    sMembers是一个redis集合操作函数,用于获取指定key值的集合信息。于是马上想到了CI封装的redis类库,找到system/libraries/Cache/drivers/Cache_redis.php 文件。

    经过代码阅读,发现在构造函数 __construct中有一行代码读取了_ci_redis_serialized这个key对应的值:

    // Initialize the index of serialized values.
    $serialized = $this->_redis->sMembers('_ci_redis_serialized');
    empty($serialized) OR $this->_serialized = array_flip($serialized);
    

    完整阅读完代码后发现,CI的redis类库在存储数据时,会区分普通字符串数据,和对象或数组数据,对于对象或数组,在存储到redis之前,会进行序列化操作,并把这个数据的key值存储到redis集合中(这个集合使用_ci_redis_serialized作为key);在取出数据时,首先从集合中查找对应的key是否存在,假如存在则进行反序列化操作。

    也就是说,随着redis中存储的数组或对象越来越多,集合中存储的key也就越来越多。而每次redis的操作,都会触发一次取集合数据的操作。当数据量较大时,这个取集合数据的网络延时也会变得比较大。

    问题解决

    改写CI的redis类库,在构造函数中去掉取集合数据的操作,并通过 sIsMember 方法来判断当前的key是否为对象或数组。

    总结

    • 对于一些顽疾问题,追根溯源时别忘了借助一些外部工具

    • 不要太相信框架

    相关文章

      网友评论

          本文标题:一次基于CI的Redis性能问题定位

          本文链接:https://www.haomeiwen.com/subject/wzcjmftx.html