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端封装了Redis客户端,客户端基于jedis开发
    6. 批量写/删除:不保证事务

    RedisKey

    [java] view plaincopy
     
    1. public class RedisKey implements Serializable{  
    2.     private static final long serialVersionUID = 1L;  
    3.       
    4.     //每个业务不同的family  
    5.     private String family;  
    6.       
    7.     private String key;  
    8.           
    9.     ......    
    10.     //物理保存在Redis上的key为经过MurmurHash之后的值  
    11.     private String makeRedisHashKey(){  
    12.         return String.valueOf(MurmurHash.hash64(makeRedisKeyString()));  
    13.     }  
    14.       
    15.     //ReidsKey由family.key组成  
    16.     private String makeRedisKeyString(){  
    17.         return family +":"+ key;  
    18.     }  
    19.   
    20.     //返回用户的经过Hash之后RedisKey  
    21.     public String getRedisKey(){  
    22.         return makeRedisHashKey();  
    23.     }  
    24.     .....  
    25. }  


    Family的存在时为了避免多个业务key冲突,给每个业务定义自己独立的Faimily
    出于性能考虑,参考Redis存储设计,实际保存在Redis上的key为经过hash之后的值

    接口

    目前支持的接口包括:

    [java] view plaincopy
     
    1. public interface RedisUseInterface{  
    2.     /** 
    3.      * 通过RedisKey获取value 
    4.      *  
    5.      * @param redisKey 
    6.      *           redis中的key 
    7.      * @return  
    8.      *           成功返回value,查询不到返回NULL 
    9.      */  
    10.     public String get(final RedisKey redisKey) throws Exception;  
    11.       
    12.     /** 
    13.      * 插入<k,v>数据到Redis 
    14.      *  
    15.      * @param redisKey 
    16.      *           the redis key 
    17.      * @param value 
    18.      *           the redis value 
    19.      * @return  
    20.      *           成功返回"OK",插入失败返回NULL 
    21.      */  
    22.     public String set(final RedisKey redisKey, final String value) throws Exception;  
    23.       
    24.     /** 
    25.      * 批量写入数据到Redis 
    26.      *  
    27.      * @param redisKeys 
    28.      *           the redis key list 
    29.      * @param values 
    30.      *           the redis value list 
    31.      * @return  
    32.      *           成功返回"OK",插入失败返回NULL 
    33.      */  
    34.     public String mset(final ArrayList<RedisKey> redisKeys, final ArrayList<String> values) throws Exception;  
    35.       
    36.       
    37.     /** 
    38.      * 从Redis中删除一条数据 
    39.      *  
    40.      * @param redisKey 
    41.      *           the redis key 
    42.      * @return  
    43.      *           an integer greater than 0 if one or more keys were removed 0 if none of the specified key existed 
    44.      */  
    45.     public Long del(RedisKey redisKey) throws Exception;  
    46.       
    47.     /** 
    48.      * 从Redis中批量删除数据 
    49.      *  
    50.      * @param redisKey 
    51.      *           the redis key 
    52.      * @return  
    53.      *           返回成功删除的数据条数 
    54.      */  
    55.     public Long del(ArrayList<RedisKey> redisKeys) throws Exception;  
    56.       
    57.     /** 
    58.      * 插入<k,v>数据到Redis 
    59.      *  
    60.      * @param redisKey 
    61.      *           the redis key 
    62.      * @param value 
    63.      *           the redis value 
    64.      * @return  
    65.      *           成功返回"OK",插入失败返回NULL 
    66.      */  
    67.     public String setByte(final RedisKey redisKey, final byte[] value) throws Exception;  
    68.       
    69.     /** 
    70.      * 插入<k,v>数据到Redis 
    71.      *  
    72.      * @param redisKey 
    73.      *           the redis key 
    74.      * @param value 
    75.      *           the redis value 
    76.      * @return  
    77.      *           成功返回"OK",插入失败返回NULL 
    78.      */  
    79.     public String setByte(final String redisKey, final byte[] value) throws Exception;  
    80.       
    81.     /** 
    82.      * 通过RedisKey获取value 
    83.      *  
    84.      * @param redisKey 
    85.      *           redis中的key 
    86.      * @return  
    87.      *           成功返回value,查询不到返回NULL 
    88.      */  
    89.     public byte[] getByte(final RedisKey redisKey) throws Exception;  
    90.       
    91.     /** 
    92.      * 在指定key上设置超时时间 
    93.      *  
    94.      * @param redisKey 
    95.      *           the redis key 
    96.      * @param seconds 
    97.      *           the expire seconds 
    98.      * @return  
    99.      *           1:success, 0:failed 
    100.      */  
    101.     public Long expire(RedisKey redisKey, int seconds) throws Exception;  
    102. }  

    写Redis流程

    1. 计算Redis Key Hash值
    2. 根据Hash值获取Redis Node编号
    3. 从sentinel获取Redis Node的Master
    4.  写数据到Redis

    [java] view plaincopy
     
    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.  轮询读

    [java] view plaincopy
     
    1. //获取读哪个Redis Node  
    2. int slot = getSlot(keyHash);  
    3. RedisDataNode redisNode =  rdList.get(slot);  
    4.   
    5. //根据权重选取一个工作Instatnce  
    6. int rn = redisNode.getWorkInstance();  
    7.   
    8. //轮询  
    9. int cursor = rn;  
    10. do {              
    11.     try {  
    12.         JedisPool jp = redisNode.getInstance(cursor).getJp();  
    13.         return getImpl(jp, key);  
    14.     } catch (Exception e) {  
    15.         log.error("Maybe a redis instance is down, slot : [" + slot + "]" + e);  
    16.         e.printStackTrace();  
    17.         cursor = (cursor + 1) % redisNode.getInstanceCount();  
    18.         if(cursor == rn){  
    19.             throw e;  
    20.         }  
    21.     }  
    22. while (cursor != rn);  




    权重计算

    初始化的时候,会给每个Redis Instatnce赋一个权重值weight
    根据权重获取Redis Instance的代码:

    [java] view plaincopy
     
      1. public int getWorkInstance() {  
      2.     //没有定义weight,则完全随机选取一个redis instance  
      3.     if(maxWeight == 0){  
      4.         return (int) (Math.random() * RANDOM_SIZE % redisInstanceList.size());  
      5.     }  
      6.       
      7.     //获取随机数  
      8.     int rand = (int) (Math.random() * RANDOM_SIZE % maxWeight);  
      9.     int sum = 0;  
      10.   
      11.     //选取Redis Instance  
      12.     for (int i = 0; i < redisInstanceList.size(); i++) {  
      13.         sum += redisInstanceList.get(i).getWeight();  
      14.         if (rand < sum) {  
      15.             return i;  
      16.         }  
      17.     }  
      18.       
      19.     return 0;  
      20. }  
  • 相关阅读:
    c#时间函数
    .NET中的lock(C#版本)
    关于OpenSmtp邮件标题过长,附件名,用户名出现乱码问题的终及解决Dll文件
    Windbg 用法
    使用OpenXML将数据导入到Excel模板中
    Compiere源码workspace的两个配置文件内容
    我学MEF系列(8):MEF+Unity实现可扩展拦截器
    基于插件架构的简单的Winform框架(上)
    我学Flash/Flex(2):AS3读取XML文件内容
    我学Flash/Flex(1):Action Script3.0基础知识
  • 原文地址:https://www.cnblogs.com/zhwl/p/4702512.html
Copyright © 2011-2022 走看看