之前做了一个Redis的集群方案,跑了小半年,线上运行的很稳定
差不多可以跟大家分享下经验,前面写了一篇文章 数据在线服务的一些探索经验,可以做为背景阅读

应用

我们的Redis集群主要承担了以下服务:
1. 实时推荐
2. 用户画像
3. 诚信分值服务

集群状况

集群峰值QPS 1W左右,RW响应时间999线在1ms左右
整个集群:
1. Redis节点: 8台物理机;每台128G内存;每台机器上8个instance
2. Sentienl:3台虚拟机

集群方案


redis cluser集群怎么用代码去调用 redis集群怎么实现_java


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

 


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之后的值

接口

目前支持的接口包括:


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

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. "Maybe master is down", e);  
14.     e.printStackTrace();  
15. 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.  轮询读


 

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. "Maybe a redis instance is down, slot : [" + slot + "]" + e);  
16.         e.printStackTrace();  
17. 1) % redisNode.getInstanceCount();  
18. if(cursor == rn){  
19. throw e;  
20.         }  
21.     }  
22. } while (cursor != rn);

权重计算

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


 

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. }  
21.