zoukankan      html  css  js  c++  java
  • 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端封装了Redisclient,client基于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;
    	}



    版权声明:本文博客原创文章,博客,未经同意,不得转载。

  • 相关阅读:
    对面向对象设计原则的总结
    sql server连接字符串
    js页面加载进度条
    Yui.Compressor高性能ASP.NET开发:自动压缩CSS、JS
    asp.net利用多线程执行长时间的任务,客户端显示出任务的执行进度的示例(一)_转
    asp.net删除目录,Session丢失
    extjs ComboBox使用注意
    转:使Eclipse的智能感知可以像 Visual Studio 一样快速提示
    Android ContentProvider 填删改查 实例
    Windows Phone StackPanel 布局示例
  • 原文地址:https://www.cnblogs.com/blfshiye/p/4677089.html
Copyright © 2011-2022 走看看