yfk的专栏
学习&记录&分享

版权声明:本文为博主原创文章,未经博主允许不得转载。
之前做了一个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;
//每个业务不同的family
private 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之后RedisKey
public 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 Node
int slot = getSlot(keyHash);
RedisDataNode redisNode = rdList.get(slot);
//写Master
JedisSentinelPool 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 Node
int slot = getSlot(keyHash);
RedisDataNode redisNode = rdList.get(slot);
//根据权重选取一个工作Instatnce
int 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 instance
if(maxWeight == 0){
return (int) (Math.random() * RANDOM_SIZE % redisInstanceList.size());
}
//获取随机数
int rand = (int) (Math.random() * RANDOM_SIZE % maxWeight);
int sum = 0;
//选取Redis Instance
for (int i = 0; i < redisInstanceList.size(); i++) {
sum += redisInstanceList.get(i).getWeight();
if (rand < sum) {
return i;
}
}
return 0;
}
顶
14
踩
0
上一篇HBase原子性保证
我的同类文章
参考知识库
猜你在找
- 阅读排行
Redis集群方案及实现(42960)
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)
- 文章分类
- 文章存档
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)