yfk的专栏
学习&记录&分享
Redis集群方案及实现
版权声明:本文为博主原创文章,未经博主允许不得转载。
之前做了一个Redis的集群方案,跑了小半年,线上运行的很稳定
差不多可以跟大家分享下经验,前面写了一篇文章 数据在线服务的一些探索经验,可以做为背景阅读
应用
我们的Redis集群主要承担了以下服务:1. 实时推荐
2. 用户画像
3. 诚信分值服务
集群状况
集群峰值QPS 1W左右,RW响应时间999线在1ms左右整个集群:
1. Redis节点: 8台物理机;每台128G内存;每台机器上8个instance
2. Sentienl:3台虚拟机
集群方案
Redis Node由一组Redis Instance组成,一组Redis Instatnce可以有一个Master Instance,多个Slave Instance
Redis官方的cluster还在beta版本,参看Redis cluster tutorial
在做调研的时候,曾经特别关注过KeepAlived+VIP 和 Twemproxy
不过最后还是决定基于Redis Sentinel实现一套,整个项目大概在1人/1个半月
整体设计
1. 数据Hash分布在不同的Redis Instatnce上2. M/S的切换采用Sentinel
3. 写:只会写master Instance,从sentinel获取当前的master Instane
4. 读:从Redis Node中基于权重选取一个Redis Instance读取,失败/超时则轮询其他Instance
5. 通过RPC服务访问,RPC server端封装了Redis客户端,客户端基于jedis开发
6. 批量写/删除:不保证事务
RedisKey
public class RedisKey implements Serializable{private static final long serialVersionUID = 1L;//每个业务不同的familyprivate String family;private String key;...... //物理保存在Redis上的key为经过MurmurHash之后的值private String makeRedisHashKey(){return String.valueOf(MurmurHash.hash64(makeRedisKeyString()));}//ReidsKey由family.key组成private String makeRedisKeyString(){return family +":"+ key;}//返回用户的经过Hash之后RedisKeypublic String getRedisKey(){return makeRedisHashKey();}.....
}
Family的存在时为了避免多个业务key冲突,给每个业务定义自己独立的Faimily
出于性能考虑,参考Redis存储设计,实际保存在Redis上的key为经过hash之后的值
接口
目前支持的接口包括:public interface RedisUseInterface{/*** 通过RedisKey获取value* * @param redisKey* redis中的key* @return * 成功返回value,查询不到返回NULL*/public String get(final RedisKey redisKey) throws Exception;/*** 插入<k,v>数据到Redis* * @param redisKey* the redis key* @param value* the redis value* @return * 成功返回"OK",插入失败返回NULL*/public String set(final RedisKey redisKey, final String value) throws Exception;/*** 批量写入数据到Redis* * @param redisKeys* the redis key list* @param values* the redis value list* @return * 成功返回"OK",插入失败返回NULL*/public String mset(final ArrayList<RedisKey> redisKeys, final ArrayList<String> values) throws Exception;/*** 从Redis中删除一条数据* * @param redisKey* the redis key* @return * an integer greater than 0 if one or more keys were removed 0 if none of the specified key existed*/public Long del(RedisKey redisKey) throws Exception;/*** 从Redis中批量删除数据* * @param redisKey* the redis key* @return * 返回成功删除的数据条数*/public Long del(ArrayList<RedisKey> redisKeys) throws Exception;/*** 插入<k,v>数据到Redis* * @param redisKey* the redis key* @param value* the redis value* @return * 成功返回"OK",插入失败返回NULL*/public String setByte(final RedisKey redisKey, final byte[] value) throws Exception;/*** 插入<k,v>数据到Redis* * @param redisKey* the redis key* @param value* the redis value* @return * 成功返回"OK",插入失败返回NULL*/public String setByte(final String redisKey, final byte[] value) throws Exception;/*** 通过RedisKey获取value* * @param redisKey* redis中的key* @return * 成功返回value,查询不到返回NULL*/public byte[] getByte(final RedisKey redisKey) throws Exception;/*** 在指定key上设置超时时间* * @param redisKey* the redis key* @param seconds* the expire seconds* @return * 1:success, 0:failed*/public Long expire(RedisKey redisKey, int seconds) throws Exception;
}
写Redis流程
1. 计算Redis Key Hash值2. 根据Hash值获取Redis Node编号
3. 从sentinel获取Redis Node的Master
4. 写数据到Redis
//获取写哪个Redis Nodeint slot = getSlot(keyHash);RedisDataNode redisNode = rdList.get(slot);//写MasterJedisSentinelPool jp = redisNode.getSentinelPool();Jedis je = null;boolean success = true;try {je = jp.getResource();return je.set(key, value);} catch (Exception e) {log.error("Maybe master is down", e);e.printStackTrace();success = false;if (je != null)jp.returnBrokenResource(je);throw e;} finally {if (success && je != null) {jp.returnResource(je);}}
读流程
1. 计算Redis Key Hash值2. 根据Hash值获取Redis Node编号
3. 根据权重选取一个Redis Instatnce
4. 轮询读
//获取读哪个Redis Nodeint slot = getSlot(keyHash);RedisDataNode redisNode = rdList.get(slot);//根据权重选取一个工作Instatnceint rn = redisNode.getWorkInstance();//轮询int cursor = rn;do { try {JedisPool jp = redisNode.getInstance(cursor).getJp();return getImpl(jp, key);} catch (Exception e) {log.error("Maybe a redis instance is down, slot : [" + slot + "]" + e);e.printStackTrace();cursor = (cursor + 1) % redisNode.getInstanceCount();if(cursor == rn){throw e;}}} while (cursor != rn);
权重计算
初始化的时候,会给每个Redis Instatnce赋一个权重值weight根据权重获取Redis Instance的代码:
public int getWorkInstance() {//没有定义weight,则完全随机选取一个redis instanceif(maxWeight == 0){return (int) (Math.random() * RANDOM_SIZE % redisInstanceList.size());}//获取随机数int rand = (int) (Math.random() * RANDOM_SIZE % maxWeight);int sum = 0;//选取Redis Instancefor (int i = 0; i < redisInstanceList.size(); i++) {sum += redisInstanceList.get(i).getWeight();if (rand < sum) {return i;}}return 0;}
顶
14
踩
0
上一篇HBase原子性保证
下一篇Vector Clock理解
我的同类文章
参考知识库
猜你在找
- 联系方式
新浪微博:周晓敏_yfk
Mail:zxmever@gmail.com
- 博客专栏
Hive应用 文章:23篇 阅读:217029 |
网络编程 文章:5篇 阅读:20656 |
Hadoop Streaming实战 文章:14篇 阅读:79933 |
- 阅读排行
Redis集群方案及实现(42960)
Linux下多线程查看工具(pstree、ps、pstack)(38544)
hive修改表模式(31241)
Kafka(二):环境搭建&测试(30631)
Hadoop数据传输工具sqoop(28806)
Kafka(一):基础(25121)
hive数据导入(19647)
hive UDF(18999)
TcMalloc,A Big Surprise!(18019)
Redis Cluster(Redis 3.X)设计要点(17715)
- 文章分类
c/c++(26)
hadoop(30)
Linux(16)
STL(10)
Program(22)
Design Pattern(14)
Architecture(16)
好文转载(5)
开源学习分享(25)
算法&数据结构(11)
工程实践(8)
web(4)
Redis(13)
Android(1)
网络编程(5)
数据仓库(30)
传输(4)
- 文章存档
2016年07月(1)
2015年12月(1)
2014年10月(2)
2014年08月(1)
2014年07月(1)
2014年06月(1)
2014年05月(2)
2014年04月(1)
2014年03月(3)
2014年01月(2)
2013年12月(5)
2013年11月(1)
2013年10月(1)
2013年09月(2)
2013年08月(1)
2013年07月(1)
2013年06月(1)
2013年05月(1)
2013年04月(1)
2013年03月(2)
2013年02月(2)
2013年01月(2)
2012年12月(2)
2012年11月(2)
2012年10月(5)
2012年09月(2)
2012年08月(7)
2012年07月(6)
2012年06月(1)
2012年05月(3)
2012年04月(6)
2012年03月(3)
2012年02月(13)
2012年01月(5)
2011年12月(5)
2011年11月(9)
2011年10月(10)
2011年09月(8)
2011年08月(7)
2011年07月(6)
2011年06月(10)
2011年05月(16)
2011年04月(2)
- 推荐文章
* 致JavaScript也将征服的物联网世界
* 从苏宁电器到卡巴斯基:难忘的三年硕士时光
* 作为一名基层管理者如何利用情商管理自己和团队(一)
* Android CircleImageView圆形ImageView
* 高质量代码的命名法则