Redis集群方案及实现

标签: redis 集群 | 发表时间:2014-08-31 01:20 | 作者:yfkiss
出处:http://blog.csdn.net

之前做了一个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上
1. M/S的切换采用Sentinel
2. 写:只会写master Instance,从sentinel获取当前的master Instane
3. 读:从Redis Node中基于权重选取一个Redis Instance读取,失败/超时则轮询其他Instance
4. 通过RPC服务访问,RPC server端封装了Redis客户端,客户端基于jedis开发
5. 批量写/删除:不保证事务

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;
	}



作者:yfkiss 发表于2014-8-30 17:20:08 原文链接
阅读:75 评论:0 查看评论

相关 [redis 集群] 推荐:

Redis集群功能说明

- gOODiDEA - NoSQLFan
虽然目前可以通过在客户端做hash的方法来构建Redis集群,但Redis原生的集群支持还是颇受期待. 本文是对Redis集群功能官方描述文档的一个翻译,译者是@PPS萝卜同学,也感谢他的投稿分享. 这篇文档主要是为了说明正在进展中的Redis集群功能. 文档主要分为两个部分,前一部分主要介绍我在非稳定分支已完成的代码,后一部分主要介绍还有哪些功能待实现.

redis集群(主从配置)

- - 操作系统 - ITeye博客
市面上太多kv的缓存,最常用的就属memcache了,但是memcache存在单点问题,不过小日本有复制版本,但是使用的人比较少,redis的出现让kv内存存储的想法成为现实. 今天主要内容便是redis主从实现简单的集群,实际上redis的安装配置砸门ttlsa之前就有个文章,废话少说,进入正题吧.

Redis集群明细文档

- - CSDN博客架构设计推荐文章
  Redis目前版本是没有提供集群功能的,如果要实现多台Redis同时提供服务只能通过客户端自身去实现(Memchached也是客户端实现分布式). 目前根据文档已经看到Redis正在开发集群功能,其中一部分已经开发完成,但是具体什么时候可以用上,还不得而知. 文档来源: http://redis.io/topics/cluster-spec.

BDRP分布式redis集群

- - 百度运维团队技术博客
BDRP(baidu distributed redis platform)是包含 twemproxy, redis,redis-sentinel等多个模块开发的分布式redis平台. bdrp已经在github上进行了开源, bdrp的github项目点这里. 目前redis集群架构主要有以下几个组件: twemproxy:redis的代理系统,可以选择多种数据分片算法 redis:集群的redis存储节点 sentinel:redis官方的集群高可用组件,可以监控redis主节点故障,并进行主备切换.

Redis集群“倾斜”问题

- - 今天
对分布式存储系统的架构和运维管理,如何保证每个Node的数据存储容量和请求量尽量均衡,是非常重要的. 本文介绍Redis大集群运维过程中,常见导致数据和请求量“倾斜”的场景,及规避措施. Redis数据容量或请求量严重”倾斜”的影响. 以下从运维的角度解释,Redis数十节点的集群,出现数据容量和请求量倾斜情况下,存在的一些痛点:.

Redis核心解读–集群管理工具(Redis-sentinel)

- - NoSQLFan
Redis作为高性能的key-value存储,一直在单实例上表现良好,但是长期以来一直缺乏一种官方的 高可用方案支持. 于是 Redis-sentinel应运而生,提供了对客户端透明的高可用支持. 下面文章对Redis- sentinel的原理进行了系统的讲解. 文章来源: www.wzxue.com.

基于Redis Sentinel的Redis集群(主从&Sharding)高可用方案

- - 开源软件 - ITeye博客
本文主要介绍一种通过Jedis&Sentinel实现Redis集群高可用方案,该方案需要使用Jedis2.2.2及以上版本(强制),Redis2.8及以上版本(可选,Sentinel最早出现在Redis2.4中,Redis2.8中Sentinel更加稳定),Redis集群是以分片(Sharding)加主从的方式搭建,满足可扩展性的要求;.

Redis cluster tutorial Redis集群教程 官方教程 翻译 (一)

- - 互联网 - ITeye博客
这篇文档是一个总体介绍, 不使用复杂的分布式概念. 本文介绍如何建立一个集群, 测试和使用, 详细说明请参看 Redis Cluster specification. 注意, 如果你打算实际使用Redis集群, 推荐看正式的规范文档. Redis集群现在还在alpha测试, 请加入Redis邮件列表, 或Github repository的讨论组.

Staircar:Tumblr的Redis集群控制层

- 悟怡 - NoSQLFan
Tumblr是世界上最流行的轻博客服务,其用户量在最近的一次统计中已经达到2090万,超过了全球最大的博客服务WordPress. 而我们今天要介绍的是Tumblr通知系统的架构,其通知系统由一个叫Staircar的轻量级HTTP服务器和其下层的大规模Redis集群组成. 在Tumblr初期,其通知系统是由MySQL+Memcached的传统架构组成,但是由于通知系统庞大的添加操作,导致MySQL负担非常大,经常搞得InnoDB global transaction max(1024)都超出了.