2023面試-答案總結

zhumengyang發表於2023-03-08
    以下內容全部來源於網際網路,個人東湊西拼 - -

SQL如何最佳化
就是開始我跟他說這個最佳化是分為幾種方式,幾種步驟,第一個是延伸語句,就是你會那個to sql,或者var_dump列印出來這個搜口,獲取到這個SQL之後,或者說在那個es裡邊獲取到這個對應的solog,找到了這個SQL以後。
然後對搜尋進行一次explain,然後看它的索引,如果沒有的話就去加,但是,你這個索引也要看那個表中的索引的數量和那個表的資料量大小,如果表表的資料量超大,就是可能會導致一個就是你的索引無法命中,這個時候你去主動命中還是不主動命中,這就是由你來選擇的,或者是說你退一步加多少,加你去沒有辦法改了,你就去加一個資料字典,但是這個資料字典還是要去重新跑一遍資料才能加的。
然後另外的一種層面,就是在你已出來之後,你去看你那個表兒,將那個表兒的對應那些欄位什麼的,這些都給它鎖住,最好讓它命中於ID,然後另外一個點就是對錶兒進行最佳化,那表兒最佳化是什麼?表兒最佳化就是首先第一個欄位的型別,第二個就是它那個,你的這個奈米值的鎖死,就是鎖的長短,還有你的那個,這個比如說無負號,這從讓它從正處開始跑,這種是一個演演算法兒問題。
然後第三步就是表,就是正兒八經那個表,你說這個表的資料量如果說過於大,然後導致這個circle慢,那怎麼辦?那就是還是那麼幾種方法,第一種就是做分表,我說別的表分不知道,但是用那個末日引擎去做分表,我這個這個我是瞭解的做過,然後還有另外一種方式叫邏輯分表,邏輯分表就是透過程式碼來實現,透過指令碼來實現,然後這個根據實際業務,如果是統計類的,你怎麼實現都無所謂,然後如果說是是一直在應用於這個,那就正常的末日區分表就好了,然後還有就是對這個對這個。
還有一個就是程式碼問題,程式碼問題就很簡單,就是看你的折扣,他的你的程式碼的拼拼裝,這個折扣是怎麼拼裝的,他應用哪幾層,或者說你用ORM,你可不可以不用orm,或者甚至於說在常規這種,就是遮蔽這個叉S這種情況的下,你是不是可以讓他使用一個原生直接執行,這樣他速度是最快。
搜尋最佳化還有啥嗎?沒了,我也不知道還有啥了,分割槽區區什麼區分,那個什麼區分,那個什麼東西區分那個區分那個那個那個那個分割槽我沒做過,我也不知道對,如果說你還是嫌慢,那加es,加es不就簡單了嗎,不就快了嗎?因為es。
讀寫分離、高可用


binlog 是什麼
1、MySQL的二進位制日誌binlog可以說是MySQL最重要的日誌,它記錄了所有的DDL和DML語句(除了資料查詢語句select),以事件形式記錄,還包含語句所執行的消耗的時間,MySQL的二進位制日誌是事務安全型的。
a、DDL
—-Data Definition Language 資料庫定義語言
主要的命令有create、alter、drop等,ddl主要是用在定義或改變表(table)的結構,資料型別,表之間的連線和約束等初始工作上,他們大多在建表時候使用。
b、DML
—-Data Manipulation Language 資料操縱語言
主要命令是slect,update,insert,delete,就像它的名字一樣,這4條命令是用來對資料庫裡的資料進行操作的語言
2、mysqlbinlog常見的選項有一下幾個:
從二進位制日誌中讀取指定等於時間戳或者晚於本地計算機的時間
從二進位制日誌中讀取指定小於時間戳或者等於本地計算機的時間 取值和上述一樣
從二進位制日誌中讀取指定position 事件位置作為開始。
從二進位制日誌中讀取指定position 事件位置作為事件截至
3、一般來說開啟binlog日誌大概會有1%的效能損耗。
4、binlog日誌有兩個最重要的使用場景。
a、mysql主從複製:mysql replication在master端開啟binlog,master把它的二進位制日誌傳遞給slaves來達到master-slave資料一致的目的。
b、資料恢復:透過mysqlbinlog工具來恢復資料。

binlog日誌包括兩類檔案:
1)、二進位制日誌索引檔案(檔名字尾為.index)用於記錄所有的二進位制檔案。
2)、二進位制日誌檔案(檔名字尾為.00000*)記錄資料庫所有的DDL和DML(除了資料查詢語句select)語句事件

php-fpm 與 swoole 的異同
常駐記憶體
一個請求透過 nginx 轉發到 php 來執行,中間是透過 php-fpm 來溝通連線的,透過一種叫 cgi 的協議來通訊。

在 php-fpm 還未出現之前,php 一般都是透過 php-cgi 這個 php 官方元件來與 web server 進行通訊的,它的特點是每次執行都需要重新載入一次 php.ini 中的配置,並且每有一個請求都會重新啟動一個程式來執行,執行完畢後再銷燬掉。

這種方式每次都會重新解析 php.ini、重新載入全部擴充套件,並重新初始化全部資料結構,這無疑是對 cpu 效能的浪費行為,於是就出現了 fast cgi。

fast cgi 的應用體現便是 php-fpm。透過一個 master 程式管理多個 worker 程式的方式,在主程式啟動時便將 php.ini 中的配置等資訊載入記憶體,worker 程式建立時會繼承 master 程式的資料,並且 worker 程式處理完一個請求後不會銷燬,而是繼續等待下一個請求。所以只需要一次載入,php.ini 與擴充套件和初始化資料這部分的效能便被節省出來了。雖然 php.ini 的配置初始化被節省掉了,但是我們平時使用的 laravel 等 php 開發框架中同樣有冗長的 ioc 容器初始化,依賴建立的過程,這個問題 php-fpm 就無能為力了。

那麼說完了 php-fpm,這些和 swoole 又有什麼關係呢?相同點就在於,swoole 也是常駐記憶體的,也是一個 master 管理多個 worker 程式,所以也節省掉了多次載入 php.ini 等配置的消耗。

並且,由於swoole本身就是一個PHP擴充套件,它的啟動是在php指令碼內開始的,因此可以看做是php開發框架的一部分,那麼php框架初始化的那一系列重複初始化便同樣被節省掉了。這便是 swoole 高效能的原因。

swoole 在宣傳上寫的是為瞭解決傳統 php-fpm 模式併發慢的問題而誕生的,那麼就帶來一個問題:

php-fpm 模式為什麼慢?

php-fpm 模式是以多程式方式來執行的,一個 master 程式建立並管理多個 work 程式。master 程式只負責接收請求和返回響應,剩下的執行工作交給 work 程式來執行。

也就是說每一個請求都對應一個 work 程式,同一時刻,伺服器上有多少 work 程式,這臺伺服器就可以處理多少的併發。
這麼一看是不是覺得 php-fpm 的併發能力特別差?假設不考慮伺服器配置問題,預設的 400 個程式數同時就只能支援 400 的併發。
實際情況肯定沒有這麼差,假設很多的指令碼只需要 0.001 秒就處理完成了,如果所有的請求都可以快速處理的話,那麼我們可以說 1 秒鐘的併發數就等於 400*1000=40 萬的併發。

這麼一看是不是覺得 php-fpm 的效能也沒這麼差了?

但是,如果你的業務資料量很大,mysql 的查詢效率不高,每次請求都需要花費 1 秒鐘的時間才能返回響應的話呢?
那麼每秒鐘的併發數就從 40W 又下降回 400 了。

而 swoole,就是為瞭解決這個問題所開發出來的一個 php 擴充套件,它使得每個 worker 程式不會因為 1 秒鐘的 io 阻塞而白白讓 cpu 浪費 1 秒鐘的效能。

swoole 的執行方式
按照剛剛的那個例子來解釋的話,swoole 的處理方式就是在一個 worker 程式開始進行 mysql io 查詢的時候就將這個請求任務暫時掛起,立馬開始執行下一個請求,然後等到第一個請求中的 mysql io 資料返回之後,再切換回第一個請求中繼續執行程式碼返回響應。這樣一來,對於 cpu 來說,它一直在執行程式碼,沒有因為請求中 mysql 的 1 秒 io 耗時處於空閒狀態。

那麼,既然那 1 秒的 io 耗時沒有對 cpu 產生影響,那麼對於伺服器來說,每一秒鐘的併發數和之前一樣仍然是 40W,只不過由於每個請求還是有 1 秒的耗時,所以單個請求的響應時間依然是 1 秒鐘,但是對於 cpu 來說,它每秒處理的請求數量並沒有減少,因為對於 cpu 來說一個請求的 io 耗時是 1 秒,1000 個請求的總耗時依舊是 1 秒。

同步與非同步

什麼是同步

我們平時編寫的 php 程式碼就是同步程式碼。php 直譯器一行一行的編譯執行我們的程式碼,碰到資料庫查詢,或者第三方介面呼叫,或者系統磁碟讀寫。這些不歸 php 當前程式管轄的部分都是 io 操作,它們可能是磁碟 io,可能是網路 io。而同步的程式碼一旦碰到這些 io 操作,它們就會停下來等待,等待 mysql 返回查詢結果,等待第三方介面返回響應,等待 linxu 檔案系統返回磁碟讀取結果。在等待的過程中,cpu 的效能就被浪費掉了。

什麼是非同步

非同步程式碼就是說程式碼在執行到一個需要等待的 io 操作時,不在原地傻等,而是繼續向下執行其他程式碼,等到 io 操作有返回結果通知的時候再回過頭來執行處理邏輯。

一個簡單的例子就是 js 中的 Ajax,下面這個例子當中,js 把 Ajax 請求傳送出去就開始執行下一行程式碼了,所以是先 alert 2,然後等到 Ajax 響應返回再執行回撥函式

對於生活中的例子來說,非同步就是一個人同時使用洗衣機洗衣服與使用電飯煲做飯,假設洗衣機洗一次衣服要 40 分鐘,電飯煲煮飯也需要 40 分鐘,那麼這兩件事都完成需要多長時間呢?

是的,也是 40 分鐘(最多多出一些把衣服和米分別放進機器的可以忽略不計的時間),因為人把衣服放進洗衣機就可以去做其他事了,不會守在洗衣機旁傻等,可以去開電飯煲了。而洗衣機洗好衣服以後,會有滴滴聲提示人衣服已經洗好了。

回到一開始那個 swoole 併發數的例子,那些需要 1 秒鐘來查詢 mysql 資料的請求,它們的那 1 秒 io 操作也沒有讓 cpu 進行傻等,所以對於 cpu 來說,io 操作已經無法影響它的併發數了,因為它始終在工作,並沒有浪費等待時間。

解析一下,如果使用非同步的方式,那麼會有兩個比較關鍵的點:

發起 io 操作,新增回撥函式
等任務完成後執行回撥函式

非同步程式設計完全沒有浪費 cpu 一點效能,那如果所有的 io 耗時操作都用非同步操作會怎麼樣呢?
瞭解 node.js 的朋友可能經常聽見一個詞回撥地獄。

前端開發中很少會有人在 Ajax 中巢狀 Ajax,但是如果你想透過非同步的方式來提升程式碼的效能,那麼不可避免的,只要你的程式中有多個 io 操作,那它們就會向下面這段程式碼一樣變成層層巢狀,很快這段程式碼就變得不可維護了,甚至是修改的時候都會讓人十分頭疼。

而 swoole 的出現,就是為瞭解決同步程式碼浪費效能的問題,讓同步執行的程式碼變為非同步執行,同時使用協程降低非同步回撥程式設計時的心智負擔。

cpu 上下文切換

現在的電腦,一邊寫程式碼,一邊查檔案,一邊聽音樂都是很常見的,因為 cpu 的核心數很多可以同時做好幾件事。但是你在一開始學習 for 迴圈的時候一定聽老師說過,當年的單核 cpu 寫迴圈一定要小心,因為一旦出現死迴圈了,那麼整臺電腦都會卡死只能重啟了。

cpu 在執行程式碼的時候是同步的,所以理論上來講同一時刻只能做一件事,哪怕不進行死迴圈,按理說之前的老電腦也沒辦法做到同時寫程式碼與查檔案以及聽音樂這些事才對,並且就算是現在的四核八核 cpu,那我也是可以同時開十幾個網頁,同時播放影片的。

讓單核 cpu 同時執行多工的魔法就是上下文切換了,主要的原理就是 cpu 在同時進行玩遊戲與播放音樂時,先執行一會遊戲,然後馬上切到音樂程式上執行一會,不斷地在這些應用之間來回切換執行,因為 cpu 的計算速度是遠超人腦反應時間的,所以在人類眼中,這些應用就像是在同時執行一樣。

那到底什麼是上下文呢?就是程式執行中所需要的資料,包括儲存在記憶體中的,以及 cpu 多級暫存器中的這些資料。線上程與程式切換的時候,需要把這些資料儲存起來,等到它們恢復執行的時候再把資料讀取回程式來執行。

本文主要是介紹 swoole 的,swoole 的重點在於非同步與協程,為什麼要提到上下文切換呢?因為不論是多程式、多執行緒還是協程,它們本質上都需要用到上下文切換來實現的。

多程式模式,是由系統來決定每個程式的執行分片時長。而多執行緒由於它們一定有一個父級程式,所以每個執行緒的執行分片時長則是由程式來決定的。這也是為什麼多執行緒語言的教程裡都會提到不是執行緒開的越多越好的原因,多執行緒會有執行緒爭搶和系統排程的開銷。同時,由於 cpu 同一段時間內運算速度的總量是固定的,所以執行緒只需要儘量把 cpu 空閒的算力佔滿就好,開過多的執行緒反而會因為增加系統執行緒排程開銷造成業務部分執行緒效能的下降。

那麼協程與多執行緒多程式又有什麼不同呢?透過例項來對比:

php-cgi 便是多程式的一種體現,每個請求對應一個 cgi 程式,帶來的缺點是程式頻繁建立銷燬的開銷以及每次都需要載入 php.ini 配置的效能浪費。

php-fpm 多程式模式的改良,透過 master/worker 的模式,讓多程式少了重新載入配置與頻繁建立銷燬程式的開銷。

假設 php 有多執行緒,省略多次 php.ini 的載入,省略多次開發框架初始化,相應的帶來執行緒排程開銷,多執行緒搶佔式模型需要注意資料訪問的執行緒安全,需要給資料加鎖,並帶來鎖爭搶與死鎖問題。

協程,省略多次 php.ini 載入,省略多次開發框架初始化,由於協程是使用者態的執行緒,所以由程式碼來控制什麼時候進行切換,沒有執行緒排程開銷。並且 swoole 以同步的方式編寫非同步程式碼,協程的切換由底層排程器自行切換,開發者無需關注執行緒鎖與死鎖問題。

swoole 的協程切換是基於 io 來排程的,也就是說它只會在遇到 io 操作的時候才會進行切換,透過節省 io 等待時間來提高伺服器效能,因此 swoole 的協程是無法進行併發計算的。不過遇到需要平行計算的場景,swoole 也提供了多程式的執行方式,如果需要多程式協同操作同一個資料,就需要加程式鎖了。

事件迴圈–非同步是如何實現的

現在我們已經知道多程式,多執行緒,協程都是非同步的程式設計方式了,那麼非同步是怎麼實現的呢?
這是一個大問題,先從最基礎的看起,基礎非同步程式設計就是非同步回撥模式,也就是在執行任務的同時傳入一個回撥函式,等到任務執行完畢,回撥函式自然而然的就開始執行了。類似 js 的 Ajax 一樣,發起一個 Ajax 請求的時候便是發起了非同步任務,同時在 $.ajax 方法的第三個引數傳入一個匿名函式,等到後端返回響應以後再繼續執行回撥函式中的程式碼。

那麼就出現了一個問題,是誰來通知當前程式非同步任務已經完成了的呢?

做過 im 通訊朋友都知道,兩個客戶端的對話除了傳送訊息,最難實現的還是接收訊息,因為需要服務端主動做推送。如果不使用 WebSocket 的話,要實現服務端推送就只能使用長連線 + 輪詢的方式了。接收訊息的那一方客戶端需要每隔一段時間就請求一次伺服器,看看有沒有訊息傳送給自己。對於非同步回撥來說,它的實現方式也是有異曲同工之處。

處理非同步回撥的部分叫做事件迴圈,可以理解為每個程式有一個死迴圈,不斷的檢視當前有沒有待執行的任務、已經執行完需要通知的回撥。當我們進行非同步任務呼叫的時候,就是向這個迴圈中投遞了一個任務與對應的回撥。當任務完成的時候,迴圈便把任務從監聽陣列中去除,並執行回撥。

下面來看一個簡單的事件迴圈的例子。

可以看到,EventLoop 類中維護了一個 event 陣列,用來儲存需所有需要監聽的事件。在呼叫 addEventHandler 方法時,則需要將事件的型別、引數,以及回撥函式一同傳入。

當呼叫 run 方法時,這個迴圈就被開啟了,可以看到 run 方法中是一個 while 死迴圈,用來不斷的檢測是否有已完成的任務。而 while 迴圈內層的 foreach 則是為了檢視所有事件中是否有已完成的單個任務。

而 processTimers 與 processIOEvents 方法則代表了 swoole 中典型的兩種事件,io 事件與定時器。由於 linux 系統中萬物皆檔案的特性,很多看似是網路 io 的功能,其實都要用到檔案系統來實現,所以 processIOEvents 方法需要傳入 fp 檔案指標以及 read 與 write 兩種讀寫事件。例如假設我們投遞的是讀取事件,那麼就呼叫 fread 函式來讀取檔案,並把讀取到的資料傳遞給回撥函式來執行。這就是一個事件迴圈的回撥過程了。

在理解了時間迴圈以後,那麼事件迴圈與 swoole 與多程式、多執行緒、協程之間有什麼關係呢?

沒錯,無論是多程式、多執行緒還是協程,它們底層都依賴事件迴圈來實現非同步,例如程式與執行緒之間切換的時候如何通知對應的程式與執行緒?依賴系統級事件迴圈。例如協程之間多個協程的切換要如何通知對應的協程?也是依賴事件迴圈。不過 swoole 為了降低上下文切換帶來的消耗,沒有依賴系統級事件迴圈而是自己實現了一套,swoole 的協程上下文切換都是記憶體讀取,避免了 cpu 暫存器、堆疊以及系統核心態與使用者態之間的切換,因此切換開銷極小。

總結

說了這麼多概念,那麼 swoole 到底是什麼呢?它融合了 php-fpm 的結構模式,最佳化了單程式的效能浪費,弱化了多執行緒的排程開銷,遮蔽了非同步回撥的複雜邏輯,是一個常駐記憶體的高效能 web 擴充套件。

做一個不嚴謹的類比,你也可以認為 swoole 是一個語言層面實現的 php-fpm,畢竟 swoole 也支援完全的多程式模式,這種模式下與 php-fpm 的執行方式大同小異。不過由於在語言層面便常駐記憶體了,所以帶來的福利便是在啟動 php 指令碼的開發框架時,只需要一次載入便儲存在記憶體中了,避免了 php-fpm 每個請求都重新初始化框架的效能浪費。那麼同樣的由於服務常駐記憶體了,所以哪怕是在開發過程中,程式碼相關的改動都需要重啟一下 swoole 服務。

而 swoole 的架構,對應下面這張圖,便是 master、manager、worker 的結構,在 swoole 服務啟動時,master 程式便 fork 出 manager 程式來對 worker 程式進行建立和管理,master 程式自己則透過 reactor 執行緒來接受與分發請求,master 程式接收到的請求透過 reactor 執行緒直接傳送到 worker 程式中,而 worker 程式負責對請求進行具體的處理。如果開啟了協程模式,並且程式碼也是以協程的方式執行,則一個 worker 可能會一段時間內 (例如 1s) 處理多個請求。因為每個請求遇到 io 等待時,worker 便切換協程直接開始處理下一個請求了,直到 io 任務返回結果,worker 再切換回上一個請求將響應返回給 master 程式。

swoole 對效能的提升帶來的代價是程式設計思維的轉變,因為常駐記憶體了,所以編寫業務程式碼時,對記憶體變數的使用就需要更加小心,避免造成記憶體洩露。因為基於非同步程式設計,所以要理解非同步的思想,避免寫出同步阻塞的程式碼。


RabbitMQ使用的設計模式主要有以下幾種:

1. 生產者-消費者模式:生產者將訊息傳送到RabbitMQ,消費者從RabbitMQ接收訊息。

2. 訂閱-釋出模式:釋出者將訊息釋出到RabbitMQ,訂閱者從RabbitMQ訂閱訊息。

3. 路由模式:釋出者將訊息釋出到RabbitMQ,消費者從RabbitMQ接收訊息,並使用路由鍵進行過濾。

4. 萬用字元模式:釋出者將訊息釋出到RabbitMQ,消費者從RabbitMQ接收訊息,並使用萬用字元進行過濾。

5. 訊息確認模式:消費者從RabbitMQ接收訊息,並確認訊息的接收。

6. 訊息持久化模式:RabbitMQ將訊息持久化到磁碟,以便在伺服器重啟後恢復訊息


RabbitMQ使用的設計模式主要有生產者-消費者模式、釋出-訂閱模式、路由模式和萬用字元模式。

生產者-消費者模式:生產者將訊息傳送到訊息佇列,消費者從訊息佇列中接收訊息,這種模式可以實現非同步處理,提高系統的吞吐量。RabbitMQ中使用這種模式可以實現訊息的非同步處理,提高系統的吞吐量。

釋出-訂閱模式:釋出者將訊息釋出到訊息佇列,訂閱者從訊息佇列中訂閱訊息,這種模式可以實現一對多的訊息傳遞,提高系統的可伸縮性。RabbitMQ中使用這種模式可以實現一對多的訊息傳遞,提高系統的可伸縮性。

路由模式:釋出者將訊息釋出到訊息佇列,消費者從訊息佇列中接收訊息,這種模式可以實現訊息的路由,提高系統的可伸縮性。RabbitMQ中使用這種模式可以實現訊息的路由,提高系統的可伸縮性。

萬用字元模式:釋出者將訊息釋出到訊息佇列,消費者從訊息佇列中接收訊息,這種模式可以實現訊息的靈活路由,提高系統的可伸縮性。RabbitMQ中使用這種模式可以實現訊息的靈活路由,提高系統的可伸縮性。

Swoole是一個PHP擴充套件,它使用C語言編寫,它提供了一個非同步多執行緒伺服器,可以用來構建高效能的網路應用程式,如Web伺服器,網路爬蟲,訊息佇列,RPC伺服器等。

Swoole的原理是基於事件驅動和非同步程式設計的,它使用epoll或kqueue等Linux核心特性來實現高效能的網路通訊,並且支援非同步MySQL,非同步Redis,資料庫連線池,AsyncTask等功能。

Swoole的核心是一個事件迴圈,它會不斷地檢查網路事件,如果有新的連線請求,它會觸發一個回撥函式,然後處理連線,如果有資料可讀,它會觸發另一個回撥函式,然後處理資料,如果有資料可寫,它會觸發另一個回撥函式,然後傳送資料。

Swoole還支援定時器,可以定時執行任務,支援非同步MySQL,非同步Redis,資料庫連線池,AsyncTask等功能,可以極大地提高程式的效能。

Swoole的核心是一個事件迴圈,它會不斷地檢查網路事件,如果有新的連線請求,它會觸發一個回撥函式,然後處理連線,如果有資料可讀,它會觸發另一個回撥函式,然後處理資料,如果有資料可寫,它會觸發另一個回撥函式,然後傳送資料


1.什麼是程式、執行緒、協程?區別?優缺點?
定義:
(1)程式是系統進行資源分配和排程的獨立單位

(2)執行緒是程式的實體,是CPU排程和分配的基本單位

(3)協程,又稱微執行緒,自帶CUP上下文,是比執行緒更小的執行單元,佔用資源小,效率高

區別:
(1)一個程式至少有一個程式,一個程式至少有一個執行緒

(2)執行緒的劃分尺度小於程式(資源比程式少),使得多執行緒程式的併發性高

(3)程式在執行過程中擁有獨立的記憶體單元,而多個執行緒共享記憶體,從而極大的提高了程式的執行效率

(4)執行緒不能夠獨立執行,必須依存在程式總

優缺點:
程式:

優點:順序程式的特點:既有封閉性和可再現性

程式的併發執行和資源共享,多道程式設計出現後,實現了程式的併發執行和資源共享,提高了系統的效率和系統的資源利用率

缺點:作業系統排程切換多個執行緒比切換排程程式在速度上快的多,而且程式間記憶體無法共享,通訊也比較麻煩。

執行緒之間由於共享程式記憶體空間,所以交換資料非常方便,在建立或撤銷程式時,由於系統都要為之分配和回收資源,導致系統的開銷明顯大於建立或撤銷執行緒時的開銷

執行緒:

優點:

它是一種非常”節儉”的多工操作方式。在Linux系統下,啟動一個新的程式必須分配給它獨立的地址空間,建立眾多的資料表來維護它的程式碼段、堆疊段和資料段,這是一種”昂貴”的多工工作方式。而執行於一個程式中的多個執行緒,它們彼此之間使用相同的地址空間,共享大部分資料,啟動一個執行緒所花費的空間遠遠小於啟動一個程式所花費的空間,而且,執行緒間彼此切換所需的時間也遠遠小於程式間切換所需要的時間。當然,在具體的系統上,這個資料可能會有較大的區別;執行緒間方便的通訊機制,由於同一程式下的執行緒之間共享資料空間,所以一個執行緒的資料可以直接為其它執行緒所用,這不僅快捷,而且方便;使多CPU系統更加有效。作業系統會保證當執行緒數不大於CPU數目時,不同的執行緒執行於不同的CPU上;

缺點:呼叫時,要儲存執行緒狀態,頻繁排程,需要佔用大量的機時

程式設計上容易出錯(執行緒同步問題)

2.多程式、多執行緒優缺點
1.多執行緒的優點:

無需跨程式邊界

程式邏輯和控制方式簡單

所有執行緒可以直接共享記憶體和變數等

執行緒方式消耗的總資源比程式方式好

缺點:

每個執行緒與主程式共用地址空間,受限於2GB地址空間

執行緒之間的同步和加鎖控制比較麻煩

一個執行緒的崩潰可能影響到整個程式的穩定性

到達一定的執行緒數程度後,即使再增加cpu也無法提高效能

執行緒能夠提高的總效能有限,並且執行緒多了之後,執行緒本身的排程也是一麻煩事情,需要消耗較多的CPU

3.針對於爬蟲 應 選擇多執行緒還是多程式?
多程式:密集CPU任務,需要充分使用多核CPU資源(伺服器,大量的平行計算的時候)用多程式

缺點:多個程式之間通訊成本高,切換開銷大

多執行緒:密集I/O任務(網路I/O 磁碟I/O 資料庫I/O)使用多執行緒合適

缺點:同一個時間切片只能執行一個執行緒,不能做到高並行,但是可以做到高併發

協程:又稱微執行緒,在單執行緒上執行多個任務,用函式切換,開銷極小,不透過作業系統排程,沒有執行緒。程式的切換開銷

多執行緒請求返回是無序的,哪個執行緒有資料返回就處理哪個執行緒,而協程返回的資料是有序的

缺陷:單執行緒執行,處理密集CPU和本地磁碟IO的時候,效能較低。處理網路I/O效能還是比較高.

總體來看 ,多執行緒是最佳人選


Swoole/Hyperf 全域性變數如何實現

在 Swoole 的持久化應用下,一個 Worker 內的全域性變數是 Worker 內共享的,而從協程的介紹我們可以知道同一個 Worker 內還會存在多個協程並存在協程切換,也就意味著一個 Worker 會在一個時間週期內同時處理多個協程(或直接理解為請求)的程式碼,也就意味著如果使用了全域性變數來儲存狀態可能會被多個協程所使用,也就是說不同的請求之間可能會混淆資料,這裡的全域性變數指的是 $_GET/$_POST/$_REQUEST/$_SESSION/$_COOKIE/$_SERVER等$_開頭的變數、global 變數,以及 static 靜態屬性。
那麼當我們需要使用到這些特性時應該怎麼辦?

對於全域性變數,均是跟隨著一個 請求(Request) 而產生的,而 Hyperf 的 請求(Request)/響應(Response) 是由 hyperf/http-message 透過實現 PSR-7 處理的,故所有的全域性變數均可以在 請求(Request) 物件中得到相關的值;

對於 global 變數和 static 變數,在 PHP-FPM 模式下,本質都是存活於一個請求生命週期內的,而在 Hyperf 內因為是 CLI 應用,會存在 全域性週期 和 請求週期(協程週期) 兩種長生命週期。

全域性週期,我們只需要建立一個靜態變數供全域性呼叫即可,靜態變數意味著在服務啟動後,任意協程和程式碼邏輯均共享此靜態變數內的資料,也就意味著存放的資料不能是特別服務於某一個請求或某一個協程;

協程週期,由於 Hyperf 會為每個請求自動建立一個協程來處理,那麼一個協程週期在此也可以理解為一個請求週期,在協程內,所有的狀態資料均應存放於 Hyperf\Context\Context 類中,透過該類的 get、set 來讀取和儲存任意結構的資料,這個 Context(協程上下文) 類在執行任意協程時讀取或儲存的資料都是僅限對應的協程的,同時在協程結束時也會自動銷燬相關的上下文資料。

攜程上下文

由於同一個程式內協程間是記憶體共享的,但協程的執行/切換是非順序的,也就意味著我們很難掌控當前的協程是哪一個(事實上可以,但通常沒人這麼幹),所以我們需要在發生協程切換時能夠同時切換對應的上下文。
在 Hyperf 裡實現協程的上下文管理將非常簡單,基於 Context 類的 set、get、has、override靜態方法即可完成上下文資料的管理,透過這些方法設定和獲取的值,都僅限於當前的協程,在協程結束時,對應的上下文也會自動跟隨釋放掉,無需手動管理,無需擔憂記憶體洩漏的風險。


事務與ACID

何為事務呢?書上給予的概念多而難於理解,筆者對事務的理解:一系列操作組成,要麼全部成功,要麼全部失敗。它具備ACID四大特性,在併發下,可能存在髒讀、幻讀、不可重複讀的併發問題,於是又引出了四大隔離級別。

01事務ACID特性

MYSQL作為一個關係型資料庫,以最常見的InnoDB引擎來說,是如何保證ACID的。

(Atomicity)原子性:一些列操作要麼全部成功,要麼全部失敗

(Isolation)隔離性:事務的結果只有提交了其他事務才可見

(Consistency)一致性:資料庫總時從一個一致狀態變到另一個一致狀態(事務修改前後的資料總體保證一致 轉賬)

(Durability)永續性:事務提交後,對資料修改永久的

02原子性

在聊原子性之前,我得先給大家普及一個東西——undo log,這是啥玩意兒呢?如果想要詳細瞭解或則想知道它具體內部咋實現的可以仔細去看書,這裡我就簡單分享我的理解,知道這些,面試基本夠用啦。

undo log,它是一種回滾日誌,既可以用來實現隔離性MVCC,也可以保證原子性。MVCC待會談論。實現原子性的關鍵,是事務回滾時能夠撤銷所有已經成功執行的sql語句。

當事務對資料庫進行修改時,InnoDB會生成對應的undo log,undo log會儲存事務開始前老版本的資料,當事務發生異常,便會rollback回滾到老版本狀態。當發生回滾時,InnoDB會根據undo log的內容做相反邏輯操作。

  • insert語句,回滾時會執行 delete;
  • delete語句,回滾時會執行insert;
  • update語句,回滾時便執行相反的update,把資料改回來。

總之,MYSQL的原子性便是由undo log來保證,undo log的作用我做了一下歸納總結:

作用:undolog記錄事務開始前老版本資料,用於實現回滾,保證原子性,實現MVCC,會將資料修改前的舊版本儲存在undolog,然後行記錄有個隱藏欄位回滾指標指向老版本。

03永續性

在聊永續性之前,我們得先知道redo log。老規矩,想深入學習理解看書噢,這裡只做筆者面試回答分享。

我們以一個生活小案例來理解一下下:

redo log,是一種物理日誌。它類似於一個卸貨的小推車,我們卸貨若是每下一件物品就拿著去入庫,那豈不是特浪費時間(效率低、還要找到合適存庫位置)。此時,若有一個小推車,我們將貨物首先存放在小推車,當推車滿了再往庫裡存,豈不大大增加了效率。

MYSQL中也用了類似思想,我們再更新資料庫時,先將更新操作記錄在redo log日誌,等redo log滿了或則MYSQL空閒了再刷盤。

其實就是MySQL裡經常說到的WAL技術,WAL的全稱是Write-Ahead Logging,它的關鍵點就是先寫日誌,再寫磁碟,也就是先裝小推車,等不忙的時候再裝庫。

總之,MYSQL的永續性便是由redo log來保證,redo log的作用我做了一下歸納總結:

redo log

物理日誌

作用:會記錄事務開啟後對資料做的修改,crash-safe

特性:空間一定,寫完後會迴圈寫,有兩個指標write pos指向當前記錄位置,checkpoint指向將擦除的位置,redolog相當於是個取貨小車,貨物太多時來不及一件一件入庫太慢了這樣,就先將貨物放入小車,等到貨物不多或則小車滿了或則店裡空閒時再將小車貨物送到庫房。用於crash-safe,資料庫異常斷電等情況可用redo log恢復。

以下只作瞭解:

寫入流程:先寫redo log buffer,然後wite到檔案系統的page cache,此時並沒有持久化,然後fsync持久化到磁碟

寫入策略:根據innodb_flush_log_at_trx_commit引數控制(我的記憶:innodb以事務的什麼提交方式重新整理日誌)

0——>事務提交時只把redo log留在redo log buffer

1——>將redo log直接持久化到磁碟(所以有個雙“1”配置,後面會講)

2——>只是把redo log寫到page cache

04隔離性

說到隔離性,我們都知道MYSQL有四種隔離級別,用來解決存在的併發問題。髒讀、幻讀、不可重複讀。

那麼不同隔離級別,隔離性是怎樣實現的呢?具體實現原理是怎樣的呢?接下來我們就談談,看不懂沒關係,老規矩,結尾會進行總結滴!

一句話:鎖+MVCC。

1、表鎖

  • lock table table_name read/write
  • myisam執行select自動加讀鎖,執行update/delete/insert自動加寫鎖
  • 表加了讀鎖,不會阻塞其他執行緒的讀操作,阻塞寫操作
  • 表加了寫鎖,讀寫操作都阻塞

2、行鎖

鎖的型別

  • 間隙鎖-gap lock:鎖定區間範圍,防止幻讀,左開右開,只在可重複讀隔離級別下生效—|—為了阻止多個事務將記錄插入到同一範圍內,而這會導致幻讀問題的產生
  • 記錄鎖-record Lock:鎖定行記錄,索的索引,索引失效,為表鎖
  • 臨鍵鎖-next-key Lock:record lock+gap lock 左開右閉(解決幻讀)

鎖的模式

  • select …. for update
  • 持有寫鎖,別的不可加讀鎖,也不可加寫鎖
  • select …. lock in share mode
  • 持有讀鎖,別的可以再加讀鎖,不可加寫鎖
  • 共享鎖-讀鎖-S鎖
  • 排他鎖-寫鎖-X鎖
  • 意向鎖:讀意向鎖+寫意向鎖
  • 自增鎖

需要的時候加上,並不是馬上釋放,等事務提交才釋放,兩階段鎖協議

3、全域性鎖——全庫邏輯備份

4、死鎖

  • 兩個或多個事務在同一資源上相互佔用,並請求加鎖時,造成相互等待,無限阻塞
  • innodb回滾擁有最少排他行級鎖的事務
  • 設定鎖等待超時時間

樂觀鎖與悲觀鎖

  • 悲觀鎖用資料庫自帶鎖機制——寫多
  • 樂觀鎖用version版本機制或CAS演演算法——讀多寫少,很少發生衝突情況
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章