Codis调研

来源:互联网 发布:犀牛软件mac版 编辑:程序博客网 时间:2024/05/21 10:55

Cache调研

一、 使用什么

       Redis是一个开源,先进的key-value存储,并用于构建高性能,可扩展的Web应用程序的完美解决方案,是目前的主流内存数据库(noSQL)。所以选择redis作为服务端缓存应该是个不错的选择。其次考虑到单点性能问题(如内存有限,带宽压力,动态扩容等),我们需要一个高效灵活的redis集群。目前较为主流的redis集群有如下几种:
1. redis3.0集群:耦合度高,升级困难,客户端需要修改,不够成熟,社区不活跃等;
2. Twemproxy:使用广泛,基于代理,静态拓扑,运维困难,无法平滑的扩/缩容;
3. Cassandra:曾被Facebook弃用,现在又渐渐被启用,适合大规模数据的存储,了解的不是很多,在国内比较冷门,社区不活跃;
4. Codis1.0:业务不停机,平滑扩容,无状态proxy,负载均衡,无单点,充分利用多核,运维工具齐全(dashboard,redis-port,codis-ha),但是它依赖zookeeper,修改了官方redis。
5. Codis 2.0:除了支持zookeeper还支持etcd,可插拔的存储引擎,pipeline的引入提升了性能。相对于codis1.0更加稳定高效;
6. RebornDB:下一代codis,功能更加强大,但是稳定性还没有经过实践的检验,不够成熟。

二、 codis2.0简介

       codis2.0是一个用GO/C编写的基于proxy based的redis集群,它是被作为Twitter的分布式redis集群Twemproxy的替代品开发出来的,旨在集成前者的优点和改进其缺点。它支持多个无状态的proxy,每个proxy支持多个redis实例,并且支持动态扩容(不停止业务的情况向)。

1. codis2.0 特点

  1. 无状态的Proxy based
  2. 无需重启客户端动态的添加或移除redis实例或者proxy实例,安全透明的数据迁移
  3. 支持redis以及基于redis协议的数据库
  4. 图形界面运维工具dashboard和管理员工具
  5. 支持大多数redis命令,完全兼容Twemproxy
  6. 支持原生的redis客户端
           Codis 是一个分布式 Redis 解决方案, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生的 Redis Server 没有明显的区别 (不支持的命令列表), 上层应用可以像使用单机的 Redis 一样使用, Codis 底层会处理请求的转发, 不停机的数据迁移等工作, 所有后边的一切事情, 对于前面的客户端来说是透明的, 可以简单的认为后边连接的是一个内存无限大的 Redis 服务.
    Codis 由四部分组成:
    • Codis Proxy (codis-proxy)
    • Codis Manager (codis-config)
    • Codis Redis (codis-server)
    • ZooKeeper
           codis-proxy 是客户端连接的 Redis 代理服务, codis-proxy 本身实现了 Redis 协议, 表现得和一个原生的 Redis 没什么区别 (就像 Twemproxy), 对于一个业务来说, 可以部署多个 codis-proxy, codis-proxy 本身是无状态的.
           codis-config 是 Codis 的管理工具, 支持包括, 添加/删除 Redis 节点, 添加/删除 Proxy 节点, 发起数据迁移等操作. codis-config 本身还自带了一个 http server, 会启动一个 dashboard, 用户可以直接在浏览器上观察 Codis 集群的运行状态.
           codis-server 是 Codis 项目维护的一个 Redis 分支, 基于 2.8.21 开发, 加入了 slot 的支持和原子的数据迁移指令. Codis 上层的 codis-proxy 和 codis-config 只能和这个版本的 Redis 交互才能正常运行.
           Codis 依赖 ZooKeeper 来存放数据路由表和 codis-proxy 节点的元信息, codis-config 发起的命令都会通过 ZooKeeper 同步到各个存活的 codis-proxy.
           Codis 支持按照 Namespace 区分不同的产品, 拥有不同的 product name 的产品, 各项配置都不会冲突.
  7. Codis2.0 集群搭建(见另一个文档)
  8. Codis2.0数据迁移

三、codis2.0数据迁移

       安全和透明的数据迁移是 Codis 提供的一个重要的服务, 也是 Codis 区别于 Twemproxy 等静态的分布式 Redis 解决方案的地方.
       数据迁移的最小单位是 key, 我们在 codis redis 中添加了一些指令, 实现基于key的迁移, 如 SLOTSMGRT等 (命令列表), 每次会将特定 slot 一个随机的 key 发送给另外一个 codis redis 实例, 这个命令会确认对方已经接收, 同时删除本地的这个 k-v 键值, 返回这个slot的剩余 key的数量, 整个操作是原子的.在 codis-config 管理工具中, 每次迁移任务的最小单位是 slot如: 将slot id 为 [0-511] 的slot的数据, 迁移到 server group 2上, –delay 参数表示每迁移一个 key 后 sleep 的毫秒数, 默认是 0, 用于限速.
       $ bin/codis-config slot migrate 0 511 2 –delay=10
       迁移的过程对于上层业务来说是安全且透明的, 数据不会丢失, 上层不会中止服务.
       注意, 迁移的过程中打断是可以的, 但是如果中断了一个正在迁移某个slot的任务, 下次需要先迁移掉正处于迁移状态的 slot, 否则无法继续 (即迁移程序会检查同一时刻只能有一个 slot 处于迁移状态).

四、 Auto Rebalance

支持动态的根据实例内存, 自动对slot进行迁移, 以均衡数据分布.

$ bin/codis-config slot rebalance

要求:
• 所有的codis-server都必须设置了maxmemory参数
• 所有的 slots 都应该处于 online 状态, 即没有迁移任务正在执行
• 所有 server group 都必须有 Master

五、 HA(High Available)

       因为codis的proxy是无状态的,可以比较容易的搭多个proxy来实现高可用性并横向扩容。
       对Java用户来说,可以使用经过我们修改过的Jedis,Jodis ,来实现proxy层的HA。它会通过监控zk上的注册信息来实时获得当前可用的proxy列表,既可以保证高可用性,也可以通过轮流请求所有的proxy实现负载均衡。
       对下层的redis实例来说,codis的设计者认为,当一个group的master挂掉的时候,应该让管理员清楚,并手动的操作,因为这涉及到了数据一致性等问题。因此codis不会自动的将某个slave升级成master。 不过我们也提供一种解决方案:codis-ha。这是一个通过codis开放的api实现自动切换主从的工具。该工具会在检测到master挂掉的时候将其下线并选择其中一个slave提升为master继续提供服务。
       需要注意,codis将其中一个slave升级为master时,该组内其他slave实例是不会自动改变状态的,这些slave仍将试图从旧的master上同步数据,因而会导致组内新的master和其他slave之间的数据不一致。因为redis的slave of命令切换master时会丢弃slave上的全部数据,从新master完整同步,会消耗master资源。因此建议在知情的情况下手动操作。使用 codis-config server add slave 命令刷新这些节点的状态即可。codis-ha不会自动刷新其他slave的状态。

六、codis性能调研

测试环境

[link text]( “操作系统”)
[link text]( “虚拟机配置”)

  • CentOs7
  • go version go1.4.2 linux/amd64
  • Redis x 4

1. benchmark测试

redis-benchmark -h 192.168.1.201 -p 6379 -c 100 -n 100000
  1. 测试结果

2. Jodis测试

1.java代码

java程序在win7上执行,访问的是win7上的虚拟机上搭建的codis集群,集群只有一个proxy,4个redis实例。

import java.io.FileReader;import java.io.IOException;import redis.clients.jedis.Jedis;import redis.clients.jedis.JedisPoolConfig;import com.wandoulabs.jodis.JedisResourcePool;import com.wandoulabs.jodis.RoundRobinJedisPool;public class jodistest {    public static void main(String[] args){        jodistest test = new jodistest();        test.testByJedisUseShortKey();        test.testByJedisUseLongKey();        test.testByJodisUseShortKey();        test.testByJodisUseLongKey();    }    void testByJedisUseShortKey()    {        System.out.println("Enter:"+" testByJedisUseShortKey function.");        Jedis jedis = new Jedis("192.168.239.128", 19000);        //set        long startTime=System.currentTimeMillis();         long key = 0;        for(;key < 10000;key++)        {            String keyStr = Long.toString(key);            jedis.set(keyStr,keyStr);        }        long endTime=System.currentTimeMillis();        System.out.println("Jedis:short:set:"+(endTime-startTime)+" ms");         //get        startTime=System.currentTimeMillis();         key = 0;        for(;key < 10000;key++)        {            String keyStr = Long.toString(key);            jedis.get(keyStr);        }        endTime=System.currentTimeMillis();        System.out.println("Jedis:short:get:"+(endTime-startTime)+" ms");          //delete        startTime=System.currentTimeMillis();         key = 0;        for(;key < 10000;key++)        {            String keyStr = Long.toString(key);            jedis.del(keyStr);        }        endTime=System.currentTimeMillis();        System.out.println("Jedis:short:del:"+(endTime-startTime)+" ms");        jedis.close();        System.out.println("Leave:"+" testByJedisUseShortKey function.");    }    void testByJedisUseLongKey()    {        System.out.println("Enter:"+" testByJedisUseLongKey function.");        Jedis jedis = new Jedis("192.168.239.128", 19000);        int C = 0;        //String key = readFileToString("docs/key.txt");        String key = "appllo";        //set        long startTime=System.currentTimeMillis();         String keyStr = key;        for(C = 0;C < 10000;C++)        {            keyStr += "0";            jedis.set(keyStr,keyStr);        }        long endTime=System.currentTimeMillis();        System.out.println("Jedis:long:set:"+(endTime-startTime)+" ms");         //get        startTime=System.currentTimeMillis();         keyStr = key;        for(C = 0;C < 10000;C++)        {            keyStr += "0";            jedis.get(keyStr);        }        endTime=System.currentTimeMillis();        System.out.println("Jedis:long:get:"+(endTime-startTime)+" ms");         //delete        startTime=System.currentTimeMillis();         keyStr = key;        for(C = 0;C < 10000;C++)        {            keyStr += "0";            jedis.del(keyStr);        }        endTime=System.currentTimeMillis();        System.out.println("Jedis:long:del:"+(endTime-startTime)+" ms");        jedis.close();        System.out.println("Leave:"+" testByJedisUseLongKey function.");    }    void testByJodisUseShortKey()    {        System.out.println("Enter:"+" testByJodisUseShortKey function.");        JedisResourcePool jedisPool = new RoundRobinJedisPool("192.168.239.128:2181", 30000,                                         "/zk/codis/db_test/proxy", new JedisPoolConfig());        try {            //set            long startTime=System.currentTimeMillis();             for(long key = 0;key < 10000;key++)            {                Jedis jedis1 = jedisPool.getResource();                String keyStr = Long.toString(key);                jedis1.set(keyStr,keyStr);                jedis1.close();  //还给连接池            }            long endTime=System.currentTimeMillis();            System.out.println("Jodis:short:set:"+(endTime-startTime)+" ms");            //get            startTime=System.currentTimeMillis();             for(long key = 0;key < 10000;key++)            {                Jedis jedis2 = jedisPool.getResource();                String keyStr = Long.toString(key);                jedis2.get(keyStr);                jedis2.close();  //还给连接池            }            endTime=System.currentTimeMillis();            System.out.println("Jodis:short:get:"+(endTime-startTime)+" ms");            //delete            startTime=System.currentTimeMillis();             for(long key = 0;key < 10000;key++)            {                Jedis jedis3 = jedisPool.getResource();                String keyStr = Long.toString(key);                jedis3.del(keyStr);                jedis3.close();  //还给连接池            }            endTime=System.currentTimeMillis();            System.out.println("Jodis:short:del:"+(endTime-startTime)+" ms");            jedisPool.close();        }        catch(Exception e){                      e.printStackTrace();        }        System.out.println("Leave:"+" testByJodisUseShortKey function.");    }    void testByJodisUseLongKey()    {        System.out.println("Enter:"+" testByJodisUseLongKey function.");        JedisResourcePool jedisPool = new RoundRobinJedisPool("192.168.239.128:2181", 30000,             "/zk/codis/db_test/proxy", new JedisPoolConfig());        try {               //String key = readFileToString("key.txt");               String key = "appllo";               //set               long startTime=System.currentTimeMillis();               String keyStr = key;               for(long C = 0;C < 10000;C++)               {                   Jedis jedis1 = jedisPool.getResource();                   keyStr += "0";                   jedis1.set(keyStr,keyStr);                   jedis1.close();  //还给连接池               }               long endTime=System.currentTimeMillis();               System.out.println("Jodis:long:set:"+(endTime-startTime)+" ms");               //get               startTime=System.currentTimeMillis();               keyStr = key;               for(long C = 0;C < 10000;C++)               {                   Jedis jedis2 = jedisPool.getResource();                   keyStr += "0";                   jedis2.get(keyStr);                   jedis2.close();  //还给连接池               }               endTime=System.currentTimeMillis();               System.out.println("Jodis:long:get:"+(endTime-startTime)+" ms");               //delete               startTime=System.currentTimeMillis();                keyStr = key;               for(long C = 0;C < 10000;C++)               {                   Jedis jedis3 = jedisPool.getResource();                   keyStr += "0";                   jedis3.del(keyStr);                   jedis3.close();  //还给连接池               }               endTime=System.currentTimeMillis();               System.out.println("Jodis:long:del:"+(endTime-startTime)+" ms");               jedisPool.close();        }        catch(Exception e){                      e.printStackTrace();        }        System.out.println("Leave:"+" testByJodisUseLongKey function.");    }    /**     * 以行为单位读取文件,常用于读面向行的格式化文件     */    public  String readFileToString(String fileName) {        File file = new File(fileName);        BufferedReader reader = null;        String str = new String();        try {            System.out.println("以行为单位读取文件内容,一次读一整行:");            reader = new BufferedReader(new FileReader(file));            String tempString = null;            int line = 1;            // 一次读入一行,直到读入null为文件结束            while ((tempString = reader.readLine()) != null) {                // 显示行号                System.out.println("line " + line + ": " + tempString);                str += tempString;                line++;            }            reader.close();        } catch (IOException e) {            e.printStackTrace();        } finally {            if (reader != null) {                try {                    reader.close();                } catch (IOException e1) {                }            }        }        return str;    }}

2.测试结果

Enter: testByJedisUseShortKey function.Jedis:short:set:4741 msJedis:short:get:4376 msJedis:short:del:4440 msLeave: testByJedisUseShortKey function.Enter: testByJedisUseLongKey function.Jedis:long:set:10000 msJedis:long:get:9192 msJedis:long:del:6503 msLeave: testByJedisUseLongKey function.Enter: testByJodisUseShortKey function.Jodis:short:set:5002 msJodis:short:get:4360 msJodis:short:del:4018 msLeave: testByJodisUseShortKey function.Enter: testByJodisUseLongKey function.Jodis:long:set:9068 msJodis:long:get:9228 msJodis:long:del:6671 msLeave: testByJodisUseLongKey function.

通过实验发现:jodis的响应时间为0.5ms-1.0ms左右,当键值key长度在10左右时,响应时间为0.5ms,而当键值key长度为10KB时,响应时间为1.0ms,其实测试过64KB以上的键值key,但是会抛异常,出现超时异常。

参考文献

  1. AliRedis单机180w QPS, 8台服务器构建1000w QPS Cache集群
  2. 基于Twemproxy的Redis集群方案
  3. 最大的Redis集群:新浪Redis集群揭秘
  4. 高可用、开源的Redis缓存集群方案
  5. Redis 3.0正式版发布,正式支持Redis集群
  6. 高效运维最佳实践:Redis集群技术及Codis实践
  7. 国内哪些互联网公司使用了 Cassandra 数据库?
  8. Facebook 为什么不用 Cassandra 了?
  9. codis集群部署实战
  10. Codis 使用文档
  11. 豌豆荚Redis解决方案Codis安装使用
  12. Codis FAQ
  13. Codis作者黄东旭细说分布式Redis架构设计和踩过的那些坑们
  14. Reborn 使用文档
  15. 豌豆荚Redis解决方案Codis源码剖析:Proxy代理
  16. Codis性能测试
0 0