Redis上踩过的一些坑

来自: http://blog.csdn.net//chenleixing/article/details/50530419

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

分为5个部分:

一、 周期性出现connect timeout

二、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:connect time out

3. 分析和怀疑:

业务端一般认为redis出现问题,就是redis云有问题,人的“正常”思维:看别人错误容易,发现自己难,扯多了, 出现这个有很多原因:

(1). 网络原因:比如是否存在跨机房、网络割接等等。

(2). 慢查询,因为redis是单线程,如果有慢查询的话,会阻塞住之后的操作。

(3). value值过大?比如value几十兆,当然这种情况比较少,其实也可以看做是慢查询的一种

(4). aof重写/rdb fork发生?瞬间会堵一下Redis服务器。

(5). 其他..................

4. 查询原因

演讲者一开始怀疑是网络问题,但是并未发现问题,观察各种对比图表,tcp listenOverFlow和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 如何处理客户端连接

二、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. package com.sohu.cache.inspect.impl;
  2. import com.sohu.cache.alert.impl.BaseAlertService;
  3. import com.sohu.cache.entity.InstanceInfo;
  4. import com.sohu.cache.inspect.InspectParamEnum;
  5. import com.sohu.cache.inspect.Inspector;
  6. import com.sohu.cache.util.IdempotentConfirmer;
  7. import com.sohu.cache.util.TypeUtil;
  8. import org.apache.commons.collections.MapUtils;
  9. import org.apache.commons.lang.StringUtils;
  10. import redis.clients.jedis.Jedis;
  11. import java.util.Collections;
  12. import java.util.LinkedHashMap;
  13. import java.util.List;
  14. import java.util.Map;
  15. import java.util.concurrent.TimeUnit;
  16. public class RedisIsolationPersistenceInspector extends BaseAlertService implements Inspector {
  17. public static final int REDIS_DEFAULT_TIME = 5000;
  18. @Override
  19. public boolean inspect(Map<InspectParamEnum, Object> paramMap) {
  20. // 某台机器和机器下所有redis实例
  21. final String host = MapUtils.getString(paramMap, InspectParamEnum.SPLIT_KEY);
  22. List<InstanceInfo> list = (List<InstanceInfo>) paramMap.get(InspectParamEnum.INSTANCE_LIST);
  23. // 遍历所有的redis实例
  24. for (InstanceInfo info : list) {
  25. final int port = info.getPort();
  26. final int type = info.getType();
  27. int status = info.getStatus();
  28. // 非正常节点
  29. if (status != 1) {
  30. continue;
  31. }
  32. if (TypeUtil.isRedisDataType(type)) {
  33. Jedis jedis = new Jedis(host, port, REDIS_DEFAULT_TIME);
  34. try {
  35. // 从redis info中索取持久化信息
  36. Map<String, String> persistenceMap = parseMap(jedis);
  37. if (persistenceMap.isEmpty()) {
  38. logger.error("{}:{} get persistenceMap failed", host, port);
  39. continue;
  40. }
  41. // 如果正在进行aof就不做任何操作,理论上要等待它完毕,否则
  42. if (!isAofEnabled(persistenceMap)) {
  43. continue;
  44. }
  45. // 上一次aof重写后的尺寸和当前aof的尺寸
  46. long aofCurrentSize = MapUtils.getLongValue(persistenceMap, "aof_current_size");
  47. long aofBaseSize = MapUtils.getLongValue(persistenceMap, "aof_base_size");
  48. // 阀值大于60%
  49. long aofThresholdSize = (long) (aofBaseSize * 1.6);
  50. double percentage = getPercentage(aofCurrentSize, aofBaseSize);
  51. // 大于60%且超过60M
  52. if (aofCurrentSize >= aofThresholdSize && aofCurrentSize > (64 * 1024 * 1024)) {
  53. // bgRewriteAof 异步操作。
  54. boolean isInvoke = invokeBgRewriteAof(jedis);
  55. if (!isInvoke) {
  56. logger.error("{}:{} invokeBgRewriteAof failed", host, port);
  57. continue;
  58. } else {
  59. logger.warn("{}:{} invokeBgRewriteAof started percentage={}", host, port, percentage);
  60. }
  61. // 等待Aof重写成功(bgRewriteAof是异步操作)
  62. while (true) {
  63. try {
  64. // before wait 1s
  65. TimeUnit.SECONDS.sleep(1);
  66. Map<String, String> loopMap = parseMap(jedis);
  67. Integer aofRewriteInProgress = MapUtils.getInteger(loopMap, "aof_rewrite_in_progress", null);
  68. if (aofRewriteInProgress == null) {
  69. logger.error("loop watch:{}:{} return failed", host, port);
  70. break;
  71. } else if (aofRewriteInProgress <= 0) {
  72. // bgrewriteaof Done
  73. logger.warn("{}:{} bgrewriteaof Done lastSize:{}Mb,currentSize:{}Mb", host, port,
  74. getMb(aofCurrentSize),
  75. getMb(MapUtils.getLongValue(loopMap, "aof_current_size")));
  76. break;
  77. } else {
  78. // wait 1s
  79. TimeUnit.SECONDS.sleep(1);
  80. }
  81. } catch (Exception e) {
  82. logger.error(e.getMessage(), e);
  83. }
  84. }
  85. } else {
  86. if (percentage > 50D) {
  87. long currentSize = getMb(aofCurrentSize);
  88. logger.info("checked {}:{} aof increase percentage:{}% currentSize:{}Mb", host, port,
  89. percentage, currentSize > 0 ? currentSize : "<1");
  90. }
  91. }
  92. } finally {
  93. jedis.close();
  94. }
  95. }
  96. }
  97. return true;
  98. }
  99. private long getMb(long bytes) {
  100. return (long) (bytes / 1024 / 1024);
  101. }
  102. private boolean isAofEnabled(Map<String, String> infoMap) {
  103. Integer aofEnabled = MapUtils.getInteger(infoMap, "aof_enabled", null);
  104. return aofEnabled != null && aofEnabled == 1;
  105. }
  106. private double getPercentage(long aofCurrentSize, long aofBaseSize) {
  107. if (aofBaseSize == 0) {
  108. return 0.0D;
  109. }
  110. String format = String.format("%.2f", (Double.valueOf(aofCurrentSize - aofBaseSize) * 100 / aofBaseSize));
  111. return Double.parseDouble(format);
  112. }
  113. private Map<String, String> parseMap(final Jedis jedis) {
  114. final StringBuilder builder = new StringBuilder();
  115. boolean isInfo = new IdempotentConfirmer() {
  116. @Override
  117. public boolean execute() {
  118. String persistenceInfo = null;
  119. try {
  120. persistenceInfo = jedis.info("Persistence");
  121. } catch (Exception e) {
  122. logger.warn(e.getMessage() + "-{}:{}", jedis.getClient().getHost(), jedis.getClient().getPort(),
  123. e.getMessage());
  124. }
  125. boolean isOk = StringUtils.isNotBlank(persistenceInfo);
  126. if (isOk) {
  127. builder.append(persistenceInfo);
  128. }
  129. return isOk;
  130. }
  131. }.run();
  132. if (!isInfo) {
  133. logger.error("{}:{} info Persistence failed", jedis.getClient().getHost(), jedis.getClient().getPort());
  134. return Collections.emptyMap();
  135. }
  136. String persistenceInfo = builder.toString();
  137. if (StringUtils.isBlank(persistenceInfo)) {
  138. return Collections.emptyMap();
  139. }
  140. Map<String, String> map = new LinkedHashMap<String, String>();
  141. String[] array = persistenceInfo.split("\r\n");
  142. for (String line : array) {
  143. String[] cells = line.split(":");
  144. if (cells.length > 1) {
  145. map.put(cells[0], cells[1]);
  146. }
  147. }
  148. return map;
  149. }
  150. public boolean invokeBgRewriteAof(final Jedis jedis) {
  151. return new IdempotentConfirmer() {
  152. @Override
  153. public boolean execute() {
  154. try {
  155. String response = jedis.bgrewriteaof();
  156. if (response != null && response.contains("rewriting started")) {
  157. return true;
  158. }
  159. } catch (Exception e) {
  160. String message = e.getMessage();
  161. if (message.contains("rewriting already")) {
  162. return true;
  163. }
  164. logger.error(message, e);
  165. }
  166. return false;
  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-limit normal 0 0 0

(4) 密码:redis的密码功能较弱,同时多了一次IO

(5) 修改客户端源代码,禁止掉一些危险的命令(shutdown, flushall, monitor, keys *),当然还是可以通过redis-cli来完成

(6) 添加command-rename配置,将一些危险的命令(flushall, monitor, keys * , flushdb)做rename,如果有需要的话,找到redis的运维人员处理

Java代码    

  1. rename-command FLUSHALL "随机数"
  2. rename-command FLUSHDB "随机数"
  3. rename-command KEYS "随机数"

六、模拟实验:

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 -h 127.0.0.1 -p 6379 monitor

3. 使用redis-benchmark:

Java代码    

  1. redis-benchmark -h 127.0.0.1 -p 6379 -c 500 -n 200000

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 -h 127.0.0.1 -p 6379 client list | grep monitor
  7. redis-cli -h 127.0.0.1 -p 6379 info clients
  8. redis-cli -h 127.0.0.1 -p 6379 info memory
  9. #休息100毫秒
  10. usleep 100000
  11. done

完整的日志文件:

http://dl.iteye.com/topics/download/096f5da0-4318-332e-914f-6f7c7298ddc9

四、 redis内存使用优化

一、背景: 选择合适的使用场景

很多时候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) get 5003
  3. (2) hget allUserWeiboCount 5003
  4. (3) hget 50 3

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. package com.carlosfu.redis;
  2. import java.util.ArrayList;
  3. import java.util.HashMap;
  4. import java.util.List;
  5. import java.util.Map;
  6. import java.util.Random;
  7. import org.junit.Test;
  8. import redis.clients.jedis.Jedis;
  9. /**
  10. * 一次string-hash优化
  11. * @author carlosfu
  12. * @Date 2015-11-8
  13. * @Time 下午7:27:45
  14. */
  15. public class TestRedisMemoryOptimize {
  16. private final static int TOTAL_USER_COUNT = 1000000;
  17. /**
  18. * 纯字符串
  19. */
  20. @Test
  21. public void testString() {
  22. Jedis jedis = null;
  23. try {
  24. jedis = new Jedis("127.0.0.1", 6379);
  25. List<String> kvsList = new ArrayList<String>(200);
  26. for (int i = 1; i <= TOTAL_USER_COUNT; i++) {
  27. String userId = String.valueOf(i);
  28. kvsList.add(userId);
  29. String weiboCount = String.valueOf(new Random().nextInt(100000));
  30. kvsList.add(weiboCount);
  31. if (i % 2000 == 0) {
  32. System.out.println(i);
  33. jedis.mset(kvsList.toArray(new String[kvsList.size()]));
  34. kvsList = new ArrayList<String>(200);
  35. }
  36. }
  37. } catch (Exception e) {
  38. e.printStackTrace();
  39. } finally {
  40. if (jedis != null) {
  41. jedis.close();
  42. }
  43. }
  44. }
  45. /**
  46. * 纯hash
  47. */
  48. @Test
  49. public void testHash() {
  50. String hashKey = "allUserWeiboCount";
  51. Jedis jedis = null;
  52. try {
  53. jedis = new Jedis("127.0.0.1", 6379);
  54. Map<String,String> kvMap = new HashMap<String, String>();
  55. for (int i = 1; i <= TOTAL_USER_COUNT; i++) {
  56. String userId = String.valueOf(i);
  57. String weiboCount = String.valueOf(new Random().nextInt(100000));
  58. kvMap.put(userId, weiboCount);
  59. if (i % 2000 == 0) {
  60. System.out.println(i);
  61. jedis.hmset(hashKey, kvMap);
  62. kvMap = new HashMap<String, String>();
  63. }
  64. }
  65. } catch (Exception e) {
  66. e.printStackTrace();
  67. } finally {
  68. if (jedis != null) {
  69. jedis.close();
  70. }
  71. }
  72. }
  73. /**
  74. * segment hash
  75. */
  76. @Test
  77. public void testSegmentHash() {
  78. int segment = 100;
  79. Jedis jedis = null;
  80. try {
  81. jedis = new Jedis("127.0.0.1", 6379);
  82. Map<String,String> kvMap = new HashMap<String, String>();
  83. for (int i = 1; i <= TOTAL_USER_COUNT; i++) {
  84. String userId = String.valueOf(i % segment);
  85. String weiboCount = String.valueOf(new Random().nextInt(100000));
  86. kvMap.put(userId, weiboCount);
  87. if (i % segment == 0) {
  88. System.out.println(i);
  89. int hash = (i-1) / segment;
  90. jedis.hmset(String.valueOf(hash), kvMap);
  91. kvMap = new HashMap<String, String>();
  92. }
  93. }
  94. } catch (Exception e) {
  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的毁谤(实践出真知),对于这一点我很赞同,我们从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

时间: 2024-10-08 02:31:34

Redis上踩过的一些坑的相关文章

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

来自:http://carlosfu.iteye.com/blog/2254154 分为5个部分: 一.周期性出现connect timeout 二.redis bgrewriteaof问题 三.redis内存占用飙升 四.redis内存使用优化 五.redis cluster遇到的一些问题 附赠PPT: (1) 本次:美团在Redis上踩过的一些坑PPT (2) 以往:<Redis在新浪的大规模运维经验>-演讲人:侯军伟新浪高级DBA.pdf 美团数据库运维平台介绍.pdf

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

一.现象: redis-cluster某个分片内存飙升,明显比其他分片高很多,而且持续增长.并且主从的内存使用量并不一致. 二.分析可能原因: 1.  redis-cluster的bug (这个应该不存在) 2. 客户端的hash(key)有问题,造成分配不均.(redis使用的是crc16, 不会出现这么不均的情况) 3. 存在个别大的key-value: 例如一个包含了几百万数据set数据结构(这个有可能) 4. 主从复制出现了问题. 5. 其他原因 三.调查原因: 1. 经查询,上述1-4

三分之一的程序猿之社交类app踩过的那些坑

三分之一的程序猿之社交类app踩过的那些坑 万众创新,全民创业.哪怕去年陌生人社交不管融资与否都倒闭了不知道多少家,但是依然有很多陌生人社交应用层出不穷的冒出来.各种脑洞大开,让人拍案叫起. 下面我们来挑选一些app. NO1 陌陌 陌陌为什么能火.陌陌为什么能上市,陌陌的崛起直接引领了陌生人社交的火热,后来者都称自己为“陌陌之后,下一代陌生人社交***”的称号. 像这些都项目都是想做下一个陌陌,至于为什么陌陌能成功,我会告诉你,真正的原因就是,陌陌是第一个以“约炮”为噱头的app,强占先机.其

【Fine原创】JMeter分布式测试中踩过的那些坑

最近因为项目需要,研究了性能测试的相关内容,并且最终选用了jmeter这一轻量级开源工具.因为一直使用jmeter的GUI模式进行脚本设计,到测试执行阶段工具本身对资源的过量消耗给性能测试带来了瓶颈,一般线程加到100左右就会出现工具本身无法支撑的问题,广泛了解解决办法后,发现分布式部署测试机仍是首选方案. 关于如何配置jmeter分布式部署测试机很多博客上已经描述得很详细了,这里就不再赘述,可以参考虫师的博客: http://www.cnblogs.com/fnng/archive/2012/

phonegap开发app中踩过的那些坑

把遇到的问题列出来,如果有解决方案的,偶也会写下来,如果大家有更好解决方法的,欢迎留言噢 phonegap 2.9无法触发deviceready事件 亲们可以看下控制台有木有报错,如果有提示cordova_plugins.json 404 (Not Found) ,就在www目录下新建个空文件,命名为cordova_plugins.json就好了,cordova初始化的时候会请求这个文件,但po主还没发现这个文件有啥用,但是没有这个文件的话,cordova初始化失败,自然不会触发devicere

php踩过的那些坑(4) false,NULL,0,&#39;&#39;详解

一.前方有坑 php开发过程中,难免会遇到这四个值:false,null,0,'',并且也会对这四个值进行比较,然后分别进行业务代码处理.一招不慎,就会踩到坑,影响数据判断的正确性和安全性,以至于造成代码不健壮,给程序的测试和运行造成很多的麻烦. 看如下代码: $a = NULL; $b = ''; $c = 0; $d = false; echo ($a == $b)?1:0; // 输出1 echo ($a === $b)?1:0; // 输出0 echo ($a == $c)?1:0; /

Android + Sqlite + Unity3D 踩过的那些坑 &amp; 全流程简介

全文较长,但是面面俱到的写了我遇到的很多问题,希望能帮到你 前言: 最近在弄网安实操结合的小游戏 Demo,跟老师目前讨论的是需要发布手机平台,第一步是Android平台,因为微信小程序表现不出那种真实感,所以继续用Unity3D进行开发.虽然我对unity3d有过几年的开发经验,但是没有真正接触过手机平台,都是在PC和web端.接下来是踩过的那些坑,更多的是跟sqlite相关的. 最后卡住我的问题是这个 android - Unity SQLite conection unable to op

[原创] 关于免费VPN我踩过的那些坑

关于免费VPN我踩过的那些坑 因为工作的关系,笔者经常需要用到VPN, 访问国外国网站,你懂的. 我曾经试着自己购买VPS搭建过VPN, 被封了后就没心情再维护了,毕竟直接买VPN比VPS便宜太多.时间一长,也就有了一些经验. 这里总结成表格的形式,分享给大家: [NydusVPN] 知乎推荐的香港VPN, 比直通车好,线路稳定性好,办公游戏适合.注册前7天内可以无条件退款哦.  官方网站  [Astrill] 老牌VPN,但近两年被封底得太厉害,现在已经不太给力了. 注册第一个月能免费使用(付

安装python爬虫scrapy踩过的那些坑和编程外的思考

这些天应朋友的要求抓取某个论坛帖子的信息,网上搜索了一下开源的爬虫资料,看了许多对于开源爬虫的比较发现开源爬虫scrapy比较好用.但是以前一直用的java和php,对python不熟悉,于是花一天时间粗略了解了一遍python的基础知识.然后就开干了,没想到的配置一个运行环境就花了我一天时间.下面记录下安装和配置scrapy踩过的那些坑吧. 运行环境:CentOS 6.0 虚拟机 开始上来先得安装python运行环境.然而我运行了一下python命令,发现已经自带了,窃(大)喜(坑).于是go