`
carlosfu
  • 浏览: 584734 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
Ba8b5055-9c58-3ab0-8a1c-e710f0495d2c
BigMemory实战与理...
浏览量:31586
53b2087e-c637-34d2-b61d-257846f73ade
RedisCluster开...
浏览量:151372
C9f66038-7478-3388-8086-d20c1f535495
缓存的使用与设计
浏览量:125784
社区版块
存档分类
最新评论

美团在Redis上踩过的一些坑-2.bgrewriteaof问题

阅读更多

 

   转载请注明出处哈:http://carlosfu.iteye.com/blog/2254154


    

 一、背景

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代码示例:

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<InspectParamEnum, Object> paramMap) {
        // 某台机器和机器下所有redis实例
        final String host = MapUtils.getString(paramMap, InspectParamEnum.SPLIT_KEY);
        List<InstanceInfo> list = (List<InstanceInfo>) 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> map = new LinkedHashMap<String, String>();
        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();
    }
}

 

 

 

 

附图一张:

 

 

 
  • 大小: 225.4 KB
  • 大小: 480.5 KB
  • 大小: 220 KB
  • 大小: 267.9 KB
  • 大小: 172.6 KB
  • 大小: 274.4 KB
  • 大小: 339 KB
  • 大小: 329.4 KB
  • 大小: 401.7 KB
  • 大小: 43.4 KB
分享到:
评论
1 楼 timer_yin 2017-12-22  
请教下一台物理机上最多部署多少个实例都取决于哪些因素

相关推荐

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

    在使用Redis的过程中,他们遇到了一些问题,特别是关于Redis内存占用飙升的问题。下面我们将深入探讨这个问题以及可能的解决方案。 Redis内存占用飙升的原因多种多样,可能是由于以下几点: 1. **数据结构不当**:...

    redis-5.0.14-1.el7.remi.x86-64.rpm安装包(含有部署手册)

    redis-5.0.14-1.el7.remi.x86_64.rpm安装包(含有部署手册) redis-5.0.14-1.el7.remi.x86_64.rpm安装包(含有部署手册) redis-5.0.14-1.el7.remi.x86_64.rpm安装包(含有部署手册) redis-5.0.14-1.el7.remi.x86_64.rpm...

    redis-5.0.5.tar.gz

    redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-5.0.5.redis-...

    Redis-x64-5.0.14.1

    - 可以通过`redis-benchmark.exe`进行性能测试,评估Redis在当前配置下的性能表现。 - 使用`redis-check-aof.exe`和`redis-check-rdb.exe`定期检查数据文件的完整性,确保数据安全。 5. **注意事项**: - ...

    redis-3.2.2.gem redis-3.2.2.gem redis-3.2.2.gem

    这个压缩包"redis-3.2.2.gem"包含的是Redis 3.2.2版本的源代码或者安装包,主要用于在Ruby环境中安装和使用Redis。Ruby社区使用gem作为包管理器,它允许开发者方便地管理和部署Ruby应用程序的依赖。 Redis 3.2.2是...

    Redis-x64-5.0.14.msi和Redis-x64-5.0.14.zip

    2. **服务化**:为了使Redis在系统启动时自动运行,可以将`redis-server.exe`配置为Windows服务。这可以通过命令行工具`sc create`或使用`.msi`安装包实现。 3. **数据持久化**:Redis支持多种持久化方式,包括RDB...

    RedisDesktopManager Windows版 redis-desktop-manager-0.9.3.817.zip

    通过这个压缩包中的"redis-desktop-manager-0.9.3.817.exe"文件,用户可以安装和运行RedisDesktopManager。该可执行文件是经过编译的Windows程序,包含了所有必要的库和资源,使得用户无需额外配置环境即可直接使用...

    redis+redis-desktop-manager-0.8.3.3850+笔记

    2. 解压:`tar -zxvf redis-2.8.13.tar.gz` 3. 编译:`cd redis-2.8.13`,然后`make` 4. 安装:`sudo make install` 5. 启动Redis服务:`src/redis-server` 6. 配置:可以通过修改`redis.conf`来配置Redis服务,例如...

    redis-stack-server-6.2.6-v7.rhel7.x86-64.tar.gz

    在 `redis-stack-server-6.2.6-v7` 压缩包中,我们可以期待找到以下关键组件: 1. **Redis Server**: 核心的键值存储服务,提供高速的数据读写操作。 2. **Redis Sentinel**: 高可用性解决方案,监控、故障检测以及...

    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-x64-3.2.100.zip

    总的来说,Redis-x64-3.2.100.zip提供了在Windows上部署Redis服务器的全部必需组件,为开发人员和系统管理员提供了高效、灵活的键值存储解决方案。无论是用作缓存、数据库还是消息传递,Redis都能提供出色的服务。

    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-...

    RedisDesktopManager Windows版 redis-desktop-manager-0.8.8.384.zip

    它提供了直观的图形用户界面,使得在Windows操作系统上操作和管理Redis服务器变得轻松易行。标题中的"redis-desktop-manager-0.8.8.384.zip"指的是该软件的特定版本,即0.8.8.384,它被打包成一个ZIP压缩文件,方便...

    tomcat-redis-session-manager-2.0.0.jar

    tomcat-redis-session-manager-2.0.0.jar,可用于Tomcat8下Redis的Session共享,亲测可用,还需要下载另外两个jar包:commons-pool2-2.4.2.jar和jedis-2.9.0.jar,maven仓库有,此处不再上传

    redis-6.2.6-x64-windows.zip

    2. **Windows兼容性**:Redis通常是为Linux设计的,但通过编译可以使其运行在Windows平台上。这个压缩包确保了与Windows 10和Windows Server 2016的兼容性,这为Windows开发者提供了一个方便的键值存储解决方案。 3...

    redis可视化工具redis-desktop-manager-0.8.8.384

    redis可视化工具redis-desktop-manager-0.8.8.384。。。。

    Redis-x64-5.0.14.1.msi

    总结来说,Redis-x64-5.0.14.1.msi 是 Redis 在 Windows 平台上的安装包,提供了高效的数据存储和处理能力,适用于各种应用场景,如缓存、计数器、发布订阅等。正确安装并配置 Redis,可以极大地提升应用程序的响应...

    Redis-x64-3.0.504&Redis;-x64-3.2.100&redis;-desktop-manager-0.9.3.817

    windows系统redis安装文件,Redis-x64-3.0.504(稳定版);Redis-x64-3.2.100(预发行版);redis-desktop-manager-0.9.3.817(redis界面工具)。具体安装方法详见:...

    Redis稳定版 Redis-x64-5.0.14.1.zip

    在Redis-x64-5.0.14.1版本中,可能包括以下内容: - `redis-server`: Redis服务器进程,负责处理客户端请求。 - `redis-cli`: 官方提供的命令行客户端,用于与Redis服务器交互。 - `redis.conf`: 默认配置文件,...

Global site tag (gtag.js) - Google Analytics