概述
本文描述的twemproxy基于nutcracker-0.2.4版本。
twemproxy是memcached 和redis的协议层面的代理,其提供的features如下:
注:
twemproxy不会增加redis的性能指标数据,据业界测算,使用twemproxy相比直接使用redis会带来~10%的性能下降。
但是单个redis进程的内存管理能力有限。据测算,单个redis进程内存超过20G之后,效率会急剧下降。目前,我们给出的建议值是单个redis最好配置在8G以内。8G以上的redis缓存需求,通过twemproxy来提供支持。
本文将描述twemproxy+redis的下载、安装、部署和配置过程。其中,其distribution是本文的重点之一。
部署
编译twemproxy
基本过程如下:
从http://code.google.com/p/twemproxy/downloads/list下载对应版本的代码
然后解压缩、配置、编译、安装
twemproxy的命令行参数
twemproxy的配置详解
twemproxy的配置信息填写在nutcracker.yml之中,默认的查找位置是在conf目录下,也可以通过-c参数指定。
nutcracker.yml的例子:
一个twemproxy可以被配置成多个角色。
详细的配置信息如下:
l listen
twemproxy监听的端口。可以以ip:port或name:port的形式来书写。
l hash
可以选择的key值的hash算法:
> one_at_a_time
> md5
> crc16
> crc32(crc32 implementation compatible with libmemcached)
> crc32a(correct crc32 implementation as per the spec)
> fnv1_64
> fnv1a_64
> fnv1_32
> fnv1a_32
> hsieh
> murmur
> jenkins
如果没选择,默认是fnv1a_64。
l hash_tag
hash_tag允许根据key的一个部分来计算key的hash值。hash_tag由两个字符组成,一个是hash_tag的开始,另外一个是hash_tag的结束,在hash_tag的开始和结束之间,是将用于计算key的hash值的部分,计算的结果会用于选择服务器。
例如:如果hash_tag被定义为”{}”,那么key值为"user:{user1}:ids"和"user:{user1}:tweets"的hash值都是基于”user1”,最终会被映射到相同的服务器。而"user:user1:ids"将会使用整个key来计算hash,可能会被映射到不同的服务器。
l distribution
存在ketama、modula和random3种可选的配置。其含义如下:
ketama
ketama一致性hash算法,会根据服务器构造出一个hash ring,并为ring上的节点分配hash范围。ketama的优势在于单个节点添加、删除之后,会最大程度上保持整个群集中缓存的key值可以被重用。
modula
modula非常简单,就是根据key值的hash值取模,根据取模的结果选择对应的服务器。
random
random是无论key值的hash是什么,都随机的选择一个服务器作为key值操作的目标。
l timeout
单位是毫秒,是连接到server的超时值。默认是永久等待。
l backlog
监听TCP 的backlog(连接等待队列)的长度,默认是512。
l preconnect
是一个boolean值,指示twemproxy是否应该预连接pool中的server。默认是false。
l redis
是一个boolean值,用来识别到服务器的通讯协议是redis还是memcached。默认是false。
l server_connections
每个server可以被打开的连接数。默认,每个服务器开一个连接。
l auto_eject_hosts
是一个boolean值,用于控制twemproxy是否应该根据server的连接状态重建群集。这个连接状态是由server_failure_limit 阀值来控制。
默认是false。
l server_retry_timeout
单位是毫秒,控制服务器连接的时间间隔,在auto_eject_host被设置为true的时候产生作用。默认是30000 毫秒。
l server_failure_limit
控制连接服务器的次数,在auto_eject_host被设置为true的时候产生作用。默认是2。
l servers
一个pool中的服务器的地址、端口和权重的列表,包括一个可选的服务器的名字,如果提供服务器的名字,将会使用它决定server的次序,从而提供对应的一致性hash的hash ring。否则,将使用server被定义的次序。
高容量的twemproxy群集部署方案
目标:面向大容量redis实例的需求配置方案。
根据以往的测试结论,基于redis server的性能考虑,单个redis的实例的内存总量需控制在8G以内(最大不能超过20G),而实际上应用对redis的内存的需求可能会远远大于8G,因此需要一个保持redis server性能不下降,但可以有效扩充redis server的容量的方案。twemproxy是一个恰当的选择。
根据实际需要的redis的内存量,来规划twemproxy群集中redis的实例数量,然后,通过twemproxy提供该群集的统一的访问入口,这样即有效的扩充了redis server的内存总量,又避免了单个redis server内存过大导致的问题。
扩充twemproxy群集的并发能力
存在既需要redisserver的内存容量,也需要redisserver的并发能力的情况。理论上twemproxy群集能提供的并发规模的总量是所有redis服务器并发量的总和(会因为使用twemproxy存在略微的下降),而扩展并发量的方式是增加twemproxy群集的访问入口:
客户端使用Jedis配置好这几个访问入口,就可以同时使用多个twemproxy入口来访问twemproxy群集。
一致性hash的选择
在twemproxy的配置的章节有对distribution的描述,这部分就是twemproxy的群集的一致性hash算法的配置,有3个选择:
ketama,modula和random
先说random,是随机的选择一个redis server作为最终操作的目标,这个适合只读的场景,需要配合数据加载。
ketama是一种基于key-range的一致性hash算法,它的优势是一个redisserver down掉之后,整个群集做re-hash,会有一部分key-range与以前的key-range重合。这种特性也是只适合做比较单纯cache。
modula的方式是根据key的hash取模,来选择目标的redisserver。这种方式,显而易见,如果一个redis server down掉之后,如果整个群集做re-hash,所有的key值的目标都会错乱。
而是否做整个群集的re-hash,这由twemproxy的Liveness配置来决定。
Liveness配置的开启由auto_eject_hosts来检测,轮询的周期由server_retry_timeout来决定,而server_failure_limit则决定如果几次轮询失败,会将该redis server从群集中摘除。
twemproxy的Liveness需要根据情况谨慎配置。
配置tips
使用server name
书写twemproxy的servers pool,我们可以这么写:
也可以这么写:
提供一个名字的好处是可以将对servers pool中server定义的次序的依赖性转换为对server名字的依赖性。
使用Hash Tags
hash tag的具体解释可以看我们对twemproxy的配置方面的描述,简单的说,hash tag可以根据key的一部分作为选择redis server的键值,从而来干预内容存在何处。
hash tag很简单,就2个字符,前面是引导字符,后面是结束字符,在这两个字符中间的被作为最终用于作为群集一致性hash的key值。
例如:
hash tag是”{}”,而传递过来的rediskey值是"user:{user1}:ids"和"user:{user1}:tweets",这两个key值在选择redisserver时使用的一致性hash的key值都是”user1”,这两个key值都会被存在相同的redis server上。
注:
如果在key中没找到对应的hash_tags模式,会使用整个key作为一致性hash的key值。
关于key值长度的限制
memcache限制key值在250字符以内,redis则没什么限制,由于twemproxy将key值存放在连续的内存之中,所以twemproxy的key值的最大长度受到mbuf长度的限制。
mbuf的长度由-m指定,默认是16384字节,一般够用了。如果遇到key值过长的问题,可以调整这个参数。
mbuf的含义&调整
最小512字节,最大65536字节,默认16384字节。可以通过命令行的-m参数调整。
mbuf是twemproxy引以为傲的zero-copy技术的底层支撑,zero-copy意味着从客户端接收的数据直接被提交到redis-server,不需要经过中间的copy环节(看似不难,实际上操作起来很难做到)。
很明显,大尺寸的mbuf会增加性能,减少分包的次数,但是会增加对内存的消耗。
如何估计twemproxy的mbuf对内存的需求呢?公式如下:
max(client_connections, server_connections) * 2 *mbuf-size
因为存在client-> twemproxy以及twemproxy->redis-server两个连接,所以mbuf是需要双份的。
大多客户端的连接会大于服务器连接池预设的连接数。我们假设1000个客户端连接,mbuf-size是16KB,那么大概会消耗掉1000*2*16KB=32M左右的内存。
使用Timeout配置参数
在twemproxy中可以指定timeout配置参数:
默认的情况下,twemproxy到redis server的连接没有超时,默认是永久等待。这也许不是最终期望的行为。twemproxy运行指定一个timeout的毫秒数,在指定的时间内如果没得到响应会返回客户端:
SERVER_ERROR Connection timed out\r\n
配置redis-server的连接数
默认的情况下,twemproxy会为每个redis-server准备一个连接,在这个连接中,传递所有来着客户端的命令。这样做的好处是当存在多个对相同key的操作的时候,我们可以将这些操作串行化。
twemproxy可以使用server_connections配置每个redis-server的最大连接数,还可以使用preconnect来配置是否预连接redis-server,不过需要小心多连接出现的竞争条件。
例如:客户端连续发出set foo xxx和get foo两个命令,如果存在多connection,两个命令可能会被分配到多个connection上执行,那么那个会被先执行呢?这是个不确定的问题。
这个参数也需要谨慎的使用,对于单个redis-server来说,增加server connection并不能简单的提高并发的性能,因为redis-server本身是单一进程的模型。如果后面是一个具有并发能力的redis-server(比如,aliredis),这个配置的优势就能发挥出来了。