歡迎光臨
每天分享高質量文章

運維的苦,誰懂?一次“心驚肉跳”的遷庫經歷!

來自:51CTO技術棧(微訊號:blog51cto)

作者:甘兵,編輯:陶家龍、孫淑娟

連結:http://blog.51cto.com/ganbing/2105842

IT 運維工程師一直是個“苦逼”的職業,“鋤禾日當午,不如運維苦,對著破電腦,一調一下午”是對運維工作的一個形象的描述。下麵看看本文作者一次驚心肉跳的資料庫遷移經歷。

事件起源

整個事件的起源還要從我最近入職了一家區塊鏈金融公司說起,公司業務發展比較迅猛,突破百萬使用者也是近在眼前。

整個系統都在阿裡雲上執行,每天都能看到使用者的不斷增長,即興奮又擔憂,為什麼這麼說呢?

由於我過來的時候,公司業務就已經上線了,系統接過來之後,快速瞭解了所有的應用服務都是在 Docker Swarm 跑起來的,也包括 MySQL 資料庫。

按照這種使用者量發展下去,MySQL 在容器中執行用不了多久肯定會撐不住,以至於我就有了遷庫的想法。

我開始隱隱的擔憂起來,畢竟不想每天提心吊膽的做運維。所以立即重新規劃了新的方案和大家一起探討。

最終總監和相關技術負責人都敲定用 RDS 做為資料庫新的方案,周星馳的功夫中也說到:“天下武功,唯快不破”,於是就開始幹起來。

遷移計劃

原架構圖

如上圖所示,分析一下原來的架構圖:

  • 從入口層(CDN)→到安全層(WAF)最後到達應用層 (ECS叢集)。

  • Docker Swarm 打通了 ECS 叢集中的每臺伺服器,在每臺 ECS 宿主機安裝 Docker engine 並部署了公司需要的應用服務和資料庫(Nginx、PHP、Redis、MySQL等)。

  • MySQL 容器透過本檔案掛載到容器中實現資料持久化。

  • 業務專案以 PHP 為主,PHP 也是執行在容器中,透過 PHP 指定的配置檔案連線到 MySQL 容器中。

隨便展示一下其中一個庫的 docker-compose yaml 檔案:

version: "3"
services:
 ussbao:
   # replace username/repo:tag with your name and image details
   image: 隱藏此映象資訊
   deploy:
     replicas: 1
     restart_policy:
       condition: on-failure
   environment:
     MYSQL_ROOT_PASSWORD: 隱藏此資訊
   volumes:
     - "/data//mysql/db1/:/var/lib/mysql/"
     - "/etc/localtime:/etc/localtime"
     - "/etc/timezone:/etc/timezone"
networks:
 default:
   external:
     name: 隱藏此資訊

從上面的資訊可以看出來,每個庫只運行了一個 MySQL 容器,並沒有主從或讀寫分離的方案。

而且也沒有對資料庫做任何最佳化,資料庫這樣跑下去讓筆者很擔憂,正常來說,都會把資料庫獨立部署執行。

調整後架構圖

從上圖可以看出來,筆者只是把 MySQL 獨立出來了,開通 RDS 實體來跑資料庫,當然還開通了其他的一些服務(比如 OSS、雲 Redis 等),這些不是本文的重點,就沒有畫出來。

Nginx 和 PHP 服務還是在 Docker Swarm 中執行。本文只是對遷移後出了問題的庫進行分享,下麵來看看遷移的方案吧。

遷移流程方案

遷移流程的方案:開通 RDS 實體→備份 SQL匯入到 RDS修改資料庫配置檔案測試驗證。

遷移步驟如下:

  • 根據業務量規劃開通 RDS 實體,建立資料庫和使用者

  • 提前做好 RDS 白名單,新增允許訪問 RDS 的 IP 地址

  • mysqldump 備份 Docker 中的 MySQL

  • 把備份好的 .sql 檔案匯入到 RDS 中

  • 修改 PHP 專案的資料庫配置檔案

  • 清空 PHP 專案的快取檔案或目錄

  • 測試驗證

  • RDS 定時備份

具體遷移細節就不展示了,我是在夜深人靜的時候進行遷移操作的,確定大半夜沒人訪問我們的 App 和網站了才開乾的。

我們的業務情況有點像股市,我們是晚上 12 點不許操作和交易,第 2 天早上 9 點開盤,9 點鐘是併發的高峰期,就像朝陽大悅城上午開門一樣,大批的顧客同時併發過來了。

所以那天晚上在 12 點 15 分準時開乾,按計劃和提前準備的配置、命令、指令碼進行操作的。

把 Docker 中執行的 MySQL 遷移到 RDS 上非常順利,好幾個庫的遷移不到半個小時就結束了,並且把網站和 App 的流程都跑了一遍,也都是妥妥的。

最終把提前準備好的備份指令碼放在 crontab 中定時執行,可以看下指令碼內容:

#!/bin/bash
#資料庫IP
dbserver='*******'
#資料庫使用者名稱
dbuser='ganbing'
#資料庫密碼
dbpasswd='************'
#備份資料庫,多個庫用空格隔開
dbname='db1 db2 db3'
#備份時間
backtime=`date +%Y%m%d%H%M`
out_time=`date +%Y%m%d%H%M%S`
#備份輸出路徑
backpath='/data/backup/mysql/'
logpath=''/data/backup/logs/'

echo "################## ${backtime} #############################"
echo "開始備份"
#日誌記錄頭部
echo "" >> ${logpath}/${dbname}_back.log
echo "-------------------------------------------------" >> ${logpath}/${dbname}_back.log
echo "備份時間為${backtime},備份資料庫 ${dbname} 開始" >> ${logpath}/${dbname}_back.log

#正式備份資料庫
for DB in $dbname; do
 source=`/usr/bin/mysqldump  -h ${dbserver} -u ${dbuser} -p${dbpasswd} ${DB} > ${backpath}/${DB}-${out_time}.sql` 2>> ${backpath}/mysqlback.log;
 #備份成功以下操作
 if [ "$?" == 0 ];then
   cd $backpath
   #為節約硬碟空間,將資料庫壓縮
   tar zcf ${DB}-${backtime}.tar.gz ${DB}-${backtime}.sql > /dev/null
   #刪除原始檔案,只留壓縮後檔案
   rm -f ${DB}-${backtime}.sql
   #刪除15天前備份,也就是隻儲存15天內的備份
   find $backpath -name "*.tar.gz" -type f -mtime +15 -exec rm -rf {} ; > /dev/null 2>&1
   echo "資料庫 ${dbname} 備份成功!!" >> ${logpath}/${dbname}_back.log
 else
 #備份失敗則進行以下操作
 echo "資料庫 ${dbname} 備份失敗!!" >> ${logpath}/${dbname}_back.log
 fi
done

echo "完成備份"
echo "################## ${backtime} #############################"

到了 1 點鐘,確定沒問題後發通知到群裡,發微信給領導表示已遷移完成,進行很順利,然後筆者打車回家,睡覺。

雪崩來臨

其實這一晚筆者睡得也不踏實,到了 8 點半就醒了,因為我們 9 點鐘開盤,會有大量的客戶湧進,每天開始產生新的交易(買入和賣出),給大家看下截圖:

果不其然,9 點過後,我開啟 App,一切正常,點選切換幾個介面後,發現其中一個功能的請求超時了,一直在轉,然後緊接著其他功能也超時了。

完了,出問題了。趕緊開電腦查問題,過了一會兒群裡就開始沸騰了(反映好多客戶開啟 App 都顯示請求超時了),我的電話也第一時間響了,技術總監打來的,問我怎麼回事,我說正在開電腦排查。

緊急處理

排查問題

電腦開啟後,首先想到的就是 RDS 資料庫出了問題,登入阿裡雲,進入 RDS 中的 DMS 資料管理控制檯,一進去就傻眼了 “CPU 爆了”,這麼多連線數,如下圖:

進入會話去看看,發現會話“炸鍋了”,發現幾百頁的 select 都擠在 ub_user_calculate 這個表中,這個表資料量相對大一些,目前有 200 多萬條資料,如下圖:

我的自然反應就是去檢視此表的結構,但發現此表沒有索引,我被驚訝到了,竟然沒有索引,這…… 

然後筆者傳回源資料庫檢視這張表,也發現沒有索引,由此可以確定我導過來的這張表就是沒有建立索引,如下圖:

當資料庫中出現訪問表的 SQL 沒建立索引,會導致全表掃描,如果表的資料量很大,掃描大量的資料,執行效率過慢,佔用資料庫連線,連線數堆積很快達到資料庫的最大連線數設定,新的應用請求將會被拒絕導致故障發生。

解決問題

我趕緊把此事反映給開發負責人,表明問題根源找到了,會話鎖死了,是由其中的一張表沒有索引而導致的,問詢需要給哪幾個欄位加索引。

然後接著操作增加索引:

點選儲存後,發現建立索引的 SQL 一直卡死著,如下圖所示:

突然想起來還有一堆會話在那裡,先 Kill 掉所有會話吧,不然索引肯定建立不了,然後又發現會話根本殺不完,如下圖:

怎麼辦呢?會話殺不完…沒辦法,先把訪問入口切斷吧,反正現在使用者訪問也超時,就毅然決定先把域名停了,訪問入口給切斷了,然後在增加索引。索引加上了,發現 CPU 還下不去,如下圖:

為了快速讓 CPU 降下去,重啟這個實體吧:

實體重啟完後,CPU 下去了,會話也下去了:

開啟入口層的域名訪問吧,再次觀察現在的會話和 CPU 等況,如下圖:

這就對了,會話也正常了,通知領導業務恢復。

再來看一下伺服器 CPU 的情況(遷移 MySQL 後的情況),明顯逐漸好轉。

索引使用策略及最佳化

建立索引註意事項:

  • 在經常查詢而不經常增刪改操作的欄位加索引。

  • order by 與 group by 後應直接使用欄位,而且欄位應該是索引欄位。

  • 一個表上的索引不應該超過 6 個。

  • 索引欄位的長度固定,且長度較短。

  • 索引欄位重覆不能過多,如果某個欄位為主鍵,那麼這個欄位不用設為索引。

  • 在過濾性高的欄位上加索引。

使用索引註意事項:

  • 使用 like 關鍵字時,前置 % 會導致索引失效。

  • 使用 null 值會被自動從索引中排除,索引一般不會建立在有空值的列上。

  • 使用 or 關鍵字時,or 左右欄位如果存在一個沒有索引,有索引欄位也會失效。

  • 使用 != 運運算元時,將放棄使用索引。因為範圍不確定,使用索引效率不高,會被引擎自動改為全表掃描。

  • 不要在索引欄位進行運算。

  • 在使用複合索引時,最左字首原則,查詢時必須使用索引的第一個欄位,否則索引失效;並且應儘量讓欄位順序與索引順序一致。

  • 避免隱式轉換,定義的資料型別與傳入的資料型別保持一致。

參考連結:https://help.aliyun.com/document_detail/52274.html?spm=a2c4g.11174283.6.812.ZGPyBQ

總結

此次故障雖然是表沒有索引造成的,但是我是有責任的,沒有挨個表檢查一下表的結構。

透過此次故障也可以看出來開發在設計表的時候真的要非常的重視,註意細節。

還有就是之前在容器中執行的 MySQL 也時不時的出現 CPU 瓶頸(比如 CPU 使用率偶爾會達到 80% 以上),我應該提前發現這些問題,徹底排查找出問題所在原因再進行遷庫的操作。

甘兵,高階運維工程師,6 年運維工作經驗。曾就職於國家網際網路應急中心、天音控股等企業。擁有豐富系統運維經驗,大型網路架構設計經驗。熱衷於開源技術的研究,關註的技術方向 Docker、DevOps 等。


●編號166,輸入編號直達本文

●輸入m獲取文章目錄

推薦↓↓↓

 

Python程式設計

更多推薦18個技術類微信公眾號

涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

贊(0)

分享創造快樂