码迷,mamicode.com
首页 > Web开发 > 详细

Redis学习之海量小数据的存储详解

时间:2017-01-12 18:43:55      阅读:114      评论:0      收藏:0      [点我收藏+]

标签:

最近有个需求,需要存储海量小数据,大概几十亿的规模,每个数据是6位的数字加一个32位的md5(16进制显示)。因为数据很小,数据总量并不算大,我们计划根据md5做分片,存储到多个redis中,每个redis大概存储1亿的数据,纯数据大概 (6+32)*10^9 = 3.8G ,这是redis数据库很擅长存的量。

  1 快速加载数据到redis

  redis已经非常快了,高达 10w/s ,但面对亿级别的数据,也需要将近20分钟。如果使用pipeline的话,redis还可以更快,达到 40w/s ,5分钟就可以轻松写入1亿数据。

  redis自带的 redis-cli 的 --pipe 参数可以实现快速加载数据,但是需要我们把数据转成redis协议。 --pipe-timeout 参数设置为0,防止redis响应太晚redis-cli过早退出。下面例子中的pl脚本就是拼redis协议的。但是pl的性能稍弱,还没到redis的吞吐量瓶颈,自己CPU先100%了,为此,使用20个进程,每个进程500万数据,这样redis的CPU使用率到了100%,数据加载可以在5分钟内完成。

  我们用 ps -eo ’pid rss pmem cmd’ | grep redis 和redis的 info 查看redis的内存使用。

  2 最直观的存储方式

  time head -n 5000000 data | ./redis-pipe-1.pl | redis-cli --pipe --pipe-timeout 0 redis-pipe-1.pl 最核心的是 print join("\r\n", "*3", ’$3’, "SET", ’$’.$keylen, $key, ’$1’, 1), "\r\n"; 其中key是6位数字加32位md5串,共38位。

  内存使用情况

  5490 9033980 6.8 redis-server *:6379

  used_memory_human:8.45Gdb0:keys=100000000,expires=0,avg_ttl=0

  从内存使用上看,8G左右,是预估3.8G的2倍多。因为redis的内部数据结构,1个指针就是8位,在加上小value,slab内存分配策略,2倍也没有特别不正常。

  3 使用二进制存储

  md5 本身是 16 位的unsigned char,为了转成可见字符用了16进制显示,变成了32位。本来想用base64 24位就可以了,后来觉得redis支持二进制,为啥不直接存16位的unsigned char。 在./redis-pipe-2.pl里面把32位的16进制显示改成了16位的数据

  my @chars = ();my $hex = "";foreach (split //, $md5) {

  $hex .= $_;

  if (length($hex) == 2) {

  push(@chars, chr(hex($hex)));

  $hex = "";

  }

  }

  $key = $appid . join("", @chars);

  $keylen = length($key);

  内存使用情况

  12343 7437316 5.6 redis-server *:6379used_memory_human:6.96Gdb0:keys=100000000,expires=0,avg_ttl=0

  从内存使用上看,减少1.5G左右,和预期差不多 16*10^9 = 1.6G

  到现在为止,是从数据本身来减少内存使用。而根据分析redis自身的数据结构消耗占了一半左右,怎么减少redis数据结构的消耗呢?

  4 使用SET和HSET混合的数据组织方式

  先看两个很有意思的配置,是专门为小Hash做准备(使用HSET),当Hash中的条目小于512,并且每个value小于64个字节时,Redis内部采用特殊的编码方式,可以使内存平均节省5倍。

  hash-max-ziplist-entries 512hash-max-ziplist-value 64

  我们可以把key-value的结构拆解成key-smallhash这样的结构来降低内存的使用

  my ($appid, $md5) = split /\s/, $line;my @chars = ();my $hex = "";foreach (split //, $md5) {

  $hex .= $_;

  if (length($hex) == 2) {

  push(@chars, chr(hex($hex)));

  $hex = "";

  }

  }

  my $hash = $appid . join("", @chars[0 .. 2]);my $hashlen = length($hash);

  my $key = join("", @chars[3 .. @chars-1]);my $keylen = length("$key");

  print join("\r\n", "*4", ’$4’, "HSET", ’$’.$hashlen, $hash, ’$’.$keylen, $key, ’$1’, 1), "\r\n";

  三个unsigned char大概是 2^24 = 16777216 如果有1亿记录的话,每个hash自身平均6个key-value

  内存使用情况

  8593 4052120 3.0 redis-server *:6379

  used_memory_human:3.31Gdb0:keys=16733972,expires=0,avg_ttl=0

  内存使用3.31G,比裸数据 (6+16)*10^9 = 2.2G 只多了50%左右。不仅是省内存,这种方式还有个优势,内存占用 不会随着条目数线性增长 。因为最多16777216个条目,就算数据导了2亿,也只是每个hash到平均12个左右。

  5 额外需要关注的问题

  1. 本来准备把6位的数字转成4位的整数存储,可以额外节省200M,后来放弃了,因为数字转成int,各个语言的互操作性有隐患。

  2. 我们的redis读不是特别多,需要测试hash的压缩存储对性能的影响,但我估计没影响,因为默认是开的。

来源:跬步blog

Redis学习之海量小数据的存储详解

标签:

(0)
(0)
   
举报
评论 一句话评论(0
0条  
登录后才能评论!
jiangjie190
加入时间:2016-02-19
  关注此人  发短消息
文章分类
jiangjie190”关注的人------(0
jiangjie190”的粉丝们------(0
© 2014 mamicode.com 版权所有 京ICP备13008772号-2
迷上了代码!