当Redis单机内存和QPS达到瓶颈时,很多人第一反应是上Redis Cluster,但其实还有一条更轻量的路:在应用和Redis之间加一层代理。Twemproxy(又叫nutcracker)就是Twitter开源的这样一款代理中间件,它本身不存储数据,只负责把请求按照一致性哈希分发到后端多个Redis实例上。对应用来说,只需要连代理端口,完全感知不到后端有几个节点。这篇文章从原理、安装、配置到客户端接入,把Twemproxy的使用完整过一遍。

Twemproxy的工作原理和适用场景
Twemproxy的核心定位是“快速、轻量的Redis和Memcached代理”。它由C语言编写,采用单线程事件驱动模型(基于epoll),单实例可以处理数万甚至十几万的QPS,自身开销非常小。应用连接Twemproxy后,代理会在内部维护到后端每个Redis实例的连接池,多个客户端连接可以复用这些后端连接,减少了Redis端的连接数压力,这一点对连接数敏感的场景特别有用。
在数据分发上,Twemproxy支持一致性哈希(ketama)、取模哈希(modula)、随机(random)三种分发算法。数据按照key做哈希后落到固定的后端节点上,读取时走同样的算法找到对应节点,从而实现客户端无感知的分片读写。它还支持auto_eject_hosts功能,配合server_failure_limit和server_retry_timeout参数,可以在某个节点持续失败时自动把它剔除出去,请求暂时由剩余节点承接,起到基础的容灾作用。
适合使用Twemproxy的场景包括:Redis单机内存不够、想要平滑拆分成多个实例的场景;客户端连接数过多、想收敛连接的场景;无法升级到Redis Cluster但需要横向扩展的场景。需要注意的是,它不支持事务、部分多key命令,也不能动态在线添加节点,这些限制后面会详细说明。
Linux环境下编译安装Twemproxy
Twemproxy没有预编译的二进制包,需要在Linux上自行编译。以CentOS或Ubuntu为例,先安装编译依赖,然后从GitHub拉取源码执行autoreconf和configure:
# CentOS安装依赖 yum install -y autoconf automake libtool git gcc make # Ubuntu安装依赖 apt-get install -y autoconf automake libtool git gcc make # 下载源码并编译 git clone https://github.com/twitter/twemproxy.git cd twemproxy autoreconf -fvi ./configure --enable-debug=log make make install
编译安装完成后,可执行文件nutcracker会被安装到系统路径下,执行nutcracker -h能看到帮助信息说明安装成功。如果只想快速验证,也可以直接执行src/nutcracker -v查看版本号。编译时加--enable-debug=log可以开启日志调试,生产环境建议去掉,避免影响性能。
建议再做一步系统参数优化,修改/etc/security/limits.conf,把twemproxy运行用户的最大文件描述符数调大,例如加一行* soft nofile 65535和* hard nofile 65535,因为代理会持有大量客户端连接和后端连接,默认的1024很容易不够用。
nutcracker.yml配置文件详解
Twemproxy的配置是一个YAML文件,通常命名为nutcracker.yml。下面是一个典型的Redis分片配置:
redis_shard:
listen: 127.0.0.1:22121
hash: fnv1a_64
hash_tag: "{}"
distribution: ketama
timeout: 500
redis: true
preconnect: true
auto_eject_hosts: true
server_connections: 2
server_retry_timeout: 3000
server_failure_limit: 3
servers:
- 127.0.0.1:6379:1 redis1
- 127.0.0.1:6380:1 redis2
- 127.0.0.1:6381:1 redis3逐个说明关键参数的含义。listen是代理监听的地址和端口,应用就连接这个端口;hash指定哈希函数,常用fnv1a_64或md5;distribution指定分发算法,ketama是一致性哈希,增删节点时只影响相邻的数据,modula则是简单取模,任何节点变化都会导致大规模key重新分布;redis: true表示后端是Redis协议,不加这个参数会被当成Memcached协议处理;hash_tag用于指定key中花括号内的部分参与哈希,比如key写成user:{1000}:name和user:{1000}:age,这两个key会落在同一个节点上。
auto_eject_hosts配合server_failure_limit使用:当某个节点连续失败次数超过3次,代理会在server_retry_timeout定义的毫秒数内(这里是3秒)把它临时剔除,之后周期性重试,恢复后自动加回。要注意被剔除期间,原本落在该节点的key会通过ketama环落到下一个节点,如果后端Redis是纯缓存用途,这个机制很好用;如果存储了持久数据,被剔除期间写入的key会落到别的节点,恢复后读取就可能miss,这是使用Twemproxy必须理解的坑。
servers列表的格式是“IP:端口:权重 名称”,权重影响ketama环上虚拟节点的数量。同一份配置可以定义多个pool,一个nutcracker进程可以同时代理多个Redis集群,每个pool用不同的监听端口区分。
启动命令如下:
# 前台启动便于调试 nutcracker -c /etc/nutcracker.yml -v 7 -o /var/log/nutcracker.log # 后台守护进程方式启动,-d指定监控端口 nutcracker -c /etc/nutcracker.yml -d 22222 -s 42222 -o /var/log/nutcracker.log
启动后用redis-cli -p 22121连上去,执行set和get验证数据是否正常分发到后端三个实例。也可以通过stats端口(这里是42222)执行nc 127.0.0.1 42222查看代理的运行统计信息,包括每个后端节点的请求数、错误数、当前连接数等,日常运维主要靠这个端口监控。
客户端接入示例
应用侧的接入方式非常简单,因为Twemproxy完全兼容Redis协议,任何Redis客户端都可以直接使用,唯一要改的就是把连接地址从Redis实例换成代理地址。以Java的Jedis为例:
JedisPool pool = new JedisPool(
"127.0.0.1", 22121); // 直接连Twemproxy端口
try (Jedis jedis = pool.getResource()) {
jedis.set("user:1000:name", "zhangsan");
String name = jedis.get("user:1000:name");
System.out.println(name);
}PHP的phpredis扩展同样只需要改host和port:
<?php
$redis = new Redis();
// 连接Twemproxy,而不是直连Redis
$redis->connect('127.0.0.1', 22121);
$redis->set('user:1000:age', 28);
echo $redis->get('user:1000:age');
?>需要特别注意的是,经过代理后部分命令不可用或不建议使用。涉及多个key且key可能分布在不同节点的命令,比如mget跨节点、keys、flushall、事务multi/exec、发布订阅subscribe等,要么直接报错,要么行为不符合预期。对于mget这类命令,建议在业务代码里拆成循环单key请求,或者依赖客户端批量接口由代理自动拆分(部分客户端支持)。这一点在技术选型时要提前评估业务是否依赖这些特性。
Twemproxy的局限与选型建议
Tweetproxy最大的局限是在线扩容困难。由于ketama哈希环在节点变化时会重新分布一部分key,而Redis本身没有数据迁移机制,扩容后这部分key会读不到。常见做法是扩容前通过双写加逐步迁移数据,或者选择业务低峰期重启代理并接受一定的缓存miss(缓存场景下miss会自动回源重建)。如果是持久化存储场景,扩容成本明显更高,需要提前规划好分片数量,比如一步到位规划出足够多的节点。
另一个常见问题是单点风险。Twemproxy自身是一个单点,生产环境通常结合LVS、Keepalived或者部署多个代理实例加客户端负载均衡来保证高可用。同时后端每个Redis实例建议配置主从加Sentinel,但要注意Sentinel的自动切换对Twemproxy并不完全透明,因为代理默认不会感知主从切换,需要借助auto_eject_hosts等机制间接兜底,或者用twemproxy的分支版本(如美团Codis的思路)来解决。
总体来说,如果业务场景是纯缓存、key操作以单key为主、希望低成本地把Redis横向扩展,Twemproxy依然是一个稳定可靠的选择,它在各大公司经历了多年生产验证。如果需要动态扩缩容、多key操作和完善的集群管理,那么Redis Cluster或者Codis这类方案更合适。理解自己的业务模型,再对照代理的能力边界做选择,才是正确的打开方式。