1. 程式人生 > >Redis上踩過的一些坑-美團

Redis上踩過的一些坑-美團

上上週和同事(龍哥)參加了360組織的網際網路技術訓練營第三期,美團網的DBA負責人侯軍偉給大家介紹了美團網在redis上踩得一些坑,講的都是乾貨和坑。

    分為5個部分: 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一個隨機數),但是提高工程師的水平才是關鍵。      

一、背景

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. publicclass RedisIsolationPersistenceInspector extends BaseAlertService implements Inspector {  
  17.     publicstaticfinalint REDIS_DEFAULT_TIME = 5000;  
  18.     @Override
  19.     publicboolean 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.             finalint port = info.getPort();  
  26.             finalint 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.                                 } elseif (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.         returntrue;  
  98.     }  
  99.     privatelong getMb(long bytes) {  
  100.         return (long) (bytes / 1024 / 1024);  
  101.     }  
  102.     privateboolean isAofEnabled(Map<String, String> infoMap) {  
  103.         Integer aofEnabled = MapUtils.getInteger(infoMap, "aof_enabled"null);  
  104.         return aofEnabled != null && aofEnabled == 1;  
  105.     }  
  106.     privatedouble getPercentage(long aofCurrentSize, long aofBaseSize) {  
  107.         if (aofBaseSize == 0) {  
  108.             return0.0D;  
  109.         }  
  110.         String format = String.format("%.2f", (Double.valueOf(aofCurrentSize - aofBaseSize) * 100 / aofBaseSize));  
  111.         return Double.parseDouble(format);  
  112.     }  
  113. 相關推薦

    Redis一些-3.redis內存占用飆升(轉載)

    car shu idl 運維人員 監控 images 我想 依然 服務器 一、現象: redis-cluster某個分片內存飆升,明顯比其他分片高很多,而且持續增長。並且主從的內存使用量並不一致。 二、分析可能原因: 1. redis-clus

    Redis一些

    上上週和同事(龍哥)參加了360組織的網際網路技術訓練營第三期,美團網的DBA負責人侯軍偉給大家介紹了美團網在redis上踩得一些坑,講的都是乾貨和坑。     分為5個部分: 1. 背景:       大部分網際網路公司都會有Mysql

    Redis一些-5.redis cluster遇到的一些問題

    由於演講時間有限,有關Redis-Cluster,演講者沒做太多介紹,簡單的介紹了一些Redis-Cluster概念作用和遇到的兩個問題,我們在Redis-Cluster也有很多運維經驗,將來的文章會介紹。 但是講演者反覆強調,不要聽信網上對於Redis-Clus

    Redis一些-

    上上週和同事(龍哥)參加了360組織的網際網路技術訓練營第三期,美團網的DBA負責人侯軍偉給大家介紹了美團網在redis上踩得一些坑,講的都是乾貨和坑。     分為5個部分: 1. 背景:       大部分網際網路公司都會

    Redis一些-2.bgrewriteaof問題

     一、背景 1. AOF:     Redis的AOF機制有點類似於Mysql binlog,是Redis的提供的一種持久化方式(另一種是RDB),它會將所有的寫命令按照一定頻率(no, always, every seconds)寫入到日誌檔案中,當Redis停

    Redis一些-3.redis記憶體佔用飆升

     一、現象:     redis-cluster某個分片記憶體飆升,明顯比其他分片高很多,而且持續增長。並且主從的記憶體使用量並不一致。   二、分析可能原因:  1.  redis-cluster的bug (這個應該不存在)  2. 客戶端的hash(key)有問題,造成分配不均。(redis使用的是cr

    在vue

    1.首先是前後端分離,採用mock造資料 2.元件註冊->全域性註冊->在main.js中import xx from '.....' 然後註冊Vue.component('xxx',xxx) 然後在頁面中呼叫元件                  ->

    那些年,在nodejs(一)

    自己寫nodejs也有一段時間,踩過很多坑(而且大部分是自己給自己埋),也見過很多別人踩過的坑,原因其實也很簡單,要麼是對這個知識點理解不夠深入,要麼就是編碼的習慣不好。這段響應朋春大牛的號召,打算陸陸續續整理下這些坑,算是給自己一個備忘,同時也希望能對大家有所幫助。 1.

    那些年,在nodejs

    原文:http://cnodejs.org/topic/4fc7789a8be5d070121141cd ----------------------------------------------------------- 自己寫nodejs也有一段時間,踩過很多坑(而

    那些年在WebView

    之前我在Android中使用WebView與JS互動全解析一文中,介紹了通過Webview和JS的互動方式,但Webview這個控制元件簡直是讓人又愛又恨,各種你想不到的錯誤在各種奇怪的手機上,各種不一樣的版本里,所以我想通過這篇部落格總結Webview開發中的

    配置nginx與php-fpm,(附裝nginx,php-fpm,mysql,redis教程)

    近來在centos7上搭建了lnmp的環境,遇到了個坑。如果沒有裝nmp的話可以看這個文章 https://blog.csdn.net/qq_39677681/article/details/82025445 我所遇到的坑: 要讓php-fpm能正常在伺服器上正常解析php,要配置這兩個

    關於webpack的安裝與使用----及一些

    webpack是我們前端開發人員很好用的一個打包工具 因為它支援一鍵打包,以及一些命令也很人性化。目的是將所有的檔案 整合與一個js檔案 避免二次請求 對伺服器減壓 直接走入正題吧。 第一步 首先 新建一個資料夾 資料夾一定不能為中文 否則會出現一些未知的錯誤,這個坑我已經踩過了~~ 直

    在ubuntu 14.04LIFT: Learned Invariant Feature Points 環境配置所流過的淚(3)

    1.重灌nvidia 顯示卡驅動 2.重灌cuda8.0和cudnn 3。重新測試 看來執行不能用sudo 最後也沒管 2. theano nvcc compiler not found on $PATH 重新配置檔案 解決方案:重灌了一遍cuda 和c

    yarn 提交shell 指令碼時

    遇到一個坑爹的問題,用yarn 跑一個自己寫的指令碼,提示失敗,錯誤結果如下 16/12/22 16:47:47 INFO distributedshell.Client: Initializing Client 16/12/22 16:47:47 INFO

    前端總結:CSS/js 以及一些冷知識(永久更新)

    css css樣式這一塊坑比較多,有些情況不按常理出牌,在考慮不同螢幕顯示效果, 精確控制樣式和大小時就會遇到這些坑。 1、 行內元素(比如img)預設會在末尾(或底部)留白/間隙,無法去除。解決辦法:通過display:block讓其以塊級元素進行顯示。(同

    談談陌陌爭霸在資料庫方面( Redis 篇)

    第一次事故出在 2 月 3 日,新年假期還沒有過去。由於整個假期都相安無事,運維也相對懈怠。 中午的時候,有一臺資料服務主機無法被遊戲伺服器訪問到,影響了部分使用者登陸。線上嘗試修復連線無果,只好開始了長達 2 個小時的停機維護。 在維護期間,初步確定了問題。是由於上午一臺從機的記憶體耗盡,導致了從機的資料

    swagger實踐 及一些

    首先就是我們專案中用的swagger2,編輯的時候已經升級到3.0.0了 有空嘗試下。 然後至少要是個spring的專案,支援@configuration這個註解的版本,我們專案中用的spring4.1.0。 然後就是開開心心的碼程式碼了 @Conf

    Vue專案開發的一些問題和

    父元件修改子元件data資料 以及 父元件呼叫子元件定義的方法 —— ref 子元件: <template> <div class="keyboard-wrapper" :class="isShow"></div> </t

    redis集群部署及

    join port rmi ems asm ica 去掉 tar.bz2 方式 本文目標 要在單臺機器上搭建Redis集群,方式是通過不同的TCP端口啟動多個實例,然後組成集群,同時記錄在搭建過程中踩過的坑。 安裝準備 cento

    SpringBoot2.0(13)整合Redis詳解及(Could not get a resource from the pool)

    SpringBoot2.0整合Redis 首先安裝的過程就不提了。上一個專案的redis是配置在Windows下的,整合很簡單,也沒有做什麼配置。這次為了進行測試,裝在了linux下。在SpringBoot整合的過程中遇到了一些小坑,分享一下。 po