Redis “瘦身”指南

前言

Redis 应该是开发者最经常使用的缓存服务器了,它丰富的数据结构,快速高效的内存操做能帮助开发者迅速完成复杂功能的设计,能够说让人一旦使用事后很难再离开它了,甚至在一些业务中,彻底能够用 Redis 替代传统的关系型数据库 Mysql。css

做为一个内存型数据库,Redis 常常会遇到内存问题,今天咱们来谈一下 Redis 常见的内存满的问题,介绍一下给 Redis “瘦身”的通用方式。html

文章常常被人爬,并且还不注明原地址,我在这里的更新和纠错无法同步,这里注明一下原文地址:http://www.cnblogs.com/zhenbianshu/p/7642682.html 以防误人子弟。web


Redis内存回收

Redis 服务器的最大占用内存量由配置项 maxmemory 决定,咱们能够经过 config set maxmemory 2GB 的格式来配置。一旦 Redis 内存满,全部引发内存增长的操做都会被返回 error。做为专业 Redis 服务器咱们一般将此项设置为0,以服务器系统内存来做为限制;redis

那么 Redis 使用内存达到了上限怎么办?Redis 为咱们提供了几种选项以自动回收内存,能够经过配置项 maxmemory-policy 来配置;sql

  • noeviction 不回收;
  • allkeys-lru 从全部键中删除最近最少使用的键;
  • volatile-lru 从设置了过时时间的键中删除最近最少使用的键;
  • allkeys-random 从全部键中随机删除;
  • volatile-random 从设置了过时时间的键中随机删除;
  • volatile-ttl 从设置了过时时间的键中选择存活时间最短的键删除;

最大内存回收策略须要根据业务来配置,若是纯粹作缓存,allkeys-lru无疑是最合适的。若是存储了稍微重要的数据,为了防止 Redis 误删一些重要键,则须要选用 noeviction数据库

allkeys-lruallkeys-random 在内存满时都有键可删,能够腾出内存,但若是配置了其余的策略,数据库用久了(根据业务量),随着业务发展和数据积累,一般会累积到到服务器内存占用率高,利用率低的状况,则可能会遇到内存占用满的问题。缓存


问题起因

产生问题的缘由有:服务器

持久键废弃

这是致使此问题的最多见状况。数据结构

有时候是开发人员的锅,开发不规范,未给有时效性的键设置过时时间,后续又不进行手动删除,键就成为无人管的孤儿键了。dom

还多是整个业务慢慢被废弃,不知道哪一天起,业务总体已再也不维护了,一批键天然也就没用了。比这更严重的是,若是使用 List 传递数据,消费进程已被中止,但生产进程未同步中止,还在往 Redis 里写数据。

过时键未回收

这个缘由首先要谈到 Redis 的两种过时键删除策略:

  • 惰性删除:在读取键时发现键已过时,则将其删除。
  • 按期删除:Redis 会从全部设置了过时时间的键中选取 100 个,删除已过时的键,若是已过时的键超过 25 个,则再次进行此操做。 此删除操做由配置项 hz 决定,Redis 默认每秒进行 10 次;

若是咱们产生过时键的速度很快,最多可致使 Redis 25% 的过时键没有被及时删除。


遍历清除垃圾键

由上,明白了问题产生的缘由,解决 Redis 内存满的方法就明确了:清除这些垃圾键。 因而就面临着两个问题:

如何遍历键

对于查找键,咱们首先想到的是 KEYS,但 KEYS 的时间复杂度是O(n),n 是 Redis 内键的总数,若是 Redis 内键不少仍是会有性能问题,致使其余命令被阻塞的。

这里介绍一个键遍历命令: SCAN

SCAN cursor:

0 => cursor, // cursor = 0 遍历结束
1 => array(key1, key2...)

须要注意的是 SCAN 命令是在版本2.8.0 加入的,若是是以前的版本,能够考虑解析 Redis 的 RDB 文件来获取全部的键。有坑,参见我以前的文章:扩充你的工具箱 - 大行文件的处理

如何判断键是否垃圾

咱们有三种异常键须要处理:

  • 过时键:这些键会在被 SCAN 到时被自动删除,再也不考虑。若是是解析 RDB 文件获取到的键,在查询时也会被自动删除;
  • 长时间未读写的键,极可能是业务再也不须要的键;
  • 占用大量内存的键,有多是在不停地写,但未消费。

这里介绍 Redis 的另外一个命令 OBJECT,使用它能够从内部查看 key 对象的状态。使用 OBJECT IDLETIME key 来获取 key 的闲置时间,咱们能够判断 key 闲置时间大于一个时间段(根据业务自定)的为已废弃。

此外还能使用 OBJECT REFCOUNT key获取 key 引用所储存的值的次数,OBJECT ENCODING key 获取 key 储存的值所使用的内部表示。

获取键大小

而获取 Redis 某键占用内存大小,则经过另外一个命令 DEBUG OBJECT 来获取,此命令会返回比OBJECT命令更详细的内部数据。

DEBUG OBJECT test
Value at:0x7fb0ee16ebd0 refcount:1 encoding:embstr serializedlength:6 lru:12362780 lru_seconds_idle:4

结果包括内存地址、引用数、内部编码表示、序列化后的长度、最近最少使用标识值,闲置时间,咱们能够解析此结果串来获取对应的数据。

须要注意,key 做为复合键拥有大量字段时使用 DEBUG 命令计算内存会使 Redis 阻塞较长时间,且 Redis 官方并不建议在客户端使用此命令

咱们也能够先使用 TYPE key 获取键的类型,再根据类型获取其键的大小,如对字符串使用LEN,对 哈希表使用HLEN

要注意在删除特别大的复合键时,建议先逐步清空键内的字段,防止因字段过多,Redis 阻塞较长时间。

管道加速

Redis 支持 pipeline 管道技术,一次 请求/响应 服务器能实现处理并响应多个请求。这样就能够将多个命令同时发送到服务器,不等待回复,直接在最后获取多个结果。

PHP 中使用 MULTI(Redis::PIPELINE)EXEC() 命令来实现管道;

脚本实现

下面是个简单的脚本:

$redis = new Redis();
$redis->connect('127.0.0.1');
do {
    $keys = $redis->scan($cursor);

    $pipeline = $redis->multi(Redis::PIPELINE);
    foreach ($keys as $key) {
        $idle_time = $redis->object('idletime', $key);
        if ($idle_time > 180 * 24 * 3600) {
            $pipeline->del($key);
        }
        // todo 判断类型进而判断占用内存大小,再删除
    }
    $pipeline->exec();
} while ($cursor != 0);

从根源避免问题

以上的脚本确定也会在删除键时影响 Redis 的效率,最好的状况仍是从根源就避免此类状况,如下是一些建议:

  • 规范化开发;

    首先是键命名要规范,让人见名知义,这样在人工排错或删除时也有判断依据,而后最好有完善的 Redis 键文档,以保证业务在很长时间,经手多人后也能资料可查。

  • 使用 HashSet 替代 Key-Value

    将业务中某一族的键以 HashSet 的方式存储,以替代普通的 key-value 类型。不只能够省去为每一个键设置前缀以节约内存,也便于统一管理。

  • 有时效性的键注意设置过时时间;

  • 合理设置定时清除过时键频率 hz,在 Redis 不作多余操做的状况下,使过时键尽可能能被删除;

  • 作好 Redis 内存的监控,在达到某个阈值时查找问题并解决。


小结

最后多絮叨两句经验:

Redis假死

我在使用守护进程时 Redis 有假死状况,PHP 和 Redis 都不报错,但命令都返回 false,这种状况可使用 Redis 的 ping() 命令,来探测 Redis 链接是否还在,若是不在则再创建新的链接。此问题极可能是由服务器配置引发的,若是您有知道此问题的起因或有好的解决办法,烦请指点一二。

危险命令

不要在没看文档的状况下在线上使用 Redis 命令,例如 debug segfault,别问我怎么知道的。

嗯,但愿你们都能处理好跟 Redis 这个好朋友的关系。

关于本文有什么问题能够在下面留言交流,若是您以为本文对您有帮助,能够点击下面的 推荐 支持一下我。博客一直在更新,欢迎 关注

相关文章
相关标签/搜索