前言線上定位問(wèn)題時(shí),主要靠監(jiān)控和日志顾画。一旦超出監(jiān)控的范圍取劫,則排查思路很重要,按照流程化的思路來(lái)定位問(wèn)題研侣,能夠讓我們?cè)诙ㄎ粏?wèn)題時(shí)從容谱邪、淡定,快速的定位到線上的問(wèn)題庶诡。
線上問(wèn)題定位思維導(dǎo)圖一 服務(wù)器層面1.1 磁盤(pán)1.1.1 問(wèn)題現(xiàn)象當(dāng)磁盤(pán)容量不足的時(shí)候惦银,應(yīng)用時(shí)常會(huì)拋出如下的異常信息:
java.io.IOException: 磁盤(pán)空間不足
或是類似如下告警信息:1.1.2 排查思路****1.1.2.1 利用 df 查詢磁盤(pán)狀態(tài)利用以下指令獲取磁盤(pán)狀態(tài):
df -h
結(jié)果是:可知 / 路徑下占用量最大。1.1.2.2 利用 du 查看文件夾大小利用以下指令獲取目錄下文件夾大心┦摹:
du -sh *
結(jié)果是:可知root文件夾占用空間最大瓮恭,然后層層遞推找到對(duì)應(yīng)的最大的一個(gè)或數(shù)個(gè)文件夾蝗碎。1.1.2.3 利用 ls 查看文件大小利用以下指令獲取目錄下文件夾大畜翱 :
ls -lh
結(jié)果是:可以找到最大的文件是日志文件时呀,然后使用rm指令進(jìn)行移除以釋放磁盤(pán)。1.1.3 相關(guān)命令****1.1.3.1 df主要是用于顯示目前在 Linux 系統(tǒng)上的文件系統(tǒng)磁盤(pán)使用情況統(tǒng)計(jì)晴玖。(1)常用參數(shù)啟動(dòng)參數(shù):
1.1.3.2 du主要是為了顯示目錄或文件的大小读存。(1)常用參數(shù)啟動(dòng)參數(shù):
1.1.3.3 ls主要是用于顯示指定工作目錄下的內(nèi)容的信息为流。(1)常用參數(shù)啟動(dòng)參數(shù):
1.2 CPU過(guò)高1.2.1 問(wèn)題現(xiàn)象當(dāng)CPU過(guò)高的時(shí)候,接口性能會(huì)快速下降让簿,同時(shí)監(jiān)控也會(huì)開(kāi)始報(bào)警敬察。1.2.2 排查思路****1.2.2.1 利用 top 查詢CPU使用率最高的進(jìn)程利用以下指令獲取系統(tǒng)CPU使用率信息:
top
結(jié)果是:從而可以得知pid為14201的進(jìn)程使用CPU最高。1.2.3 相關(guān)命令****1.2.3.1 top(1)常用參數(shù)啟動(dòng)參數(shù):
top進(jìn)程內(nèi)指令參數(shù):
二 應(yīng)用層面2.1 Tomcat假死案例分析2.1.1 發(fā)現(xiàn)問(wèn)題監(jiān)控平臺(tái)發(fā)現(xiàn)某個(gè)Tomcat節(jié)點(diǎn)已經(jīng)無(wú)法采集到數(shù)據(jù)拜英,連上服務(wù)器查看服務(wù)器進(jìn)程還在静汤,netstat -anop|grep 8001端口也有監(jiān)聽(tīng),查看日志打印時(shí)斷時(shí)續(xù)居凶。
2.2.2 查詢?nèi)罩?/strong>查看NG日志虫给,發(fā)現(xiàn)有數(shù)據(jù)進(jìn)入到當(dāng)前服務(wù)器(有8001和8002兩個(gè)Tomcat),NG顯示8002節(jié)點(diǎn)訪問(wèn)正常侠碧,8001節(jié)點(diǎn)有404錯(cuò)誤打印抹估,說(shuō)明Tomcat已經(jīng)處于假死狀態(tài),這個(gè)Tomcat已經(jīng)不能正常工作了弄兜。過(guò)濾Tomcat節(jié)點(diǎn)的日志药蜻,發(fā)現(xiàn)有OOM的異常,但是重啟后,有時(shí)候Tomcat掛掉后替饿,又不會(huì)打印如下OOM的異常:
TopicNewController.getTopicSoftList() error="Java heap space
2.2.3 獲取內(nèi)存快照在一次OOM發(fā)生后立刻抓取內(nèi)存快照,需要執(zhí)行命令的用戶與JAVA進(jìn)程啟動(dòng)用戶是同一個(gè)语泽,否則會(huì)有異常:
/data/program/jdk/bin/jmap -dump:live,format=b,file=/home/www/jmaplogs/jmap-8001-2.bin 18760
內(nèi)存dump文件比較大,有1.4G视卢,先壓縮踱卵,然后拉取到本地用7ZIP解壓。linux壓縮dump為.tgz据过。在windows下用7zip需要經(jīng)過(guò)2步解壓:
.bin.tgz---.bin.tar--.bin
2.2.4 分析內(nèi)存快照文件使用Memory Analyzer解析dump文件惋砂,發(fā)現(xiàn)有很明顯的內(nèi)存泄漏提示。
點(diǎn)擊查看詳情绳锅,發(fā)現(xiàn)定位到了代碼的具體某行西饵,一目了然:
查看shallow heap與retained heap能發(fā)現(xiàn)生成了大量的Object(810325個(gè)對(duì)象),后面分析代碼發(fā)現(xiàn)是上報(bào)softItem對(duì)象超過(guò)300多萬(wàn)個(gè)對(duì)象鳞芙,在循環(huán)的時(shí)候眷柔,所有的數(shù)據(jù)全部保存在某個(gè)方法中無(wú)法釋放,導(dǎo)致內(nèi)存堆積到1.5G原朝,從而超過(guò)了JVM分配的最大數(shù)闯割,從而出現(xiàn)OOM。
java.lang.Object[810325] @ 0xb0e971e0
2.2.5 相關(guān)知識(shí)****2.2.5.1 JVM內(nèi)存
2.2.5.2 內(nèi)存分配的流程
如果通過(guò)逃逸分析竿拆,則會(huì)先在TLAB分配,如果不滿足條件才在Eden上分配宾尚。2.2.4.3 GC
(1)GC觸發(fā)的場(chǎng)景
(2)GC RootsGC Roots有4種對(duì)象:
虛擬機(jī)棧(棧楨中的本地變量表)中的引用的對(duì)象丙笋,就是平時(shí)所指的java對(duì)象谢澈,存放在堆中。
方法區(qū)中的類靜態(tài)屬性引用的對(duì)象御板,一般指被static修飾引用的對(duì)象锥忿,加載類的時(shí)候就加載到內(nèi)存中。
方法區(qū)中的常量引用的對(duì)象怠肋。
本地方法棧中JNI(native方法)引用的對(duì)象敬鬓。
(3)GC算法
串行只使用單條GC線程進(jìn)行處理,而并行則使用多條笙各。
多核情況下钉答,并行一般更有執(zhí)行效率,但是單核情況下杈抢,并行未必比串行更有效率数尿。
STW會(huì)暫停所有應(yīng)用線程的執(zhí)行,等待GC線程完成后再繼續(xù)執(zhí)行應(yīng)用線程惶楼,從而會(huì)導(dǎo)致短時(shí)間內(nèi)應(yīng)用無(wú)響應(yīng)右蹦。
Concurrent會(huì)導(dǎo)致GC線程和應(yīng)用線程并發(fā)執(zhí)行,因此應(yīng)用線程和GC線程互相搶用CPU歼捐,從而會(huì)導(dǎo)致出現(xiàn)浮動(dòng)垃圾何陆,同時(shí)GC時(shí)間不可控。
(4)新生代使用的GC算法
新生代算法都是基于Coping的豹储,速度快贷盲。
Parallel Scavenge:吞吐量?jī)?yōu)先。
吞吐量=運(yùn)行用戶代碼時(shí)間 /(運(yùn)行用戶代碼時(shí)間 + 垃圾收集時(shí)間)
(5)老年代使用的GC算法
Concurrent Mark-Sweep(CMS)(6)垃圾收集器總結(jié)
(8)GC日志格式(a)監(jiān)控內(nèi)存的OOM場(chǎng)景不要在線上使用jmap手動(dòng)抓取內(nèi)存快照颂翼,其一系統(tǒng)OOM時(shí)手工觸發(fā)已經(jīng)來(lái)不及晃洒,另外在生成dump文件時(shí)會(huì)占用系統(tǒng)內(nèi)存資源,導(dǎo)致系統(tǒng)崩潰朦乏。只需要在JVM啟動(dòng)參數(shù)中提取設(shè)置如下參數(shù)球及,一旦OOM觸發(fā)會(huì)自動(dòng)生成對(duì)應(yīng)的文件,用MAT分析即可呻疹。
# 內(nèi)存OOM時(shí)吃引,自動(dòng)生成dump文件
如果Young GC比較頻繁,5S內(nèi)有打印一條刽锤,或者有Old GC的打印镊尺,代表內(nèi)存設(shè)置過(guò)小或者有內(nèi)存泄漏,此時(shí)需要抓取內(nèi)存快照進(jìn)行分享并思。(b)Young Gc日志
2020-09-23T01:45:05.487+0800: 126221.918: [GC (Allocation Failure) 2020-09-23T01:45:05.487+0800: 126221.918: [ParNew: 1750755K->2896K(1922432K), 0.0409026 secs] 1867906K->120367K(4019584K), 0.0412358 secs] [Times: user=0.13 sys=0.01, real=0.04 secs]
(c)Old GC日志
2020-10-27T20:27:57.733+0800: 639877.297: [Full GC (Heap Inspection Initiated GC) 2020-10-27T20:27:57.733+0800: 639877.297: [CMS: 165992K->120406K(524288K), 0.7776748 secs] 329034K->120406K(1004928K), [Metaspace: 178787K->178787K(1216512K)], 0.7787158 secs] [Times: user=0.71 sys=0.00, real=0.78 secs]
2.2 應(yīng)用CPU過(guò)高2.2.1 發(fā)現(xiàn)問(wèn)題一般情況下會(huì)有監(jiān)控告警進(jìn)行提示:
2.2.2 查找問(wèn)題進(jìn)程利用top查到占用cpu最高的進(jìn)程pid為14庐氮,結(jié)果圖如下:
2.2.3 查找問(wèn)題線程利用 top -H -p 查看進(jìn)程內(nèi)占用cpu最高線程,從下圖可知宋彼,問(wèn)題線程主要是activeCpu Thread弄砍,其pid為417仙畦。
2.2.4 查詢線程詳細(xì)信息
首先利用 printf "%x \n" 將tid換為十六進(jìn)制:xid。
再利用 jstack | grep nid=0x -A 10 查詢線程信息(若進(jìn)程無(wú)響應(yīng)音婶,則使用 jstack -f )慨畸,信息如下:
2.2.5 分析代碼由上一步可知該問(wèn)題是由 CpuThread.java 類引發(fā)的,故查詢項(xiàng)目代碼衣式,獲得如下信息:
2.2.6 獲得結(jié)論根據(jù)代碼和日志分析寸士,可知是由于限制值max太大,致使線程長(zhǎng)時(shí)間循環(huán)執(zhí)行碴卧,從而導(dǎo)致問(wèn)題出現(xiàn)弱卡。三 Mysql3.1 死鎖3.1.1 問(wèn)題出現(xiàn)最近線上隨著流量變大,突然開(kāi)始報(bào)如下異常螟深,即發(fā)生了死鎖問(wèn)題:
Deadlock found when trying to get lock; try restarting transaction ;
3.1.2 問(wèn)題分析****3.1.2.1 查詢事務(wù)隔離級(jí)別利用 select @@tx_isolation 命令獲取到數(shù)據(jù)庫(kù)隔離級(jí)別信息:
3.1.2.2 查詢數(shù)據(jù)庫(kù)死鎖日志利用 show engine innodb status 命令獲取到如下死鎖信息:
由上可知谐宙,是由于兩個(gè)事物對(duì)這條記錄同時(shí)持有S鎖(共享鎖)的情況下,再次嘗試獲取該條記錄的X鎖(排它鎖)界弧,從而導(dǎo)致互相等待引發(fā)死鎖凡蜻。3.1.2.3 分析代碼根據(jù)死鎖日志的SQL語(yǔ)句,定位獲取到如下偽代碼邏輯:
@Transactional(rollbackFor = Exception.class)
3.1.2.4 獲得結(jié)論分析獲得產(chǎn)生問(wèn)題的加鎖時(shí)序如下垢箕,然后修改代碼實(shí)現(xiàn)以解決該問(wèn)題划栓。
3.2 慢SQL3.1.1 問(wèn)題出現(xiàn)應(yīng)用TPS下降,并出現(xiàn)SQL執(zhí)行超時(shí)異程趸瘢或者出現(xiàn)了類似如下的告警信息忠荞,則常常意味著出現(xiàn)了慢SQL。
3.1.2 問(wèn)題分析分析執(zhí)行計(jì)劃:利用explain指令獲得該SQL語(yǔ)句的執(zhí)行計(jì)劃帅掘,根據(jù)該執(zhí)行計(jì)劃委煤,可能有兩種場(chǎng)景。
SQL不走索引或掃描行數(shù)過(guò)多等致使執(zhí)行時(shí)長(zhǎng)過(guò)長(zhǎng)修档。
SQL沒(méi)問(wèn)題碧绞,只是因?yàn)槭聞?wù)并發(fā)導(dǎo)致等待鎖,致使執(zhí)行時(shí)長(zhǎng)過(guò)長(zhǎng)吱窝。
3.1.3 場(chǎng)景一 ****3.1.3.1 優(yōu)化SQL通過(guò)增加索引讥邻,調(diào)整SQL語(yǔ)句的方式優(yōu)化執(zhí)行時(shí)長(zhǎng), 例如下的執(zhí)行計(jì)劃:
該SQL的執(zhí)行計(jì)劃的type為ALL院峡,同時(shí)根據(jù)以下type語(yǔ)義兴使,可知無(wú)索引的全表查詢,故可為其檢索列增加索引進(jìn)而解決照激。
3.1.4 場(chǎng)景二****3.1.4.1 查詢當(dāng)前事務(wù)情況可以通過(guò)查看如下3張表做相應(yīng)的處理:
-- 當(dāng)前運(yùn)行的所有事務(wù)
(1)查看當(dāng)前的事務(wù)有哪些:lock_table字段能看到被鎖的索引的表名发魄,lock_mode可以看到鎖類型是X鎖,lock_type可以看到是行鎖record。3.1.4.2 分析根據(jù)事務(wù)情況俩垃,得到表信息欠母,和相關(guān)的事務(wù)時(shí)序信息:
DROP TABLE IF EXISTS `emp`;
A事物鎖住一條記錄欢策,不提交,B事物需要更新此條記錄赏淌,此時(shí)會(huì)阻塞,如下圖是執(zhí)行順序:
3.1.4.3 解決方案
(1)修改方案由前一步的結(jié)果,分析事務(wù)間加鎖時(shí)序啄清,例如可以通過(guò)tx_query字段得知被阻塞的事務(wù)SQL,trx_state得知事務(wù)狀態(tài)等六水,找到對(duì)應(yīng)代碼邏輯,進(jìn)行優(yōu)化修改辣卒。(2)臨時(shí)修改方案trx_mysql_thread_id是對(duì)應(yīng)的事務(wù)sessionId掷贾,可以通過(guò)以下命令殺死長(zhǎng)時(shí)間執(zhí)行的事務(wù),從而避免阻塞其他事務(wù)執(zhí)行荣茫。
kill 105853
3.3 連接數(shù)過(guò)多3.3.1 問(wèn)題出現(xiàn)常出現(xiàn)too many connections異常,數(shù)據(jù)庫(kù)連接到達(dá)最大連接數(shù)想帅。3.3.2 解決方案解決方案:
通過(guò)set global max_connections=XXX增大最大連接數(shù)。
先利用show processlist獲取連接信息啡莉,然后利用kill殺死過(guò)多的連港准。
常用腳本如下:
排序數(shù)據(jù)庫(kù)連接的數(shù)目
3.4 相關(guān)知識(shí)3.4.1 索引 ****3.4.1.1 MySql不同的存儲(chǔ)引擎
3.4.1.2 InnoDB B+Tree索引實(shí)現(xiàn)
主鍵索引(聚集索引):
葉子節(jié)點(diǎn)data域保存了完整的數(shù)據(jù)的地址。
主鍵與數(shù)據(jù)全部存儲(chǔ)在一顆樹(shù)上咧欣。
Root節(jié)點(diǎn)常駐內(nèi)存浅缸。
每個(gè)非葉子節(jié)點(diǎn)一個(gè)innodb_page_size大小,加速磁盤(pán)IO。
磁盤(pán)的I/O要比內(nèi)存慢幾百倍魄咕,而磁盤(pán)慢的原因在于機(jī)械設(shè)備尋找磁道慢衩椒,因此采用磁盤(pán)預(yù)讀,每次讀取一個(gè)磁盤(pán)頁(yè)(page:計(jì)算機(jī)管理存儲(chǔ)器的邏輯塊-通常為4k)的整倍數(shù)哮兰。
如果沒(méi)有主鍵,MySQL默認(rèn)生成隱含字段作為主鍵毛萌,這個(gè)字段長(zhǎng)度為6個(gè)字節(jié),類型為長(zhǎng)整形喝滞。
輔助索引結(jié)構(gòu)與主索引相同,但葉子節(jié)點(diǎn)data域保存的是主鍵指針阁将。
InnoDB以表空間Tablespace(idb文件)結(jié)構(gòu)進(jìn)行組織,每個(gè)Tablespace 包含多個(gè)Segment段囤躁。
每個(gè)段(分為2種段:葉子節(jié)點(diǎn)Segment&非葉子節(jié)點(diǎn)Segment)冀痕,一個(gè)Segment段包含多個(gè)Extent。
一個(gè)Extent占用1M空間包含64個(gè)Page(每個(gè)Page 16k)狸演,InnoDB B-Tree 一個(gè)邏輯節(jié)點(diǎn)就分配一個(gè)物理Page言蛇,一個(gè)節(jié)點(diǎn)一次IO操作。
一個(gè)Page里包含很多有序數(shù)據(jù)Row行數(shù)據(jù)宵距,Row行數(shù)據(jù)中包含F(xiàn)iled屬性數(shù)據(jù)等信息腊尚。
InnoDB存儲(chǔ)引擎中頁(yè)的大小為16KB,一般表的主鍵類型為INT(占用4個(gè)字節(jié))或BIGINT(占用8個(gè)字節(jié))满哪,指針類型也一般為4或8個(gè)字節(jié)婿斥,也就是說(shuō)一個(gè)頁(yè)(B+Tree中的一個(gè)節(jié)點(diǎn))中大概存儲(chǔ)16KB/(8B+8B)=1K個(gè)鍵值(因?yàn)槭枪乐等芭瘢瑸榉奖阌?jì)算,這里的K取值為[10]^3)民宿。也就是說(shuō)一個(gè)深度為3的B+Tree索引可以維護(hù) 10^3 * 10^3 * 10^3 = 10億 條記錄娇妓。
每個(gè)索引的左指針都是比自己小的索引/節(jié)點(diǎn),右指針是大于等于自己的索引/節(jié)點(diǎn)活鹰。3.4.2 B+ Tree索引檢索****3.4.2.1 主鍵索引檢索
select * from table where id = 1
3.4.2.2 輔助索引檢索
select * from table where name = 'a'
3.4.3 事物的隔離級(jí)別****3.4.3.1 如何查看數(shù)據(jù)庫(kù)的事務(wù)隔離級(jí)別使用如下命令可以查看事務(wù)的隔離級(jí)別:
show variables like 'tx_isolation';
阿里云上的rds的隔離級(jí)別是read committed 哈恰,而不是原生mysql的“可重復(fù)讀(repeatable-read)。
Repeatable read不存在幻讀的問(wèn)題志群,RR隔離級(jí)別保證對(duì)讀取到的記錄加鎖 (記錄鎖)着绷,同時(shí)保證對(duì)讀取的范圍加鎖,新的滿足查詢條件的記錄不能夠插入 (間隙鎖)锌云,不存在幻讀現(xiàn)象荠医。
在MYSQL的事務(wù)引擎中,INNODB是使用范圍最廣的桑涎。它默認(rèn)的事務(wù)隔離級(jí)別是REPEATABLE READ(可重復(fù)讀)彬向,在標(biāo)準(zhǔn)的事務(wù)隔離級(jí)別定義下,REPEATABLE READ是不能防止幻讀產(chǎn)生的石洗。INNODB使用了next-key locks實(shí)現(xiàn)了防止幻讀的發(fā)生幢泼。
在默認(rèn)情況下,mysql的事務(wù)隔離級(jí)別是可重復(fù)讀讲衫,并且innodb_locks_unsafe_for_binlog參數(shù)為OFF缕棵,這時(shí)默認(rèn)采用next-key locks。所謂Next-Key Locks涉兽,就是Record lock和gap lock的結(jié)合招驴,即除了鎖住記錄本身,還要再鎖住索引之間的間隙枷畏”鹄澹可以設(shè)置為ON,則RR隔離級(jí)別時(shí)會(huì)出現(xiàn)幻讀拥诡。
3.4.3.2 多版本并發(fā)控制MVCCMySQL InnoDB存儲(chǔ)引擎触趴,實(shí)現(xiàn)的是基于多版本的并發(fā)控制協(xié)議——MVCC (Multi-Version Concurrency Control) (注:與MVCC相對(duì)的,是基于鎖的并發(fā)控制渴肉,Lock-Based Concurrency Control)冗懦。MVCC最大的好處,相信也是耳熟能詳:讀不加鎖仇祭,讀寫(xiě)不沖突披蕉。在讀多寫(xiě)少的OLTP應(yīng)用中,讀寫(xiě)不沖突是非常重要的,極大的增加了系統(tǒng)的并發(fā)性能没讲。在MVCC并發(fā)控制中眯娱,讀操作可以分成兩類:快照讀 (snapshot read)與當(dāng)前讀 (current read)∨来眨快照讀:簡(jiǎn)單的select操作徙缴,屬于快照讀,不加鎖贰谣。(當(dāng)然娜搂,也有例外,下面會(huì)分析)吱抚。
select * from table where ?;
當(dāng)前讀:特殊的讀操作,插入/更新/刪除操作考廉,屬于當(dāng)前讀秘豹,需要加鎖。
select * from table where ? lock in share mode; 加S鎖 (共享鎖)
3.4.4.3 場(chǎng)景模擬修改事務(wù)隔離級(jí)別的語(yǔ)句:
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
(1)臟讀場(chǎng)景模擬
DROP TABLE IF EXISTS `employee`;
臟讀場(chǎng)景模擬(2)不可重復(fù)讀模擬
DROP TABLE IF EXISTS `employee`;
不可重復(fù)讀的重點(diǎn)是修改: 同樣的條件, 你讀取過(guò)的數(shù)據(jù), 再次讀取出來(lái)發(fā)現(xiàn)值不一樣了昌粤。
(3)幻讀場(chǎng)景模擬表結(jié)構(gòu)與數(shù)據(jù)如下:id不是主鍵既绕,也不是唯一索引,只是一個(gè)普通索引涮坐,事務(wù)隔離級(jí)別設(shè)置的是RR凄贩,可以模擬到GAP鎖產(chǎn)生的場(chǎng)景。
DROP TABLE IF EXISTS `emp`;
修改id=20的數(shù)據(jù)后袱讹,會(huì)在加多個(gè)鎖:20會(huì)被加X(jué)鎖,[10-20],[20-30]之間會(huì)被加GAP鎖疲扎。
幻讀的重點(diǎn)在于新增或者刪除 (數(shù)據(jù)條數(shù)變化)。同樣的條件, 第1次和第2次讀出來(lái)的記錄數(shù)不一樣捷雕。在標(biāo)準(zhǔn)的事務(wù)隔離級(jí)別定義下椒丧,REPEATABLE READ是不能防止幻讀產(chǎn)生的。INNODB使用了2種技術(shù)手段(MVCC AND GAP LOCK)實(shí)現(xiàn)了防止幻讀的發(fā)生救巷。3.4.4 Innodb的多種鎖****3.4.4.1 鎖類型
表鎖的優(yōu)勢(shì):開(kāi)銷泻;加鎖快浦译;無(wú)死鎖棒假。
表鎖的劣勢(shì):鎖粒度大,發(fā)生鎖沖突的概率高精盅,并發(fā)處理能力低帽哑。
加鎖的方式:自動(dòng)加鎖。查詢操作(SELECT)渤弛,會(huì)自動(dòng)給涉及的所有表加讀鎖祝拯,更新操作(UPDATE、DELETE、INSERT)佳头,會(huì)自動(dòng)給涉及的表加寫(xiě)鎖鹰贵。也可以顯示加鎖。
共享讀鎖:lock table tableName read
獨(dú)占寫(xiě)鎖:lock table tableName write
批量解鎖:unlock tables
3.4.4.2 行鎖
只在Repeatable read和Serializable兩種事務(wù)隔離級(jí)別下才會(huì)取得上面的鎖康嘉。3.4.4.3 意向鎖(1)場(chǎng)景在mysql中有表鎖碉输,LOCK TABLE my_tabl_name READ; 用讀鎖鎖表,會(huì)阻塞其他事務(wù)修改表數(shù)據(jù)亭珍。LOCK TABLE my_table_name WRITe; 用寫(xiě)鎖鎖表敷钾,會(huì)阻塞其他事務(wù)讀和寫(xiě)。Innodb引擎又支持行鎖肄梨,行鎖分為共享鎖阻荒,一個(gè)事務(wù)對(duì)一行的共享只讀鎖。排它鎖众羡,一個(gè)事務(wù)對(duì)一行的排他讀寫(xiě)鎖侨赡。這兩中類型的鎖共存的問(wèn)題考慮這個(gè)例子:事務(wù)A鎖住了表中的一行,讓這一行只能讀粱侣,不能寫(xiě)羊壹。之后,事務(wù)B申請(qǐng)整個(gè)表的寫(xiě)鎖齐婴。如果事務(wù)B申請(qǐng)成功油猫,那么理論上它就能修改表中的任意一行,這與A持有的行鎖是沖突的柠偶。數(shù)據(jù)庫(kù)需要避免這種沖突情妖,就是說(shuō)要讓B的申請(qǐng)被阻塞,直到A釋放了行鎖嚣州。(2)問(wèn)題數(shù)據(jù)庫(kù)要怎么判斷這個(gè)沖突呢鲫售?(3)答案無(wú)意向鎖的情況下:
step1:判斷表是否已被其他事務(wù)用表鎖鎖表
step2:判斷表中的每一行是否已被行鎖鎖住。
有意向鎖的情況下:
step1:不變
step2:發(fā)現(xiàn)表上有意向共享鎖该肴,說(shuō)明表中有些行被共享行鎖鎖住了情竹,因此,事務(wù)B申請(qǐng)表的寫(xiě)鎖會(huì)被阻塞匀哄。
(4)總結(jié)在無(wú)意向鎖的情況下秦效,step2需要遍歷整個(gè)表,才能確認(rèn)是否能拿到表鎖。而在意向鎖存在的情況下涎嚼,事務(wù)A必須先申請(qǐng)表的意向共享鎖阱州,成功后再申請(qǐng)一行的行鎖,不需要再遍歷整個(gè)表法梯,提升了效率苔货。因此意向鎖主要是為了實(shí)現(xiàn)多粒度鎖機(jī)制(白話:為了表鎖和行鎖都能用)犀概。3.4.4.4 X/S鎖
3.4.4.5 一條SQL的加鎖分析
-- select操作均不加鎖,采用的是快照讀夜惭,因此在下面的討論中就忽略了
組合分為如下幾種場(chǎng)景:
(1)組合7的GAP鎖詳解讀
Insert操作姻灶,如insert [10,aa],首先會(huì)定位到[6,c]與[10,b]間诈茧,然后在插入前产喉,會(huì)檢查這個(gè)GAP是否已經(jīng)被鎖上,如果被鎖上敢会,則Insert不能插入記錄曾沈。因此,通過(guò)第一遍的當(dāng)前讀鸥昏,不僅將滿足條件的記錄鎖上 (X鎖)塞俱,與組合三類似。同時(shí)還是增加3把GAP鎖吏垮,將可能插入滿足條件記錄的3個(gè)GAP給鎖上敛腌,保證后續(xù)的Insert不能插入新的id=10的記錄,也就杜絕了同一事務(wù)的第二次當(dāng)前讀惫皱,出現(xiàn)幻象的情況。既然防止幻讀尤莺,需要靠GAP鎖的保護(hù)旅敷,為什么組合五、組合六颤霎,也是RR隔離級(jí)別媳谁,卻不需要加GAP鎖呢?GAP鎖的目的友酱,是為了防止同一事務(wù)的兩次當(dāng)前讀晴音,出現(xiàn)幻讀的情況。而組合五缔杉,id是主鍵锤躁;組合六,id是unique鍵或详,都能夠保證唯一性系羞。一個(gè)等值查詢,最多只能返回一條記錄霸琴,而且新的相同取值的記錄椒振,一定不會(huì)在新插入進(jìn)來(lái),因此也就避免了GAP鎖的使用梧乘。(2)結(jié)論
Repeatable Read隔離級(jí)別下澎迎,id列上有一個(gè)非唯一索引,對(duì)應(yīng)SQL:delete from t1 where id = 10; 首先,通過(guò)id索引定位到第一條滿足查詢條件的記錄夹供,加記錄上的X鎖灵份,加GAP上的GAP鎖,然后加主鍵聚簇索引上的記錄X鎖罩引,然后返回各吨;然后讀取下一條,重復(fù)進(jìn)行袁铐。直至進(jìn)行到第一條不滿足條件的記錄[11,f]揭蜒,此時(shí),不需要加記錄X鎖剔桨,但是仍舊需要加GAP鎖屉更,最后返回結(jié)束。
什么時(shí)候會(huì)取得gap lock或nextkey lock 這和隔離級(jí)別有關(guān),只在REPEATABLE READ或以上的隔離級(jí)別下的特定操作才會(huì)取得gap lock或nextkey lock洒缀。
3.4.5 線上問(wèn)題處理****3.4.5.1 觀察問(wèn)題的幾個(gè)常見(jiàn)庫(kù)表首先可以通過(guò)下屬兩個(gè)命令來(lái)查看mysql的相應(yīng)的系統(tǒng)變量和狀態(tài)變量瑰谜。
# status代表當(dāng)前系統(tǒng)的運(yùn)行狀態(tài),只能查看树绩,不能修改
MySQL 5.7.6開(kāi)始后改成了從如下表獲热浴:
performance_schema.global_variables
之前是從如下表獲取:
INFORMATION_SCHEMA.GLOBAL_VARIABLES
比較常用的系統(tǒng)變量和狀態(tài)變量有:
# 查詢慢SQL查詢是否開(kāi)啟
3.5 一些建議3.5.1 小表驅(qū)動(dòng)大表
nb_soft_nature:小表
nb_soft:大表
package_name:都是索引
MySQL 表關(guān)聯(lián)的算法是Nest Loop Join(嵌套循環(huán)連接)饺饭,是通過(guò)驅(qū)動(dòng)表的結(jié)果集作為循環(huán)基礎(chǔ)數(shù)據(jù)渤早,然后一條一條地通過(guò)該結(jié)果集中的數(shù)據(jù)作為過(guò)濾條件到下一個(gè)表中查詢數(shù)據(jù),然后合并結(jié)果瘫俊。(1)小表驅(qū)動(dòng)大表
nb_soft_nature 中只有24條數(shù)據(jù)鹊杖,每條數(shù)據(jù)的package_name連接到nb_soft表中做查詢,由于package_name在nb_soft表中有索引扛芽,因此一共只需要24次掃描即可骂蓖。(2)大表驅(qū)動(dòng)小表
同上征讲,需要100多萬(wàn)次掃描才能返回結(jié)果3.5.2 使用自增長(zhǎng)主鍵結(jié)合B+Tree的特點(diǎn)流礁,自增主鍵是連續(xù)的龙优,在插入過(guò)程中盡量減少頁(yè)分裂吸申,即使要進(jìn)行頁(yè)分裂埂材,也只會(huì)分裂很少一部分氮帐。并且能減少數(shù)據(jù)的移動(dòng)昆庇,每次插入都是插入到最后榄棵〕案總之就是減少分裂和移動(dòng)的頻率筐钟。**四 Redis **4.1 問(wèn)題處理思路
4.2 內(nèi)存告警時(shí)常會(huì)出現(xiàn)下述異常提示信息:
OOM command not allowed when used memory
4.2.1 設(shè)置合理的內(nèi)存大小設(shè)置maxmemory和相對(duì)應(yīng)的回收策略算法,設(shè)置最好為物理內(nèi)存的3/4赋朦,或者比例更小篓冲,因?yàn)閞edis復(fù)制數(shù)據(jù)等其他服務(wù)時(shí)李破,也是需要緩存的。以防緩存數(shù)據(jù)過(guò)大致使redis崩潰壹将,造成系統(tǒng)出錯(cuò)不可用嗤攻。(1)通過(guò)redis.conf 配置文件指定
maxmemory xxxxxx
(2)通過(guò)命令修改
config set maxmemory xxxxx
4.2.2 設(shè)置合理的內(nèi)存淘汰策略
(1)通過(guò)redis.conf 配置文件指定
maxmemory-policy allkeys-lru
4.2.3 查看大key(1)有工具的情況下:安裝工具dbatools redisTools,列出最大的前N個(gè)key
/data/program/dbatools-master/redisTools/redis-cli-new -h <ip> -p <port> --bigkeys --bigkey-numb 3
得到如下結(jié)果:
Sampled 122114 keys in the keyspace!
原生命令為:
/usr/local/redis-3.0.5/src/redis-cli -c -h <ip> -p <port> --bigkeys
分析rdb文件中的全部key/某種類型的占用量:
rdb -c memory dump.rdb -t list -f dump-formal-list.csv
查看某個(gè)key的內(nèi)存占用量:
[root@iZbp16umm14vm5kssepfdpZ redisTools]# redis-memory-for-key -s <ip> -p <port> x
(2)無(wú)工具的情況下可利用以下指令評(píng)估key大小:
debug object key
4.3 Redis的慢命令4.3.1 設(shè)置Redis的慢命令的時(shí)間閾值(單位:微妙)(1)通過(guò)redis.conf配置文件方式
# 執(zhí)行時(shí)間大于多少微秒(microsecond诽俯,1秒 = 1,000,000 微秒)的查詢進(jìn)行記錄妇菱。
(2)通過(guò)命令方式
# 配置查詢時(shí)間超過(guò)1毫秒的, 第一個(gè)參數(shù)單位是微秒
4.3.2 查看Redis的慢命令
slowlog get
4.4 連接過(guò)多(1)通過(guò)redis.conf 配置文件指定最大連接數(shù)
maxclients 10000
(2)通過(guò)命令修改
config set maxclients xxx
4.5 線上Redis節(jié)點(diǎn)掛掉一個(gè)之后的處理流程4.5.1 查看節(jié)點(diǎn)狀態(tài)執(zhí)行 cluster nodes 后發(fā)現(xiàn)會(huì)有一個(gè)節(jié)點(diǎn)dead:
[rgp@iZ23rjcqbczZ ~]$ /data/program/redis-3.0.3/bin/redis-cli -c -h <ip> -p <port>
4.5.2 移除錯(cuò)誤節(jié)點(diǎn)
(1)一開(kāi)始執(zhí)行如下的刪除操作失敗暴区,需要針對(duì)于每一個(gè)節(jié)點(diǎn)都執(zhí)行 cluster forget:
ip:port> cluster forget 61c70a61ad91bbac231e33352f5bdb9eb0be6289
(2)刪除掛掉的節(jié)點(diǎn):
[rgp@iZ23rjcqbczZ ~]$ /data/program/redis-3.0.3/bin/redis-trib.rb del-node m3 b643d7baa69922b3fdbd1e25ccbe6ed73587b948
(3)清理掉節(jié)點(diǎn)配置目錄下的rdb aof nodes.conf 等文件闯团,否則節(jié)點(diǎn)的啟動(dòng)會(huì)有如下異常:
[ERR] Node s3 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.
4.5.3 恢復(fù)節(jié)點(diǎn)
(1)后臺(tái)啟動(dòng)Redis某個(gè)節(jié)點(diǎn):
/data/program/redis-3.0.3/bin/redis-server /data/program/redis-3.0.3/etc/7001/redis.conf &
(2)將該節(jié)點(diǎn)添加進(jìn)集群:
[root@iZ23rjcqbczZ rgp]# /data/program/redis-3.0.3/bin/redis-trib.rb add-node --slave --master-id 6147bf416ef216b6a1ef2f100d15de4f439b7352 s3 m3
s3:本次待添加的從節(jié)點(diǎn)ip:port
m3:主節(jié)點(diǎn)的ip:port
6147bf416ef216b6a1ef2f100d15de4f439b7352:主節(jié)點(diǎn)編號(hào)
五 網(wǎng)絡(luò)5.1 排查流程5.1.1 現(xiàn)象出現(xiàn)在非壓測(cè)或者高峰期的情況下,突然出現(xiàn)大量的503等錯(cuò)誤碼仙粱,頁(yè)面無(wú)法打開(kāi)房交。5.1.2 查看是否遭受了DOS攻擊當(dāng)Server上有大量半連接狀態(tài)且源IP地址是隨機(jī)的,則可以斷定遭到SYN攻擊了伐割,使用如下命令可以讓之現(xiàn)行候味。
netstat -n|grep SYN_RECV
5.1.3 查看TCP連接狀態(tài)首先利用以下查看tcp總連接數(shù),判斷連接數(shù)是否正常:
netstat -anoe|grep 8000|wc -l 查看8000
然后利用如下命令判斷各個(gè)狀態(tài)的連接數(shù)是否正常:
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
根據(jù)上述信息隔心,如果TIME_WAIT 狀態(tài)數(shù)量過(guò)多白群,可利用如下命令查看連接CLOSE_WAIT最多的IP地址,再結(jié)合業(yè)務(wù)分析問(wèn)題:
netstat -n|grep TIME_WAIT|awk '{print $5}'|awk -F: '{print $1}'|sort|uniq -c|sort -nr|head -10
5.2 相關(guān)知識(shí)5.2.1 TCP連接TCP三次握手四次揮手
為什么在第3步中客戶端還要再進(jìn)行一次確認(rèn)呢硬霍?這主要是為了防止已經(jīng)失效的連接請(qǐng)求報(bào)文段突然又傳回到服務(wù)端而產(chǎn)生錯(cuò)誤的場(chǎng)景:
所謂"已失效的連接請(qǐng)求報(bào)文段"是這樣產(chǎn)生的川抡。正常來(lái)說(shuō),客戶端發(fā)出連接請(qǐng)求须尚,但因?yàn)檫B接請(qǐng)求報(bào)文丟失而未收到確認(rèn)。于是客戶端再次發(fā)出一次連接請(qǐng)求侍咱,后來(lái)收到了確認(rèn)耐床,建立了連接。數(shù)據(jù)傳輸完畢后楔脯,釋放了連接撩轰,客戶端一共發(fā)送了兩個(gè)連接請(qǐng)求報(bào)文段,其中第一個(gè)丟失昧廷,第二個(gè)到達(dá)了服務(wù)端堪嫂,沒(méi)有"已失效的連接請(qǐng)求報(bào)文段"。
現(xiàn)在假定一種異常情況木柬,即客戶端發(fā)出的第一個(gè)連接請(qǐng)求報(bào)文段并沒(méi)有丟失皆串,只是在某些網(wǎng)絡(luò)節(jié)點(diǎn)長(zhǎng)時(shí)間滯留了,以至于延誤到連接釋放以后的某個(gè)時(shí)間點(diǎn)才到達(dá)服務(wù)端眉枕。本來(lái)這個(gè)連接請(qǐng)求已經(jīng)失效了恶复,但是服務(wù)端收到此失效的連接請(qǐng)求報(bào)文段后怜森,就誤認(rèn)為這是客戶端又發(fā)出了一次新的連接請(qǐng)求。于是服務(wù)端又向客戶端發(fā)出請(qǐng)求報(bào)文段谤牡,同意建立連接副硅。假定不采用三次握手,那么只要服務(wù)端發(fā)出確認(rèn)翅萤,連接就建立了恐疲。由于現(xiàn)在客戶端并沒(méi)有發(fā)出連接建立的請(qǐng)求,因此不會(huì)理會(huì)服務(wù)端的確認(rèn)套么,也不會(huì)向服務(wù)端發(fā)送數(shù)據(jù)培己,但是服務(wù)端卻以為新的傳輸連接已經(jīng)建立了,并一直等待客戶端發(fā)來(lái)數(shù)據(jù)违诗,這樣服務(wù)端的許多資源就這樣白白浪費(fèi)了漱凝。采用三次握手的辦法可以防止上述現(xiàn)象的發(fā)生。比如在上述的場(chǎng)景下诸迟,客戶端不向服務(wù)端的發(fā)出確認(rèn)請(qǐng)求茸炒,服務(wù)端由于收不到確認(rèn),就知道客戶端并沒(méi)有要求建立連接阵苇。SYN攻擊時(shí)一種典型的DDOS攻擊壁公,檢測(cè)SYN攻擊的方式非常簡(jiǎn)單,即當(dāng)Server上有大量半連接狀態(tài)且源IP地址是隨機(jī)的绅项,則可以斷定遭到SYN攻擊了紊册,使用如下命令可以讓之現(xiàn)行:
netstat -nap | grep SYN_RECV
5.2.2 一些常見(jiàn)問(wèn)題(1)為什么TCP連接的建立只需要三次握手而TCP連接的釋放需要四次握手呢?因?yàn)榉?wù)端在LISTEN狀態(tài)下,收到建立請(qǐng)求的SYN報(bào)文后快耿,把ACK和SYN放在一個(gè)報(bào)文里發(fā)送給客戶端囊陡。而連接關(guān)閉時(shí),當(dāng)收到對(duì)方的FIN報(bào)文時(shí)掀亥,僅僅表示對(duì)方?jīng)]有需要發(fā)送的數(shù)據(jù)了撞反,但是還能接收數(shù)據(jù),己方未必?cái)?shù)據(jù)已經(jīng)全部發(fā)送給對(duì)方了搪花,所以己方可以立即關(guān)閉遏片,也可以將應(yīng)該發(fā)送的數(shù)據(jù)全部發(fā)送完畢后再發(fā)送FIN報(bào)文給客戶端來(lái)表示同意現(xiàn)在關(guān)閉連接。從這個(gè)角度而言撮竿,服務(wù)端的ACK和FIN一般都會(huì)分開(kāi)發(fā)送吮便。(2)如果已經(jīng)建立了連接,但是客戶端突然出現(xiàn)故障了怎么辦幢踏?TCP還設(shè)有一個(gè)彼栊瑁活計(jì)時(shí)器,顯然房蝉,客戶端如果出現(xiàn)故障授账,服務(wù)器不能一直等下去枯跑,白白浪費(fèi)資源。服務(wù)器每收到一次客戶端的請(qǐng)求后都會(huì)重新復(fù)位這個(gè)計(jì)時(shí)器白热,時(shí)間通常是設(shè)置為2小時(shí)敛助,若兩小時(shí)還沒(méi)有收到客戶端的任何數(shù)據(jù),服務(wù)器就會(huì)發(fā)送一個(gè)探測(cè)報(bào)文段屋确,以后每隔75秒鐘發(fā)送一次纳击。若一連發(fā)送10個(gè)探測(cè)報(bào)文仍然沒(méi)反應(yīng),服務(wù)器就認(rèn)為客戶端出了故障攻臀,接著就關(guān)閉連接焕数。(3)為什么TIME_WAIT狀態(tài)需要經(jīng)過(guò)2MSL(最大報(bào)文段生存時(shí)間)才能返回到CLOSE狀態(tài)?雖然按道理刨啸,四個(gè)報(bào)文都發(fā)送完畢堡赔,我們可以直接進(jìn)入CLOSE狀態(tài)了,但是我們必須假象網(wǎng)絡(luò)是不可靠的设联,有可以最后一個(gè)ACK丟失善已。所以TIME_WAIT狀態(tài)就是用來(lái)重發(fā)可能丟失的ACK報(bào)文。在Client發(fā)送出最后的ACK回復(fù)离例,但該ACK可能丟失换团。Server如果沒(méi)有收到ACK,將不斷重復(fù)發(fā)送FIN片段宫蛆。所以Client不能立即關(guān)閉艘包,它必須確認(rèn)Server接收到了該ACK。Client會(huì)在發(fā)送出ACK之后進(jìn)入到TIME_WAIT狀態(tài)耀盗。Client會(huì)設(shè)置一個(gè)計(jì)時(shí)器想虎,等待2MSL的時(shí)間。如果在該時(shí)間內(nèi)再次收到FIN叛拷,那么Client會(huì)重發(fā)ACK并再次等待2MSL磷醋。所謂的2MSL是兩倍的MSL(Maximum Segment Lifetime)。MSL指一個(gè)片段在網(wǎng)絡(luò)中最大的存活時(shí)間胡诗,2MSL就是一個(gè)發(fā)送和一個(gè)回復(fù)所需的最大時(shí)間。如果直到2MSL淌友,Client都沒(méi)有再次收到FIN煌恢,那么Client推斷ACK已經(jīng)被成功接收,則結(jié)束TCP連接震庭。六 業(yè)務(wù)異常日志6.1 問(wèn)題出現(xiàn)主要是通過(guò)業(yè)務(wù)日志監(jiān)控主動(dòng)報(bào)警或者是查看錯(cuò)誤日志被動(dòng)發(fā)現(xiàn):
6.2 日志分析6.2.1 確認(rèn)日志格式日志格式如下:
<property name="METRICS_LOG_PATTERN"
6.2.2 在日志文件中檢索異常利用如下命令可獲得異常的詳細(xì)信息:
cat error.log|grep -n " java.lang.reflect.InvocationTargetException"
根據(jù)日志格式和日志信息瑰抵,可獲得traceId為489d71fe-67db-4f59-a916-33f25d35cab8,然后利用以下指令獲取整個(gè)流程的日志信息:
cat biz.log |grep -n '489d71fe-67db-4f59-a916-33f25d35cab8'
6.2.3 代碼分析然后根據(jù)上述流程日志找到對(duì)應(yīng)的代碼實(shí)現(xiàn)器联,然后進(jìn)行具體的業(yè)務(wù)分析二汛。