肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

前言

性能調優,無疑是個龐大的話題,也是不少項目中很是重要的一環,性能調優難作是衆所周知的,畢竟性能調優涵蓋的面實在是太多了,在這裏我就大概的講一下企業中最經常使用的四種調優——JVM調優、MySQL調優、Nginx調優以及Tomcat調優,一家之言,有什麼說的不對的還請多包涵補充。css

篇幅所限,有些東西是確定寫不到的,因此本文只是挑了一些重要部分來剖析,若是須要完整詳細的掌握性能調優,能夠來領取系統整理的性能調優筆記和相關學習資料html

話很少說,坐穩扶好,發車嘍!前端

1、Jvm性能調優

一、JVM類加載機制詳解

以下圖所示,JVM類加載機制分爲五個部分:加載,驗證,準備,解析,初始化,下面咱們就分別來看一下這五個過程。java

肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

1.1 加載

在加載階段,虛擬機須要完成如下三件事情:node

1)經過一個類的全限定名來獲取定義此類的二進制字節流。注意這裏的二進制字節流不必定非得要從一個Class文件獲取,這裏既能夠從ZIP包中讀取(好比從jar包和war包中讀取),也能夠從網絡中獲取,也能夠在運行時計算生成(動態代理),也能夠由其它文件生成(好比將JSP文件轉換成對應的Class類)。mysql

2)將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構。nginx

3)在Java堆中生成一個表明這個類的java.lang.Class對象,做爲方法區這些數據的訪問入口。web

相對於類加載過程的其餘階段,加載階段(準確地說,是加載階段中獲取類的二進制字節流的動做)是開發期可控性最強的階段,由於加載階段既可使用系統提供的類加載器來完成,也能夠由用戶自定義的類加載器去完成,開發人員們能夠經過定義本身的類加載器去控制字節流的獲取方式。面試

1.2 驗證

這一階段的主要目的是爲了確保Class文件的字節流中包含的信息是否符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。正則表達式

1.3 準備

準備階段是正式爲類變量分配內存並設置類變量的初始值階段,即在方法區中分配這些變量所使用的內存空間。注意這裏所說的初始值概念,好比一個類變量定義爲:

public static int v = 8080;

實際上變量v在準備階段事後的初始值爲0而不是8080,將v賦值爲8080的putstatic指令是程序被編譯後,存放於類構造器<client>方法之中,這裏咱們後面會解釋。

可是注意若是聲明爲:

public static final int v = 8080;

在編譯階段會爲v生成ConstantValue屬性,在準備階段虛擬機會根據ConstantValue屬性將v賦值爲8080。

1.4 解析

解析階段是指虛擬機將常量池中的符號引用替換爲直接引用的過程。符號引用就是class文件中的:

  • CONSTANT_Class_info
  • CONSTANT_Field_info
  • CONSTANT_Method_info

等類型的常量。

下面咱們解釋一下符號引用和直接引用的概念:

  • 符號引用與虛擬機實現的佈局無關,引用的目標並不必定要已經加載到內存中。各類虛擬機實現的內存佈局能夠各不相同,可是它們能接受的符號引用必須是一致的,由於符號引用的字面量形式明肯定義在Java虛擬機規範的Class文件格式中。

  • 直接引用能夠是指向目標的指針,相對偏移量或是一個能間接定位到目標的句柄。若是有了直接引用,那引用的目標一定已經在內存中存在。

1.5 初始化

初始化階段是類加載最後一個階段,前面的類加載階段以後,除了在加載階段能夠自定義類加載器之外,其它操做都由JVM主導。到了初始階段,纔開始真正執行類中定義的Java程序代碼。

初始化階段是執行類構造器<clint>方法的過程。<clint>方法是由編譯器自動收集類中的類變量的賦值操做和靜態語句塊中的語句合併而成的。虛擬機會保證<clint>方法執行以前,父類的<clint>方法已經執行完畢。p.s: 若是一個類中沒有對靜態變量賦值也沒有靜態語句塊,那麼編譯器能夠不爲這個類生成<clint>()方法。

注意如下幾種狀況不會執行類初始化:

  • 經過子類引用父類的靜態字段,只會觸發父類的初始化,而不會觸發子類的初始化。
  • 定義對象數組,不會觸發該類的初始化。
  • 常量在編譯期間會存入調用類的常量池中,本質上並無直接引用定義常量的類,不會觸發定義常量所在的類。
  • 經過類名獲取Class對象,不會觸發類的初始化。
  • 經過Class.forName加載指定類時,若是指定參數initialize爲false時,也不會觸發類初始化,其實這個參數是告訴虛擬機,是否要對類進行初始化。
  • 經過ClassLoader默認的loadClass方法,也不會觸發初始化動做。

1.6 類加載器

虛擬機設計團隊把類加載階段中的「經過一個類的全限定名來獲取描述此類的二進制字節流」這個動做放到Java虛擬機外部去實現,以便讓應用程序本身決定如何去獲取所須要的類。實現這個動做的代碼模塊被稱爲「類加載器」。

對於任意一個類,都須要由加載它的類加載器和這個類自己一同確立其在Java虛擬機中的惟一性。這句話能夠表達得更通俗一些:比較兩個類是否「相等」,只有在這兩個類是由同一個類加載器加載的前提之下才有意義,不然,即便這兩個類是來源於同一個Class文件,只要加載它們的類加載器不一樣,那這兩個類就一定不相等。這裏所指的「相等」,包括表明類的Class對象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回結果,也包括了使用instanceof關鍵字作對象所屬關係斷定等狀況。若是沒有注意到類加載器的影響,在某些狀況下可能會產生具備迷惑性的結果。

JVM提供了3種類加載器:

  • 啓動類加載器(Bootstrap ClassLoader):負責加載 JAVA_HOME\lib 目錄中的,或經過-Xbootclasspath參數指定路徑中的,且被虛擬機承認(按文件名識別,如rt.jar)的類。
  • 擴展類加載器(Extension ClassLoader):負責加載 JAVA_HOME\lib\ext 目錄中的,或經過java.ext.dirs系統變量指定路徑中的類庫。
  • 應用程序類加載器(Application ClassLoader):負責加載用戶路徑(classpath)上的類庫。

JVM經過雙親委派模型進行類的加載,固然咱們也能夠經過繼承java.lang.ClassLoader實現自定義的類加載器。

jvm classloader

雙親委派模型要求除了頂層的啓動類加載器外,其他的類加載器都應當有本身的父類加載器。這裏類加載器之間的父子關係通常不會以繼承(Inheritance)的關係來實現,而是都使用組合(Composition)關係來複用父加載器的代碼。

雙親委派模型的工做過程是:若是一個類加載器收到了類加載的請求,它首先不會本身去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每個層次的類加載器都是如此,所以全部的加載請求最終都應該傳送到頂層的啓動類加載器中,只有當父加載器反饋本身沒法完成這個加載請求(它的搜索範圍中沒有找到所需的類)時,子加載器纔會嘗試本身去加載。

採用雙親委派的一個好處是好比加載位於rt.jar包中的類java.lang.Object,無論是哪一個加載器加載這個類,最終都是委託給頂層的啓動類加載器進行加載,這樣就保證了使用不一樣的類加載器最終獲得的都是一樣一個Object對象。

在有些情境中可能會出現要咱們本身來實現一個類加載器的需求,因爲這裏涉及的內容比較普遍,我想之後單獨寫一篇文章來說述,不過這裏咱們仍是稍微來看一下。咱們直接看一下jdk中的ClassLoader的源碼實現:

protected synchronized Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException {
    // First, check if the class has already been loaded
    Class c = findLoadedClass(name);
    if (c == null) {
        try {
            if (parent != null) {
                c = parent.loadClass(name, false);
            } else {
                c = findBootstrapClass0(name);
            }
        } catch (ClassNotFoundException e) {
            // If still not found, then invoke findClass in order
            // to find the class.
            c = findClass(name);
        }
    }
    if (resolve) {
        resolveClass(c);
    }
    return c;
}
  • 首先經過Class c = findLoadedClass(name);判斷一個類是否已經被加載過。
  • 若是沒有被加載過執行if (c == null)中的程序,遵循雙親委派的模型,首先會經過遞歸從父加載器開始找,直到父類加載器是Bootstrap ClassLoader爲止。
  • 最後根據resolve的值,判斷這個class是否須要解析。

而上面的findClass()的實現以下,直接拋出一個異常,而且方法是protected,很明顯這是留給咱們開發者本身去實現的,這裏咱們之後咱們單獨寫一篇文章來說一下如何重寫findClass方法來實現咱們本身的類加載器。

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

二、JVM內存模型

肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

2.1 各部分的功能

這幾個存儲區最主要的就是棧區和堆區,那麼什麼是棧什麼是堆呢?說的簡單點,棧裏面存放的是基本的數據類型和引用,而堆裏面則是存放各類對象實例的。
肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

堆與棧分開設計是爲何呢?

  • 棧存儲了處理邏輯、堆存儲了具體的數據,這樣隔離設計更爲清晰
  • 堆與棧分離,使得堆能夠被多個棧共享。
  • 棧保存了上下文的信息,所以只能向上增加;而堆是動態分配

棧的大小能夠經過-XSs設置,若是不足的話,會引發java.lang.StackOverflowError的異常

棧區

線程私有,生命週期與線程相同。每一個方法執行的時候都會建立一個棧幀(stack frame)用於存放 局部變量表、操做棧、動態連接、方法出口。

存放對象實例,全部的對象的內存都在這裏分配。垃圾回收主要就是做用於這裏的。

  • 堆得內存由-Xms指定,默認是物理內存的1/64;最大的內存由-Xmx指定,默認是物理內存的1/4。
  • 默認空餘的堆內存小於40%時,就會增大,直到-Xmx設置的內存。具體的比例能夠由-XX:MinHeapFreeRatio指定
  • 空餘的內存大於70%時,就會減小內存,直到-Xms設置的大小。具體由-XX:MaxHeapFreeRatio指定。

所以通常都建議把這兩個參數設置成同樣大,能夠避免JVM在不斷調整大小。

2.2 程序計數器

這裏記錄了線程執行的字節碼的行號,在分支、循環、跳轉、異常、線程恢復等都依賴這個計數器。

2.3 方法區

類型信息、字段信息、方法信息、其餘信息

2.4總結

肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

三、垃圾收集機制詳解

3.1如何定義垃圾

有兩種方式,一種是引用計數(可是沒法解決循環引用的問題);另外一種就是可達性分析。

判斷對象能夠回收的狀況:

  • 顯示的把某個引用置位NULL或者指向別的對象
  • 局部引用指向的對象
  • 弱引用關聯的對象

3.2 垃圾回收的方法

3.2.1Mark-Sweep標記-清除算法

肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

這種方法優勢就是減小停頓時間,可是缺點是會形成內存碎片。

3.2.2 Copying複製算法

肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

這種方法不涉及到對象的刪除,只是把可用的對象從一個地方拷貝到另外一個地方,所以適合大量對象回收的場景,好比新生代的回收。

3.2.3 Mark-Compact標記-整理算法

肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

這種方法能夠解決內存碎片問題,可是會增長停頓時間。

3.2.4 Generational Collection 分代收集

最後的這種方法是前面幾種的合體,即目前JVM主要採起的一種方法,思想就是把JVM分紅不一樣的區域。每種區域使用不一樣的垃圾回收方法。

肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

上面能夠看到堆分紅兩個個區域:

  • 新生代(Young Generation):用於存放新建立的對象,採用複製回收方法,若是在s0和s1之間複製必定次數後,轉移到年老代中。這裏的垃圾回收叫作minor GC;
  • 年老代(Old Generation):這些對象垃圾回收的頻率較低,採用的標記整理方法,這裏的垃圾回收叫作 major GC。

這裏能夠詳細的說一下新生代複製回收的算法流程:

在新生代中,分爲三個區:Eden, from survivor, to survior。

  • 當觸發minor GC時,會先把Eden中存活的對象複製到to Survivor中;
  • 而後再看from survivor,若是次數達到年老代的標準,就複製到年老代中;若是沒有達到則複製到to survivor中,若是to survivor滿了,則複製到年老代中。
  • 而後調換from survivor 和 to survivor的名字,保證每次to survivor都是空的等待對象複製到那裏的。

3.3 垃圾回收器

肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

3.3.1 串行收集器 Serial

這種收集器就是以單線程的方式收集,垃圾回收的時候其餘線程也不能工做。
肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

3.3.2 並行收集器 Parallel

以多線程的方式進行收集
肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

3.3.3併發標記清除收集器 Concurrent Mark Sweep Collector, CMS

大體的流程爲:初始標記--併發標記--從新標記--併發清除

肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

3.3.4 G1收集器 Garbage First Collector

大體的流程爲:初始標記--併發標記--最終標記--篩選回收

肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

篇幅所限,關於類字節碼文件、調優工具以及GC日誌分析這裏就不寫了,若是有感興趣的朋友能夠點擊領取我整理的完整JVM性能調優筆記,裏面會有詳細敘述。

2、Mysql性能調優

一、SQL執行原理詳解

肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

1.1 SQL Server組成部分

肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

1.1.1 關係引擎:主要做用是優化和執行查詢。

包含三大組件:

(1)命令解析器:檢查語法和轉換查詢樹。

(2)查詢執行器:優化查詢。

(3)查詢優化器:負責執行查詢。

1.1.2 存儲引擎:管理全部數據及涉及的IO

包含三大組件:

(1)事務管理器:經過鎖來管理數據及維持事務的ACID屬性。

(2)數據訪問方法:處理對行、索引、頁、行版本、空間分配等的I/O請求。

(3)緩衝區管理器:管理SQL Server的主要內存消耗組件Buffer Pool。

1.1.3Buffer Pool

包含SQL Server的全部緩存。如計劃緩存和數據緩存。

1.1.4事務日誌

記錄事務的全部更改。保證事務ACID屬性的重要組件。

1.1.5數據文件

數據庫的物理存儲文件。

6.SQL Server網絡接口
創建在客戶端和服務器之間的網絡鏈接的協議層

1.2 查詢的底層原理

肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

1.2.1 當客戶端執行一條T-SQL語句給SQL Server服務器時,會首先到達服務器的網絡接口,網絡接口和客戶端之間有協議層。

1.2.2 客戶端和網絡接口之間創建鏈接。使用稱爲「表格格式數據流」(TDS) 數據包的 Microsoft 通訊格式來格式化通訊數據。

1.2.3 客戶端發送TDS包給協議層。協議層接收到TDS包後,解壓並分析包裏面包含了什麼請求。

1.2.4 命令解析器解析T-SQL語句。命令解析器會作下面幾件事情:

(1)檢查語法。發現有語法錯誤就返回給客戶端。下面的步驟不執行。

(2)檢查緩衝池(Buffer Pool)中是否存在一個對應該T-SQL語句的執行計劃緩存。

(3)若是找到已緩存的執行計劃,就從執行計劃緩存中直接讀取,並傳輸給查詢執行器執行。

(4)若是未找到執行計劃緩存,則在查詢執行器中進行優化併產生執行計劃,存放到Buffer Pool中。

1.2.5 查詢優化器優化SQL語句

當Buffer Pool中沒有該SQL語句的執行計劃時,就須要將SQL傳到查詢優化器,經過必定的算法,分析SQL語句,產生一個或多個候選執行計劃。選出開銷最小的計劃做爲最終執行計劃。而後將執行計劃傳給查詢執行器。

1.2.6 查詢執行器執行查詢

查詢執行器把執行計劃經過OLE DB接口傳給存儲引擎的數據訪問方法。

1.2.7 數據訪問方法生成執行代碼

數據訪問方法將執行計劃生成SQL Server可操做數據的代碼,不會實際執行這些代碼,傳送給緩衝區管理器來執行。

1.2.8 緩衝區管理器讀取數據。

先在緩衝池的數據緩存中檢查是否存在這些數據,若是存在,就把結果返回給存儲引擎的數據訪問方法;若是不存在,則從磁盤(數據文件)中讀出數據並放入數據緩存中,而後將讀出的數據返回給存儲引擎的數據訪問方法。

1.2.9 對於讀取數據,將會申請共享鎖,事務管理器分配共享鎖給讀操做。

1.2.10存儲引擎的數據訪問方法將查詢到的結果返回關係引擎的查詢執行器。

1.2.11 查詢執行器將結果返回給協議層。

1.2.12 協議層將數據封裝成TDS包,而後協議層將TDS包傳給客戶端。

二、索引底層剖析

2.1爲什麼要有索引?

通常的應用系統,讀寫比例在10:1左右,並且插入操做和通常的更新操做不多出現性能問題,在生產環境中,咱們遇到最多的,也是最容易出問題的,仍是一些複雜的查詢操做,所以對查詢語句的優化顯然是重中之重。提及加速查詢,就不得不提到索引了。

2.2 什麼是索引?

索引在MySQL中也叫作「鍵」或者"key"(primary key,unique key,還有一個index key),是存儲引擎用於快速找到記錄的一種數據結構。索引對於良好的性能很是關鍵,尤爲是當表中的數據量愈來愈大時,索引對於性能的影響愈發重要,減小io次數,加速查詢。(其中primary key和unique key,除了有加速查詢的效果以外,還有約束的效果,primary key 不爲空且惟一,unique key 惟一,而index key只有加速查詢的效果,沒有約束效果)

索引優化應該是對查詢性能優化最有效的手段了。索引可以輕易將查詢性能提升好幾個數量級。
索引至關於字典的音序表,若是要查某個字,若是不使用音序表,則須要從幾百頁中逐頁去查。

強調:一旦爲表建立了索引,之後的查詢最好先查索引,再根據索引定位的結果去找數據

2.3 索引原理

索引的目的在於提升查詢效率,與咱們查閱圖書所用的目錄是一個道理:先定位到章,而後定位到該章下的一個小節,而後找到頁數。類似的例子還有:查字典,查火車車次,飛機航班等,下面內容看不懂的同窗也不要緊,能明白這個目錄的道理就好了。 那麼你想,書的目錄佔不佔頁數,這個頁是否是也要存到硬盤裏面,也佔用硬盤空間。

你再想,你在沒有數據的狀況下先建索引或者說目錄快,仍是已經存在好多的數據了,而後再去建索引,哪一個快,確定是沒有數據的時候快,由於若是已經有了不少數據了,你再去根據這些數據建索引,是否是要將數據所有遍歷一遍,而後根據數據創建索引。你再想,索引創建好以後再添加數據快,仍是沒有索引的時候添加數據快,索引是用來幹什麼的,是用來加速查詢的,那對你寫入數據會有什麼影響,確定是慢一些了,由於你但凡加入一些新的數據,都須要把索引或者說書的目錄從新作一個,因此索引雖然會加快查詢,可是會下降寫入的效率。

2.4 索引的數據結構

前面講了索引的基本原理,數據庫的複雜性,又講了操做系統的相關知識,目的就是讓你們瞭解,如今咱們來看看索引怎麼作到減小IO,加速查詢的。任何一種數據結構都不是憑空產生的,必定會有它的背景和使用場景,咱們如今總結一下,咱們須要這種數據結構可以作些什麼,其實很簡單,那就是:每次查找數據時把磁盤IO次數控制在一個很小的數量級,最好是常數數量級。那麼咱們就想到若是一個高度可控的多路搜索樹是否能知足需求呢?就這樣,b+樹應運而生。

肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

三、Mysql鎖機制與事務隔離級別詳解

肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

3.1 爲何須要學習數據庫鎖知識

即便咱們不會這些鎖知識,咱們的程序在通常狀況下仍是能夠跑得好好的。由於這些鎖數據庫隱式幫咱們加了

  • 對於UPDATE、DELETE、INSERT語句,InnoDB自動給涉及數據集加排他鎖(X)
  • MyISAM在執行查詢語句SELECT前,會自動給涉及的全部表加讀鎖,在執行更新操做(UPDATE、DELETE、INSERT等)前,會自動給涉及的表加寫鎖

只會在某些特定的場景下才須要手動加鎖,學習數據庫鎖知識就是爲了:

  • 能讓咱們在特定的場景下派得上用場
  • 更好把控本身寫的程序
  • 在跟別人聊數據庫技術的時候能夠搭上幾句話
  • 構建本身的知識庫體系!在面試的時候不虛

3.2 表鎖簡單介紹

首先,從鎖的粒度,咱們能夠分紅兩大類:

  • 表鎖
    • 開銷小,加鎖快;不會出現死鎖;鎖定力度大,發生鎖衝突機率高,併發度最低
  • 行鎖
    • 開銷大,加鎖慢;會出現死鎖;鎖定粒度小,發生鎖衝突的機率低,併發度高

不一樣的存儲引擎支持的鎖粒度是不同的:

  • InnoDB行鎖和表鎖都支持
  • MyISAM只支持表鎖

InnoDB只有經過索引條件檢索數據才使用行級鎖,不然,InnoDB將使用表鎖

  • 也就是說,InnoDB的行鎖是基於索引的

表鎖下又分爲兩種模式

  • 表讀鎖(Table Read Lock)
  • 表寫鎖(Table Write Lock)
  • 從下圖能夠清晰看到,在表讀鎖和表寫鎖的環境下:讀讀不阻塞,讀寫阻塞,寫寫阻塞
    • 讀讀不阻塞:當前用戶在讀數據,其餘的用戶也在讀數據,不會加鎖
    • 讀寫阻塞:當前用戶在讀數據,其餘的用戶不能修改當前用戶讀的數據,會加鎖!
    • 寫寫阻塞:當前用戶在修改數據,其餘的用戶不能修改當前用戶正在修改的數據,會加鎖!
    • 寫鎖和其餘鎖均布兼容,只有讀和讀之間兼容
      肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

從上面已經看到了:讀鎖和寫鎖是互斥的,讀寫操做是串行

  • 若是某個進程想要獲取讀鎖,同時另一個進程想要獲取寫鎖。在mysql裏邊,寫鎖是優先於讀鎖的
  • 寫鎖和讀鎖優先級的問題是能夠經過參數調節的:max_write_lock_countlow-priority-updates

值得注意的是:

  • MyISAM能夠支持查詢和插入操做的併發進行。能夠經過系統變量concurrent_insert來指定哪一種模式,在MyISAM中它默認是:若是MyISAM表中沒有空洞(即表的中間沒有被刪除的行),MyISAM容許在一個進程讀表的同時,另外一個進程從表尾插入記錄。
  • 可是InnoDB存儲引擎是不支持的

    3.3 MVCC和事務的隔離級別

    數據庫事務有不一樣的隔離級別,不一樣的隔離級別對鎖的使用是不一樣的,鎖的應用最終致使不一樣事務的隔離級別

MVCC(Multi-Version Concurrency Control)多版本併發控制,能夠簡單地認爲:MVCC就是行級鎖的一個變種(升級版)。

  • 事務的隔離級別就是經過鎖的機制來實現,只不過隱藏了加鎖細節
    在表鎖中咱們讀寫是阻塞的,基於提高併發性能的考慮,MVCC通常讀寫是不阻塞的(因此說MVCC不少狀況下避免了加鎖的操做)

  • MVCC實現的讀寫不阻塞正如其名:多版本併發控制--->經過必定機制生成一個數據請求時間點的一致性數據快照(Snapshot),並用這個快照來提供必定級別(語句級或事務級)的一致性讀取。從用戶的角度來看,好像是數據庫能夠提供同一數據的多個版本。
    快照有兩個級別:

  • 語句級
    針對於Read committed隔離級別
  • 事務級別
    針對於Repeatable read隔離級別
    咱們在初學的時候已經知道,事務的隔離級別有4種:
  • Read uncommitted
    會出現髒讀,不可重複讀,幻讀
  • Read committed
    會出現不可重複讀,幻讀
  • Repeatable read
    會出現幻讀(但在Mysql實現的Repeatable read配合gap鎖不會出現幻讀!)
  • Serializable
    串行,避免以上的狀況!

    Read uncommitted會出現的現象--->髒讀:一個事務讀取到另一個事務未提交的數據

  • 例子:A向B轉帳,A執行了轉帳語句,但A尚未提交事務,B讀取數據,發現本身帳戶錢變多了!B跟A說,我已經收到錢了。A回滾事務【rollback】,等B再查看帳戶的錢時,發現錢並無多。
  • 出現髒讀的本質就是由於操做(修改)完該數據就立馬釋放掉鎖,致使讀的數據就變成了無用的或者是錯誤的數據。
    Read committed避免髒讀的作法其實很簡單:

  • 就是把釋放鎖的位置調整到事務提交以後,此時在事務提交前,其餘進程是沒法對該行數據進行讀取的,包括任何操做
    但Read committed出現的現象--->不可重複讀:一個事務讀取到另一個事務已經提交的數據,也就是說一個事務能夠看到其餘事務所作的修改

  • 注:A查詢數據庫獲得數據,B去修改數據庫的數據,致使A屢次查詢數據庫的結果都不同【危害:A每次查詢的結果都是受B的影響的,那麼A查詢出來的信息就沒有意思了】
    上面也說了,Read committed是語句級別的快照!每次讀取的都是當前最新的版本!

Repeatable read避免不可重複讀是事務級別的快照!每次讀取的都是當前事務的版本,即便被修改了,也只會讀取當前事務版本的數據。

呃...若是仍是不太清楚,咱們來看看InnoDB的MVCC是怎麼樣的吧(摘抄《高性能MySQL》)

InnoDB 的 MVCC,是經過在每行記錄後面保存兩個隱藏的列來實現的,這兩個列一個保存了行的建立時間,一個保存了行的過時(刪除)時間。固然存儲的並非真正的時間值,而是系統版本號。每開始一個新的事務,系統版本號會自動遞增,事務開始的時候的系統版本號會做爲事務的版本號,用來和查詢到的每行記錄的版本號進行比較。
select
  InnoDB 會根據如下兩個條件檢查每行記錄:
    a. InnoDB 只查找版本早於當前事務版本的數據行,這樣能夠確保事務讀取到的數據,要麼是在事務開始前就存在的,要麼是事務自身插入或更新的
    b. 行的刪除版本要麼未定義要麼大於當前事務版本號,確保了事務讀取到的行,在事務開始前未被刪除

至於虛讀(幻讀):是指在一個事務內讀取到了別的事務插入的數據,致使先後讀取不一致。

  • 注:和不可重複讀相似,但虛讀(幻讀)會讀到其餘事務的插入的數據,致使先後讀取不一致
  • MySQL的Repeatable read隔離級別加上GAP間隙鎖已經處理了幻讀了。

3、Nginx調優

一、Nginx定義

nginx 經常使用作靜態內容服務和反向代理服務器,以及頁面前端高併發服務器。適合作負載均衡,直面外來請求轉發給後面的應用服務(tomcat什麼的)

二、熟練掌握Nginx核心配置

2.1 全局配置塊

user  root;  #運行worker進程的帳戶,user   用戶   [組],默認以nobody帳戶運行
worker_processes  7;  #要使用的worker進程數,可設置爲數值、auto(根據機器性能自動設置),默認值1

error_log  logs/error.log;  #nginx進程(master+worker)的日誌設置,保存位置、輸出級別,此即爲默認保存位置
#error_log  logs/error.log  notice;  #輸出級別可選,由低到高依次爲:debug(輸出信息最多),info,notice,warn,error,erit(輸出信息最少)

pid  logs/nginx.pid;  #nginx主進程的pid的保存位置,此即爲默認值

worker_rlimit_nofile 65535;  #單個worker進程可打開的最大文件描述符數

worker_processes:

實際運營時通常設置爲很接近CPU的線程數,好比說CPU是8線程,通常設置爲六、7。

咱們本身開發、用時通常設置爲一、2便可,否則太吃資源。

worker_rlimit_nofile:

r是read,limit是限制,單個worker進程最多隻能打開指定個數的文件,超過便不能再讀取文件。打開一次文件便會產生一個文件描述符。

此設置是爲了防止單個worker進程消耗大量的系統資源。

ps  -ef | grep nginx 查詢下nginx的進程:

肝了15000字性能調優系列專題(JVM、MySQL、Nginx and Tomcat),看不完先收

無論設置多少個worker進程,主進程只有一個(即運行sbin/nginx)。

主進程由Linux當前登陸的帳戶運行,工做進程由user指令指定的帳戶運行。第一列數字是進程的PID。

nginx工做進程和nginx主進程都是Linux中的進程,但主進程(父進程)能夠控制worker進程(子進程)的開啓、結束。

master進程能夠看作老闆,worker進程能夠看作打工仔。

2.2 events塊

events {
   accept_mutex on;  #防止驚羣
   multi_accept on;  #容許單個worker進程可同時接收多個網絡鏈接的請求,默認爲off
  use epoll;  #設置worker進程使用高效模式
   worker_connections 1024;  #指定單個worker進程最多可創建的網絡鏈接數,默認值1024。
}

accept_mutex:

驚羣現象:一個網絡鏈接到來,全部沉睡的worker進程都會被喚醒,但只用一個worker處理鏈接,其他被喚醒的worker又開始沉睡。

設置爲on:要使用幾個worker就喚醒幾個,不所有喚醒,默認值就是on。

設置爲off:一概所有喚醒。一片worker醒來是要佔用資源的,會影響性能。

use:

指定nginx的工做模式,可選的值:select、poll、kqueue、epoll、rtsig、/dev/poll。

其中select、poll都是標準模式,kqueue、epoll都是高效模式,

kqueue是在BSD系統中用的,epoll是在Linux系統中用的。(BSD是Unix的一個分支,Linux是一種類Unix系統)。

全局塊中的worker_processes、events塊中的worker_connections是nginx支持高併發的關鍵,這2個數值相乘即nginx可創建的最大鏈接數。

一個鏈接要用一個文件來保存,

worker_connections設置的單個worker進程的最大鏈接數,受全局塊中worker_rlimit_nofile設置的單個worker進程可打開的最大文件數限制。

而worker_rlimit_nofile只是nginx對單個worker進程的限制,要受Linux系統對單個進程可打開的最大文件描述符數限制。

Linux默認單個進程最多隻能打開1024個文件描述符,須要咱們修改下Linux的資源限制,設置單個進程可打開的最大文件描述符數:

ulimit -n 65536

ulimit命令能夠限制單個進程使用的系統資源的尺寸、數量,包括內存、緩衝區、套接字、棧、隊列、CPU佔用時間等。

可用ulimit --help查看參數。

2.3 http塊

http{
    #http全局塊
    #server塊
}

能夠有多個server塊。

(1)http全局塊

http全局塊的配置做用於整個http塊(http塊內的全部server塊)。

include       mime.types;  #將conf/mime.types包含進來
    default_type  application/octet-stream;  #設置默認的MIME類型,二進制流。若是使用的MIME類型在mime.types中沒有,就看成默認類型處理。
    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '  
    #                '$status $body_bytes_sent "$http_referer" '
    #                '"$http_user_agent" "$http_x_forwarded_for"';   access_log logs/access.log; #設置日誌,這個日誌保存的是客戶端請求的信息,包括客戶端地址、使用的瀏覽器、瀏覽器內核版本、請求的url、請求時間、請求方式、響應狀態等。   #access_log  logs/access.log  main;  #可指定日誌格式,上面定義的main格式即默認格式。保存位置默認是logs/access.log

    sendfile         on;  #開啓文件高效傳輸模式,默認爲off,不開啓。
    #tcp_nopush      on;  #若是響應體積過大,默認會分多個批次傳輸給客戶端,設置爲on會一次性傳給客戶端,可防止網絡阻塞
   #tcp_nodelay     on;   #若是響應體積太小,默認會放在緩衝區,緩衝區滿了才刷給客戶端,設置爲on直接刷給客戶端,可防止網絡阻塞
    keepalive_timeout  65;  #與客戶端保持鏈接的超時時間,在指定時間內,若是客戶端沒有向Nginx發送任何數據(無活動),Nginx會關閉該鏈接。
    gzip  on;  #使用gzip模塊壓縮響應數據。啓用後響應體積變小,傳輸到客戶端所需時間更少,節省帶寬,但nginx壓縮、客戶端解壓都有額外的時間、資源開銷,nginx的負擔也會加大。
 
    upstream  servers{  #設置負載均衡器,可同時設置多個負載均衡器。負載均衡器的名稱中不能含有_,此處指定名稱爲servers
        server  192.168.1.7:8080;  #tomcat服務器節點
        server  192.168.1.8:8081;        server  192.168.1.7:8080 down;  #down表示該節點下線,暫不使用     server  192.168.1.8:8081 backup;  #backup表示該節點是備胎,只有在其餘節點忙不過來時纔會啓用(好比一些節點出故障了、其餘節點負載變大)。     server  192.168.1.8:8081 max_fails=3  fail_timeout=60s;  #若是對該節點的請求失敗3次,就60s內暫時不使用該節點,60s後恢復使用
    }

日誌格式經常使用的值:

  • $remote_addr  客戶端的ip地址
  • $time_local : 訪問時間與時區
  • $request : 請求的url與http協議
  • $status : 請求狀態,成功是200
  • $http_referer :從那個頁面連接訪問過來的
  • $http_user_agent :客戶端瀏覽器的信息

(2)server模塊

server{     #server全局塊     listen       80;  #要監聽的端口
        server_name  localhost;  #虛擬主機(即域名),要在dns上註冊過纔有效,沒有註冊的話只能用localhost。可指定多個虛擬主機,空格分開便可
        charset utf-8;  #使用的字符集。
        #access_log  logs/host.access.log  main;  #在http全局塊、server全局塊中任意一處設置日誌便可。http全局塊已經設置了日誌,此處可不用設置。     #錯誤頁設置     error_page  404  /404.html;  #html目錄下默認只有index.html(nginx首頁)、50x.html,須要本身寫404.html        location = /404.html {            root   html;  #指定404.html所在目錄,此處使用相對路徑,nginx主目錄下的html目錄,也可使用絕對路徑        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }     #處理靜態資源     location ~* \.(html|css|js|gif|jpg|png|mp4)$ {  #使用正則表達式匹配url,若是請求的是這些文件,就使用下面的處理方式            root static;    #若是使用nginx處理靜態資源,需使用root指定靜態資源所在目錄。在nginx主目錄下新建目錄static,把靜態資源放進去便可。       expires 30d;  #設置緩存過時時間             #proxy_pass http://192.168.1.10:80;  #若是使用apache等其餘機器處理靜態資源,使用proxy_pass轉發過去便可,多臺機器集羣時使用負載均衡器便可。        }
         #設置默認處理方式     location / {  #若是url沒有指定匹配,就使用默認的處理方式來處理            root   html;  #指定處理請求的根目錄。nginx自己做爲web服務器直接處理客戶端請求時,好比請求login.jsp,會調用root指定目錄下的login來處理請求。            index  index.html index.htm;  #指定nginx服務器的首頁地址。root、index2項配置都是必需的。            proxy_pass http://servers; #指定要使用的負載均衡器,轉發給其中某個節點處理。如不設置此項(代理),則默認nginx自己做爲web服務器,直接處理請求,會到root指定目錄下找請求的文件        }
    }

設置的錯誤頁面是nginx做爲web服務器(處理靜態資源)出現問題時,好比nginx上的靜態資源找不到,返回給客戶端的。

若是是tomcat出現的問題,好比tomcat上的xxx.jsp找不到,返回的是tomcat的錯誤頁面,不是nginx的。

若是使用nginx自己要做爲web服務器,直接處理客戶端請求,好比處理靜態資源,要將全局塊中user 設置爲運行nginx的帳戶(即當前登錄Linux的帳戶),

不然worker進程(默認nobody帳戶)無權限讀取當前帳戶(即運行nginx主進程的帳戶)的靜態資源,客戶端會顯示403禁止訪問。

可使用正則表達式來過濾客戶端ip,也能夠把客戶端的ip過濾規則寫在文件中,而後包含進來。

三、掌握Nginx負載算法配置

(1)輪詢

將列表中的服務器排成一圈,從前日後,找空閒的服務器來處理請求。

輪詢適合服務器性能差很少的狀況。默認使用的就是輪詢,不須要設置什麼。

(2)加權輪詢

upstream  servers{
    server  192.168.1.7:8080 weight=1;
    server  192.168.1.8:8081 weight=2;
}

設置權重,權重大的輪到的機會更大,適合服務器性能有明顯差異的狀況。

(3)ip_hash

upstream  servers{
    ip_hash;
    server  192.168.1.7:8080;
    server  192.168.1.8:8081;
}

根據客戶端ip的hash值來轉發請求,同一客戶端(ip)的請求都會被轉發給同一個服務器處理,可解決session問題。

(4)url_hash(第三方)

upstream  servers{
    hash $request_uri;
    server  192.168.1.7:8080;
    server  192.168.1.8:8081;
}

根據請求的url來轉發,會將url相同的請求轉發給同一服務器處理。

一直處理某個url,服務器上通常都有該url的緩存,可直接從緩存中獲取數據做爲響應返回,減小時間開銷。

(5)fair(第三方)

upstream  servers{
    fair;
    server  192.168.1.7:8080;
    server  192.168.1.8:8081;
}

根據服務器響應時間來分發請求,響應時間短的分發的請求多。

fair 公平,nginx先計算每一個節點的平均響應時間,響應時間短說明該節點負載小(閒),要多轉發給它;響應時間長說明該節點負載大,要少轉發給它。

ip_hash、url_hash都是使用特定節點來處理特定請求,若是特定節點故障,nginx會剔除不可用的節點,將特定請求轉發給其它節點處理,url_hash影響不大,但ip_hash會丟失以前的session數據。

4、Tomcat調優

一、基礎參數設置

在server.xml中配置:

  • maxThreads:Tomcat使用線程來處理接收的每一個請求。這個值表示Tomcat可建立的最大的線程數。
  • acceptCount:指定當全部可使用的處理請求的線程數都被使用時,能夠放處處理隊列中的請求數,超過這個數的請求將不予處理。
  • connnectionTimeout:網絡鏈接超時,單位:毫秒。設置爲0表示永不超時,這樣設置有隱患的。一般可設置爲30000毫秒。
  • minSpareThreads:Tomcat初始化時建立的線程數。
  • maxSpareThreads:一旦建立的線程超過這個值,Tomcat就會關閉再也不須要的socket線程

    二、Tomat的4種鏈接方式對比

    tomcat默認的http請求處理模式是bio(即阻塞型,下面第二種),每次請求都新開一個線程處理。下面作一個介紹

    <Connector port="8081" protocol="org.apache.coyote.http11.Http11NioProtocol" 
      connectionTimeout="20000" redirectPort="8443"/>
    <Connector port="8081" protocol="HTTP/1.1" connectionTimeout="20000"
      redirectPort="8443"/>
    <Connector executor="tomcatThreadPool"
      port="8081" protocol="HTTP/1.1"
      connectionTimeout="20000"
      redirectPort="8443" />
    <Connector executor="tomcatThreadPool"
      port="8081" protocol="org.apache.coyote.http11.Http11NioProtocol"
      connectionTimeout="20000"
      redirectPort="8443" />

    咱們姑且把上面四種Connector按照順序命名爲 NIO, HTTP, POOL, NIOP。測試性能對比,數值爲每秒處理的請求數,越大效率越高

    NIO   HTTP   POOL  NIOP
    281   65     208    365
    666   66     110    398
    692   65     66     263
    256   63     94     459
    440   67     145    363

    得出結論:NIOP > NIO > POOL > HTTP 雖然Tomcat默認的HTTP效率最低,可是根據測試次數能夠看出是最穩定的。且這只是一個簡單頁面測試,具體會根據複雜度有所波動。

配置參考:Linux系統每一個進程支持的最大線程數是1000,windos是2000。具體跟服務器的內存,Tomcat配置的數量有關聯。

<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"
              maxThreads="500" minSpareThreads="25" maxSpareThreads="250"
              enableLookups="false" redirectPort="8443" acceptCount="300" connectionTimeout="20000" disableUploadTimeout="true"/>

三、Tomcat的集羣

Tomcat的部署,是一臺服務器部署一個Tomcat(上線多個項目),仍是一臺服務器部署多個tomact(每一個tomcat部署1~n個項目)。多核必選配置多個Tomcat,微服務多線程的思想模式。

四、Tomcat內存設置

修改/bin/catalina.sh,增長以下設置:

JAVA_OPTS='-Xms【初始化內存大小】 -Xmx【可使用的最大內存】'

須要把這個兩個參數值調大,大小的能夠根據服務器內存的大小進行調整。例如:

JAVA_OPTS='-Xms1024m –Xmx2048m'

服務器是8G 內存,跑了3個tomcat服務,給分配了2G的內存,由於還有其餘進程。


本篇文章寫到這裏差很少就結束了,固然也有不少東西尚未寫到,不過限於篇幅也是沒轍,我整理了很詳細的JVM、MySQL、NGINX和Tomcat的學習筆記以及資料,須要的朋友直接點擊領取就能夠了。

最後,碼字不易,因此,能夠點個贊和收藏嗎兄弟們!


end

相關文章
相關標籤/搜索