首页  ·  知识 ·  数据库
Redis集群方案及实现
网友  CSDN博客  NOSQL  编辑:Editha   图片来源:网络
我们的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


  1. //获取写哪个Redis Node  

  2. int slot = getSlot(keyHash);  

  3. RedisDataNode redisNode =  rdList.get(slot);  

  4.   

  5. //写Master  

  6. JedisSentinelPool jp = redisNode.getSentinelPool();  

  7. Jedis je = null;  

  8. boolean success = true;  

  9. try {  

  10.     je = jp.getResource();  

  11.     return je.set(key, value);  

  12. } catch (Exception e) {  

  13.     log.error("Maybe master is down", e);  

  14.     e.printStackTrace();  

  15.     success = false;  

  16.     if (je != null)  

  17.         jp.returnBrokenResource(je);  

  18.     throw e;  

  19. } finally {  

  20.     if (success && je != null) {  

  21.         jp.returnResource(je);  

  22.     }  

  23. }  


读流程

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;  

}  


本文作者:网友 来源:CSDN博客
CIO之家 www.ciozj.com 微信公众号:imciow
    >>频道首页  >>网站首页   纠错  >>投诉
版权声明:CIO之家尊重行业规范,每篇文章都注明有明确的作者和来源;CIO之家的原创文章,请转载时务必注明文章作者和来源;
延伸阅读
也许感兴趣的
我们推荐的
主题最新
看看其它的