1. 程式人生 > >美團在Redis上踩過的一些坑-2.bgrewriteaof問題

美團在Redis上踩過的一些坑-2.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;