Redis集群(Twemproxy)
Redis集群(Twemproxy)

Redis集群(Twemproxy)

Twemproxy简介

Github地址: https://github.com/twitter/twemproxy
twemproxy是Twitter开源的一缓存服务器管理工具.
主要用来管理Redis集群, 减少应用程序与Redis服务直接连接的数量.
twemproxy原理与nginx反向代理类似, twemproxy作为代理接收多个程序的访问, 并按照一定的路由规则将请求转发给后台的各个缓存服务器, 再原路返回.

Twemproxy特点

  • 轻量
  • 快速
  • 减少应用程序与缓存服务器的连接数, 保持twemproxy与缓存服务器的长连接
  • 同时支持多个缓存服务器池
  • 支持代理到多台缓存服务器上
  • 自动跨多台服务器分片
  • 通过yaml配置文件进行配置
  • 支持多种hash算法(包括一致性hash)和分片算法(包括ketama)
  • 可以部署多台平级代理, 通过负载均衡避免单点twemproxy故障, 将读写分散到不同的twemproxy代理
  • 可配置为剔除故障节点
  • 支持pipelining, 将多个连接请求组成reids pipelining统一向redis请求
  • 可以结合sentinel进行缓存节点的主备切换

Twemproxy缺点

  • 由于比直连多了一层代理, 相比单节点缓存服务器, 有一定的性能损失(官方数据为20%损失)
  • 不支持事务
  • 部分命令不支持(如 keys, sets等)

Twemproxy架构

通常情况下, 一个Redis节点池包含多个Redis节点, 每个节点有1主多从, 通过sentinel进行主备切换.
如果使用sentinel进行Redis节点主备切换, 切换时会重启twemproxy, 导致代理短暂不可用.
而一个Redis节点池可以由多个twemproxy管理, 少数负责写, 多数读, twemproxy之间可以通过HAProxy进行负载均衡, 从而避免单个twemproxy故障.
image.png

Twemproxy搭建

以Ubuntu18.04-LTS为例, 本机搭建4个Redis服务(端口为7001-7004)作为twemproxy的分片, 每个分片一个主节点, 不使用从节点

  • 下载autoconf和twemproxy
root@iZ2ze26pixxe9t9kmg6tvhZ:/home# wget http://ftp.gnu.org/gnu/autoconf/autoconf-2.69.tar.gz
root@iZ2ze26pixxe9t9kmg6tvhZ:/home# wget https://codeload.github.com/twitter/twemproxy/zip/master
  • 创建autoconf和twemproxy安装目录
root@iZ2ze26pixxe9t9kmg6tvhZ:/home# mkdir autoconf twemproxy
root@iZ2ze26pixxe9t9kmg6tvhZ:/home# ls 
autoconf twemproxy
root@iZ2ze26pixxe9t9kmg6tvhZ:/home# cd twemproxy
root@iZ2ze26pixxe9t9kmg6tvhZ:/home# mkdir run
  • 编译安装autoconf
# 解压
root@iZ2ze26pixxe9t9kmg6tvhZ:/home# tar -zxvf autoconf-2.69.tar.gz
root@iZ2ze26pixxe9t9kmg6tvhZ:/home# cd autoconf-2.69
# 编译安装
root@iZ2ze26pixxe9t9kmg6tvhZ:/home/autoconf-2.69# ./configure –prefix=/home/autoconf
root@iZ2ze26pixxe9t9kmg6tvhZ:/home/autoconf-2.69# make && make install
root@iZ2ze26pixxe9t9kmg6tvhZ:/home/autoconf-2.69# 
  • 编译安装twemproxy
root@iZ2ze26pixxe9t9kmg6tvhZ:/home# unzip master.zip
root@iZ2ze26pixxe9t9kmg6tvhZ:/home# cd twemproxy-master
root@iZ2ze26pixxe9t9kmg6tvhZ:/home/twemproxy-master# autoconf -fvi
root@iZ2ze26pixxe9t9kmg6tvhZ:/home/twemproxy-master# ./configure –prefix=/home/twemproxy
root@iZ2ze26pixxe9t9kmg6tvhZ:/home/twemproxy-master# make -j 8
root@iZ2ze26pixxe9t9kmg6tvhZ:/home/twemproxy-master# make install
  • 设置环境变量
root@iZ2ze26pixxe9t9kmg6tvhZ:/home# echo “PATH=$PATH:/home/twemproxy/sbin” >> /etc/profile
root@iZ2ze26pixxe9t9kmg6tvhZ:/home# source /etc/profile
root@iZ2ze26pixxe9t9kmg6tvhZ:/home# 
  • 创建twemproxy配置文件
root@iZ2ze26pixxe9t9kmg6tvhZ:/home# cd /home/twemproxy
root@iZ2ze26pixxe9t9kmg6tvhZ:/home/twemproxy# ls
share sbin run
root@iZ2ze26pixxe9t9kmg6tvhZ:/home/twemproxy# cd sbin
root@iZ2ze26pixxe9t9kmg6tvhZ:/home/twemproxy/sbin# mkdir conf
root@iZ2ze26pixxe9t9kmg6tvhZ:/home/twemproxy/sbin# cd conf
root@iZ2ze26pixxe9t9kmg6tvhZ:/home/twemproxy/sbin/conf# vim nutcracker.yml

配置文件内容:

# alpha使用host:port:weight进行一致性hash, alpha使用的是redis协议
alpha:
  listen: 0.0.0.0:22121
  # 选择fnv1a_64算法
  hash: fnv1a_64
  # 分片选择ketama
  distribution: ketama
  auto_eject_hosts: true
  redis: true
  server_retry_timeout: 2000
  server_failure_limit: 1
  # redis节点
  servers:
    - 127.0.0.1:7001:1
    - 127.0.0.1:7002:1
    - 127.0.0.1:7003:1
    - 127.0.0.1:7004:1

启动twemproxy

root@iZ2ze26pixxe9t9kmg6tvhZ:/home/twemproxy/sbin# nutcracker -d -c /home/twemproxy/conf/nutcracker.yml -p /home/twemproxy/run/redisproxy.pid -o /home/twemproxy/run/redisproxy.log
root@iZ2ze26pixxe9t9kmg6tvhZ:/home/twemproxy/sbin# 
  • 确认twemproxy启动成功
root@iZ2ze26pixxe9t9kmg6tvhZ:/home/twemproxy/sbin# netstat -ntlp | grep nutcracker 
tcp        0      0 0.0.0.0:22121           0.0.0.0:*               LISTEN      8342/./nutcracker   
tcp        0      0 0.0.0.0:22222           0.0.0.0:*               LISTEN      8342/./nutcracker   
root@iZ2ze26pixxe9t9kmg6tvhZ:/home/twemproxy/sbin# 
  • 使用redis-cli测试twemproxy
./redis-cli -p 22121
127.0.0.1:22121> set name aaa
OK
127.0.0.1:22121> set test testvalue
OK
127.0.0.1:22121> get name
"aaa"

存储多对key:value之后,分别在四个redis服务查看key的分布:

root@iZ2ze26pixxe9t9kmg6tvhZ:/home/redis/bin# ./redis-cli -p 7001
127.0.0.1:7001> keys *
1) "password"
2) "aaa"
3) "bbb"
127.0.0.1:7001> exit
root@iZ2ze26pixxe9t9kmg6tvhZ:/home/redis/bin# ./redis-cli -p 7002
127.0.0.1:7002> keys *
1) "hello"
127.0.0.1:7002> exit
root@iZ2ze26pixxe9t9kmg6tvhZ:/home/redis/bin# ./redis-cli -p 7003
127.0.0.1:7003> keys *
1) "test"
2) "testkey"
3) "name"
4) "twemproxy"
5) "user"
127.0.0.1:7003> exit
root@iZ2ze26pixxe9t9kmg6tvhZ:/home/redis/bin# ./redis-cli -p 7004
127.0.0.1:7004> keys *
1) "mysql"
2) "gender"
3) "key1"
127.0.0.1:7004> exit
root@iZ2ze26pixxe9t9kmg6tvhZ:/home/redis/bin# 

可以看到四个redis服务被twemproxy作为分片存储不同的key:value

  • Java应用程序使用Twemproxy\n需要引入jedis的maven依赖
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.7.1</version>
</dependency>

通过jedis使用twemproxy

package cn.zack.jedis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisClient {
    private Jedis jedis;//非切片客户端连接
    private JedisPool jedisPool;//非切片连接池

    public RedisClient() {
        initialPool();
        jedis = jedisPool.getResource();
    }

    /**
     * 初始化非切片池
     */
    private void initialPool() {
        JedisPoolConfig config = new JedisPoolConfig();\n        config.setMaxTotal(20);
        config.setMaxIdle(5);
        config.setMaxWaitMillis(1000L);
        // 由于twemproxy不支持ping, 如果不禁ping会导致连接失败
        config.setTestOnBorrow(false);
        jedisPool = new JedisPool(config, "182.92.237.247", 22121, 20000);
    }
    public void show() {
        // 存
        jedis.set("testkey", "value");
        // 取
        System.out.println(jedis.get(
"testkey"));
        // 释放资源
        jedisPool.returnResource(jedis);
    }
}
package cn.zack;

import cn.zack.jedis.RedisClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootRedisApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootRedisApplication.class, args);
        new RedisClient().show();
    }
}