`

Redis上踩过的一些坑-美团

 
阅读更多

上上周和同事(龙哥)参加了360组织的互联网技术训练营第三期,美团网的DBA负责人侯军伟给大家介绍了美团网在redis上踩得一些坑,讲的都是干货和坑。

分为5个部分:

二、redis bgrewriteaof问题

三、redis内存占用飙升

四、redis内存使用优化

五、redis cluster遇到的一些问题

一、周期性出现connect timeout

1. 背景:
大部分互联网公司都会有Mysql或者Oracle的DBA,但是在Nosql方面一般不会设置专门的DBA。不过对于一些知名的互联网公司来说,Nosql的使用量是巨大的,所以通常让Mysql的DBA或者单独聘请工程师来维护一些Nosql数据库,比如:
Redis, Hbase, Memcache(其实严格讲不是nosql), Mongodb, Cassandra。从讲座看美团网应该是有专职的Redis DBA。所以作为业务开发人员不需要自己安装、配置、运维Redis,只需要找Redis DBA来申请就可以了。
这里为了简化说明:Redis DBA提供的服务叫做Redis云,业务开发人员叫做业务端(redis的使用者)
2. 现象:
业务端在使用redis云提供的redis服务后,经常出现connect timeout:
Java代码收藏代码
  1. redis.clients.jedis.exceptions.JedisConnectionException
  2. java.net.SocketException
  3. java.net.SocketTimeoutException:connecttimeout
3. 分析和怀疑:
业务端一般认为redis出现问题,就是redis云有问题,人的“正常”思维:看别人错误容易,发现自己难,扯多了, 出现这个有很多原因:
(1).网络原因:比如是否存在跨机房、网络割接等等。
(2). 慢查询,因为redis是单线程,如果有慢查询的话,会阻塞住之后的操作。
(3).value值过大?比如value几十兆,当然这种情况比较少,其实也可以看做是慢查询的一种
(4). aof重写/rdb fork发生?瞬间会堵一下Redis服务器。
(5). 其他..................
4.查询原因
演讲者一开始怀疑是网络问题,但是并未发现问题,观察各种对比图表,tcplistenOverFlow和timeout经常周期出现。(赞一下这个监控,我们监控现在还没有这个层面的)
有关listenOverFlow:
查看现有的连接数是否大于设置的backlog,如果大于就丢弃,并相应的参数值加1。其中backlog是由程序和系统参数net.core.somaxconn共同设置,当backlog的值大于系统设置的net.core.somaxconn时则取net.core.somaxconn的值,否则取程序设置的backlog值。这种出错的方式也被记录在TcpListenOverflows中(其只记录了连接个数不足而产生溢出错误的次数!)。
觉得可能和TCP相关,于是分析了Tcp三次握手:最后一次握手客户端的请求会进入服务器端的一个队列(可以认为是下三图)中,如果这个队列满了,就会发生上面的异常。(accept)
(1) TCP三次握手:
(2) redis客户端与redis服务器交互的过程(本质就是TCP请求)
(3)I/O 多路复用程序通过队列向文件事件分派器传送套接字的过程
(4) 和redis有什么关系呢?
由于Redis的单线程模型(对命令的处理和连接的处理都是在一个线程中),如果存在慢查询的话,会出现上面的这种情况,造成新的accept的连接进不了队列。
如果上面的图没法理解的话,看看这张图:
5. 解决方法:
(1) 对慢查询进行持久化,比如定时存放到mysql之类。(redis的慢查询只是一个list,超过list设置的最大值,会清除掉之前的数据,也就是看不到历史)
(2) 对慢查询进行报警(频率、数量、时间)等等因素
(3) 打屁股,哈哈:
(4) 其实应该做的是:对业务端进行培训,告诉他们一下redis开发的坑,redis不是万金油,这个和Mysql DBA要培训Mysql使用者一样,否则防不胜防。
比如他执行了 monitor, keys *, flushall, drop table, update table set a=1; 这种也是防不胜防的(当然也可以做限制,利用rename-command一个随机数),但是提高工程师的水平才是关键。

二、redis bgrewriteaof问题

一、背景

1. AOF:

Redis的AOF机制有点类似于Mysql binlog,是Redis的提供的一种持久化方式(另一种是RDB),它会将所有的写命令按照一定频率(no, always, every seconds)写入到日志文件中,当Redis停机重启后恢复数据库。

2. AOF重写:

(1) 随着AOF文件越来越大,里面会有大部分是重复命令或者可以合并的命令(100次incr = set key 100)

(2) 重写的好处:减少AOF日志尺寸,减少内存占用,加快数据库恢复时间。

二、单机多实例可能存在Swap和OOM的隐患:

由于Redis的单线程模型,理论上每个redis实例只会用到一个CPU, 也就是说可以在一台多核的服务器上部署多个实例(实际就是这么做的)。但是Redis的AOF重写是通过fork出一个Redis进程来实现的,所以有经验的Redis开发和运维人员会告诉你,在一台服务器上要预留一半的内存(防止出现AOF重写集中发生,出现swap和OOM)。

三、最佳实践

1. meta信息:作为一个redis云系统,需要记录各个维度的数据,比如:业务组、机器、实例、应用、负责人多个维度的数据,相信每个Redis的运维人员都应该有这样的持久化数据(例如Mysql),一般来说还有一些运维界面,为自动化和运维提供依据

例如如下:

2. AOF的管理方式:

(1) 自动:让每个redis决定是否做AOF重写操作(根据auto-aof-rewrite-percentage和auto-aof-rewrite-min-size两个参数):

(2) crontab: 定时任务,可能仍然会出现多个redis实例,属于一种折中方案。

(3) remote集中式:

最终目标是一台机器一个时刻,只有一个redis实例进行AOF重写。

具体做法其实很简单,以机器为单位,轮询每个机器的实例,如果满足条件就运行(比如currentSize和baseSize满足什么关系)bgrewriteaof命令。

期间可以监控发生时间、耗时、频率、尺寸的前后变化

策略 优点 缺点
自动 无需开发

1. 有可能出现(无法预知)上面提到的Swap和OOM

2. 出了问题,处理起来其实更费时间。

AOF控制中心(remote集中式)

1. 防止上面提到Swap和OOM。

2. 能够收集更多的数据(aof重写的发生时间、耗时、频率、尺寸的前后变化),更加有利于运维和定位问题(是否有些机器的实例需要拆分)。

控制中心需要开发。

一台机器轮询执行bgRewriteAof代码示例:

Java代码收藏代码
  1. packagecom.sohu.cache.inspect.impl;
  2. importcom.sohu.cache.alert.impl.BaseAlertService;
  3. importcom.sohu.cache.entity.InstanceInfo;
  4. importcom.sohu.cache.inspect.InspectParamEnum;
  5. importcom.sohu.cache.inspect.Inspector;
  6. importcom.sohu.cache.util.IdempotentConfirmer;
  7. importcom.sohu.cache.util.TypeUtil;
  8. importorg.apache.commons.collections.MapUtils;
  9. importorg.apache.commons.lang.StringUtils;
  10. importredis.clients.jedis.Jedis;
  11. importjava.util.Collections;
  12. importjava.util.LinkedHashMap;
  13. importjava.util.List;
  14. importjava.util.Map;
  15. importjava.util.concurrent.TimeUnit;
  16. publicclassRedisIsolationPersistenceInspectorextendsBaseAlertServiceimplementsInspector{
  17. publicstaticfinalintREDIS_DEFAULT_TIME=5000;
  18. @Override
  19. publicbooleaninspect(Map<InspectParamEnum,Object>paramMap){
  20. //某台机器和机器下所有redis实例
  21. finalStringhost=MapUtils.getString(paramMap,InspectParamEnum.SPLIT_KEY);
  22. List<InstanceInfo>list=(List<InstanceInfo>)paramMap.get(InspectParamEnum.INSTANCE_LIST);
  23. //遍历所有的redis实例
  24. for(InstanceInfoinfo:list){
  25. finalintport=info.getPort();
  26. finalinttype=info.getType();
  27. intstatus=info.getStatus();
  28. //非正常节点
  29. if(status!=1){
  30. continue;
  31. }
  32. if(TypeUtil.isRedisDataType(type)){
  33. Jedisjedis=newJedis(host,port,REDIS_DEFAULT_TIME);
  34. try{
  35. //从redisinfo中索取持久化信息
  36. Map<String,String>persistenceMap=parseMap(jedis);
  37. if(persistenceMap.isEmpty()){
  38. logger.error("{}:{}getpersistenceMapfailed",host,port);
  39. continue;
  40. }
  41. //如果正在进行aof就不做任何操作,理论上要等待它完毕,否则
  42. if(!isAofEnabled(persistenceMap)){
  43. continue;
  44. }
  45. //上一次aof重写后的尺寸和当前aof的尺寸
  46. longaofCurrentSize=MapUtils.getLongValue(persistenceMap,"aof_current_size");
  47. longaofBaseSize=MapUtils.getLongValue(persistenceMap,"aof_base_size");
  48. //阀值大于60%
  49. longaofThresholdSize=(long)(aofBaseSize*1.6);
  50. doublepercentage=getPercentage(aofCurrentSize,aofBaseSize);
  51. //大于60%且超过60M
  52. if(aofCurrentSize>=aofThresholdSize&&aofCurrentSize>(64*1024*1024)){
  53. //bgRewriteAof异步操作。
  54. booleanisInvoke=invokeBgRewriteAof(jedis);
  55. if(!isInvoke){
  56. logger.error("{}:{}invokeBgRewriteAoffailed",host,port);
  57. continue;
  58. }else{
  59. logger.warn("{}:{}invokeBgRewriteAofstartedpercentage={}",host,port,percentage);
  60. }
  61. //等待Aof重写成功(bgRewriteAof是异步操作)
  62. while(true){
  63. try{
  64. //beforewait1s
  65. TimeUnit.SECONDS.sleep(1);
  66. Map<String,String>loopMap=parseMap(jedis);
  67. IntegeraofRewriteInProgress=MapUtils.getInteger(loopMap,"aof_rewrite_in_progress",null);
  68. if(aofRewriteInProgress==null){
  69. logger.error("loopwatch:{}:{}returnfailed",host,port);
  70. break;
  71. }elseif(aofRewriteInProgress<=0){
  72. //bgrewriteaofDone
  73. logger.warn("{}:{}bgrewriteaofDonelastSize:{}Mb,currentSize:{}Mb",host,port,
  74. getMb(aofCurrentSize),
  75. getMb(MapUtils.getLongValue(loopMap,"aof_current_size")));
  76. break;
  77. }else{
  78. //wait1s
  79. TimeUnit.SECONDS.sleep(1);
  80. }
  81. }catch(Exceptione){
  82. logger.error(e.getMessage(),e);
  83. }
  84. }
  85. }else{
  86. if(percentage>50D){
  87. longcurrentSize=getMb(aofCurrentSize);
  88. logger.info("checked{}:{}aofincreasepercentage:{}%currentSize:{}Mb",host,port,
  89. percentage,currentSize>0?currentSize:"<1");
  90. }
  91. }
  92. }finally{
  93. jedis.close();
  94. }
  95. }
  96. }
  97. returntrue;
  98. }
  99. privatelonggetMb(longbytes){
  100. return(long)(bytes/1024/1024);
  101. }
  102. privatebooleanisAofEnabled(Map<String,String>infoMap){
  103. IntegeraofEnabled=MapUtils.getInteger(infoMap,"aof_enabled",null);
  104. returnaofEnabled!=null&&aofEnabled==1;
  105. }
  106. privatedoublegetPercentage(longaofCurrentSize,longaofBaseSize){
  107. if(aofBaseSize==0){
  108. return0.0D;
  109. }
  110. Stringformat=String.format("%.2f",(Double.valueOf(aofCurrentSize-aofBaseSize)*100/aofBaseSize));
  111. returnDouble.parseDouble(format);
  112. }
  113. privateMap<String,String>parseMap(finalJedisjedis){
  114. finalStringBuilderbuilder=newStringBuilder();
  115. booleanisInfo=newIdempotentConfirmer(){
  116. @Override
  117. publicbooleanexecute(){
  118. StringpersistenceInfo=null;
  119. try{
  120. persistenceInfo=jedis.info("Persistence");
  121. }catch(Exceptione){
  122. logger.warn(e.getMessage()+"-{}:{}",jedis.getClient().getHost(),jedis.getClient().getPort(),
  123. e.getMessage());
  124. }
  125. booleanisOk=StringUtils.isNotBlank(persistenceInfo);
  126. if(isOk){
  127. builder.append(persistenceInfo);
  128. }
  129. returnisOk;
  130. }
  131. }.run();
  132. if(!isInfo){
  133. logger.error("{}:{}infoPersistencefailed",jedis.getClient().getHost(),jedis.getClient().getPort());
  134. returnCollections.emptyMap();
  135. }
  136. StringpersistenceInfo=builder.toString();
  137. if(StringUtils.isBlank(persistenceInfo)){
  138. returnCollections.emptyMap();
  139. }
  140. Map<String,String>map=newLinkedHashMap<String,String>();
  141. String[]array=persistenceInfo.split("\r\n");
  142. for(Stringline:array){
  143. String[]cells=line.split(":");
  144. if(cells.length>1){
  145. map.put(cells[0],cells[1]);
  146. }
  147. }
  148. returnmap;
  149. }
  150. publicbooleaninvokeBgRewriteAof(finalJedisjedis){
  151. returnnewIdempotentConfirmer(){
  152. @Override
  153. publicbooleanexecute(){
  154. try{
  155. Stringresponse=jedis.bgrewriteaof();
  156. if(response!=null&&response.contains("rewritingstarted")){
  157. returntrue;
  158. }
  159. }catch(Exceptione){
  160. Stringmessage=e.getMessage();
  161. if(message.contains("rewritingalready")){
  162. returntrue;
  163. }
  164. logger.error(message,e);
  165. }
  166. returnfalse;
  167. }
  168. }.run();
  169. }
  170. }

附图一张:

三、redis内存占用飙升

一、现象:
redis-cluster某个分片内存飙升,明显比其他分片高很多,而且持续增长。并且主从的内存使用量并不一致。
二、分析可能原因:
1. redis-cluster的bug (这个应该不存在)
2. 客户端的hash(key)有问题,造成分配不均。(redis使用的是crc16, 不会出现这么不均的情况)
3. 存在个别大的key-value: 例如一个包含了几百万数据set数据结构(这个有可能)
4. 主从复制出现了问题。
5. 其他原因
三、调查原因:
1. 经查询,上述1-4都不存在
2. 观察info信息,有一点引起了怀疑: client_longes_output_list有些异常。
3. 于是理解想到服务端和客户端交互时,分别为每个客户端设置了输入缓冲区和输出缓冲区,这部分如果很大的话也会占用Redis服务器的内存。
从上面的client_longest_output_list看,应该是输出缓冲区占用内存较大,也就是有大量的数据从Redis服务器向某些客户端输出。
于是使用client list命令(类似于mysql processlist) redis-cli -h host -p port client list | grep -v "omem=0",来查询输出缓冲区不为0的客户端连接,于是查询到祸首monitor,于是豁然开朗.
monitor的模型是这样的,它会将所有在Redis服务器执行的命令进行输出,通常来讲Redis服务器的QPS是很高的,也就是如果执行了monitor命令,Redis服务器在Monitor这个客户端的输出缓冲区又会有大量“存货”,也就占用了大量Redis内存。
四、紧急处理和解决方法
进行主从切换(主从内存使用量不一致),也就是redis-cluster的fail-over操作,继续观察新的Master是否有异常,通过观察未出现异常。
查找到真正的原因后,也就是monitor,关闭掉monitor命令的进程后,内存很快就降下来了。
五、 预防办法:
1. 为什么会有monitor这个命令发生,我想原因有两个:
(1). 工程师想看看究竟有哪些命令在执行,就用了monitor
(2). 工程师对于redis学习的目的,因为进行了redis的托管,工程师只要会用redis就可以了,但是作为技术人员都有学习的好奇心和欲望。
2. 预防方法:
(1) 对工程师培训,讲一讲redis使用过程中的坑和禁忌
(2) 对redis云进行介绍,甚至可以让有兴趣的同学参与进来
(3) 针对client做限制,但是官方也不建议这么做,官方的默认配置中对于输出缓冲区没有限制。
Java代码收藏代码
  1. client-output-buffer-limitnormal000
(4) 密码:redis的密码功能较弱,同时多了一次IO
(5) 修改客户端源代码,禁止掉一些危险的命令(shutdown, flushall, monitor, keys *),当然还是可以通过redis-cli来完成
(6) 添加command-rename配置,将一些危险的命令(flushall, monitor, keys * , flushdb)做rename,如果有需要的话,找到redis的运维人员处理
Java代码收藏代码
  1. rename-commandFLUSHALL"随机数"
  2. rename-commandFLUSHDB"随机数"
  3. rename-commandKEYS"随机数"
六、模拟实验:
1. 开启一个空的Redis(最简,直接redis-server)
Java代码收藏代码
  1. redis-server
初始化内存使用量如下:
Java代码收藏代码
  1. #Memory
  2. used_memory:815072
  3. used_memory_human:795.97K
  4. used_memory_rss:7946240
  5. used_memory_peak:815912
  6. used_memory_peak_human:796.79K
  7. used_memory_lua:36864
  8. mem_fragmentation_ratio:9.75
  9. mem_allocator:jemalloc-3.6.0
client缓冲区:
Java代码收藏代码
  1. #Clients
  2. connected_clients:1
  3. client_longest_output_list:0
  4. client_biggest_input_buf:0
  5. blocked_clients:0
2. 开启一个monitor:
Java代码收藏代码
  1. redis-cli-h127.0.0.1-p6379monitor
3. 使用redis-benchmark:
Java代码收藏代码
  1. redis-benchmark-h127.0.0.1-p6379-c500-n200000
4. 观察
(1) info memory:内存一直增加,直到benchmark结束,monitor输出完毕,但是used_memory_peak_human(历史峰值)依然很高--观察附件中日志
(2)info clients:client_longest_output_list: 一直在增加,直到benchmark结束,monitor输出完毕,才变为0--观察附件中日志
(3)redis-cli -h host -p port client list | grep "monitor" omem一直很高,直到benchmark结束,monitor输出完毕,才变为0--观察附件中日志
监控脚本:
Java代码收藏代码
  1. while[1==1]
  2. do
  3. now=$(date"+%Y-%m-%d_%H:%M:%S")
  4. echo"=========================${now}==============================="
  5. echo"#Client-Monitor"
  6. redis-cli-h127.0.0.1-p6379clientlist|grepmonitor
  7. redis-cli-h127.0.0.1-p6379infoclients
  8. redis-cli-h127.0.0.1-p6379infomemory
  9. #休息100毫秒
  10. usleep100000
  11. done
完整的日志文件:
一、背景: 选择合适的使用场景
很多时候Redis被误解并乱用了,造成的Redis印象:耗内存、价格成本很高:
1.为了“赶时髦”或者对于Mysql的“误解”在一个并发量很低的系统使用Redis,将原来放在Mysql数据全部放在Redis中。
----(Redis比较适用于高并发系统,如果是一些复杂Mis系统,用Redis反而麻烦,因为单从功能讲Mysql要更为强大,而且Mysql的性能其实已经足够了。)
2. 觉得Redis就是个KV缓存
-----(Redis支持多数据结构,并且具有很多其他丰富的功能)
3. 喜欢做各种对比,比如Mysql, Hbase, Redis等等
-----(每种数据库都有自己的使用场景,比如Hbase吧,我们系统的个性化数据有1T,此时放在Redis根本就不合适,而是将一些热点数据放在Redis)
总之就是在合适的场景,选择合适的数据库产品。
附赠两个名言:
Evan Weaver, Twitter, March 2009 写道
Everything runs from memory in Web 2.0!
Tim Gray 写道
Tape is Dead, Disk is Tape, Flash is Disk, RAM Locality is king.
(磁带已死,磁盘是新磁带,闪存是新磁盘,随机存储器局部性是为王道)
二、一次string转化为hash的优化
1. 场景:
用户id: userId,
用户微博数量:weiboCount
userId(用户id) weiboCount(微博数)
1 2000
2

10

3

288

.... ...
1000000 1000
2. 实现方法:
(1) 使用Redis字符串数据结构, userId为key, weiboCount作为Value
(2) 使用Redis哈希结构,hashkey只有一个, key="allUserWeiboCount",field=userId,fieldValue= weiboCount
(3) 使用Redis哈希结构, hashkey为多个, key=userId/100, field=userId%100, fieldValue= weiboCount
前两种比较容易理解,第三种方案解释一下:每个hashKey存放100个hash-kv,field=userId%100,也就是
userId hashKey field
1 0 1
2 0

2

3 0

3

... .... ...
99 0 99
100 1 0
101 1 1
.... ... ...
9999 99 99
100000 1000 0

3. 获取方法:

Java代码收藏代码
  1. #获取userId=5003用户的微博数
  2. (1)get5003
  3. (2)hgetallUserWeiboCount5003
  4. (3)hget503

4. 内存占用量对比(100万用户 userId:1~1000000)

Java代码收藏代码
  1. #方法一Memory
  2. used_memory:85999592
  3. used_memory_human:82.02M
  4. used_memory_rss:96043008
  5. used_memory_peak:85999592
  6. used_memory_peak_human:82.02M
  7. used_memory_lua:36864
  8. mem_fragmentation_ratio:1.12
  9. mem_allocator:jemalloc-3.6.0
  10. #方法二Memory
  11. used_memory:101665632
  12. used_memory_human:96.96M
  13. used_memory_rss:110702592
  14. used_memory_peak:101665632
  15. used_memory_peak_human:96.96M
  16. used_memory_lua:36864
  17. mem_fragmentation_ratio:1.09
  18. mem_allocator:jemalloc-3.6.0
  19. #方法三Memory
  20. used_memory:9574136
  21. used_memory_human:9.13M
  22. used_memory_rss:17285120
  23. used_memory_peak:101665632
  24. used_memory_peak_human:96.96M
  25. used_memory_lua:36864
  26. mem_fragmentation_ratio:1.81
  27. mem_allocator:jemalloc-3.6.0

内存使用量:

5. 导入数据代码(不考虑代码优雅性,单纯为了测试,勿喷)
Java代码收藏代码
  1. packagecom.carlosfu.redis;
  2. importjava.util.ArrayList;
  3. importjava.util.HashMap;
  4. importjava.util.List;
  5. importjava.util.Map;
  6. importjava.util.Random;
  7. importorg.junit.Test;
  8. importredis.clients.jedis.Jedis;
  9. /**
  10. *一次string-hash优化
  11. *@authorcarlosfu
  12. *@Date2015-11-8
  13. *@Time下午7:27:45
  14. */
  15. publicclassTestRedisMemoryOptimize{
  16. privatefinalstaticintTOTAL_USER_COUNT=1000000;
  17. /**
  18. *纯字符串
  19. */
  20. @Test
  21. publicvoidtestString(){
  22. Jedisjedis=null;
  23. try{
  24. jedis=newJedis("127.0.0.1",6379);
  25. List<String>kvsList=newArrayList<String>(200);
  26. for(inti=1;i<=TOTAL_USER_COUNT;i++){
  27. StringuserId=String.valueOf(i);
  28. kvsList.add(userId);
  29. StringweiboCount=String.valueOf(newRandom().nextInt(100000));
  30. kvsList.add(weiboCount);
  31. if(i%2000==0){
  32. System.out.println(i);
  33. jedis.mset(kvsList.toArray(newString[kvsList.size()]));
  34. kvsList=newArrayList<String>(200);
  35. }
  36. }
  37. }catch(Exceptione){
  38. e.printStackTrace();
  39. }finally{
  40. if(jedis!=null){
  41. jedis.close();
  42. }
  43. }
  44. }
  45. /**
  46. *纯hash
  47. */
  48. @Test
  49. publicvoidtestHash(){
  50. StringhashKey="allUserWeiboCount";
  51. Jedisjedis=null;
  52. try{
  53. jedis=newJedis("127.0.0.1",6379);
  54. Map<String,String>kvMap=newHashMap<String,String>();
  55. for(inti=1;i<=TOTAL_USER_COUNT;i++){
  56. StringuserId=String.valueOf(i);
  57. StringweiboCount=String.valueOf(newRandom().nextInt(100000));
  58. kvMap.put(userId,weiboCount);
  59. if(i%2000==0){
  60. System.out.println(i);
  61. jedis.hmset(hashKey,kvMap);
  62. kvMap=newHashMap<String,String>();
  63. }
  64. }
  65. }catch(Exceptione){
  66. e.printStackTrace();
  67. }finally{
  68. if(jedis!=null){
  69. jedis.close();
  70. }
  71. }
  72. }
  73. /**
  74. *segmenthash
  75. */
  76. @Test
  77. publicvoidtestSegmentHash(){
  78. intsegment=100;
  79. Jedisjedis=null;
  80. try{
  81. jedis=newJedis("127.0.0.1",6379);
  82. Map<String,String>kvMap=newHashMap<String,String>();
  83. for(inti=1;i<=TOTAL_USER_COUNT;i++){
  84. StringuserId=String.valueOf(i%segment);
  85. StringweiboCount=String.valueOf(newRandom().nextInt(100000));
  86. kvMap.put(userId,weiboCount);
  87. if(i%segment==0){
  88. System.out.println(i);
  89. inthash=(i-1)/segment;
  90. jedis.hmset(String.valueOf(hash),kvMap);
  91. kvMap=newHashMap<String,String>();
  92. }
  93. }
  94. }catch(Exceptione){
  95. e.printStackTrace();
  96. }finally{
  97. if(jedis!=null){
  98. jedis.close();
  99. }
  100. }
  101. }
  102. }
三、结果对比
redis核心对象 数据类型 + 编码方式 + ptr 分段hash也不会造成drift
方案 优点 缺点
string

直观、容易理解

  1. 内存占用较大
  2. key值分散、不变于计算整体
hash

直观、容易理解、整合整体

  1. 内存占用大
  2. 一个key占用过大内存,如果是redis-cluster会出 现data drift

segment-hash

内存占用量小,虽然理解不够直观,但是总体上是最优的。

理解不够直观。

四、结论:
在使用Redis时,要选择合理的数据结构解决实际问题,那样既可以提高效率又可以节省内存。所以此次优化方案三为最佳。
附图一张:redis其实是一把瑞士军刀:

由于演讲时间有限,有关Redis-Cluster,演讲者没做太多介绍,简单的介绍了一些Redis-Cluster概念作用和遇到的两个问题,我们在Redis-Cluster也有很多运维经验,将来的文章会介绍。

但是讲演者反复强调,不要听信网上对于Redis-Cluster的毁谤(实践出真知),对于这一点我很赞同,我们从Redis-Cluster beta版 RC1~4 到现在的3.0-release均没有遇到什么大问题(线上维护600个实例)。

一、Redis-Cluster

有关Redis-Cluster的详细介绍有很多这里就不多说了,可以参考:

1.redis-cluster研究和使用

2.Redis Cluster 3.0.5集群实践

3.本博客的一些Redis-Cluster的介绍(未更新完毕)

4. Redis设计与实现那本书(作者:黄建宏):非常的推荐看这本书。

总之Redis-Cluster是一个无中心的分布式Redis存储架构,解决了Redis高可用、可扩展等问题。

二、两个问题:

1. Redis-Cluster主从节点不要在同一个机器部署

(1) 以我们的经验看redis实例本身基本不会挂掉,通常是机器出了问题(断电、机器故障)、甚至是机架、机柜出了问题,造成Redis挂掉。

(2) 如果Redis-Cluster的主从都在一个机器上,那么如果这台机器挂了,主从全部挂掉,高可用就无法实现。(如果full converage=true,也就意味着整个集群挂掉)

(3) 通常来讲一对主从所在机器:不跨机房、要跨机架、可以在一个机柜。

2. Redis-Cluster误判节点fail进行切换

(1) Redis-Cluster是无中心的架构,判断节点失败是通过仲裁的方式来进行(gossip和raft),也就是大部分节点认为一个节点挂掉了,就会做fail判定。

(2) 如果某个节点在执行比较重的操作(flushall, slaveof等等)(可能短时间redis客户端连接会阻塞(redis单线程))或者由于网络原因,造成其他节点认为它挂掉了,会做fail判定。

(3) Redis-Cluster提供了cluster-node-timeout这个参数(默认15秒),作为fail依据(如果超过15秒还是没反应,就认为是挂掉了),具体可以参考这篇文章:Redis-Cluster的FailOver失败案例分析

以我们的经验看15秒完全够用。

三、未来要介绍的问题:

1. Redis-Cluster客户端实现Mget操作。

2.Redis-Cluster--Too many Cluster redirections异常

3. Redis-Cluster无底洞问题解析。

4. 两个Redis-Cluster集群,meet操作问题后的恶果。

5.Redis-Cluster配置之full converage问题。

6.Redis-Cluster故障转移测试

7. Redis-Cluster常用运维技巧。

8. Redis-Cluster一键开通。

9. Redis-Cluster客户端jedis详解。

四、附赠一些不错的资料:

  1. Redis-Cluster的FailOver失败案例分析
  2. Redis Cluster 迁移遇到的各种坑及解决方案
  3. Redis Cluster架构优化
  4. Redis常见集群方案、Codis实践及与Twemproxy比较
  5. Redis Cluster架构优化
  6. 【运维实践】鱼与熊掌:使用redis-cluster需要注意些什么?
  7. Docker及和Redis Cluster的化学反应(上)By 芒果TV
  8. Docker及和Redis Cluster的化学反应(下)By 芒果TV
  9. Redis cluster使用经验——网易有道
  10. Redis Cluster浅析和Bada对比
  11. 互联网Redis应用场景探讨
  12. Redis集群技术及Codis实践
  13. 谈Twitter的百TB级Redis缓存实践
  14. Hadoop、Spark、HBase与Redis的适用性讨论
  15. Codis作者黄东旭细说分布式Redis架构设计和踩过的那些坑们

以上转自:http://carlosfu.iteye.com/blog/2254154

分享到:
评论

相关推荐

    美团在Redis上踩过的一些坑-3.redis内存占用飙升

    3. **监控和报警**:通过工具如`redis-monitor.log`等监控Redis的运行状态,设置内存使用量的阈值报警。 4. **限流和降级策略**:在面临高并发请求时,实施限流和降级策略,保护Redis服务器。 5. **使用缓存穿透、...

    tomcat-redis-session-manager-1.2-tomcat-7-java-7

    tomcat-redis-session-manager-1.2-tomcat-7-java-7tomcat-redis-session-manager-1.2-tomcat-7-java-7tomcat-redis-session-manager-1.2-tomcat-7-java-7tomcat-redis-session-manager-1.2-tomcat-7-java-7tomcat-...

    redis-stack-server 7.2.0 安装包合集

    redis-stack-server-7.2.0-v9.arm64.snap redis-stack-server-7.2.0-v9.bionic.arm64.tar.gz redis-stack-server-7.2.0-v9.bionic.x86_64.tar.gz redis-stack-server-7.2.0-v9.bullseye.x86_64.tar.gz redis-stack-...

    redis-6.2.6-x64-windows.zip

    这个"redis-6.2.6-x64-windows.zip"压缩包包含了针对Windows 10和Windows Server 2016等64位操作系统的Redis版本。以下是关于Redis 6.2.6在Windows环境下的详细知识点: 1. **Redis版本**:6.2.6是Redis的一个稳定...

    spring-data-redis-2.6.1-API文档-中文版.zip

    赠送jar包:spring-data-redis-2.6.1.jar; 赠送原API文档:spring-data-redis-2.6.1-javadoc.jar; 赠送源代码:spring-data-redis-2.6.1-sources.jar; 赠送Maven依赖信息文件:spring-data-redis-2.6.1.pom; ...

    redis-7.2-x64-for-windows-bin.zip

    这个压缩包"redis-7.2-x64-for-windows-bin.zip"是Redis 7.2版本的64位Windows构建,包含了运行Redis服务所需的所有组件。 1. **Redis 7.2**: Redis是一个开源的内存数据结构存储系统,它可以用作数据库、缓存和...

    redis校验工具redis-full-check

    **Redis 全面检查工具:redis-full-check** Redis 是一款高性能的键值存储系统,广泛应用于缓存、数据库和消息中间件等场景。在实际应用中,为了确保 Redis 的稳定性和数据一致性,需要定期对 Redis 实例进行健康...

    redis 免安装 redis客户端 redis-desktop-manager-0.8.8.384

    接下来,关于“redis-desktop-manager-0.8.8.384.exe”文件,这是一个 Redis 客户端工具,名为 Redis Desktop Manager。它提供了一个图形用户界面(GUI),使得用户可以方便地管理 Redis 服务器,包括查看键值、执行...

    Redis-x64-7.0.5-windows11

    4. **运行服务**:通过`redis-server`命令启动Redis服务,使用`redis-cli`进行客户端交互。 5. **配置文件**:修改`redis.conf`配置文件,根据需求设置端口、日志级别、持久化策略等参数。 6. **安全考虑**:默认...

    美团在Redis上踩过的一些坑-目录(本人非美团)

    NULL 博文链接:https://carlosfu.iteye.com/blog/2254154

    redis-5.0.7-x64-for-windows-bin.rar

    redis-5.0.7-x64-for-windows编译-bin.rar Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类keyvalue存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Python...

    flink-connector-redis-2.10-1.1.5-API文档-中文版.zip

    赠送jar包:flink-connector-redis_2.10-1.1.5.jar; 赠送原API文档:flink-connector-redis_2.10-1.1.5-javadoc.jar; 赠送源代码:flink-connector-redis_2.10-1.1.5-sources.jar; 赠送Maven依赖信息文件:flink-...

    Redis-6.2.6-x64-Windows

    Windows下的Redis6.2.6版本 由于目前6版本以上Redis的windows版不好找到,故此上传至CSDN,方便大家下载使用。

    Redis windows下载 Redis-x64-3.2.100.zip

    redis-server --service-install redis.windows-service.conf --loglevel verbose 2 常用的redis服务命令。 卸载服务:redis-server --service-uninstall 开启服务:redis-server --service-start 停止服务:redis-...

    Redis管理工具redisplus-3.2.0-win-x86_64

    RedisPlus是为Redis可视化管理开发的一款开源免费的桌面客户端软件,支持Windows 、Linux、Mac三大系统平台,RedisPlus提供更加高效、方便、快捷的使用体验,有着更加现代化的用户界面风格。该软件支持单机、集群...

    spring-session-data-redis-2.0.4.RELEASE-API文档-中英对照版.zip

    赠送jar包:spring-session-data-redis-2.0.4.RELEASE.jar; 赠送原API文档:spring-session-data-redis-2.0.4.RELEASE-javadoc.jar; 赠送源代码:spring-session-data-redis-2.0.4.RELEASE-sources.jar; 赠送...

    redis-6.2.4-x64-windows-bin.zip

    修复不受支持的配置上的 redis-benchmark 崩溃 ( #8916 ) 其他错误修复: 修复 UNLINK 在 deleted consumer groups 的 stream key 上的崩溃( #8932 ) SINTERSTORE:当不存在任何 sources 时添加丢失的 keyspace ...

    C# windows redis-2.4.5-win32-win64.rar和redis服务安装软件

    在Windows环境下,为了便于管理和使用Redis,通常会有一些专门的工具和包装程序,如本例中的"C# windows redis-2.4.5-win32-win64.rar",这个压缩包提供了在Windows上安装和运行Redis的解决方案。 Redis 2.4.5是...

    spring-data-redis-2.5.5-API文档-中文版.zip

    赠送jar包:spring-data-redis-2.5.5.jar; 赠送原API文档:spring-data-redis-2.5.5-javadoc.jar; 赠送源代码:spring-data-redis-2.5.5-sources.jar; 赠送Maven依赖信息文件:spring-data-redis-2.5.5.pom; ...

    redis 服务 redis可视化工具 redis-desktop-manager-0.8.8.384 redis-2.4.5-win32-win64

    redis 服务 redis可视化工具 redis-desktop-manager-0.8.8.384 redis-2.4.5-win32-win64 redis 服务 redis可视化工具 redis-desktop-manager-0.8.8.384 redis-2.4.5-win32-win64

Global site tag (gtag.js) - Google Analytics