使用memcache替代redis

Published On May 16, 2017

category project | tags redis memcache


memcache和redis简单对比

memcache是一个纯粹的key-value内存缓存系统,没有持久化,没有磁盘IO。后继者Redis不仅有丰富的数据结构,比如队列,字典,集合等,而且支持不同的持久化方式,snapshot或aof,已经从一个key-value的缓存系统发展到了一个功能强大的内存型数据库,支持事务,消息的发布订阅,主从备份,lua脚本编程等,当然,redis也支持作为一个LRU(Least Recently Used)的缓存系统。它们都支持集群扩容。

我们不难发现,无论是作为数据库还是缓存系统,redis是大多数情况下的首先。

使用redis作为缓存

使用redis作为数据库

事实上,很多项目仅仅将redis作为缓存来用,有以下两个原因:

  1. 内存的一大特点是down机(比如停电)时所有数据都会丢失。redis的aof(append only file)持久化方式会将每个写命令都追加到一个日志文件中,比snapshot具有更高的可靠性 为了同时追求性能和可靠性,一般将aof设置成每秒保存一次数据到硬盘(fsync every second)。这也意味着redis最多会丢失一秒钟内的数据更新。正是因为这一点,很多开发者都不敢使用redis作为数据库,而是使用更安全的数据库,比如rdb mysql或nosql mongodb。其实mysql是否能保证每次更新都写入磁盘,我们就不得而知了。

其实redis的aof持久化可以设置成对每个写请求都保存到磁盘(fsync at every query),从而保证在down机的时候不会丢失任何数据。

fsync是什么意思呢? fsync是操作系统提供的函数:int fsync(int fd); 用来告诉操作系统将数据真正写到硬盘,从而保证对文件的写操作更新到硬盘,也就是说在程序里调用write对一个文件进行更新,实际上只是提交到操作系统层面,操作系统采用异步的方式更新磁盘,即flush output buffer 如果操作系统在真正写入磁盘之前掉电,那么这些更新就会丢失,但如果仅仅是程序崩溃是不会丢失更新的。

将redis配置成每次更新(set,delete, etc)操作都执行fsynnc会严重降低写操作的性能(通过测试,我发现相差10倍以上),完全失去内存型数据存储的优势。因此很少这么干。

  1. 大多应用只有很少的数据需要频繁访问,大部分数据都是历史数据,而且数据量随着时间增长,redis会将数据全部加载到内存,显然不适合使用redis来保存所有的数据。

将redis作为LRU缓存系统

将redis作为cache只需要简单的配置即可,基于默认的配置修改以下地方:

# 以守护进程的方式运行
daemonize yes

# 注释下面三行,关闭snapshot
#save 900 1
#save 300 10
#save 60 10000

# 指定redis占用的最大内存,单位是字节
# 注意:我用的测试机是64g内存,这里设置了10g,请根据你的内存情况进行相应调整
maxmemory 10737418240

# 开启lru缓存
maxmemory-policy allkeys-lru

memcache真的就无用武之地了吗?

尽管redis总是各种情况下的首选,但memcache从诞生之初就是一个高性能的LRU缓存系统,在缓存方面应该不输redis。

When to use Memcached

Why Redis beats Memcached for caching

Because Redis is newer and has more features than Memcached, Redis is almost always the better choice. However, Memcached could be preferable when caching relatively small and static data, such as HTML code fragments. Memcached's internal memory management, while not as sophisticated as that of Redis, is more efficient in the simplest use cases because it consumes comparatively less memory resources for metadata. Strings (the only data type supported by Memcached) are ideal for storing data that's only read, because strings require no further processing.

这篇文章的观点是memcache更善于缓存小文件,并且在内存管理方面更高效。实践出真知,让我们用一个简单的测试来对比一下。

memcache与redis在缓存方面的对比

  • 版本
  • redis-3.2.8
    • 按照上面修改配置然后启动,./src/redis-server redis.conf
  • memcached-1.4.
    • 启动:memcached -d -p 11211 -u memcached -m 10240 -c 1024 -P /var/run/memcached/memcached.pid
  • 客户端
  • redis-py:python社区最受欢迎的redis客户端
  • pymemcache:pinterest开源的memcache python客户端,支持noreply

    The intent is to avoid having to wait for a return packet after executing a mutation command (such as a set or add).

  • 测试脚本
    import sys
    from time import time,sleep
    
    if cache == 'redis':
        import redis
        client = redis.StrictRedis(host='localhost',port = 6579)
    elif cache == 'memcache':
        from pymemcache.client.base import Client
        client = Client(('localhost', 11211))
    else:
        print('usage: python [this_script] redis|memcache 40000')
    num = int(sys.argv[2])
    
    prefix = 'key'
    if len(sys.argv) > 3:
        prefix = sys.argv[3]
    
    # file size is 256k
    value = '0'*1024*256
    
    start = time()
    for i in range(num):
        client.set('%s_%d' % (prefix, i), value)
    print(cache + ':')
    print('  write %d times: %f' % (num, time() - start))
    

测试过程如下: 每次写入10g(256k*40000)的内容, 第一轮:

$ python redis_vs_mc.py redis 40000
redis:
  write 40000 times: 14.890719
$ python redis_vs_mc.py memcache 40000
memcache:
  write 40000 times: 10.024113

第二轮: 使用不同的key,确保旧的数据会被淘汰:

$ python redis_vs_mc.py redis 40000 foo
redis:
  write 40000 times: 15.097220
$ python redis_vs_mc.py memcache 40000 foo
memcache:
  write 40000 times: 4.563392

结果让我很吃惊,在缓存方面,memcache的性能要远远好于redis。其中一个可能的原因是memcache使用了noreply的特性,另一个原因可能是memcache使用多线程的方式运行。

通过下面的命令可以看到memcache启动了6个线程

top -H -p $(ps -ef|grep memcache|grep -v grep|awk '{print $2}')

top 通过top命令直观的看到redis实际占用的内存达到14g,而memcache不超过10g。

memcache没有自带的命令行客户端工具,使用如下命令输出memcache的统计信息:

echo "stats" | nc localhost 11211

直接看输出的末尾

STAT bytes 8912955780
STAT curr_items 33990
STAT total_items 80000
STAT evictions 46010

使用info命令查看redis的信息(以下只包含我们关心的几项数据)

used_memory_human:10.00G
used_memory_rss_human:14.21G
maxmemory_human:10.00G
mem_fragmentation_ratio:1.42

# Keyspace
db0:keys=32758
used_memory_human是redis中数据占用的内存,redis内部有个内存管理系统,负责分配内存给数据 used_memory_rss_human是操作系统分配给redis的内存,也就是redis实际占用的内存,与top命令显示的resident set size一致。 mem_fragmentation_ratio是以上两个值的比例,这个值越高,说明内存利用率越低,内存碎片率严重。

可以看到经过一轮淘汰后,memcache缓存占用的内存低,缓存的文件数反而多一些。

通过以上对比发现,作为缓存系统,memcache写入速度和内存管理都要优于redis

放了4天假后,我通过top命令惊讶的发现redis占用的内存居然变成了19g。。。再执行了一下info命令,结果mem_fragmentation_ratio达到了1.91。这一定是bug!

实践

我们cdn系统之前使用redis缓存热点数据,一台服务器设置的内存上限是100g。然而运维长期抱怨redis实际占用的内存太高,已经接近200G。通过info命令发现mem_fragmentation_ratio达到了1.7,有效负荷很低。上面的实验也复现了这个问题,不少人在github提过同样的issue,但是官方似乎并没给出什么好的答复:

对于缓存html文件,并不需要redis所提供的那些复杂数据结构,因此我们将redis换成memcache,后来内存利用率的问题基本解决了。

参考


qq email facebook github
© 2018 - Xurui Yan. All rights reserved
Built using pelican