基于Twemproxy的群集部署方案

时间:2022-04-19 01:28:37

本文描述的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作为一致性hashkey值。

 

关于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),这个配置的优势就能发挥出来了。