Redis集羣方案及實現Sentienl

轉載原文鏈接:http://blog.csdn.net/yfkiss/article/details/38944179

之前做了一個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 plain copy
  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 plain copy
  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 plain copy
  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 plain copy
  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 plain copy
  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. }  


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章