(转)Redis上踩过的一些坑-美团

上上周和同事(龙哥)参加了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的使用者)

《(转)Redis上踩过的一些坑-美团》

   2. 现象:

       业务端在使用redis云提供的redis服务后,经常出现connect timeout:

Java代码 

redis.clients.jedis.exceptions.JedisConnectionException  

java.net.SocketException  

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三次握手: 

《(转)Redis上踩过的一些坑-美团》

  (2) redis客户端与redis服务器交互的过程(本质就是TCP请求)

《(转)Redis上踩过的一些坑-美团》

  (3) I/O 多路复用程序通过队列向文件事件分派器传送套接字的过程

《(转)Redis上踩过的一些坑-美团》

   (4) 和redis有什么关系呢?

        由于Redis的单线程模型(对命令的处理和连接的处理都是在一个线程中),如果存在慢查询的话,会出现上面的这种情况,造成新的accept的连接进不了队列。

《(转)Redis上踩过的一些坑-美团》

    如果上面的图没法理解的话,看看这张图:

《(转)Redis上踩过的一些坑-美团》

   5. 解决方法:

    (1) 对慢查询进行持久化,比如定时存放到mysql之类。(redis的慢查询只是一个list,超过list设置的最大值,会清除掉之前的数据,也就是看不到历史)

    (2) 对慢查询进行报警(频率、数量、时间)等等因素

    (3) 打屁股,哈哈:

《(转)Redis上踩过的一些坑-美团》

     (4) 其实应该做的是:对业务端进行培训,告诉他们一下redis开发的坑,redis不是万金油,这个和Mysql DBA要培训Mysql使用者一样,否则防不胜防。

比如他执行了 monitor, keys *, flushall, drop table, update table set a=1; 这种也是防不胜防的(当然也可以做限制,利用rename-command一个随机数),但是提高工程师的水平才是关键。

《(转)Redis上踩过的一些坑-美团》

参考文献:redis 如何处理客户端连接

 二、redis bgrewriteaof问题

一、背景

1. AOF:

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

《(转)Redis上踩过的一些坑-美团》

2. AOF重写:

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

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

《(转)Redis上踩过的一些坑-美团》

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

    由于Redis的单线程模型,理论上每个redis实例只会用到一个CPU,

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

《(转)Redis上踩过的一些坑-美团》

三、最佳实践

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

    例如如下:

《(转)Redis上踩过的一些坑-美团》

《(转)Redis上踩过的一些坑-美团》

《(转)Redis上踩过的一些坑-美团》

2. AOF的管理方式:

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

《(转)Redis上踩过的一些坑-美团》

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

 (3) remote集中式:

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

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

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

《(转)Redis上踩过的一些坑-美团》

策略优点缺点

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

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

AOF控制中心(remote集中式)1. 防止上面提到Swap和OOM。

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

控制中心需要开发。

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

Java代码 

package com.sohu.cache.inspect.impl;  

import com.sohu.cache.alert.impl.BaseAlertService;  

import com.sohu.cache.entity.InstanceInfo;  

import com.sohu.cache.inspect.InspectParamEnum;  

import com.sohu.cache.inspect.Inspector;  

import com.sohu.cache.util.IdempotentConfirmer;  

import com.sohu.cache.util.TypeUtil;  

import org.apache.commons.collections.MapUtils;  

import org.apache.commons.lang.StringUtils;  

import redis.clients.jedis.Jedis;  

import java.util.Collections;  

import java.util.LinkedHashMap;  

import java.util.List;  

import java.util.Map;  

import java.util.concurrent.TimeUnit;  

public class RedisIsolationPersistenceInspector extends BaseAlertService implements Inspector {  

public static final int REDIS_DEFAULT_TIME = 5000;  

@Override  

public boolean inspect(Map paramMap) {  

// 某台机器和机器下所有redis实例  

final String host = MapUtils.getString(paramMap, InspectParamEnum.SPLIT_KEY);  

        List list = (List) paramMap.get(InspectParamEnum.INSTANCE_LIST);  

// 遍历所有的redis实例  

for (InstanceInfo info : list) {  

final int port = info.getPort();  

final int type = info.getType();  

int status = info.getStatus();  

// 非正常节点  

if (status != 1) {  

continue;  

            }  

if (TypeUtil.isRedisDataType(type)) {  

Jedis jedis =new Jedis(host, port, REDIS_DEFAULT_TIME);  

try {  

// 从redis info中索取持久化信息  

                    Map persistenceMap = parseMap(jedis);  

if (persistenceMap.isEmpty()) {  

logger.error(“{}:{} get persistenceMap failed”, host, port);  

continue;  

                    }  

// 如果正在进行aof就不做任何操作,理论上要等待它完毕,否则  

if (!isAofEnabled(persistenceMap)) {  

continue;  

                    }  

// 上一次aof重写后的尺寸和当前aof的尺寸  

long aofCurrentSize = MapUtils.getLongValue(persistenceMap, “aof_current_size”);  

long aofBaseSize = MapUtils.getLongValue(persistenceMap, “aof_base_size”);  

// 阀值大于60%  

long aofThresholdSize = (long) (aofBaseSize * 1.6);  

double percentage = getPercentage(aofCurrentSize, aofBaseSize);  

// 大于60%且超过60M  

if (aofCurrentSize >= aofThresholdSize && aofCurrentSize > (64 * 1024 * 1024)) {  

// bgRewriteAof 异步操作。  

boolean isInvoke = invokeBgRewriteAof(jedis);  

if (!isInvoke) {  

logger.error(“{}:{} invokeBgRewriteAof failed”, host, port);  

continue;  

}else {  

logger.warn(“{}:{} invokeBgRewriteAof started percentage={}”, host, port, percentage);  

                        }  

// 等待Aof重写成功(bgRewriteAof是异步操作)  

while (true) {  

try {  

// before wait 1s  

TimeUnit.SECONDS.sleep(1);  

                                Map loopMap = parseMap(jedis);  

Integer aofRewriteInProgress = MapUtils.getInteger(loopMap,”aof_rewrite_in_progress”, null);  

if (aofRewriteInProgress == null) {  

logger.error(“loop watch:{}:{} return failed”, host, port);  

break;  

}else if (aofRewriteInProgress <= 0) {  

// bgrewriteaof Done  

logger.warn(“{}:{} bgrewriteaof Done lastSize:{}Mb,currentSize:{}Mb”, host, port,  

                                            getMb(aofCurrentSize),  

getMb(MapUtils.getLongValue(loopMap,”aof_current_size”)));  

break;  

}else {  

// wait 1s  

TimeUnit.SECONDS.sleep(1);  

                                }  

}catch (Exception e) {  

                                logger.error(e.getMessage(), e);  

                            }  

                        }  

}else {  

if (percentage > 50D) {  

long currentSize = getMb(aofCurrentSize);  

logger.info(“checked {}:{} aof increase percentage:{}% currentSize:{}Mb”, host, port,  

percentage, currentSize >0 ? currentSize : “<1”);  

                        }  

                    }  

}finally {  

                    jedis.close();  

                }  

            }  

        }  

return true;  

    }  

private long getMb(long bytes) {  

return (long) (bytes / 1024 / 1024);  

    }  

private boolean isAofEnabled(Map infoMap) {  

Integer aofEnabled = MapUtils.getInteger(infoMap,”aof_enabled”, null);  

return aofEnabled != null && aofEnabled == 1;  

    }  

private double getPercentage(long aofCurrentSize, long aofBaseSize) {  

if (aofBaseSize == 0) {  

return 0.0D;  

        }  

String format = String.format(“%.2f”, (Double.valueOf(aofCurrentSize – aofBaseSize) * 100 / aofBaseSize));  

return Double.parseDouble(format);  

    }  

private Map parseMap(final Jedis jedis) {  

final StringBuilder builder = new StringBuilder();  

boolean isInfo = new IdempotentConfirmer() {  

@Override  

public boolean execute() {  

String persistenceInfo =null;  

try {  

persistenceInfo = jedis.info(“Persistence”);  

}catch (Exception e) {  

logger.warn(e.getMessage() +”-{}:{}”, jedis.getClient().getHost(), jedis.getClient().getPort(),  

                            e.getMessage());  

                }  

boolean isOk = StringUtils.isNotBlank(persistenceInfo);  

if (isOk) {  

                    builder.append(persistenceInfo);  

                }  

return isOk;  

            }  

        }.run();  

if (!isInfo) {  

logger.error(“{}:{} info Persistence failed”, jedis.getClient().getHost(), jedis.getClient().getPort());  

return Collections.emptyMap();  

        }  

        String persistenceInfo = builder.toString();  

if (StringUtils.isBlank(persistenceInfo)) {  

return Collections.emptyMap();  

        }  

Map map =new LinkedHashMap();  

String[] array = persistenceInfo.split(“\r\n”);  

for (String line : array) {  

String[] cells = line.split(“:”);  

if (cells.length > 1) {  

map.put(cells[0], cells[1]);  

            }  

        }  

return map;  

    }  

public boolean invokeBgRewriteAof(final Jedis jedis) {  

return new IdempotentConfirmer() {  

@Override  

public boolean execute() {  

try {  

                    String response = jedis.bgrewriteaof();  

if (response != null && response.contains(“rewriting started”)) {  

return true;  

                    }  

}catch (Exception e) {  

                    String message = e.getMessage();  

if (message.contains(“rewriting already”)) {  

return true;  

                    }  

                    logger.error(message, e);  

                }  

return false;  

            }  

        }.run();  

    }  

}  

附图一张:

《(转)Redis上踩过的一些坑-美团》

 三、redis内存占用飙升

 一、现象:

    redis-cluster某个分片内存飙升,明显比其他分片高很多,而且持续增长。并且主从的内存使用量并不一致。

《(转)Redis上踩过的一些坑-美团》

二、分析可能原因:

 1.  redis-cluster的bug (这个应该不存在)

 2. 客户端的hash(key)有问题,造成分配不均。(redis使用的是crc16, 不会出现这么不均的情况)

 3. 存在个别大的key-value: 例如一个包含了几百万数据set数据结构(这个有可能)

 4. 主从复制出现了问题。

 5. 其他原因

三、调查原因:

 1. 经查询,上述1-4都不存在

 2. 观察info信息,有一点引起了怀疑: client_longes_output_list有些异常。

《(转)Redis上踩过的一些坑-美团》

3. 于是理解想到服务端和客户端交互时,分别为每个客户端设置了输入缓冲区和输出缓冲区,这部分如果很大的话也会占用Redis服务器的内存。

《(转)Redis上踩过的一些坑-美团》

从上面的client_longest_output_list看,应该是输出缓冲区占用内存较大,也就是有大量的数据从Redis服务器向某些客户端输出。

于是使用client list命令(类似于mysql processlist) redis-cli -h host

-p port client list | grep -v

“omem=0″,来查询输出缓冲区不为0的客户端连接,于是查询到祸首monitor,于是豁然开朗.

《(转)Redis上踩过的一些坑-美团》

monitor的模型是这样的,它会将所有在Redis服务器执行的命令进行输出,通常来讲Redis服务器的QPS是很高的,也就是如果执行了monitor命令,Redis服务器在Monitor这个客户端的输出缓冲区又会有大量“存货”,也就占用了大量Redis内存。

《(转)Redis上踩过的一些坑-美团》

四、紧急处理和解决方法

进行主从切换(主从内存使用量不一致),也就是redis-cluster的fail-over操作,继续观察新的Master是否有异常,通过观察未出现异常。

查找到真正的原因后,也就是monitor,关闭掉monitor命令的进程后,内存很快就降下来了。

五、 预防办法:

1. 为什么会有monitor这个命令发生,我想原因有两个:

(1). 工程师想看看究竟有哪些命令在执行,就用了monitor

(2). 工程师对于redis学习的目的,因为进行了redis的托管,工程师只要会用redis就可以了,但是作为技术人员都有学习的好奇心和欲望。

2. 预防方法:

(1) 对工程师培训,讲一讲redis使用过程中的坑和禁忌

(2) 对redis云进行介绍,甚至可以让有兴趣的同学参与进来

(3) 针对client做限制,但是官方也不建议这么做,官方的默认配置中对于输出缓冲区没有限制。

Java代码 

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代码 

rename-command FLUSHALL “随机数”  

rename-command FLUSHDB”随机数”  

rename-command KEYS”随机数”  

六、模拟实验:

1.  开启一个空的Redis(最简,直接redis-server)

Java代码 

redis-server  

初始化内存使用量如下:

Java代码 

# Memory  

used_memory:815072  

used_memory_human:795.97K  

used_memory_rss:7946240  

used_memory_peak:815912  

used_memory_peak_human:796.79K  

used_memory_lua:36864  

mem_fragmentation_ratio:9.75  

mem_allocator:jemalloc-3.6.0  

    client缓冲区:

Java代码 

# Clients  

connected_clients:1  

client_longest_output_list:0  

client_biggest_input_buf:0  

blocked_clients:0  

2. 开启一个monitor:

Java代码 

redis-cli -h 127.0.0.1 -p 6379 monitor  

3. 使用redis-benchmark:

Java代码 

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代码 

while [ 1 == 1 ]  

do  

now=$(date”+%Y-%m-%d_%H:%M:%S”)  

echo”=========================${now}===============================”  

echo” #Client-Monitor”  

redis-cli -h127.0.0.1 -p 6379 client list | grep monitor  

redis-cli -h127.0.0.1 -p 6379 info clients  

redis-cli -h127.0.0.1 -p 6379 info memory  

#休息100毫秒  

usleep100000  

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(微博数)

12000

210

3288

…….

10000001000

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,也就是

userIdhashKeyfield

101

202

303

……….

99099

10010

10111

……….

99999999

10000010000

3. 获取方法:

Java代码 

#获取userId=5003用户的微博数  

(1) get 5003  

(2) hget allUserWeiboCount 5003  

(3) hget 50 3  

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

Java代码 

#方法一 Memory  

used_memory:85999592  

used_memory_human:82.02M  

used_memory_rss:96043008  

used_memory_peak:85999592  

used_memory_peak_human:82.02M  

used_memory_lua:36864  

mem_fragmentation_ratio:1.12  

mem_allocator:jemalloc-3.6.0  

#方法二 Memory  

used_memory:101665632  

used_memory_human:96.96M  

used_memory_rss:110702592  

used_memory_peak:101665632  

used_memory_peak_human:96.96M  

used_memory_lua:36864  

mem_fragmentation_ratio:1.09  

mem_allocator:jemalloc-3.6.0  

#方法三 Memory  

used_memory:9574136  

used_memory_human:9.13M  

used_memory_rss:17285120  

used_memory_peak:101665632  

used_memory_peak_human:96.96M  

used_memory_lua:36864  

mem_fragmentation_ratio:1.81  

mem_allocator:jemalloc-3.6.0  

  内存使用量:

《(转)Redis上踩过的一些坑-美团》

5. 导入数据代码(不考虑代码优雅性,单纯为了测试,勿喷)

Java代码 

package com.carlosfu.redis;  

import java.util.ArrayList;  

import java.util.HashMap;  

import java.util.List;  

import java.util.Map;  

import java.util.Random;  

import org.junit.Test;  

import redis.clients.jedis.Jedis;  

/**

 * 一次string-hash优化

 * @author carlosfu

 * @Date 2015-11-8

 * @Time 下午7:27:45

 */  

public class TestRedisMemoryOptimize {  

private final static int TOTAL_USER_COUNT = 1000000;  

/**

     * 纯字符串

     */  

@Test  

public void testString() {  

Jedis jedis =null;  

try {  

jedis =new Jedis(“127.0.0.1”, 6379);  

List kvsList =new ArrayList(200);  

for (int i = 1; i <= TOTAL_USER_COUNT; i++) {  

                String userId = String.valueOf(i);  

                kvsList.add(userId);  

String weiboCount = String.valueOf(new Random().nextInt(100000));  

                kvsList.add(weiboCount);  

if (i % 2000 == 0) {  

                    System.out.println(i);  

jedis.mset(kvsList.toArray(new String[kvsList.size()]));  

kvsList =new ArrayList(200);  

                }  

            }  

}catch (Exception e) {  

            e.printStackTrace();  

}finally {  

if (jedis != null) {  

                jedis.close();  

            }  

        }  

    }  

/**

     * 纯hash

     */  

@Test  

public void testHash() {  

String hashKey =”allUserWeiboCount”;  

Jedis jedis =null;  

try {  

jedis =new Jedis(“127.0.0.1”, 6379);  

Map kvMap =new HashMap();  

for (int i = 1; i <= TOTAL_USER_COUNT; i++) {  

                String userId = String.valueOf(i);  

String weiboCount = String.valueOf(new Random().nextInt(100000));  

                kvMap.put(userId, weiboCount);  

if (i % 2000 == 0) {  

                    System.out.println(i);  

                    jedis.hmset(hashKey, kvMap);  

kvMap =new HashMap();  

                }  

            }  

}catch (Exception e) {  

            e.printStackTrace();  

}finally {  

if (jedis != null) {  

                jedis.close();  

            }  

        }  

    }  

/**

     * segment hash

     */  

@Test  

public void testSegmentHash() {  

int segment = 100;  

Jedis jedis =null;  

try {  

jedis =new Jedis(“127.0.0.1”, 6379);  

Map kvMap =new HashMap();  

for (int i = 1; i <= TOTAL_USER_COUNT; i++) {  

                String userId = String.valueOf(i % segment);  

String weiboCount = String.valueOf(new Random().nextInt(100000));  

                kvMap.put(userId, weiboCount);  

if (i % segment == 0) {  

                    System.out.println(i);  

int hash = (i-1) / segment;  

                    jedis.hmset(String.valueOf(hash), kvMap);  

kvMap =new HashMap();  

                }  

            }  

}catch (Exception e) {  

            e.printStackTrace();  

}finally {  

if (jedis != null) {  

                jedis.close();  

            }  

        }  

    }  

}  

三、结果对比

 redis核心对象 数据类型 + 编码方式 + ptr  分段hash也不会造成drift

方案优点缺点

string直观、容易理解内存占用较大

key值分散、不变于计算整体

hash直观、容易理解、整合整体内存占用大

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

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

四、结论:

   在使用Redis时,要选择合理的数据结构解决实际问题,那样既可以提高效率又可以节省内存。所以此次优化方案三为最佳。

附图一张: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上踩过的一些坑-美团》

总之Redis-Cluster是一个无中心的分布式Redis存储架构,解决了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秒完全够用。

《(转)Redis上踩过的一些坑-美团》

三、未来要介绍的问题:

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详解。

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

Redis-Cluster的FailOver失败案例分析

Redis

Cluster 迁移遇到的各种坑及解决方案

Redis Cluster架构优化

Redis常见集群方案、Codis实践及与Twemproxy比较

Redis

Cluster架构优化

【运维实践】鱼与熊掌:使用redis-cluster需要注意些什么?

Docker及和Redis

Cluster的化学反应(上)By 芒果TV

Docker及和Redis

Cluster的化学反应(下)By 芒果TV

Redis

cluster使用经验——网易有道

Redis

Cluster浅析和Bada对比

互联网Redis应用场景探讨

Redis集群技术及Codis实践

谈Twitter的百TB级Redis缓存实践

Hadoop、Spark、HBase与Redis的适用性讨论

Codis作者黄东旭细说分布式Redis架构设计和踩过的那些坑们

    原文作者:Hz37
    原文地址: https://www.jianshu.com/p/7edb3e4e13e8
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞