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
[java] view plaincopy
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之后的值

接口
目前支持的接口包括:
[java] view plaincopy
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
[java] view plaincopy
//获取写哪个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. 轮询读
[java] view plaincopy
//获取读哪个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的代码:
[java] view plaincopy
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;  

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值