Redis集群方案及实现 青旅半醒 2022-07-16 11:28 193阅读 0赞 之前做了一个Redis的集群方案,跑了小半年,线上运行的很稳定 差不多可以跟大家分享下经验,前面写了一篇文章[ 数据在线服务的一些探索经验][Link 1],可以做为背景阅读 ### 应用 ### 我们的Redis集群主要承担了以下服务: 1. 实时推荐 2. 用户画像 3. 诚信分值服务 ### 集群状况 ### 集群峰值QPS 1W左右,RW响应时间999线在1ms左右 整个集群: 1. Redis节点: 8台物理机;每台128G内存;每台机器上8个instance 2. Sentienl:3台虚拟机 ### 集群方案 ### ![SouthEast][] 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][view plain] 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][view plain] 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][view plain] 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][view plain] 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][view plain] 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. \} [Link 1]: http://blog.csdn.net/yfkiss/article/details/25688153 [SouthEast]: /images/20220716/1a2c32388f3940e58dd677382c413001.png [Redis cluster tutorial]: http://redis.io/topics/cluster-tutorial [view plain]: http://blog.csdn.net/yfkiss/article/details/38944179#
还没有评论,来说两句吧...