首页 > 代码库 > Redis上踩过的一些坑

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

 

 

 

 

 

附图一张:

 

 技术分享

 

 

 三、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  
(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= http://www.mamicode.com/weiboCount
(3) 使用Redis哈希结构,  hashkey为多个, key=userId/100, field=userId%100, fieldValue= http://www.mamicode.com/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.   
  11. #方法二 Memory  
  12. used_memory:101665632  
  13. used_memory_human:96.96M  
  14. used_memory_rss:110702592  
  15. used_memory_peak:101665632  
  16. used_memory_peak_human:96.96M  
  17. used_memory_lua:36864  
  18. mem_fragmentation_ratio:1.09  
  19. mem_allocator:jemalloc-3.6.0  
  20.   
  21.   
  22. #方法三 Memory  
  23. used_memory:9574136  
  24. used_memory_human:9.13M  
  25. used_memory_rss:17285120  
  26. used_memory_peak:101665632  
  27. used_memory_peak_human:96.96M  
  28. used_memory_lua:36864  
  29. mem_fragmentation_ratio:1.81  
  30. mem_allocator:jemalloc-3.6.0  

 

  内存使用量:

  技术分享

5. 导入数据代码(不考虑代码优雅性,单纯为了测试,勿喷)
 
Java代码    技术分享
  1. package com.carlosfu.redis;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.HashMap;  
  5. import java.util.List;  
  6. import java.util.Map;  
  7. import java.util.Random;  
  8.   
  9. import org.junit.Test;  
  10.   
  11. import redis.clients.jedis.Jedis;  
  12.   
  13. /** 
  14.  * 一次string-hash优化 
  15.  * @author carlosfu 
  16.  * @Date 2015-11-8 
  17.  * @Time 下午7:27:45 
  18.  */  
  19. public class TestRedisMemoryOptimize {  
  20.       
  21.     private final static int TOTAL_USER_COUNT = 1000000;  
  22.   
  23.     /** 
  24.      * 纯字符串 
  25.      */  
  26.     @Test  
  27.     public void testString() {  
  28.         Jedis jedis = null;  
  29.         try {  
  30.             jedis = new Jedis("127.0.0.1", 6379);  
  31.             List<String> kvsList = new ArrayList<String>(200);  
  32.             for (int i = 1; i <= TOTAL_USER_COUNT; i++) {  
  33.                 String userId = String.valueOf(i);  
  34.                 kvsList.add(userId);  
  35.                 String weiboCount = String.valueOf(new Random().nextInt(100000));  
  36.                 kvsList.add(weiboCount);  
  37.                 if (i % 2000 == 0) {  
  38.                     System.out.println(i);  
  39.                     jedis.mset(kvsList.toArray(new String[kvsList.size()]));  
  40.                     kvsList = new ArrayList<String>(200);  
  41.                 }  
  42.             }  
  43.         } catch (Exception e) {  
  44.             e.printStackTrace();  
  45.         } finally {  
  46.             if (jedis != null) {  
  47.                 jedis.close();  
  48.             }  
  49.         }  
  50.     }  
  51.       
  52.       
  53.       
  54.     /** 
  55.      * 纯hash 
  56.      */  
  57.     @Test  
  58.     public void testHash() {  
  59.         String hashKey = "allUserWeiboCount";  
  60.           
  61.         Jedis jedis = null;  
  62.         try {  
  63.             jedis = new Jedis("127.0.0.1", 6379);  
  64.             Map<String,String> kvMap = new HashMap<String, String>();  
  65.             for (int i = 1; i <= TOTAL_USER_COUNT; i++) {  
  66.                 String userId = String.valueOf(i);  
  67.                 String weiboCount = String.valueOf(new Random().nextInt(100000));  
  68.                 kvMap.put(userId, weiboCount);  
  69.                 if (i % 2000 == 0) {  
  70.                     System.out.println(i);  
  71.                     jedis.hmset(hashKey, kvMap);  
  72.                     kvMap = new HashMap<String, String>();  
  73.                 }  
  74.             }  
  75.         } catch (Exception e) {  
  76.             e.printStackTrace();  
  77.         } finally {  
  78.             if (jedis != null) {  
  79.                 jedis.close();  
  80.             }  
  81.         }  
  82.     }  
  83.       
  84.     /** 
  85.      * segment hash 
  86.      */  
  87.     @Test  
  88.     public void testSegmentHash() {  
  89.         int segment = 100;  
  90.         Jedis jedis = null;  
  91.         try {  
  92.             jedis = new Jedis("127.0.0.1", 6379);  
  93.             Map<String,String> kvMap = new HashMap<String, String>();  
  94.             for (int i = 1; i <= TOTAL_USER_COUNT; i++) {  
  95.                 String userId = String.valueOf(i % segment);  
  96.                 String weiboCount = String.valueOf(new Random().nextInt(100000));  
  97.                 kvMap.put(userId, weiboCount);  
  98.                 if (i % segment == 0) {  
  99.                     System.out.println(i);  
  100.                     int hash = (i-1) / segment;  
  101.                     jedis.hmset(String.valueOf(hash), kvMap);  
  102.                     kvMap = new HashMap<String, String>();  
  103.                 }  
  104.             }  
  105.         } catch (Exception e) {  
  106.             e.printStackTrace();  
  107.         } finally {  
  108.             if (jedis != null) {  
  109.                 jedis.close();  
  110.             }  
  111.         }  
  112.     }  
  113.   
  114. }  
 
 
三、结果对比
 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

Redis上踩过的一些坑