Redis Essentials 读书笔记 - 第六章: Common Pitfalls (Avoiding Traps)

Chapter 6. Common Pitfalls (Avoiding Traps)

本章讲述使用Redis的一些误区,部分例子基于Yipit (www.yipit.com)和其它一些公司的经验教训。

The wrong data type for the job

Yipit最初将要发送给用户的deal存于Set中,尽管可以运行,但开发者担心由于用户量太大,Set的内存消耗大,因此转向用Bitmap存储,结果是上线后不久,内存就满了,下来会解释原因。

教训:在提交解决方案前必须做benchmark测试。

The Set approach

Set的实现非常直接,每一个用户一个Set,即userID作为key, DealID用Set存放,例如(1, 2, 3, 4, 5, …).

The following code is a benchmark of this implementation, using 100,000 Sets with 12 deal IDs each, and each user will receive the same 12 deals.

Create a file called benchmark-set.js in the chapter 6 folder with the following code:

以下为使用Set的benchmark代码,模拟10万用户,每用户12个deal。

var redis = require("redis");
var client = redis.createClient();
var MAX_USERS = 100000;
var MAX_DEALS = 12;
var MAX_DEAL_ID = 10000;

for (var i = 0 ; i < MAX_USERS ; i++) {
  var multi = client.multi();
  for (var j = 0 ; j < MAX_DEALS ; j++) {
    multi.sadd("set:user:" + i, MAX_DEAL_ID - j, 1);
  }
  multi.exec();
}

client.quit();

运行benchmark程序,得到内存使用情况:

[redis@tt12c chapter 6]$ redis-cli FLUSHALL && node benchmark-set.js && redis-cli INFO memory
OK
# Memory
used_memory:14355424
used_memory_human:13.69M
used_memory_rss:18239488
used_memory_peak:32488744
used_memory_peak_human:30.98M
used_memory_lua:36864
mem_fragmentation_ratio:1.27
mem_allocator:jemalloc-3.6.0

The Bitmap approach

Bitmap的实现中,也是userID作为key,然后dealID存放于Bitmap中,需要发送的deal置为1。
不过Bitmap的问题在于最大的dealID决定了其存储开销,如果最大的dealID是1,000,000,Bitmap就需要消耗1,000,001 bit。

以下为使用Bitmap的benchmark代码,模拟10万用户,每用户12个deal。

var redis = require("redis");
var client = redis.createClient();
var MAX_USERS = 100000;
var MAX_DEALS = 12;
var MAX_DEAL_ID = 10000;

for (var i = 0 ; i < MAX_USERS ; i++) {
  var multi = client.multi();
  for (var j = 0 ; j < MAX_DEALS ; j++) {
    multi.setbit("bitmap:user:" + i, MAX_DEAL_ID - j, 1);
  }
  multi.exec();
}

client.quit();

运行benchmark程序,得到内存使用情况:

[redis@tt12c chapter 6]$ redis-cli FLUSHALL && node benchmark-bitmap.js && redis-cli INFO memory
OK
# Memory
used_memory:265555560
used_memory_human:253.25M
used_memory_rss:291766272
used_memory_peak:283525488
used_memory_peak_human:270.39M
used_memory_lua:36864
mem_fragmentation_ratio:1.10
mem_allocator:jemalloc-3.6.0

Bitmap实现使用了 253MB内存,而Set实现仅使用了近14M。
为何有如此大的区别,理论上Bitmap应更省内存。仔细看这段代码,比Set的实现多了MAX_DEAL_ID参数,使得Bitmap被”撑大了”,一个Bitmap需要100000个bit,约12500字节

multi.setbit("bitmap:user:" + i, MAX_DEAL_ID - j, 1);

Multiple Redis databases

Redis服务器支持多个数据库,类似于SQL数据库,如MySQL,只不过Redis用数字表示不同的数据库。

但Redis不推荐使用多个数据库,而推荐在一个机器上启动多个Redis服务,因为Redis是单线程的,这样可以更好的利用CPU core。
而且多个数据库不好管理,无法判断是谁引起问题。

Keys without a namespace

使用namespace是最佳建议,目的是为了避免key name的冲突,并且可以很好的对key进行组织。

在 SQL数据库中, namespace可以用数据库名或表名表示。而Redis并不支持namespace,因此必须模拟namespace,常用的方法是加前缀, 形式namespace:key_name。这种方法在前面例子中已经见到很多了,例如:

music-online:song:1
music-online:song:2
music-online:album:10001:metadata
music-online:album:10001:songs
music-online:author:123

Using Swap

不要用Swap,尽可能让Redis运行在内存中。因频繁的使用swap会阻塞客户端的访问。
Linux有一个kernel参数swappiness控制如何使用swap,数越大,使用swap越频繁。将它设置为0。

sysctl -w vm.swappiness=0 同时 修改/etc/sysctl.conf

Not planning and configuring the memory properly

Redis服务器需要足够的内存来执行备份,在备份是,最极端的情况需要Redis内存的两倍。

在创建RDB snapshot 和 AOF rewriting时, redis-server需要通过fork()复制自身。

如果在fork时,Redis非常忙,这时copy-on-write以及内存overcommitting已经不够,子进程可能需要和父进程同样大小的内存。

如果是Linux操作系统,在/etc/sysctl.conf中设置vm.overcommit_memory=1可加速后台的数据保存。

Redis有一配置参数maxmemory可限制Redis最多可使用的内存。

在备份开启后,Redis使用的内存不要超过可用内存的一半。

An inappropriate persistence strategy

在Yipit,有一个读密集的Redis实例变得缓慢,开始以为是代码有问题,后来发现是定期的备份(持久化)使其变慢。

在Redis开始创建RDB snapshot和AOF rewriting时,需要用fork创建子进程,然后让新的子进程来处理。在fork执行时,Redis不能对外服务,这时用户会感觉到响应慢。
Yipit的问题就是因为fork时间过长,因为Redis运行在AWS上,机器使用的是较慢的PV而非HVM虚拟机。

通过以下的措施可以缓解此问题:
* 禁止transparent huge pages内核参数 (echo never > /sys/kernel/mm/transparent_hugepage/enabled)
* 使用HVN虚拟机
* 进行复制,复制目标端用且仅用于持久化
* 减少备份的频度
* 禁止自动持久化,手工来做
* 如果数据可以很快的重建,就不要做持久化

Redislab 发布了fork时间的benchmark:https://redislabs.com/blog/testing-fork-time-on-awsxen-infrastructure.

Summary

本章讲述了Redis使用的一些误区,比较重要的有:
* 要使用namespace避免key命名冲突
* 才提交解决方案时需要先测试,做benchmark测试
* 不做或少做持久化

你可能感兴趣的:(redis,读书笔记)