写点什么

架構師訓練營第 1 期 - 第 09 周總結

用户头像
Panda
关注
发布于: 2020 年 11 月 22 日

數據庫的基本原理

數據庫架構

連接器

  • 數據庫連接也是一個網路通信

  • 為每個連接請求分配一塊專用的內存

  • 用於會話上下文管理

  • 應用程序啟動時,通常會初始化建立一些數據庫連接放在連接池裡

  • TCP 的長連接

  • 需要花費一定的時間建立對數據庫的連接,有一定的內存消耗

  • 當處理外部請求執行 SQL 做作時,就不需要花費時間建立連接

語法分析器

  • 對 SQL 命令進行分析,並產生抽象語法樹

  • example:

  • select s_grade from staff where s_city not in

   (select p_city from proj where s_empname = p_pname)

  • 如果 SQL 命令不符合語法,則回報 ERROR

  • example:

  • mysql> explain select * from users whee id = 1;

  • ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'id = 1' at line 1

語意分析與優化器

  • 將各種複雜嵌套的 SQL 進行語意等價轉換

  • 得到有限幾種關係代數計算結構

  • 選擇

  • 投影

  • 連接

  • 利用語法的特徵、執行規則及索引等信息進一步進行優化

  • 合併

  • 前置計算

  • example:

  • 原始: select f.id from orders f where f.user_id = (select id from users);

  • 轉化: select f.id from orders f join users u on f.user_id = u.id;

  • 為數據庫最核心、最高科技,代碼量最多的部分

執行計畫

  • 由優化器產生,並交於執行引擎執行,產生結果

  • example

  • Key: 索引類型

  • NULL 無索引

  • Rows: 需要處理的列數

  • Possible_keys: 潛在可以利用的索引

JDBC 兩種編程方式

即時編譯

statement.executeUpdate("UPDATE Users SET stateus = 2 WHERE userID=233");
复制代码
  • SQL 命令當成字符串輸入

預編譯

PreparedStatement updateUser = con.prepareStatement("UPDATE Users SET  stateus = ? WHERE userID = 233");updateUser.setInt(1, 2);updateUser.executeUpdate();
复制代码
  • 將要以佔位符構建 SQL 命令,先進行命令的預處理

預編譯比即時編譯好

  • 預先提交帶佔位符的 SQL 到數據庫進行預處理

  • 提前生成執行計畫

  • 給定佔位符參數、真正執行 SQL 的時候,執行引擎可以直接執行,效率更好

  • 防止 SQL 注入攻擊 (SQL Injection),安全性更好

  • 語法樹、優化、執行計畫已經生成好

  • 真實值僅當成參數傳入,並取代佔位符

  • SQL 注入 example

  • 一般: select * from users where username = 'Frank’;

  • 注入: Frank';drop table users;--

  • select * from users where username = 'Frank';drop table users;--’;

  • 若即時編譯,此時才解析 SQL 命令字串,會有三個 SQL 命令

  • select

  • drop

  • -- (註解)

  • 結果 users 表被刪除

  • 預編譯

  • select * from users where username = ?;

  • 有佔位符 '?'

  • 不會重新編譯語法樹及執行計畫

  • Frank';drop table users;-- 被當成參數傳入

  • 當成一般字串處理

儲存方式

B+ Tree

  • 多層的檢索樹

  • 增進查找性能

  • 要查找的數據存在葉節點

  • 所有葉節點連接成一個鏈表

聚簇索引 (Clustered Index)

  • 數據庫紀錄和索引存儲在一起

  • 索引字段為主鍵

  • 葉節點存放索引字段外,也存放相對應的紀錄列

  • MySQL 數據庫的主鍵就是聚簇索引

  • 主鍵 ID 和所在的紀錄列,存儲在一個 B+ 樹的葉節點中

  • 主鍵 1、2 ..........

  • 與主鍵相應的紀錄列 r1、r2 .....

非聚簇索引 (Non-clustered Index)

  • 以某非主鍵欄位構建的 B+ Tree

  • 葉節點為主鍵值 (即聚簇索引值)

回表

  • 通過非聚簇索引找到主鍵索引

  • 再通過主鍵索引找到列紀錄

  • 此過程稱之

性能優化

添加必要的索引,優化 SQL 查詢性能

  • 沒有索引,需全表掃描,檢索所有的列紀錄

  • 沒有索引

  • 列紀錄越多,性能越差

  • 對數據庫的負載壓力大

  • 優化 SQL 主要的手段,就是添加必要的索引

  • 頻繁的更新,刪除也需要索引

合理使用索引

  • 不要盲目添加索引,尤其在生產環境中

  • 添加索引的 alter 操作會消耗較長時間 (分鐘級)

  • alter 操作期間,所有增刪改操作全部阻塞

  • 對應用而言,因為連接不能釋放,事實上連查詢也被阻塞

  • 刪除不用的索引,並免不必要的增刪開銷

  • 當新增、刪除時,所有索引樹必須更新

  • 使用更小的數據類型創建索引

  • 減少空間開銷

  • 可以讀到更多的數據,性能也會提升

  • int: 4 bytes, bigint: 8 bytes

  • Timestamp: 4 bytes, Datetime: 8 bytes

數據庫事務 (Transaction)

ACID 特性

原子性 (Atomicity)

  • 事務要麼全部完成,要麼全不取消

  • 如果事務崩潰或者 SQL 執行失敗,狀態回到事務之前 (事務回滾)

隔離性 (Isolation)

  • 如果兩個事務 T1 和 T2 同時運行,事務 T1 和 T2 最終的結果是相同的,不管哪一個先結束

  • 主要靠 "鎖" 來實現

持久性 (Durability)

  • 一旦事務提交,不管發生甚麼 (崩潰或者出錯),數據要保存在數據庫中

一致性 (Consistency)

  • 只有合法的數據(依照關係約束和函數約束)才能寫入數據庫中

事務日誌

  • 記錄更新前的數據紀錄 (UNDO)

  • 記錄更新後的數據記錄 (REDO)

  • 全部記錄成功,事務正常結束

  • 若過程中記錄更新失敗,整個事務回滾

  • 利用 UNDO log 回滾

  • 若過程中更新數據庫成功,但數據庫並沒有成功回寫磁碟,或者 SQL 執行失敗

  • 可利用 REDO log 更新

  • 由事務決定調用 REDO 或 UNDO

  • UNDO : 回復原始狀態

  • REDO : 繼續完成事務

  • example

  • LSN : 按時間順序分配的唯一事務記錄日誌序列號

  • TransID: 產生操作的事物 ID

  • PageID: 被修改的數據在磁盤上的位置

  • PrevLSN: 同一個事務產生的上一條日誌記錄的指針

  • UNDO: 取消本次操作的方法,按照此方法回滾

  • REDO: 重複本次操作的方法

JVM 虛擬機架構原理

JVM 組成架構

類加載器

  • 啟動 Java 進程

  • java org.apache.catalina.startup.Bootstrap "$@" start

  • 通過類加載器,加載啟動類

  • org.apache.catalina.startup.Bootstrap

  • Java 內執行的主程序

  • 一定要有 main(...) 函數

運行時期的數據區

  • 加載進來的類,放於 "方法區" 內

  • 啟動一個主線程,執行類中的 main(...) 函數

  • 分配 "Java 棧"

  • 分配 "程序計數寄存器"

  • 存儲當前線程執行的指令

  • 每一線程獨享 "棧" 及 "計數寄存器"

  • 若 new 一個類,則會從 "堆" 中創建此類的物件實例

  • 但引用的變量存儲在每個線程的 "棧" 中

執行引擎

  • 執行計數寄存器所指的 java 字節碼(byte code)指令

  • 通過解譯或編譯成本地操作系統的本地指令

Java 虛擬機

  • 有自己的 "計數寄存器"

  • 有自己的內存管理

  • 有自己的執行引擎

  • 可以執行字節碼指令

  • 如同 CPU 一般

  • 有完整的計算機體系

Java 字節碼文件

  • Java 可以跨平台、跨操作系統、跨不同的硬件平台原因

  • 有通用的字節碼

  • 有執行引擎可以把字節碼轉換成本地的執行指令

  • Java 有 200 個左右的指令,可存儲於一個字節 (byte) 中

  • 一個指令後面帶有不同的長度的操作數

  • 文件 format

  • Magic code "cafebabe"

  • reference

  • https://www.jyt0532.com/2020/03/01/class-file-structure/

Java 字節碼文件編譯過程

  • 與 SQL 語法到執行計畫類似

Java 字節碼執行流程


  • 方法調用超過閥值,才會啟動 "JIT" 動態編譯器,編譯成本地指令

  • 編譯可以執行最佳化

  • 可增加執行效能

類加載器的雙親委託模型

  • 類加載器為分層加載

  • 累加載器高層 -> 低層

  • Bootstrap Class Loader

  • 加載 Java 自身的核心類,運行環境的類

  • jre/lib/rt.jar

  • Platform Class Loader (Extension Class Loader)

  • 加載 ext/ 下的 jar 包

  • Application Class Loader

  • 加載指定的 CLASSPATH 路徑下的類

  • User defined Class Loader

  • 必須繼承 Application Class Loader

  • 低層的加載器,不能覆蓋更高層次類加載器已經加載的類

  • 只有較高層的加載器確認過沒有加載過,才允許低層的加載器去加載

  • 自行定義 class loader 原因

  • 隔離加載

  • 在同一個 JVM 中的不同組件,想要加載同一個類的不同版本

  • 如 Tomcat

  • 可以啟動多個 war 包

  • 實現多個 web 應用

  • 多個 web 應用中,可能有同樣的 class,不同的實現

  • 為每一個 war 包,分配一個 class loader

  • 擴展加載源

  • 從網路、數據庫等處加載類

  • 字節碼加密

  • 加載自訂譯的加密字節碼

  • 在自定義的 class loader 中解密

JVM 的 "堆" 和 "棧"

  • 每一個 JVM 實例唯一對應一個 "堆"

  • 運行中所創建的所有類實例或數組均放於 "堆" 中

  • 由應用中的所有線程共享

  • 每一個新創件的線程都分配一個 "棧"

  • 每一個 Java 程序的運行是通過對 "棧" 的操作完成

  • 每個類實例的引用在 "棧" 中分配,引用指向 "堆" 中的實例

  • 線程棧

  • 方法內定義的基本類型變量,會被每個運行這個方法的線程放入自己的棧中

  • 線程的棧彼此隔離

  • 這類基本類型變量一定是線程安全的

JVM "方法區" 和 "程序計數器"

方法區

  • 存放從磁盤加載進來的字節碼

程序計數器

  • JVM 在 "堆" 中創建啟動類的實例

  • JVM 進入啟動類的 main 方法時,創建一個應用程序主線程

  • main 方法理的代碼被主線程執行

  • "棧" 裡存放方法運行期的局部變量

  • 程序計數器存放當前線程執行到哪一行字節指令

Java 線程工作內存和 "volatile"

  • 工作內存 == CPU cache

  • Java 內存模型規定

  • 多線程的情況下,線程操作主內存變量

  • 需要通過線程獨有的工作內存的副本來進行

  • 副本拷貝自主內存變量

  • 一個共享變量(類成員變數、類靜態成員變數)照內存模型,在工作內存中產生髒數據或不可見

  • 每個線程工作內存不同

  • 更新共享變量不會立刻反應至其他工作內存

  • volatile 修飾

  • 修飾共享變數

  • 保證不同線程對這個變數進行操作時的可見性

  • 一個線程修改了各個變數的值,新值對其他線程來說立即可見

Java 運行環境

JVM 垃圾回收性能分析

垃圾回收

  • 將 JVM 堆中已經不再被使用的對象清理掉,釋放內存資源

  • 通過一種可達性分析算法進行垃圾對象識別

  • 垃圾對象 - 沒有被引用的對象

標示過程

  • 從線程棧幀中的局部變量,或方法區中的靜態變量出發

  • 這些稱為 "根"

  • 將這些變量所引用的對象進行標記

  • 若引用的對象內部引用了其他對象,繼續進行標記

  • 被標記的對象都是被使用的對象

  • 沒有被標記的對象就是可回收的垃圾對象

回收方法

清理

  • 將垃圾對象佔據的內存清理

  • JVM 將所占內存空間

  • 標示為空閒

  • 記錄在一個空閒列表

  • 下次創建新對象時,從列表中找一個適合大小的空閒空間分配給新對象

  • 會產生碎片的空閒空間

  • 回收幾次後,碎片會越來越多

壓縮

  • 從堆空間頭部開始

  • 將存活的對象拷貝放在一段連續的內存空間中

  • 其餘空閒空間也是連續的空閒空間

複製

  • 將堆空間分成兩部分

  • 只在其中一部分創建對象

  • 當這一部分空間用完時,將標記可用對象複製到另外一個空間中

回收策略 - 分代回收

  • Java 對象大部分生存時間很短暫

  • 將生存時間很短的對象,創建在較小的區域

  • 回收範圍小

  • 可快速回收

  • 分成新生代、老年代

  • 新生代

  • Eden 區

  • 初始對象創建區

  • From 區

  • 若 Eden 區滿了進行垃圾回收

  • 將可用對象複製至此區

  • To 區

  • 若 From 滿了,連同 Eden 區與 From 區進行垃圾回收

  • 將可用對象複製至此區

  • 在 From 與 To 區交叉進行回收與複製

  • 老年代

  • 在新生代交叉回收與複製後,會產生一些生命周期很長的對象

  • 將這些對象複製至此區

  • Young GC

  • 對新生代進行回收

  • Full GC

  • 全量垃圾回收

  • 新生代、老年代一起回收

回收器算法

串行回收器

  • 所有應用程序線程停止

  • 對應用線程影響很大

  • stop-the-world 時間也算進響應時間內

  • 啟動一個垃圾回收線程

  • 標記對象

  • 回收

  • 早期 Java 運行在單核 CPU 使用

並行回收器

  • 所有應用程序線程停止

  • 對應用線程影響很大

  • stop-the-world 時間也算進響應時間內

  • 針對多核 CPU ,啟動多個垃圾回收線程

並發回收器 (CMS)

  • 初始化標記

  • 所有應用線程停下

  • 啟動標記線程

  • 並發標記

  • 標記線程與應用線程一起執行

  • 重標記

  • 所有應用線程停下

  • 啟動重新標記線程

  • 因為前一個並發標記時,應用程序可能改變物件引用狀態

  • 並發清理

  • 清理線程與應用線程一起執行

  • stop-the-world 時間比較短,對應用線程影響比較小

G1 回收器

  • 將內存空間分成更小的區域

  • 默認為 2000 個區域

  • 區域越小,垃圾對象標記、清理也越快

  • 每個區域也區分不同的分代角色,進行分代回收

  • 利用 -XX:MaxGCPauseMillis 進行 stop-the-world 暫停時間控管

  • 期望 GC 暫停時間的最大值

  • G1 回收器根據這個時間,動態調整回收策略

Java 啟動參數

標準參數

  • 所有 JVM 實現都必須時間這些參數的功能

  • 保證向後兼容

  • Example

  • 運行模式 : -server, -client

  • 類加載路徑 : -cp, -classpath

  • 運行調試: --verbose

  • 系統變量: --D

非標準參數

  • 默認 JVM 實現這些參數

  • 不保證所有 JVM 實現都實現

  • 不保證向後兼容

  • Example

  • -Xms : 初始堆大小

  • -Xmx : 最大堆大小

  • -Xmn : 新生代大小

  • -Xss : 線成堆棧大小

非 Stable 參數

  • 此類參數各 JVM 實現會有所不同

  • 將來可能隨時取消

  • Example

  • -XX:-UseConcMarkSweepGC

  • 啟用 CMS 垃圾回收

JVM 性能診斷工具

基本工具

  • jps

  • 查看 Host 上,所有 Java 進程 pid (jvmid)

  • 找出 JVM 進程 ID,進一步使用其他工具來監控、分析

  • 常用參數

  • -l : 輸出 Java 應用程序 main class 的完整包

  • -q : 僅顯示 pid,不顯示其他訊息

  • -m : 輸出傳遞給 main 方法的參數

  • -v : 輸出傳遞給 JVM 的參數

  • 診對 JVM 相關問題時查看

  • jstat

  • Java Virtual Machine Statistics Monitoring Tool

  • JDK 自帶輕量級工具

  • 對 Java 應用程序的資源和性能進行實時的命令行監控

  • 包括對 Heap Size、垃圾回收狀況

  • 語法

  • jstat [options] vmid [interval] [count]

  • options : 一般使用 -gcutil 查看 gc 情況

  • vmid : JVM 進程 ID

  • interval : 間隔時間,單位 ms

  • count : 打印次數,若缺省則打印無數次

  • 欄位

  • S0 : Heap 上的 Survivor space 0 區域已使用空間百分比

  • S1 : Heap 上的 Survivor space 1 區域已使用空間百分比

  • E : Heap 上的 Eden space 區域已使用空間百分比

  • O : Heap 上的 Old space 區域已使用空間百分比

  • YGC : 從應用程序啟動到採樣時,發生 Young GC 的次數

  • YGCT : 從應用程序啟動到採樣時,Young GC 使用的時間 (s)

  • FGC : 從應用程序啟動到採樣時,發生 Full GC 的次數

  • FGCT : 從應用程序啟動到採樣時,Full GC 使用的時間 (s)

  • GCT : 從應用程序啟動到採樣時,用於 GC 的總時間 (s)

  • jmap

  • 輸出所有內存對象的工具

  • 找出內存洩漏

  • 可將 JVM 中的 heap,以二進制輸出成文本

  • 使用方法

  • jmap -histo pid > a.log

  • 保存到文本

  • 可使用文本比對工具,找出 GC 回收了哪些對象

  • jmap -dump:format=b, file=f1 PID

  • 將 PID 進程的內存 heap 輸出至 f1 文件中

  • jstack

  • 查看 JVM 內線程堆棧信息

集成工具 (可視化工具)

  • JConsole

  • JVisualVM

Java 代碼優化技巧及原理

合理並謹慎使用多線程

  • 使用場景 : I/O 阻塞、多 CPU 並發,多用戶並發

  • 會有資源爭用與同步問題

  • 啟動線程數 = [ 任務執行時間 / (任務執行時間 - IO 等待時間) ] * CPU  內核數

  • 最佳啟動線程數與 CPU 內核數成正比和 IO 阻塞時間成反比

  • 如果都是 CPU 型計算任務,線程數最多不超過 CPU 內核數

  • 如果需要等待 I/O 操作,可多啟動線程

  • 提高任務並發數

  • 提高系統吞吐能力

  • 改善系統效能

競態條件與臨界區

  • 多線程訪問相同資源會有問題

  • 多線程競爭同一資源時

  • 競態條件 : 對資源訪問順序敏感

  • 當前後順序不一致或混亂時,會對結果造成不一致

  • 臨界區 : 導致競態條件發生的代碼區

  • 在臨界區使用適當的同步,就可以避免競態條件

Java 線程安全

  • 線程安全代碼 : 允許被多個線程安全執行的代碼

  • 方法局部變量

  • 局部變量不會被多個線程共享

  • 局部變量存於線程棧中

  • 線程不共享線程棧

  • 基礎類型的局部變量是線程安全的

  • 方法局部對象引用

  • 方法中創建的對象不傳遞給其他方法或設定至全局變量,則線程安全

  • 此方法引用完後,沒有其他引用,被垃圾回收

  • 對象成員變量

  • 對象成員存於堆中

  • 若多線程同時更新同一對象的同一成員,則線程不安全

Java ThreadLocal

  • 線程共享 : 可能線程不安全

  • 線程私有 : 一定安全

  • ThreadLocal : 既私有又共享

  • 在堆中,大家共享

  • 每個對象可以訪問到私有的 ThreadLocal 變量,不會產生線程安全問題

  • 使用

  • 創建一個 ThreadLocal 變量 (X 類靜態成員變量)

  • public static ThreadLocal myThreadLocal = new ThreadLocal()

  • 存儲此對象的值 (A 類 a 方法)

  • X.myThreadLocal.set("A Thread Local Value")

  • 讀取一個 ThreadLocal 對象的值 (B 類 b 方法)

  • String threadLocalValue = (String)X.myThreadLocal.get()

  • 每個線程 get 出來的值,就是自己 set 進去的

  • set 時

  • 會根據當前是哪一個 thread

  • 取出這個 thread 中的 ThreadLocalMap()

  • 再把值存入

  • 整個 map 對象線程共享,裡面的 每個 Key-Value 線程私有

Java 內存洩漏

  • 無引用的對象由垃圾回收器回收

  • Java 內存洩漏是邏輯上的洩漏,不是物理上的洩漏

  • 一個對象邏輯上不再使用,卻能存在在內存中,仍在系統某處被引用

  • 是由開發人員的錯誤引起的

  • 如果程序保留對永遠不再使用對象的引用,這些對象將會占用並耗盡內存

  • 沒有被垃圾回收

  • Example

  • 長生命週期的對象

  • 引用可能一直存在使用的框架中,沒有被釋放

  • 靜態容器

  • 不斷的往容器增加對象,卻從不清除

  • 緩存

  • 自身不對對象進行釋放管理

合理使用線程池和對象池

  • 復用線程或對象

  • 比較耗資源、耗時比較長的對象

  • 避免在程序生命週期中,創建和刪除大量對象

  • 注意

  • 池管理算法

  • 紀錄對象空閒及使用狀況

  • 對象內容清除

  • 如 ThreadLocal 的清空

使用合適的 JDK 容器類

  • 順序表、鏈表、Hash 等

  • 了解 LinkList 和 ArrayList 的區別及適用場景

  • 了解 HashMap 的算法及應用場景

  • 不同場景使用不同的容器類

  • 使用 concurrent 包

  • 如使用線程安全的 ConcurrentHashMap

  • HashMap 需應用程序自行控制同步 (鎖)

  • ConcurrentHashMap 自帶分段加鎖,效能較高

縮短對象生命週期,加速垃圾回收

  • 減少對象駐留內存的時間

  • 在使用時創建對象,用完後立即釋放

  • 避免讓對象進入老年代

  • 可手動釋放,將變量設 null 值,便釋放

  • 對象創建的步驟

  • 靜態代碼段 -> 靜態成員變量 -> 父類構造函數 -> 子類構造函數

使用 I/O buffer 及 NIO

  • 延遲寫與提前讀策略

  • 應用程序不會等待寫入及讀取

  • 提升 I/O 操作性能

  • 異步無阻塞 I/O

優先使用組合代替繼承

  • 減少對象耦合

  • 更符合面向對象的設計模式

  • 避免太深的繼承層次帶來的對象創建性能損失

  • 太多祖先類構造函數的呼叫,影響性能

合理使用單例模式

  • 類等於全局靜態對象

  • 做到無狀態的單例對象

  • 沒有成員變量或成員變量也是無狀態

  • 不用考慮線程安全問題

  • 保證線程安全

系統性能優化案例: 秒殺系統

技術挑戰

瞬間高並發

  • 帶寬耗盡

  • 服務器崩潰,猶如 DDOS 攻擊

確保使用戶遵循秒殺規則

  • 第一類秒殺器: 秒殺前不斷刷新頁面

  • 第二類秒殺器: 跳過秒殺頁面,直接進入下單頁面

核心架構方案思路

與原系統隔離

  • 避免修改影響原系統

  • 部屬一個新的秒殺服務器集群

  • 避免對原系統重構,增加新功能開發難度及時間

  • 根據新的秒殺系統需求,進行最適化設計

簡單化設計

  • 避免複雜的業務流程

  • 用戶端流程簡化

  • 砍掉不重要的分枝流程

  • 如開通支付接口

  • 延遲部分流程,分散並發

  • 如訂單完成之後的支付流程

  • 使用原有支付及訂單管理系統

  • 經過層層篩減,秒殺成功後對支付及訂單管理系統的並發量不大

  • 避免重新驗證金流正確性

降低高並發影響

  • 利用緩存 (CDN,反向代理),降低對系統的負載

  • 將動態頁面轉成靜態頁面

  • 也可減少數據庫存取

  • 規範商品圖片大小

  • 降低帶寬需求

  • 精簡優化頁面

  • 減少 HTTP 請求數

  • 精簡 CSS,JS

  • 圖片合併

  • HTML 內容壓縮

  • 設置閥門,控制訪問流量,降低後端系統負擔

  • 防止秒殺器干擾

  • 閥門可有效防止第一類秒殺器

  • 最後一刻動態生成 URL,可防止第二類秒殺器

  • 降低運營複雜性

  • 依秒殺商品列表,定時自動產生靜態頁面


发布于: 2020 年 11 月 22 日阅读数: 835
用户头像

Panda

关注

还未添加个人签名 2015.06.29 加入

还未添加个人简介

评论

发布
暂无评论
架構師訓練營第 1 期 - 第 09 周總結