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 特点
- 无状态的Proxy based
- 无需重启客户端动态的添加或移除redis实例或者proxy实例,安全透明的数据迁移
- 支持redis以及基于redis协议的数据库
- 图形界面运维工具dashboard和管理员工具
- 支持大多数redis命令,完全兼容Twemproxy
- 支持原生的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 的产品, 各项配置都不会冲突. - Codis2.0 集群搭建(见另一个文档)
- 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
- 测试结果
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,但是会抛异常,出现超时异常。
参考文献
- AliRedis单机180w QPS, 8台服务器构建1000w QPS Cache集群
- 基于Twemproxy的Redis集群方案
- 最大的Redis集群:新浪Redis集群揭秘
- 高可用、开源的Redis缓存集群方案
- Redis 3.0正式版发布,正式支持Redis集群
- 高效运维最佳实践:Redis集群技术及Codis实践
- 国内哪些互联网公司使用了 Cassandra 数据库?
- Facebook 为什么不用 Cassandra 了?
- codis集群部署实战
- Codis 使用文档
- 豌豆荚Redis解决方案Codis安装使用
- Codis FAQ
- Codis作者黄东旭细说分布式Redis架构设计和踩过的那些坑们
- Reborn 使用文档
- 豌豆荚Redis解决方案Codis源码剖析:Proxy代理
- Codis性能测试
- Codis调研
- codis
- Codis
- 调研
- 调研
- 调研
- codis评测
- codis测试
- Codis 使用
- 搭建codis
- 搭建Codis
- [CODIS原理]: CODIS-HA实现原理
- redis和codis分享
- codis安装注意事项
- Redis 集群解决方案 Codis
- Codis 使用文档
- codis集群建设方案
- 基于Docker部署codis
- Android---读取与创建JSON格式的数据
- Junit3 与Junit4 比较
- 南邮ACM-NOJ1003-Fibonacci
- Xcode 7:Storyboard Reference、Strong IBOutlet以及Scene Dock
- eclipse最有用快捷键整理
- Codis调研
- qt中某个类的头文件无法找到
- xml文件常见问题及解答
- 快速高效学习Java编程在线资源Top 20
- Jquery选择器
- MySQL命令行远程链接服务器
- MySQL
- ActiveMQ学习笔记(2)——JMS消息模型
- NSDateFormatter中dateFormat --- 日期格式含义。