您的位置:首页 > 数据库 > Redis

Redis集群方案及实现

2015-08-26 14:49 656 查看
之前做了一个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;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: