發佈日期:

計算兩個點之間的直線距離

使用math.hypot

math.hypot 是 Python 內置的數學模塊 math 中的函數。它接受兩個參數,分別代表兩點的 x 和 y 坐標差值,然後返回它們的歐幾里德距離(即直線距離)。

import math

x1, y1 = 1, 2
x2, y2 = 3, 4

distance = math.hypot(x2 - x1, y2 - y1)
print(distance)

使用np.sqrt

np.sqrt 是 NumPy 庫中的函數,用於計算給定數值的平方根。要使用 np.sqrt 計算兩點之間的距離,你需要首先計算兩點在 x 和 y 坐標軸上的差值的平方和,然後將它們相加,再使用 np.sqrt 對結果進行平方根運算。

import numpy as np

x1, y1 = 1, 2
x2, y2 = 3, 4

distance = np.sqrt((x2 - x1)**2 + (y2 - y1)**2)
print(distance)

發佈日期:

對輪廓的點做旋轉計算

使用角度的正弦和餘弦函數,將長方形的寬度和高度乘以正確的係數,以獲得旋轉後的角點座標。

下面為一個將一個長方形的四個點作45度旋轉的簡單範例:

import cv2
import numpy as np
import math

base_x = 100
base_y = 100
width = 100
height = 50
angle = 45

# 計算長方形中心點座標
center_x = base_x + (width // 2)
center_y = base_y + (height // 2)

# 將角度轉換為弧度
angle_rad = math.radians(angle)

# 計算長方形四個角的相對座標
cos_val = math.cos(angle_rad)
sin_val = math.sin(angle_rad)
x = width / 2
y = height / 2

# 計算四個角點座標
point1 = (int(center_x - x * cos_val + y * sin_val), int(center_y - x * sin_val - y * cos_val))
point2 = (int(center_x + x * cos_val + y * sin_val), int(center_y + x * sin_val - y * cos_val))
point3 = (int(center_x + x * cos_val - y * sin_val), int(center_y + x * sin_val + y * cos_val))
point4 = (int(center_x - x * cos_val - y * sin_val), int(center_y - x * sin_val + y * cos_val))

print("Point 1:", point1)
print("Point 2:", point2)
print("Point 3:", point3)
print("Point 4:", point4)

# 創建空白影像
image = np.zeros((500, 500, 3), dtype=np.uint8)

# 轉換座標為Numpy陣列
pts = np.array([point1, point2, point3, point4], dtype=np.int32)

# 繪製多邊形
cv2.polylines(image, [pts], True, (0, 255, 0), thickness=2)

# 顯示結果
cv2.imshow('Rectangle', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

顯示結果如下

發佈日期:

點到多邊形的最短距離

cv2.pointPolygonTest是OpenCV中的一個函數,用於計算點到多邊形的最短距離或點是否在多邊形內。

函數的語法如下:

_, intersection = cv2.pointPolygonTest(rect3, tuple(line1_midpoint), measureDist=False)
  • contour:多邊形的輪廓,可以是Numpy陣列或OpenCV的輪廓物件。
  • point:要計算距離的點,通常是一個(x, y)座標元組。
  • measureDist:指定是否計算點到多邊形的最短距離。如果為True,則返回距離值;如果為False,則返回一個整數值表示點的位置關係:正數表示點在多邊形內部、負數表示點在多邊形外部、0表示點在多邊形邊界上。

相關函數請參考: cv2.distanceTransform

另外要畫出多邊形可使用cv2.polylines,如以下範例

import cv2
import numpy as np

# 定義長方形的四個角點
rect1 = np.array([[100, 100], [300, 100], [300, 200], [100, 200]])

# 創建空白影像
image = np.zeros((500, 500, 3), dtype=np.uint8)

# 繪製長方形
cv2.polylines(image, [rect1], True, (0, 255, 0), thickness=2)

# 顯示結果
cv2.imshow('Image', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

發佈日期:

使用OpenCV將圖形轉正

旋轉圖片的方法

若是單純只是要把圖片做角度的旋轉,可以直接使用OpenCV 的 cv2.rotate() 函数。可按指定的方向旋轉圖像。如下:

import cv2

# 讀取圖像
image = cv2.imread('your_image.jpg')

# 將圖像旋轉90度
rotated_image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)

# 顯示旋轉後的圖像
cv2.imshow('Rotated Image', rotated_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

翻轉圖片的方法

cv2.flip() 是 OpenCV 中用於圖像翻轉的函數。它可以在水平、垂直或兩個方向上翻轉圖像。該函數接受三個參數:輸入圖像、翻轉的模式和輸出圖像的可選參數。

dst = cv2.flip(src, flipCode[, dst])

flipCode:翻轉的模式。可以是以下值之一:

  • 0:水平翻轉(沿著垂直軸翻轉)。
  • 1:垂直翻轉(沿著水平軸翻轉)。
  • -1:同時在水平和垂直方向上翻轉。

cv2.flip() 函數和 cv2.rotate() 函數都可以用於實現圖像的旋轉和翻轉,但它們的效果是不同的。

cv2.flip() 函數可以在水平和垂直方向上翻轉圖像,包括水平翻轉、垂直翻轉和同時在水平和垂直方向上翻轉。例如,使用 cv2.flip(image, -1) 可以同時在水平和垂直方向上翻轉圖像。

cv2.rotate() 函數用於對圖像進行旋轉。通過指定旋轉的角度和旋轉中心點,可以實現不同角度的旋轉。例如,使用 cv2.rotate(image, cv2.ROTATE_180_CLOCKWISE) 可以將圖像順時針旋轉180度。

雖然cv2.flip(image, -1)cv2.rotate(image, cv2.ROTATE_180_CLOCKWISE) 可以實現類似的效果,將圖像翻轉或旋轉180度,但它們的內部操作是不同的。 cv2.flip() 是基於軸對稱翻轉實現的,而 cv2.rotate() 是基於旋轉變換實現的。

針對形狀做角度校正

在許多圖像偵測的狀況,我們仍然會需要針對物件去做旋轉,首先我們一定是先用cv2.findContours取得輪廓,然後取得該物件輪廓的角度。這邊很重要的,就是要取得物件輪廓的角度,要取得角度,首先就要先去做輪廓擬合(請參考: OpenCV裡面形狀擬合的幾種方法)。

這邊我大推使用橢圓去做輪廓擬合並且取得軸心的角度,為什麼呢? 雖然cv2.minAreaRect() 可計算最小擬合矩形,但是這個矩形會非常容易受到輪廓的些微影響而改變擬合的方式,例如以下圖為例,就有可能有黑框、紅色框兩種的最小擬合矩形(會視當下輪廓取得的細微變化而改變)。也因此所取得的角度會非常多變,後續的辨識也會更困難

但是使用最小擬合橢圓,對於像上面這種左右、上下為對稱,但是長寬不同的形狀來說,非常適合使用最小擬合橢圓cv2.fitEllipse(),使用範例如下

import cv2

image = cv2.imread('./333_2023-06-08_19-57-30.jpg')
canny = cv2.Canny(image , 50, 250)
cnts, hier = cv2.findContours(canny , cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 執行最小橢圓擬合
ellipse = cv2.fitEllipse(cnts[0])
(center, axes, angle) = ellipse
cv2.ellipse(image, ellipse, (0, 255, 0), 2)
# 顯示結果
cv2.imshow('image', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

上面可以看到(center, axes, angle) = ellipse這邊的angle就是所偵測到的輪廓的角度,接著可以用cv2.warpAffine方法將圖像轉正

def rotatedDice(image, cnt):
    # 取得最小擬合橢圓並對圖像做翻轉
    ellipse = cv2.fitEllipse(cnt)
    (center, axes, angle) = ellipse
    angle = angle + 90
    rotation_matrix = cv2.getRotationMatrix2D(tuple(center), angle, 1)
    image = cv2.warpAffine(image, rotation_matrix,(image.shape[1], image.shape[0]))
    # 計算裁切位置
    mark = np.zeros_like(image)
    cv2.drawContours(mark, [cnt], 0, (255, 255, 255), -1)
    mark = cv2.warpAffine(mark, rotation_matrix,(mark.shape[1], mark.shape[0]))
    mark = cv2.cvtColor(mark, cv2.COLOR_RGB2GRAY)
    cnts, hier = cv2.findContours(mark, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    x, y, w, h = cv2.boundingRect(cnts[0])
    matting_result = image[y:y+h,x:x+w,:]
    return matting_result

從上面,我們使用cv2.warpAffine來做圖片角度的校正,warpAffine裡面有要輸入一個旋轉的矩陣的參數,在上面的範例,我們使用cv2.getRotationMatrix2D,這個參數是單純做形狀旋轉,但是在真實的世界當中,大部分3D的角度轉換也會帶有著深度的轉換,如下圖

這時候就會需要使用cv2.getAffineTransform來取得這個旋轉矩陣

import cv2
import numpy as np

# 定義三個點的坐標
point1 = (106, 92)
point2 = (28, 91)
point3 = (154, 33)

# 定義旋轉角度
rotation_angle = -45

# 創建一個空白圖像
image = np.zeros((500, 500), dtype=np.uint8)

# 在圖像上繪製三角形
cv2.drawContours(image, [np.array([point1, point2, point3])], 0, (255), thickness=2)

# 計算旋轉中心
center = np.mean([point1, point2, point3], axis=0)

# 構建旋轉矩陣
rotation_matrix = cv2.getRotationMatrix2D(tuple(center), rotation_angle, 1)

# 對整個圖像進行旋轉
rotated_image = cv2.warpAffine(image, rotation_matrix, (image.shape[1], image.shape[0]))

# 顯示旋轉後的圖像
cv2.imshow("Rotated Image", rotated_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
發佈日期:

限制ffmpeg初始連接的時間

ffmpeg中的analyzeduration和probesize

在FFmpeg中,-analyzeduration和-probesize是用於設置媒體分析的參數。

-analyzeduration參數用於指定分析媒體文件的持續時間。當你在FFmpeg中打開一個媒體文件時,它需要一些時間來分析文件的內容,以確定其格式、編解碼器和其他相關的信息。這個參數設置了分析的時間長度。較長的-analyzeduration值可能會導致更準確的分析結果,但同時也會增加打開文件的時間。預設值為5,000,000微秒(5秒)。

-probesize參數用於指定分析媒體文件時讀取的數據大小。當FFmpeg分析媒體文件時,它會從文件中讀取一些數據並進行分析。這個參數設置了從媒體文件中讀取的數據大小。較大的-probesize值可能會導致更準確的分析結果,但同時也會增加分析的時間和記憶體使用量。預設值為50,000字節。

前置metadata

播放器在網絡點播場景下去請求MP4 視頻數據,需要先獲取到文件的metadata,解析出該文件的編碼、幀率等信息後才能開始邊下邊播。如果MP4 的metadata 數據塊被編碼在文件尾部,這種情況會導致播放器只有下載完整個文件後才能成功解析並播放這個視頻。對於這種視頻,我們最好能夠在服務端將其重新編碼,將metadata 數據塊轉移到靠近文件頭部的位置,保證播放器在線請求時能較快播放。比如FFmpeg 的下列命令就可以支持這個操作:

ffmpeg -i bad.mp4 -movflags faststart good.mp4

控制讀取的數據量大小

在外部可以通過設置 probesize 和 analyzeduration 兩個參數來控制該函數讀取的數據量大小和分析時長為比較小的值來降低 avformat_find_stream_info 的耗時,從而優化播放器首屏秒開。但是,需要注意的是這兩個參數設置過小時,可能會造成預讀數據不足,無法解析出碼流信息,從而導致播放失敗、無音頻或無視頻的情況。所以,在服務端對視頻格式進行標準化轉碼,從而確定視頻格式,進而再去推算 avformat_find_stream_info 分析碼流信息所兼容的最小的 probesize 和analyzeduration,就能在保證播放成功率的情況下最大限度地區優化首屏秒開。

probesize 和 analyzeduration太短的可能影響


如果將-probesize和-analyzeduration設置得太短,可能會導致以下問題:

  • 不準確的媒體分析:probesize和analyzeduration參數用於指定媒體分析的數據大小和時間長度。如果這兩個值設置得太短,FFmpeg可能無法讀取足夠的數據或分析足夠長的時間,從而導致分析結果的不準確性。這可能會影響到媒體文件的正確解碼、格式識別和相關信息的獲取。
  • 遺漏關鍵信息:媒體文件中的關鍵信息通常在文件的早期部分或特定位置。如果probesize和analyzeduration設置得太短,FFmpeg可能無法讀取到這些關鍵信息,進而影響解碼、播放或處理過程的正確性和完整性。
  • 性能問題:probesize和analyzeduration參數的值也會影響處理媒體文件所需的時間和資源。如果值設置得太短,FFmpeg可能需要更頻繁地從媒體文件中讀取數據或進行分析,增加了I/O操作和CPU負載,進而導致性能下降。
發佈日期:

為影片產生會議紀錄及重點擷取

將影片轉為MP3

先照這篇的方式安裝FFMPEG,接著就可以使用ffmpeg將影片轉成mp3檔案

ffmpeg -i input.mp4 -vn -acodec libmp3lame output.mp3

在上述命令中,input.mp4是輸入的MP4文件路徑,output.mp3是輸出的MP3文件路徑。

從語音檔案使用AI提取文字

這個功能在WORD就有了,若是沒有WORD,GOOGLE文件也有相似的聽寫功能,以下為我使用Office內建聽寫功能的示範

先使用轉錄功能

接著選擇輸入語言為台灣國語,並上傳剛剛擷取出來的mp3檔案

選擇完檔案會開始上傳MP3並且擷取音檔內的文字,這也是為什麼一開始我會希望將mp4轉成mp3,因為含影像的檔案較大,純音檔較小,上傳較小的檔案這邊所花費的時間會少一點

當節錄文字完成後,選擇將文字加到檔案內,就會出現如下的語音謄錄文字

讓文字更易懂

使用工具: ChatGPT

一直到這邊所產生的文字,都很不容易讓人理解,因為所擷取出的文字很容易會有錯別字,例如: 【視障小孩】可能會被聽寫成【師丈小孩】,根本意義完全不同,讓人難以理解。

但是ChatGPT對於理解這樣的錯別字,比對上下文去猜出正確辭意的能力頗強,所以可以使用ChatGPT請他幫忙整理內容

例如上面的文字GTP所整理出的內容如下

接著再重複使用上面產生的內容,請GPT產生摘要、標題,我們只需要作內容審核、確認、修正即可,可以大幅節省人力唷!

另外,對CHATGPT所下的指令也會影響到產出,例如上面我使用【順成文章】的指令,所以最後面CHATGPT就自己唬爛了一些不相關的內容(甚麼不僅僅是個人問題之類的老師根本沒有講)。這時候就可以改使用【順過讓文字更好讀】這樣的指令,就比較不會產生不相關的內容。

建議可以多嘗試幾種不同的指令,直接針對他所整理過的不滿意的方向請她重新整理,直到CHATGPT給出較滿意的產出後,再自行做驗證/整理

發佈日期:

使用OBS做會議錄影

使用OBS把線上會議錄起來

若是線上會議,推薦使用OBS可以很方便的對所有類型的會議做錄影。

這邊是OBS的下載位置: https://obsproject.com/

OBS可以擷取視窗螢幕,只要是可以在桌面上執行的軟體,都可以從【+】 ->【 Window Capture】去獲取該程式的畫面並放置於OBS畫面上。使用這個方式錄影的優點是,我們可以在錄影的時候同時使用其他的軟體,並且不會被錄到除了你所選擇的應用程式以外的電腦上的操作,OBS只會持續的錄該程式的畫面。

例如下面這樣子就只會錄到所選擇的Chrome的視窗,我們在電腦上回Line訊息、開別的Chrome瀏覽網頁,都不會被錄進去

這樣的方式在Window上直接就會可以錄到畫面+聲音,但是若你的電腦是Mac,則需要另外做額外的設定,才能夠讓OBS取得電腦內部的聲音。這邊為教學文章: https://www.casper.tw/life/2021/06/13/mac-reacord-by-soundflower/

開始錄影

在上面設定完之後,按下右下的【開始錄製】並在會議結束時按下【結束錄製】,就可以在本機>影片找到錄好的影片,檔名會是錄影的時間

安裝FFMPEG

接著則要做影片的剪裁+合成,以下是一些簡單的影片操作指令

首先,下載別人已經Build好的ffmpeg: https://www.gyan.dev/ffmpeg/builds/packages/ffmpeg-5.1.2-full_build.7z

接著,把ffmpeg.exe丟到系統的路徑裡面,如C:\Windows\System32,或者將ffmpeg的路徑加到環境變數的Path裡面,讓系統能夠找的到ffmpeg的位置。

驗證方式: 開啟命令提示字元,輸入ffmpeg,如果能出現以下畫面,代表設定成功

做一些簡單的影片操作

擷取影片片段

ffmpeg -i input.mp4 -ss 00:12 -to 00:15:30 -c:v copy -c:a copy output.mp4

在上述命令中,input.mp4是輸入的視頻文件路徑,output.mp4是輸出的視頻文件路徑。 00:12表示起始時間,00:15:30表示結束時間。

刪除影片片段

ffmpeg -i input.mp4 -filter_complex "[0:v]trim=0:4,setpts=PTS-STARTPTS[v1]; [0:v]trim=8,setpts=PTS-STARTPTS[v2]; [v1][v2]concat=n=2:v=1:a=0" -c:v libx264 -preset veryfast -crf 18 output.mp4

在上述命令中,input.mp4是輸入的視頻文件路徑,output.mp4是輸出的視頻文件路徑。 0:4表示要刪除的起始時間範圍,8表示要刪除的結束時間。

合併多個影片檔案

  • 創建一個文本文件,例如input.txt,其中包含要合併的視頻文件的列表。每行包含一個視頻文件的路徑,例如:
file 'video1.mp4'
file 'video2.mp4'
file 'video3.mp4'
  • 將文件內所寫的影片使用ffmpeg合併為一個影片
ffmpeg -f concat -safe 0 -i input.txt -c copy output.mp4

為影片加上浮水印

ffmpeg -i input.mp4 -i watermark.png -filter_complex "overlay=W-w-10:H-h-10" -c:a copy output.mp4

在上述命令中,input.mp4是輸入的視頻文件路徑,watermark.png是水印圖片文件的路徑,output.mp4是輸出的帶有水印的視頻文件路徑。

-filter_complex “overlay=W-w-10:H-h-10″:使用filter_complex選項應用複雜的過濾器。 overlay過濾器用於將水印圖像疊加在視頻上。 W-w-10表示水印圖像的水平位置,H-h-10表示水印圖像的垂直位置。在這個示例中,水印位於視頻的右下角,並與邊緣保持10像素的間距。

發佈日期:

Ubuntu 18.04的apt update更新失敗

錯誤訊息

W: Failed to fetch http://repo.mysql.com/apt/ubuntu/dists/bionic/InRelease The following signatures couldn’t be verified because the public key is not available: NO_PUBKEY 467B942D3A79BD29 W: Some index files failed to download. They have been ignored, or old ones used instead.

問題原因

這個錯誤訊息是說您的系統在嘗試從 http://repo.mysql.com/apt/ubuntu/dists/bionic/ 的軟體庫中下載軟體索引時發生了問題。這可能是由於網路連線中斷或是軟體庫網址變更所導致。

訊息中提到的 “NO_PUBKEY 467B942D3A79BD29” 表示您的系統沒有此軟體庫的公鑰,導致無法驗證下載的軟體索引是否是正確的。

解決方案

要解決這個問題,您可以試著更新系統的軟體庫並且安裝相關的公鑰。在終端機中執行以下指令:

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 467B942D3A79BD29
sudo apt-get update

這個指令會從 Ubuntu 的公鑰伺服器中下載指定的公鑰,並且更新系統的軟體庫。完成後,您可以再次嘗試安裝所需的軟體,看看問題是否已經解決了。

發佈日期:

wordpress 永久連結設定失效

故障的可能原因

  1. .htaccess 檔案不正常:進入 WordPress 的管理後台,點擊「設定」→「連結」,然後按下「儲存變更」。這樣 WordPress 會重新生成 .htaccess 檔案。如果這個步驟沒有解決問題,可以手動在 WordPress 安裝目錄中找到 .htaccess 檔案,並且檢查它是否存在。
  2. 確認 WordPress 是否在正確的網址上運行:如果您的 WordPress 安裝在子目錄或子網域下,請確認永久連結設定正確設定到子目錄下這一點。
  3. 伺服器是否支援永久連結:如果您的伺服器不支援永久連結,則 WordPress 將無法使用它們。(設定方式請見下一篇)
  4. 確認您的主題是否有任何問題:某些 WordPress 主題可能會干擾永久連結的正常運作。嘗試暫時更換到 WordPress 默認主題並再次測試永久連結。

在Apache2支持永久連結

要啟用 Apache2 的 mod_rewrite 模組,您需要將以下內容添加到 Apache2 的設定檔案中:

LoadModule rewrite_module modules/mod_rewrite.so

這個設定通常會加入到 mods-enabled 目錄中的一個設定檔案中。例如,您可以創建一個名為 rewrite.load 的檔案,並將上面的設定添加到這個檔案中。請按照以下步驟進行操作:

  1. 進入 mods-enabled 目錄:
cd /etc/apache2/mods-available
  1. 創建一個名為 rewrite.load 的檔案:
sudo touch rewrite.load
  1. 打開這個檔案並添加以下內容:
LoadModule rewrite_module modules/mod_rewrite.so
  1. 儲存並關閉檔案。
  2. 重新啟動 Apache2 伺服器,以使更改生效:
sudo service apache2 restart

啟用rewrite功能

若您的 /etc/apache2/mods-available 目錄下已經有了 rewrite.load 設定檔案,您可以透過建立符號連結方式來啟用 mod_rewrite 模組。

請按照以下步驟:

  1. 進入到 /etc/apache2/mods-enabled 目錄:
cd /etc/apache2/mods-enabled
  1. 建立 rewrite.load 的符號連結:
sudo ln -s /etc/apache2/mods-available/rewrite.load
  1. 重新啟動 Apache2 伺服器,以使更改生效:
sudo service apache2 restart

執行完上述步驟後,Apache2 的 mod_rewrite 模組即會啟用。您可以再次檢查 /etc/apache2/mods-enabled 目錄,確保 rewrite.load 的符號連結已經建立。若您需要停用 mod_rewrite,則可以刪除這個符號連結即可。

發佈日期:

在K8S內ksoftirqd耗費大量CPU

問題描述

我們在K8S內架設串流伺服器,在做loadtest時發現,當流量變高之後,K8S用來管理線程的cadvisor所調用的ksoftirqd會占掉非常大的CPU使用率,並導致整個worker node變得緩慢。

相關的問題說明請見: Debugging network stalls on Kubernetes

形成原因

這是因為當使用 Kubernetes 的 NodePort 來公開一個服務時,它將在每個工作節點上開放一個端口,以便外部可以連接到該端口,並將流量轉發到服務的後端 Pod 上。當流量高峰期間,可能會導致節點的 CPU 負載增加,並導致大量的 IRQ 請求,這是因為每個數據包都會觸發一次 IRQ 請求。

IRQ是甚麼

IRQ 是中斷請求的簡寫,是一種處理設備 I/O 操作的機制。當設備有 I/O 操作時,它會觸發一個中斷請求,通知操作系統或應用程序需要處理這個 I/O 操作。在網絡通信中,每個數據包都會觸發一次 IRQ 請求,因此當流量高峰期間,大量的 IRQ 請求可能會導致節點的 CPU 負載增加,從而影響應用程序的性能。中斷請求通常由外部設備發出,例如鍵盤、鼠標、網卡、磁盤控制器等,這些設備需要與 CPU 進行通信,以便進行數據傳輸、讀寫、處理等操作。

IRQ 的實現通常涉及到硬件和軟件兩個方面。在硬件方面,通常需要在主板上預留一些專門的中斷請求線(IRQ lines),用於連接各種外設和 CPU,以便它們之間進行數據傳輸和通信。一般來說,一個計算機系統會有多個 IRQ lines,每個 IRQ line 對應一個外設或者一組相關的外設。

在軟件方面,操作系統需要通過中斷控制器來管理各個 IRQ lines,以便在接收到外設發出的中斷請求時,及時地響應並處理。一般來說,中斷控制器會將中斷請求轉發給 CPU,CPU 再執行相應的中斷處理程序來處理中斷請求。

IRQ是計算機硬件的設計決定。當網絡適配器(NIC)接收到數據包時,它會發送一個中斷請求(IRQ)給處理器,通知處理器需要處理這個數據包。處理器收到 IRQ 後,會停止當前的任務,並開始處理中斷請求。處理完中斷請求後,處理器會返回到之前的任務繼續執行。.

網絡適配器是什麼

網絡適配器,也稱為網絡接口卡(Network Interface Card,NIC),是計算機中用於實現網絡通信的硬件設備之一。它通常安裝在計算機主板上,負責接收和發送網絡數據包。網絡適配器可以連接到不同類型的網絡,例如以太網、Wi-Fi、藍牙等,以實現不同的網絡通信方式。

網絡適配器在計算機中的作用是將數據轉換為網絡能夠識別和傳輸的格式,例如將數據包封裝成以太網幀,以便在以太網上進行傳輸。同時,網絡適配器也負責監控網絡上的數據流量,並在需要時觸發 IRQ 請求,通知處理器需要處理數據包。

網絡適配器的性能對網絡通信的效率和吞吐量有很大影響。在高性能計算環境中,通常會選擇高速的網絡適配器,例如 10GbE、40GbE、100GbE 等,以滿足大規模數據傳輸的需求。

為什麼在K8S的IQR會比一般Linux主機更明顯

當一般主機傳輸大量流時,也會產生網絡適配器的 IRQ 請求。但是,這種情況下的 IRQ 請求通常可以被系統有效地處理,並不會對 CPU 的負載產生太大的影響。這是因為一般主機的網絡適配器通常採用了更為先進的中斷卸載技術,如 RSS(Receive Side Scaling)、RPS(Receive Packet Steering)等,可以將 IRQ 請求在多個 CPU 核心上進行分配,從而有效地提高系統的網絡吞吐量。

在 Kubernetes 中,由於每個節點上的 Pod 分佈不確定,因此無法像一般主機那樣進行有效的中斷卸載。此外,由於網絡流量是從 NodePort 直接傳輸到 Pod,中間可能還需要經過多個網絡層,從而增加了系統的網絡負載。因此,在高負載情況下,使用 NodePort 公開服務可能會導致大量 IRQ 請求,從而增加 CPU 的負載。為了解決這個問題,可以考慮使用負載均衡器(ingress)等技術,將流量轉發到多個節點上,以降低單個節點的負載。

解決方法

由於我們所架設的伺服器為串流伺服器,原本在一般的Linux主機裡面,我們會使用NGINX去做反向代理,讓主機知道現在的連接目標為串流,如下面這個nginx.conf的範例

http {
    include       mime.types;
    default_type  application/octet-stream; #設定所傳輸的格式為串流
    sendfile        on;
}

這樣子當有人透過http去拉取串流時,NIC就會採取長連接的方式去處理這條串流,但是,因為現在我們將串流伺服器移到K8S裡面去,連線請求處理的地方會在worker node所在的NIC上去處理,而不是在串流服務的POD去處理的。因此,即便我們在POD裡面設定連線方式為串流,在worker node上面也是不知道這件事情的。

所以我們會必須要在K8S的ingress裡面去做進一步的設定,第一步要先了解自己的K8S的ingress是用甚麼實作的,例如像我們的K8S是用kubernetes/ingress-nginx實作的

設定介紹: NGINX Configuration

這邊會有三個方式去實作

  1. ConfigMap:使用 Configmap 在 NGINX 中設置全局配置。
  2. Annotations:如果您想要特定 Ingress 規則的特定配置,請使用它。
  3. 自定義模板:當需要更具體的設置時,如open_file_cache,調整監聽選項,rcvbuf或者當無法通過 ConfigMap 更改配置時。

以下是一個使用 types 設置 MIME 類型的示例:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    nginx.ingress.kubernetes.io/server-snippet: |
      types {
        text/html                             html htm shtml;
        text/css                              css;
        text/xml                              xml;
        image/gif                             gif;
        image/jpeg                            jpeg jpg;
        application/javascript               js;
        application/atom+xml                  atom;
        application/rss+xml                   rss;
        text/mathml                           mml;
        text/plain                            txt;
        text/vnd.sun.j2me.app-descriptor      jad;
        text/vnd.wap.wml                      wml;
        text/x-component                      htc;
        image/png                             png;
        image/tiff                            tif tiff;
        image/vnd.wap.wbmp                    wbmp;
        image/x-icon                          ico;
        image/x-jng                           jng;
        image/x-ms-bmp                        bmp;
        image/svg+xml                         svg svgz;
        image/webp                            webp;
        application/font-woff                 woff;
        application/font-woff2                woff2;
        application/vnd.ms-fontobject         eot;
        application/x-font-ttf                ttc ttf;
        application/x-httpd-php               php;
        application/x-shockwave-flash         swf;
        application/json                      json;
        application/octet-stream              flv; #這邊可以設定MINE TYPE
      }
spec:
  rules:
    - host: example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-service
                port:
                  name: http

這樣子就可以正確的於ingress處理串流的連接了!

相關官方說明: Annotations – Ingress-Nginx ControllerCustom Headers – Ingress-Nginx Controller