運維不背鍋!持續兩年數據庫零故障的運維優化之道

講師介紹

运维不背锅!持续两年数据库零故障的运维优化之道

劉書安

平安科技數據庫技術部運維團隊經理

當前任職於平安科技數據庫技術部運維團隊,有十餘年的數據庫管理經驗,有多年數據庫架構設計和運維管理經驗,熟悉性能調優和故障處理。目前主要負責平安科技數據庫運維的管理工作,團隊負責運維的數據庫種類包括Oracle、PostgreSQL、MySQL、Redis、MongoDB等多數數據庫的運維管理工作。

大家好,我是來自平安科技數據庫技術部運維團隊的劉書安,從14年開始,配合平安集團的互聯網金融轉型,我們運維的數據庫也從單純的Oracle數據庫轉向到了多種數據庫的運維,當前管理的各類數據庫的實例已經超過了一萬個。在這種情況下,我們連續兩年保持了數據庫零故障的狀態。

事實上在之前,運維團隊也是天天忙著應付各種異常,長久處於高壓狀態下。之後經過我們團隊一系列的優化和改造,目前系統已穩定很多。這期間也確實發生了很多事情。接下來就跟大家簡單介紹一下我們這兩年對一些運維問題的分析和團隊管理的方法,拋磚引玉一下。

一、問題解決

首先我們從以下這張圖說起:

运维不背锅!持续两年数据库零故障的运维优化之道

這個是扁鵲向魏王介紹他們三兄弟的醫術:扁鵲自己是在病人病入膏肓時用虎狼之藥將對方救活;扁鵲的二哥是在別人生小病時將人治癒;而扁鵲的大哥則是在病情未發時剷除病因、避免生病。

在扁鵲看來,三個人的醫術排序應該是:大哥>二哥>扁鵲,但在世人眼裡卻是:扁鵲>二哥>大哥。

我比較認同扁鵲的觀點,因為我一直都覺得DB的運維人員不應該只是背鍋俠,而是應該把自己當成醫生來對待問題,不只是關注問題的解決,更需要多關注避免問題的發生。

我們在數據庫異常時去解決問題,別人可能會認為我們是高手,能把問題解決好、事情處理掉。但實際上這時的運維已經是一個被動式的處理,即便我們用了最快的手段去解決,故障已經發生了,可能還造成了比較嚴重的影響。

如果我們能提前發現這些問題並解決掉,就能避免很多故障和影響的發生。因此在我看來,運維相對高明的手段應該是:在做架構或設計時就把能想到的問題預先解決掉,確保系統的可拓展性和高可用性。運維也應該多從架構的角度去考慮問題,並將這些問題提前解決好,而非被動地等待問題發生後才去解決。

接下來和大家介紹我們團隊解決的三個案例,以及之後我們通過什麼方式避免問題的發生:

案例一

這是我們在2016年解決的案例,版本是11.2.0.4 的Oracle數據庫,這種數據庫使用SPM固化TOP SQL的執行計劃,確保系統的穩定。

运维不背锅!持续两年数据库零故障的运维优化之道

當時異常的問題是,幾乎每次發完大的版本後,已有的功能都會多少受到影響,有一些語句的執行計劃會發生異常。

我們發現,絕大多數語句都和這個語句是類似的,中間有一個SKIPSCAN(跳躍式掃描),很明顯可以看出它是一個輸入時間(對著用戶的),有一個範圍查詢。所以我們當時就基本判斷問題出在這裡了,使用的解決方案就是通過重新固化執行計劃來選擇好的執行計劃。

接著就開始分析問題產生的原因。

對於索引跳躍式掃描而言,在一般情況下,如果運維索引的第一個列沒有用到,當它開始使用到第二個列時,就只能用跳躍式的方法去進行一個索引掃描。而在分析問題原因時,因為類似的語句比較多,我們當時在固化了幾十條後,發現還有源源不斷的類似語句出現,就考慮到問題可能並沒有那麼簡單。所以我們又進行了進一步分析,最後發現可能是索引的統計信息有問題。於是我們就重新收集了索引的統計信息,至此,類似的語句問題才算是解決了。

运维不背锅!持续两年数据库零故障的运维优化之道

但其實這個問題並沒有徹底結束:

我們在處理完後又重新分析了這個索引的問題,發現索引第一列是一個空值,但不知道是誰在空列和輸入時間上建了一個符合索引,導致這個索引有可能會被使用。

發現問題後,我們就查詢了這個索引的訪問方式,看看是否全都是INDEX SKIP SCAN。後來發現,基本上訪問這個索引的語句都用的是這種索引跳躍式掃描,所以我們當時就把這個索引設置為不可用,後面把它刪除了。類似的索引,我們當時處理了有三個,之後這個系統就沒有再出現這樣類似的問題。

SPM也是一種固化執行計劃的方式,但為什麼在這個庫裡,SPM會失效呢?

之後我們分析了它的原因:是因為每次發版本時,有可能會多查一些字段,導致語句發生變化。SPM這類的固化執行計劃的方式都和語句有強關聯,只要語句上有一個小小的改動,都會導致固化的方式失效。這也是每次更新版本語句都會發生異常的原因之一。

之後我們繼續分析,為什麼這個時間索引有這種類似問題?

這是我們之前整理的一個案例分析的原因:我們在做索引的範圍查詢時,它的選擇率公式如下 ↓

运维不背锅!持续两年数据库零故障的运维优化之道

但是在不同情況下,比如這種右側索引,比如創建時間、更新時間、輸入時間,我們寫入數據都是用sysdate寫入的,那麼它永遠都在索引列的右側,類似這種方式是往裡面插入數據的。

然而我們統計信息的收集又不是一個實時收集的,主要是對一些大表,比如一個一千萬的表可能要到10%,也就是100萬的DMR量;更大的表的DMR量會更大。這就會導致我們的統計信息和當前的值永遠是過時的,就會產生這種問題。

對於這三個查詢來說:第一個查詢發生在有效範圍內,所以它可以反映出一個比較真實的數據,第二個查詢也可以反映一部分,但第三個查詢就相當於完成一個超範圍的查詢,計算出一個很低的值,這樣就會導致我們的語句偏異常。

更坑的是在OLPP系統裡,新數據查詢的幾率永遠比老數據的大,越新的數據被訪問的幾率越高,這也導致我們的語句每次都會出現異常的情況。

發現這些問題後,我們立即展開了一個行動,就是把數據庫裡所有與時間索引相關的字段都提取一下,然後定期修改索引字段上面的HIGH VALUE,統計信息裡面的HIGHVALUE,就能避免出現這種問題。

运维不背锅!持续两年数据库零故障的运维优化之道

如上圖所示,是一個範圍查詢的情況,即在一個索引前導列的區別,類似於我們在創建時間和OWNER之間建索引。如果把創建時間放在前面,把OWNER放在後面就是第一種情況;如果把OWNER放在前面,把CREATED放在後面就是第二種情況。

現在來分析這兩個不同索引的區別:

當我們把創建時間放在前面時,有一個很大的問題,我們通過時間字段去查詢時很難做到等值查詢,即不可能去發現每分每秒插入的值。對於這種查詢,我們一般都使用範圍查詢,比如查一個月或一天、一週的數據。所以大家可以看到,如果我在這個語句內查這一分鐘、這一天DBMGR創建的情況,在第一個索引裡它整個範圍都會涉及到,隨後取相關聯的三個值;但是在第二個索引裡,三個值是連在一起的,因為DBMGR 是有序的,時間也是有序的,它們就可以完成只涉及到自己相關值的值。

這裡面還有一些細小的區別:如果我先進行範圍查詢,後做等值查詢,對於這樣的索引,Filter時就會多做一個Filter步驟。但如果把這個順序調整一下,就不會有這種情況。所以我們在做這種符合索引創建的時候,就一定要儘量把等值查詢的放在前面。

运维不背锅!持续两年数据库零故障的运维优化之道

之前有一個說法:在選擇符合索引的前導列時,要把選擇率比較低的值放在前導列。

但我們覺得這個說法是不完善的:比如對於一個時間字段而言,一天有86400秒,100天就可能有800多萬的不同值,一年會有更多不同值。可如果把這個作為前導列,有時候是不適合的,因為對它來說,有可能我們是需要查詢一天或一個月的數據,而一年有365天,或者說十二個月。因此更準確的說法應該是把查詢條件中選擇率低的列做為複合索引的前導列。

所以通過這個案例,我們就把運維問題的解決分成了三個步驟:

  • 第一步:快速解決問題,確保應用恢復。對於運維人員來說,恢復應用是第一位的;

  • 第二步:看問題是不是重複性發生的。比如前面說的案例一,如果我們當時的處理方案僅是固化執行計劃,或是收集統計信息,你沒有辦法保證它以後不會再出現類似的情況。如果我們使用的是收集統計信息的方式,可能再過一個月或兩個月,這種情況又會再次發生,所以根本的解決方案是找到這個問題發生的原因,確保這次問題解決後不會再復發;

  • 第三步:避免問題,看這個問題是否屬於共性問題、其他庫裡有沒有類似問題。如果有類似問題,就要形成一種規範,去避免這種問題的發生。尤其是對於一些新的應用來說,只有當你制定規範、讓開發遵守後,後續才會減少類似問題的發生,不然就會演變成我們一邊解決問題,新問題又源源不斷髮生的情況,最後我們只能不斷地去解決這種重複發生的問題。

我記得之前有一個案例就是共性問題:當時是在一個實際的庫裡,我們分析發現它存在內存洩漏的問題,但並沒有馬上開始處理,結果第二天另一個庫也發生內存洩漏,於是我們不得不緊急重啟。

當時我們分析出問題是由某一個BUG導致後,就搜索那個BUG相關的信息,發現早在兩三年前(2014年)已經有同事解決了這個問題,只不過在另一個庫裡還打了相應的PATCH來解決問題,但就是因為沒有把這個問題推廣到所有系統裡,排查是否其他庫也存在這個問題而引起的。

從那之後我們就特別注意這種共性問題,如果每個系統、每個問題都要發生一次,代價實在是太大了,所以我們儘可能在發現共性問題後就解決掉,儘量排除其他庫也發生類似問題的情況。

案例二

第二個案例是一個版本為12.1.0.2的Oracle數據庫,每到晚上總會不定時地主機CPU持續到100%,應用同時會創建大量的數據到數據庫中。

當時我們的應急方案是把這種相關的等待時間全部批量Kill掉,因為這些系統是在比較核心的庫裡,基本上每個系統被Kill掉的進程有幾千個,代價還是比較大的。

後來這個問題發生兩次後,我們開始著手重點分析問題,通過ASH分析發現,出現這個異常等待是因為一個很簡單的語句——SELECT USER FROM SYS.DUAL。

之後我們就通過這個語句來一步步關聯, 看到底是哪個地方調用的,結果發現是在一個應用用戶的登錄TRIGGER中的用戶判斷步驟。這個USER是Oracle的內部函數,但就是這麼簡單的一個語句,就讓整個庫都Hang住了。

运维不背锅!持续两年数据库零故障的运维优化之道

然後我們開始分析原因。我們通過ASH發現該語句在我們恢復應用前有重新加載的過程。當時我們懷疑是硬件導致的,就通過這種方式去分析,結果發現是在晚上10點時被Oracle的自動任務做了一個統計信息的自動收集,收集完後,又因為它是一個登錄的Trigger,用戶在不斷登錄,在做登錄解析時這個語句就沒辦法解析,所以才導致用戶源源不斷地卡在那裡。而應用是需要新建連接的,新建的連接又無法進到庫裡面,就會導致連接數越來越多,全都卡在那裡。最後我們通過鎖定dual表統計信息的收集來從根本上解決這個問題。

案例三

第三個案例有兩個問題,但後來我們發現這兩個問題是由相同的原因引起的。

我們有一個數據庫是從10.2.0.5.X升級到10.2.0.5.18版本,升級後會不定時出現cursor:pin相關的一些等待。其實出現cursor:pin是很正常的,因為這個數據庫的負載比較高,變化也較高,但問題是它是在升級之後出現的。運營認為這是升級之後出現的異常,我們就開始著手分析問題的原因。

第二個問題是我們在應急時發現的,有時異常出現時,某個庫裡有些語句的執行次數會特別高,甚至15min能達到上億次,這對於一個正常的業務系統來說,出現這麼高的執行頻率是不正常的。

之後我們就去分析這些問題,發現這兩個問題有相同的一些點,比如語句中間出現了個函數調用;比如說這個情況下,A表如果訪問的數據量較大時,這些函數就有可能被調用很多次。

我們發現,有一個語句,它執行一次可能會出現十幾萬次的函數調用。如果在調用的過程中,關聯的那張表的執行計劃發生了變化,比如說A表走了一個全程掃描,那可能會出現幾千萬次的函數調用。

當時我們也總結了一些關於通過什麼樣的方法去快速定位、是否是函數調用導致的看法。在10g之前確實沒有什麼好的辦法,因為它裡面沒有一個顯示的關聯,就可能通過代碼去掃描,去找對應的語句。在11g後會比較簡單一些,通過AS值相關的TOP LEVEL SQL ID就可以直接關聯到是哪個語句調的函數導致的問題。

這裡還有一個問題是函數調用。因為它調用的函數可能都是特別快的,但次數有會比較高,性能波動可能帶來比較大的影響。之前我們有一個案例就發生在月底高峰,我們當時發現某個數據庫中會出現很多CBC的等待,後來又發現有一個小表被頻繁訪問,那個小表就100多行數據,但可能它相關的語句每隔15min就調用了上千萬次。

其實這麼高的併發下,出現這種CBC的等待是很正常的。不過因為它只有100多行數據,且都集中在一個數據塊裡,所以才導致這個數據塊特別熱,就會一直出現這種CBC的等待。

於是我們就找辦法解決這個熱快的問題,但又因為不能在月底衝業績時停運來做修改,所以我們就想了一個方案:建一個PCT FREE 99的索引,把表所有列的數據都包含進去,確保每個索引塊裡面只保留了一行數據,變相地把這100多行數據分到100多個塊裡。

做了這個操作後,CBC相關的問題被解決了,也順利地撐過了業務高峰期,但是第二天月初的報表發現又掉坑裡了。

因為我們在每個月月初需要上報給監管的一個報表,這種報表是屬於一個長事務,但是它在那個報表裡面也是調了之前優化的那個索引,優化後的語句雖然降低高峰期CBC的等待,但因為它是要訪問100多個數據塊,單次訪問從0.25毫秒變成了1毫秒,相當於效率降低了4倍。由於報表是一種長事務的處理,相當於那個進程比原來多花了一倍多的時間也沒跑完。所以之後我們發現這個問題後,又不得不把那個索引給幹掉了,讓它恢復原來那種狀態。

尤其是現在對IT的要求越來越高,時限的要求也越來越高,很多系統基本都是用這種敏捷的開發方式儘快地上線。新系統上線有一個很大的問題就是剛上線時壓力都不會很大、負載也不高,但其實是很多問題在開始階段被隱藏了。等到真正發生問題時,負載高了或者壓力大了再去解決問題,難度就會比較大一點。

尤其對於數據庫來說,數據庫量小的時候,比如說300、500M的數據,這個表格怎麼整改都很簡單,但等到這個表漲到300、500G甚至1、2T時再想去做這個表數據類的整改,難度就會大很多。

比如說,我們之前做分期表整改時會用這種在線重定義的方式,但對於一些比較大的表,幾百G甚至上T的表,再用這種在線重定義的方式,就會遇到各種各樣的BUG。

後來坑踩多了,我們現在對於大表的分表改造就是先同步歷史數據級改造,後做一個數據增量,方法會複雜很多。但其實如果在開始階段,我們對於這種大表就已經設計好它的分區,尤其在時間索引上,基於時間去做一個分區,可以避免很多問題。

為什麼我們的歷史庫裡有那麼多時間索引?

很大的一個原因是有很多報表是基於時間去查詢的,比如說要查這一個月或者這一天新增的一些數據的情況,都需要通過時間的字段去訪問。我之前就見過很多關於時間的索引,但最後卻因為時間索引的特性,導致系統源源不斷地出現各種各樣的問題。如果在設計階段把這些大表提前就設計成分區表,完全可以避免這些不必要的問題。

二、運維管理

因為各個公司具體情況不同,我接下來就簡單介紹一下我司關於運維管理的一些做法,給大家做個參考。

1、變更管理

相對來說,我們公司的變更管理比較嚴格,後續可能會更加嚴格。

變更管控

對於變更管控,比如在白天嚴禁做任何變更,工作時間內任何變更都不能做,即便是一些緊急或故障的修復,也是需要通過部門負責人確認、領導同意後才可以做的,確保風險可控。

變更流程

可能每個公司都有變更流程,但我們公司有一個比較特殊的地方。因為一些兼容數據庫的要求可能會高一些,流程管控的每個部分都要確保到位。

變更方案

我們的變更方案是每個人要提前去做評審和驗證,包括制定方案的同事和實施操作的同事,就需要變更實施人員提前在一個環境下做完整的驗證,確保每個步驟都是驗證通過的。

2、規範管理

架構規範

我自己之前在做架構師時,制定各種各樣的規範是一項重點的工作。可能是養成習慣了,現在也和大家一起制定各式各樣的運維規範。但我自己感受最深的是:規範一定要有統一的標準,如果做不到統一就有可能會在後續產生問題。

比如我們之前有些開發測試環境不是那麼規範,現在想改造做自動化時,發現根本就做不起來,因為每個庫的情況不一樣,自動化的腳本不可能適應所有情況來做這種標準化的改造。

把它弄成不標準是很簡單的,但要想把不標準的改成標準的,難度就大了,尤其是在我們已經形成習慣之後。

運維規範

在2014年前,我們做的是純Oracle數據庫的運維,因為之前建立的是一個傳統的金融企業,運維的都是Oracle數據庫,但2014年後我們逐步轉向了互聯網金融。因此我們陸續研究了MySQL、PG、Redis、MongDB、SQL Server、HBase等7、8種數據庫,在運維過程中遇的坑就會比較多。

最初有很多標準,但沒有一個是最佳的實踐,很多也是根據業界、自己的經驗制定出來了;還有各種不同的數據庫裡,不同的團隊制定了不同標準,最後就有各種各樣的標準了。

所以我們在運維中會發現各種各樣的問題,最後要強制去做這方面的規範整改。而且,之前的標準大部分都沒有經過大規模使用和大規模負載的驗證,很多標準並不那麼統一、規範和有效。因此,我們在運維過程中對於這種規範,還是在不斷地去優化和改進,畢竟很多情況在沒有遇到時,你真的是沒有辦法去解決這個問題。

規範優化

舉個例子,最初我們並沒有規定Redis一定要和應用放在同一個網絡區域,但隨著 Redis的負載增加,我們發現防火牆已經承受不了。當時平安的WiFi剛上線不久,但關於Redis的訪問,幾個實例每秒都有高達到上萬次調用,整個防火牆都撐不住了,還差點導致一個比較嚴重的故障。

在解決這個問題後,我們就制定了一條強制的規範:Redis這種高併發訪問的數據庫,一定要和應用放在一起,不能有出現跨牆訪問的情況。

所以這個規範也是不斷去優化的,包括我們運維的一些標準。因為在最初創建標準時,我們可能會因為使用時間不長而考慮不到一些問題。

我印象比較深刻的是MySQL剛引入時的一個問題,對於軟件的版本沒有明確到小版本,後來甚至出現有MySQL停庫時是5.6.22的版本,在維護完成後就被啟動成5.6.16的版本,最後是通過不斷地優化來確保我們的規範和實際是相結合的,避免這種問題的發生。

3、 人員發展

團隊意識

關於團隊這塊,需要提升每個人在團隊中的作用,需要確保團隊裡的每個人都是有備份的。如果發展成離開誰都不行,那對團隊的整體發展來說是不正常的。

所以我們在安排工作時,對於比較重要的工作,我會盡量不讓熟悉的同事重複去做,而是儘量讓一些不熟悉的同事參與去做。之前每走一個資深成員,都會明顯感覺到團隊的整體技能或知識少了一塊。為了避免類似問題的發生,從2017年開始我們就制定了一些策略,讓大家做知識技能的分享,每週抽取兩個下午,每個下午抽取一到兩個小時做分享。

另一個策略是技能的積累,即把我們在工作中遇到和解決的一些問題都錄入問題管理系統。這樣做有兩個好處:一是可以把重複的問題記錄下來,因為我們想要去分析哪些問題是重複發生的、哪些是有共性的,就需要有一個這樣的系統去拉對應的問題清單,最後去解決問題;二是即便人員流失了,他們之前解決的一些問題和技能也能讓團隊其他人發現,不至於每走一個人就留下一個坑。

所以我們是通過這種手段來儘量避免人員流失或變動給團隊帶來的一些問題。但說到底,其實這是沒辦法完全避免的,因為數據庫運維有一定的複雜度,需要依靠不斷地發生故障、解決故障,包括一些人為失誤來提升。

權責分明

我們的輪班人員是7×24小時,即上三班的方式來輪班的。之前團隊有一個比較嚴重的問題,當一件事情發生了,輪班人員有可能將問題交接給下一班次;或是升級給其他人後,就覺得與自己沒有關係了。還有就是風險意識不強,有一些操作沒有評估過影響就開始在生產庫裡操作。

當時我們也發生了不少問題,後來在內部重點提升兩點意識:責任人意識和風險意識。

首先你需要在生產做措施前,確保要做的操作會有什麼影響、導致什麼後果,不能在做完後才去想這個問題:比如說我們現在每天變更,都需要提前把腳本和手冊做好,讓值班人員熟悉。操作會有什麼後果?後續有什麼異常會發生?應對方法又是什麼?……這些都是需要提前評估好的。

技能提升

關於技能提升,雖然必要的培訓是必不可少的,但我們認為關鍵還是要靠自己的學習、理解和在實踐中的積累,並沒有什麼好的捷徑去實現,大多時候還是要通過不斷地解決問題、發現問題甚至包括犯錯的代價來提升的。

近期熱文

↓↓↓點這裡下載本文PPT


分享到:


相關文章: