码迷,mamicode.com
首页 > Web开发 > 详细

Redis集群方案及实现 - yfk的专栏 - 博客频道 - CSDN.NET

时间:2016-08-21 18:37:05      阅读:244      评论:0      收藏:0      [点我收藏+]

标签:

   

Redis集群方案及实现

    43035人阅读     评论(15)     收藏     举报    
技术分享     分类:    

之前做了一个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

   
   

参考知识库

  • 猜你在找

查看评论      
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场          
  技术分享  

Redis集群方案及实现 - yfk的专栏 - 博客频道 - CSDN.NET

标签:

原文地址:http://www.cnblogs.com/lhdwr/p/5793080.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!