redis使用规范

由于每个业务对redis的使用情况是不同的,单独为每个数据结构进行限制,无疑会对开发的进度和难度造成巨大影响,也失去了使用redis丰富的数据结构而不是memcached的初衷。

所以只提几条如何用好redis的建议,望各位能够采纳。

如何用好redis

slowlog

redis的slowlog是参考redis集群瓶颈优化的一个点

当业务出现性能瓶颈的时候,可以分析slowlog里占用时间较长较多的command。

在我的经验中HGETALL,DEL对于存储较多value的key,hash,会阻塞较长时间,这方面业务需要极力去规避,因为一个命令可能造成 多个客户端timeout,或者是连接池timeout,在使用连接池的场合中,read timeout或者connect timeout都是灾难性的,会极大的拖垮业务系统的TPS

BGREWRITEAOF

这个机制主要是为了减少AOF对业务的影响,在执行这个命令的时候,redis服务器会创建一个aof重写缓冲区,在重写的过程中,所有写命令会写入这个缓冲区

当数据集非常大的时候,遍历内存来重写AOF会占用很多时间,在这段时间如果有高并发写,就会占用很多很多内存,所以一定要注意大数据集是否有足够内存来做操作,或者BGREWRITEAOF命令执行时是否有很高的写并发

value的优化

实例共享

最近遇到有个业务的TPS无论如何都上不去的情况,根据测试负载压在redis的网络上,由于公司的资源限制不能加机器了,所以只好砍掉取用的数据量

这个业务的歌词文件属于存储较大的value,把这部分砍掉以后业务的TPS很快就上去了 (后期需要歌词的时候对这部分value进行压缩再存储)

分析

redis内部的value按照三种编码存储int,raw,embstr

当存储的value为一个整数,按照int对象来存储

当存储的value不为整数,且小于32字节,按照embstr来存储

当存储的value不为整数,且大于32字节,按照raw来存储

int存储整数,并且对于0-1000的常用数字做了共享对象,存储或者读写改内容会直接连接到该对象

embstr是为了短字串存储优化而实现的编码,只能进行读,当修改以后会转换为raw编码

raw顾名思义,未经加工的字串,所以当value较大时,读写这样的数据会造成网络瓶颈

结论

1.如果对字串需要修改,那么直接存大于32字节的,预留空间,这样减少了转换所需时间

2.redis的字符串是二进制安全的,因此可能会误导人什么样的东西都往里塞,

其实长value是不会压缩的,因此短value的读写效率比长value高得多,

对长value的存储,如果是纯文本,例如歌词,完全可以进行压缩,现在的速度快压缩率高的文本压缩算法非常成熟,加大业务服务器的cpu负载减少IO负载,对于网络IO密集型应用来说显然是划得来的

内存利用率的优化

参考了http://oldblog.antirez.com/post/redis-weekly-update-7.html

他提出一种叫做trick RedisHL的方法,可以显著的提高redis的内存利用率。

稍微翻译一下这篇文章:

假如要存储一个key “foo”,做以下三件事情:

1。计算”foo”的十六进制SHA1校验和:0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33

2。使用头四个字节0bee作为存储的(真实)key

3。把”foo”当作0bee的field存储

所以”SET foo bar”相当于”HSET 0bee foo bar”

具体ruby代码如下:

require 'rubygems' require 'redis' require 'digest/sha1'

class RedisHL

   def initialize(r)
       @redis = r
   end
   def get_hash_name(key)
       Digest::SHA1.hexdigest(key.to_s)[0..3]
   end
   def exists(key)
       @redis.hexists(get_hash_name(key))
   end
   def [](key)
       @redis.hget(get_hash_name(key),key)
   end
   def []=(key,value)
       @redis.hset(get_hash_name(key),key,value)
   end
   def incrby(key,incr)
       @redis.hincrby(get_hash_name(key),key,incr)
   end

end

r = Redis.new() rr = RedisHL.new(r)

rr[:foo] = "bar" puts(rr[:foo])

1000W的短K使用常规方式如果需要存储空间1.7G的话,使用这种方式只需要300M内存!

文章末还附带了一张测试数据的图片

http://antirez.com/blogdata/214/memgraph.png

分析

我来分析一下如此amazing的结果是怎样的原理

redis对于hash类型有两种实现方式,一种是ziplist,一种是hashtable。

如果直接使用get/set对总hash进行数据操作,那么在数据利用率(used/size)达到一定数量时,会开始rehash,即为二倍扩容

由于hashtable的预留空间机制,所以数据利用率其实不是很高

ziplist是一种非常高效的数据存储格式,他的指针用的很少,且没有预留空间。

在一个hash类型被创建时,他会以ziplist格式存储,达到一定规模后转为hashtable。

规模的限制可以在redis.conf中以下字段修改

hash-max-zipmap-entries 512

hash-max-zipmap-value 512

当大量的数据以ziplist存储时,内存利用率是非常高的

但是值得注意的是,ziplist的数据插入删除等等是较为消耗CPU资源的,所以对于大量的短key以及读大于写的场合来说,这个trick RedisHL的方式是很不错的

结论

1.对于大量的短key以及读大于写的场合来说,这个trick RedisHL的方式是很不错的

2.对于大量的短key以及读大于写的场合来说,直接用get/set操作,不如将一些key均衡的打包为hash类型进行操作(注意hash类型 里的field不能过多,不然就掉进前文中HGETALL的坑了),当这些hash类型以ziplist的格式存储时,内存利用率是惊人的。

twemproxy下redis使用的注意点

twemproxy是由twitter开发的一个Redis 和 Memcached 代理。

鉴于目前redis cluster是unstable版本,机制的实现还有待商榷。(不是特别看好使用gossip算法的广播机制,对网络环境要求很高,cassandra就是这种机制)

在大规模的redis集群使用中,twemproxy为代表的proxy方式成为了主流,在这之中twemproxy脱颖而出,甚至在redis作者的blog上被点名分析了,据他说proxy的性能损耗在20%左右。

http://www.antirez.com/news/44

twemproxy的机制和源码分析会在下一篇文章中放出。这一篇只是讨论一下twemproxy下redis使用的注意点。

避免使用MGET

这个是被redis作者在前文中提到的blog中点名批评的,原话是:

So I expected to see almost the same numbers with an MGET as I see when I run the MGET against a single instance, but I get only 50% of the operations per second.

在twemproxy上操作MGET的性能居然只有操作单机的50%!

我大致分析了twemproxy的源码,当MGET涉及的key分布在多个redis实例上,由于网络波动,每台实例需要提取的key数目不一样等原因,proxy不是一次性处理完整合结果反馈客户端。

而是每接受到一次以后,等待整合事件被触发,最后一次性整合然后发送结果给客户端。

消耗时间 = 消耗最多的那台redis实例 + 每次时间轮询间隔*分配的redis实例个数

这样慢也就很正常了,所以

避免在twemproxy环境下使用MGET!!!

避免使用超大field的复杂类型对象

复杂类型对象指的是hash,set之类的非string对象

这样的对象都是key field value格式的。

由于twemproxy对对象做hash,只是针对key,而不会也无法对field做hash(如果对field做hash,很多redis原生操作无法支持,而且也会造成上问中由于访问多台redis实例才能返回结果而造成的MGET坑)

所以一整个对象会存储在同一个redis实例上,如果一个对象过大,会严重影响redis的分布性能,造成存储和性能上严重的分布不均衡。

使用OBJECT指令可以查看对象的详细信息,如果核实是超大field,那么:

超大field需要进行切割,控制在每个50m左右来保证redis集群的分布均衡

你可能感兴趣的:(缓存技术)