美團在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();
    }
}

 

 

 

 

附圖一張:

 

 

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章