運(yùn)維的苦,誰(shuí)懂?一次“心驚肉跳”的遷庫(kù)經(jīng)歷?。ㄓ胁实埃?/h1> 原創(chuàng)
【51CTO.com原創(chuàng)稿件】IT 運(yùn)維工程師一直是個(gè)“苦逼”的職業(yè),“鋤禾日當(dāng)午,不如運(yùn)維苦,對(duì)著破電腦,一調(diào)一下午”是對(duì)運(yùn)維工作的一個(gè)形象的描述。下面看看本文作者一次驚心肉跳的數(shù)據(jù)庫(kù)遷移經(jīng)歷。
事件起源
整個(gè)事件的起源還要從我最近入職了一家區(qū)塊鏈金融公司說(shuō)起,公司業(yè)務(wù)發(fā)展比較迅猛,突破百萬(wàn)用戶(hù)也是近在眼前。
整個(gè)系統(tǒng)都在阿里云上運(yùn)行,每天都能看到用戶(hù)的不斷增長(zhǎng),即興奮又擔(dān)憂(yōu),為什么這么說(shuō)呢?
由于我過(guò)來(lái)的時(shí)候,公司業(yè)務(wù)就已經(jīng)上線(xiàn)了,系統(tǒng)接過(guò)來(lái)之后,快速了解了所有的應(yīng)用服務(wù)都是在 Docker Swarm 跑起來(lái)的,也包括 MySQL 數(shù)據(jù)庫(kù)。
按照這種用戶(hù)量發(fā)展下去,MySQL 在容器中運(yùn)行用不了多久肯定會(huì)撐不住,以至于我就有了遷庫(kù)的想法。
我開(kāi)始隱隱的擔(dān)憂(yōu)起來(lái),畢竟不想每天提心吊膽的做運(yùn)維。所以立即重新規(guī)劃了新的方案和大家一起探討。
最終總監(jiān)和相關(guān)技術(shù)負(fù)責(zé)人都敲定用 RDS 做為數(shù)據(jù)庫(kù)新的方案,周星馳的功夫中也說(shuō)到:“天下武功,唯快不破”,于是就開(kāi)始干起來(lái)。
遷移計(jì)劃
原架構(gòu)圖
如上圖所示,分析一下原來(lái)的架構(gòu)圖:
- 從入口層(CDN)→到安全層(WAF)→***到達(dá)應(yīng)用層 (ECS集群)。
- Docker Swarm 打通了 ECS 集群中的每臺(tái)服務(wù)器,在每臺(tái) ECS 宿主機(jī)安裝 Docker engine 并部署了公司需要的應(yīng)用服務(wù)和數(shù)據(jù)庫(kù)(Nginx、PHP、Redis、MySQL等)。
- MySQL 容器通過(guò)本文件掛載到容器中實(shí)現(xiàn)數(shù)據(jù)持久化。
- 業(yè)務(wù)項(xiàng)目以 PHP 為主,PHP 也是運(yùn)行在容器中,通過(guò) PHP 指定的配置文件連接到 MySQL 容器中。
隨便展示一下其中一個(gè)庫(kù)的 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: 隱藏此信息
從上面的信息可以看出來(lái),每個(gè)庫(kù)只運(yùn)行了一個(gè) MySQL 容器,并沒(méi)有主從或讀寫(xiě)分離的方案。
而且也沒(méi)有對(duì)數(shù)據(jù)庫(kù)做任何優(yōu)化,數(shù)據(jù)庫(kù)這樣跑下去讓筆者很擔(dān)憂(yōu),正常來(lái)說(shuō),都會(huì)把數(shù)據(jù)庫(kù)獨(dú)立部署運(yùn)行。
調(diào)整后架構(gòu)圖
從上圖可以看出來(lái),筆者只是把 MySQL 獨(dú)立出來(lái)了,開(kāi)通 RDS 實(shí)例來(lái)跑數(shù)據(jù)庫(kù),當(dāng)然還開(kāi)通了其他的一些服務(wù)(比如 OSS、云 Redis 等),這些不是本文的重點(diǎn),就沒(méi)有畫(huà)出來(lái)。
Nginx 和 PHP 服務(wù)還是在 Docker Swarm 中運(yùn)行。本文只是對(duì)遷移后出了問(wèn)題的庫(kù)進(jìn)行分享,下面來(lái)看看遷移的方案吧。
遷移流程方案
遷移流程的方案:開(kāi)通 RDS 實(shí)例→備份 SQL→導(dǎo)入到 RDS→修改數(shù)據(jù)庫(kù)配置文件→測(cè)試驗(yàn)證。
遷移步驟如下:
- 根據(jù)業(yè)務(wù)量規(guī)劃開(kāi)通 RDS 實(shí)例,創(chuàng)建數(shù)據(jù)庫(kù)和用戶(hù)
- 提前做好 RDS 白名單,添加允許訪(fǎng)問(wèn) RDS 的 IP 地址
- mysqldump 備份 Docker 中的 MySQL
- 把備份好的 .sql 文件導(dǎo)入到 RDS 中
- 修改 PHP 項(xiàng)目的數(shù)據(jù)庫(kù)配置文件
- 清空 PHP 項(xiàng)目的緩存文件或目錄
- 測(cè)試驗(yàn)證
- RDS 定時(shí)備份
具體遷移細(xì)節(jié)就不展示了,我是在夜深人靜的時(shí)候進(jìn)行遷移操作的,確定大半夜沒(méi)人訪(fǎng)問(wèn)我們的 App 和網(wǎng)站了才開(kāi)干的。
我們的業(yè)務(wù)情況有點(diǎn)像股市,我們是晚上 12 點(diǎn)不許操作和交易,第 2 天早上 9 點(diǎn)開(kāi)盤(pán),9 點(diǎn)鐘是并發(fā)的高峰期,就像朝陽(yáng)大悅城上午開(kāi)門(mén)一樣,大批的顧客同時(shí)并發(fā)過(guò)來(lái)了。
所以那天晚上在 12 點(diǎn) 15 分準(zhǔn)時(shí)開(kāi)干,按計(jì)劃和提前準(zhǔn)備的配置、命令、腳本進(jìn)行操作的。
把 Docker 中運(yùn)行的 MySQL 遷移到 RDS 上非常順利,好幾個(gè)庫(kù)的遷移不到半個(gè)小時(shí)就結(jié)束了,并且把網(wǎng)站和 App 的流程都跑了一遍,也都是妥妥的。
最終把提前準(zhǔn)備好的備份腳本放在 crontab 中定時(shí)執(zhí)行,可以看下腳本內(nèi)容:
- #!/bin/bash
- #數(shù)據(jù)庫(kù)IP
- dbserver='*******'
- #數(shù)據(jù)庫(kù)用戶(hù)名
- dbuser='ganbing'
- #數(shù)據(jù)庫(kù)密碼
- dbpasswd='************'
- #備份數(shù)據(jù)庫(kù),多個(gè)庫(kù)用空格隔開(kāi)
- dbname='db1 db2 db3'
- #備份時(shí)間
- 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 "開(kāi)始備份"
- #日志記錄頭部
- echo "" >> ${logpath}/${dbname}_back.log
- echo "-------------------------------------------------" >> ${logpath}/${dbname}_back.log
- echo "備份時(shí)間為${backtime},備份數(shù)據(jù)庫(kù) ${dbname} 開(kāi)始" >> ${logpath}/${dbname}_back.log
- #正式備份數(shù)據(jù)庫(kù)
- 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
- #為節(jié)約硬盤(pán)空間,將數(shù)據(jù)庫(kù)壓縮
- tar zcf ${DB}-${backtime}.tar.gz ${DB}-${backtime}.sql > /dev/null
- #刪除原始文件,只留壓縮后文件
- rm -f ${DB}-${backtime}.sql
- #刪除15天前備份,也就是只保存15天內(nèi)的備份
- find $backpath -name "*.tar.gz" -type f -mtime +15 -exec rm -rf {} \; > /dev/null 2>&1
- echo "數(shù)據(jù)庫(kù) ${dbname} 備份成功!!" >> ${logpath}/${dbname}_back.log
- else
- #備份失敗則進(jìn)行以下操作
- echo "數(shù)據(jù)庫(kù) ${dbname} 備份失敗!!" >> ${logpath}/${dbname}_back.log
- fi
- done
- echo "完成備份"
- echo "################## ${backtime} #############################"
到了 1 點(diǎn)鐘,確定沒(méi)問(wèn)題后發(fā)通知到群里,發(fā)微信給領(lǐng)導(dǎo)表示已遷移完成,進(jìn)行很順利,然后筆者打車(chē)回家,睡覺(jué)。
雪崩來(lái)臨
其實(shí)這一晚筆者睡得也不踏實(shí),到了 8 點(diǎn)半就醒了,因?yàn)槲覀?9 點(diǎn)鐘開(kāi)盤(pán),會(huì)有大量的客戶(hù)涌進(jìn),每天開(kāi)始產(chǎn)生新的交易(買(mǎi)入和賣(mài)出),給大家看下截圖:
果不其然,9 點(diǎn)過(guò)后,我打開(kāi) App,一切正常,點(diǎn)擊切換幾個(gè)界面后,發(fā)現(xiàn)其中一個(gè)功能的請(qǐng)求超時(shí)了,一直在轉(zhuǎn),然后緊接著其他功能也超時(shí)了。
完了,出問(wèn)題了。趕緊開(kāi)電腦查問(wèn)題,過(guò)了一會(huì)兒群里就開(kāi)始沸騰了(反映好多客戶(hù)打開(kāi) App 都顯示請(qǐng)求超時(shí)了),我的電話(huà)也***時(shí)間響了,技術(shù)總監(jiān)打來(lái)的,問(wèn)我怎么回事,我說(shuō)正在開(kāi)電腦排查。
緊急處理
排查問(wèn)題
電腦打開(kāi)后,首先想到的就是 RDS 數(shù)據(jù)庫(kù)出了問(wèn)題,登錄阿里云,進(jìn)入 RDS 中的 DMS 數(shù)據(jù)管理控制臺(tái),一進(jìn)去就傻眼了 “CPU 爆了”,這么多連接數(shù),如下圖:
進(jìn)入會(huì)話(huà)去看看,發(fā)現(xiàn)會(huì)話(huà)“炸鍋了”,發(fā)現(xiàn)幾百頁(yè)的 select 都擠在 ub_user_calculate 這個(gè)表中,這個(gè)表數(shù)據(jù)量相對(duì)大一些,目前有 200 多萬(wàn)條數(shù)據(jù),如下圖:
我的自然反應(yīng)就是去查看此表的結(jié)構(gòu),但發(fā)現(xiàn)此表沒(méi)有索引,我被驚訝到了,竟然沒(méi)有索引,這......
然后筆者返回源數(shù)據(jù)庫(kù)查看這張表,也發(fā)現(xiàn)沒(méi)有索引,由此可以確定我導(dǎo)過(guò)來(lái)的這張表就是沒(méi)有創(chuàng)建索引,如下圖:
當(dāng)數(shù)據(jù)庫(kù)中出現(xiàn)訪(fǎng)問(wèn)表的 SQL 沒(méi)創(chuàng)建索引,會(huì)導(dǎo)致全表掃描,如果表的數(shù)據(jù)量很大,掃描大量的數(shù)據(jù),執(zhí)行效率過(guò)慢,占用數(shù)據(jù)庫(kù)連接,連接數(shù)堆積很快達(dá)到數(shù)據(jù)庫(kù)的***連接數(shù)設(shè)置,新的應(yīng)用請(qǐng)求將會(huì)被拒絕導(dǎo)致故障發(fā)生。
解決問(wèn)題
我趕緊把此事反映給開(kāi)發(fā)負(fù)責(zé)人,表明問(wèn)題根源找到了,會(huì)話(huà)鎖死了,是由其中的一張表沒(méi)有索引而導(dǎo)致的,問(wèn)詢(xún)需要給哪幾個(gè)字段加索引。
然后接著操作增加索引:
點(diǎn)擊保存后,發(fā)現(xiàn)創(chuàng)建索引的 SQL 一直卡死著,如下圖所示:
突然想起來(lái)還有一堆會(huì)話(huà)在那里,先 Kill 掉所有會(huì)話(huà)吧,不然索引肯定創(chuàng)建不了,然后又發(fā)現(xiàn)會(huì)話(huà)根本殺不完,如下圖:
怎么辦呢?會(huì)話(huà)殺不完...沒(méi)辦法,先把訪(fǎng)問(wèn)入口切斷吧,反正現(xiàn)在用戶(hù)訪(fǎng)問(wèn)也超時(shí),就毅然決定先把域名停了,訪(fǎng)問(wèn)入口給切斷了,然后在增加索引。索引加上了,發(fā)現(xiàn) CPU 還下不去,如下圖:
為了快速讓 CPU 降下去,重啟這個(gè)實(shí)例吧:
實(shí)例重啟完后,CPU 下去了,會(huì)話(huà)也下去了:
開(kāi)啟入口層的域名訪(fǎng)問(wèn)吧,再次觀(guān)察現(xiàn)在的會(huì)話(huà)和 CPU 等況,如下圖:
這就對(duì)了,會(huì)話(huà)也正常了,通知領(lǐng)導(dǎo)業(yè)務(wù)恢復(fù)。
再來(lái)看一下服務(wù)器 CPU 的情況(遷移 MySQL 后的情況),明顯逐漸好轉(zhuǎn)。
索引使用策略及優(yōu)化
創(chuàng)建索引注意事項(xiàng):
- 在經(jīng)常查詢(xún)而不經(jīng)常增刪改操作的字段加索引。
- order by 與 group by 后應(yīng)直接使用字段,而且字段應(yīng)該是索引字段。
- 一個(gè)表上的索引不應(yīng)該超過(guò) 6 個(gè)。
- 索引字段的長(zhǎng)度固定,且長(zhǎng)度較短。
- 索引字段重復(fù)不能過(guò)多,如果某個(gè)字段為主鍵,那么這個(gè)字段不用設(shè)為索引。
- 在過(guò)濾性高的字段上加索引。
使用索引注意事項(xiàng):
- 使用 like 關(guān)鍵字時(shí),前置 % 會(huì)導(dǎo)致索引失效。
- 使用 null 值會(huì)被自動(dòng)從索引中排除,索引一般不會(huì)建立在有空值的列上。
- 使用 or 關(guān)鍵字時(shí),or 左右字段如果存在一個(gè)沒(méi)有索引,有索引字段也會(huì)失效。
- 使用 != 操作符時(shí),將放棄使用索引。因?yàn)榉秶淮_定,使用索引效率不高,會(huì)被引擎自動(dòng)改為全表掃描。
- 不要在索引字段進(jìn)行運(yùn)算。
- 在使用復(fù)合索引時(shí),最左前綴原則,查詢(xún)時(shí)必須使用索引的***個(gè)字段,否則索引失效;并且應(yīng)盡量讓字段順序與索引順序一致。
- 避免隱式轉(zhuǎn)換,定義的數(shù)據(jù)類(lèi)型與傳入的數(shù)據(jù)類(lèi)型保持一致。
參考鏈接:https://help.aliyun.com/document_detail/52274.html?spm=a2c4g.11174283.6.812.ZGPyBQ
總結(jié)
此次故障雖然是表沒(méi)有索引造成的,但是我是有責(zé)任的,沒(méi)有挨個(gè)表檢查一下表的結(jié)構(gòu)。
通過(guò)此次故障也可以看出來(lái)開(kāi)發(fā)在設(shè)計(jì)表的時(shí)候真的要非常的重視,注意細(xì)節(jié)。
還有就是之前在容器中運(yùn)行的 MySQL 也時(shí)不時(shí)的出現(xiàn) CPU 瓶頸(比如 CPU 使用率偶爾會(huì)達(dá)到 80% 以上),我應(yīng)該提前發(fā)現(xiàn)這些問(wèn)題,徹底排查找出問(wèn)題所在原因再進(jìn)行遷庫(kù)的操作。
福利來(lái)啦
掃描下方二維碼。關(guān)注51CTO技術(shù)棧公眾號(hào),歡迎在技術(shù)棧微信公眾號(hào)留言探討,您運(yùn)維工作中最驚心動(dòng)魄的“救火故事”,小編將選出留言最精彩的 10 名網(wǎng)友,送出《Docker從入門(mén)到實(shí)戰(zhàn)》圖書(shū)一本~活動(dòng)截止時(shí)間 5 月 4 日 12 時(shí)整,特別鳴謝機(jī)械工業(yè)出版社為本次活動(dòng)提供的圖書(shū)贊助。
內(nèi)容簡(jiǎn)介
深度剖析 Docker 的核心概念、實(shí)現(xiàn)原理、應(yīng)用技巧和生態(tài)系統(tǒng);全面涵蓋 Docker 四大管理工具、三大組件、集群編排;介紹上百個(gè)實(shí)戰(zhàn)案例,提升動(dòng)手能力;以 Docker 當(dāng)前的流行版本為例講解 Swarm 集群管理。
甘兵,高級(jí)運(yùn)維工程師,6 年運(yùn)維工作經(jīng)驗(yàn)。曾就職于國(guó)家互聯(lián)網(wǎng)應(yīng)急中心、天音控股等企業(yè)。擁有豐富系統(tǒng)運(yùn)維經(jīng)驗(yàn),大型網(wǎng)絡(luò)架構(gòu)設(shè)計(jì)經(jīng)驗(yàn)。熱衷于開(kāi)源技術(shù)的研究,關(guān)注的技術(shù)方向 Docker、DevOps 等。
【51CTO原創(chuàng)稿件,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文作者和出處為51CTO.com】











































