14 KiB
14 KiB
Polling 架構分析
概述
Polling 是 RAIDGuard X 用來定期檢測 RAID 控制器狀態的核心機制,負責:
- 取得控制器狀態
- 取得事件日誌
- 檢測連線狀態
- 發送 SNMP Trap
架構流程
┌─────────────────────────────────────────────────────────────────────┐
│ Polling 架構 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ java.util.Timer (tmPolling) │ │
│ │ ┌─────────────┬─────────────┬─────────────┬─────────────┐ │ │
│ │ │ wkPolling1 │ wkPolling2 │ wkEventPolling │wkEventPoll │ │ │
│ │ │ (12秒) │ (30秒) │ (18秒) │ forSNMP │ │ │
│ │ │ │ │ │ (22秒) │ │ │
│ │ └─────────────┴─────────────┴─────────────┴─────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────┼────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Polling() │ │ GetEvent() │ │EventPollFor │ │
│ │ 狀態輪詢 │ │ 事件取得 │ │ SNMP() │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ PageRefresh() │ │IssueCmdAgent2│ │IssueCmdAgent2│ │
│ │ 取得頁面資料 │ │ 發送命令 │ │ 發送命令 │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ In-Band API (Port 8922) │ │
│ │ - 0x01 GET_PAGE │ │
│ │ - 0xFF/0x02 Get InBand Event │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ RAID Controller │ │
│ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
Polling 任務
Timer 排程
// frmMain.java - 1460-1477
java.util.Timer tmPolling = new java.util.Timer();
// 主動式輪詢 (線上狀態)
tmPolling.schedule(new wkPolling1(this), 5000, 12001); // 每 12 秒
tmPolling.schedule(new wkPolling2(this), 5000, 3001); // 每 3 秒
// 被動式輪詢 (離線狀態 - 檢查是否重新上線)
tmPolling.schedule(new wkStatusRefresh(this), 5000, 1004); // 每 1 秒
// 事件輪詢
tmPolling.schedule(new wkEventPolling(this), 5000, 18003); // 每 18 秒
tmPolling.schedule(new wkEventPollingForSNMP(this), 5000, 22002); // 每 22 秒
| 任務 | 週期 | 說明 |
|---|---|---|
| wkPolling1 | 12 秒 | 取得控制器頁面資料 |
| wkPolling2 | 3 秒 | 取得控制器頁面資料 + GC |
| wkStatusRefresh | 1 秒 | 狀態刷新 |
| wkEventPolling | 18 秒 | 取得事件日誌 |
| wkEventPollingForSNMP | 22 秒 | 取得事件並發送 SNMP Trap |
Polling 類別
wkPolling1 (12秒)
class wkPolling1 extends java.util.TimerTask {
public void run() {
if (PageDataDB.Polling_Flag) {
fMainTmp.Polling(0x01); // iCareOffline = 0x01 (關心離線)
}
}
}
行為:
- 每 12 秒執行一次
- 只在 Polling_Flag = true 時執行
- 關心離線狀態 (iCareOffline = 0x01)
- 呼叫 Polling(0x01)
wkPolling2 (3秒)
class wkPolling2 extends java.util.TimerTask {
public void run() {
if (PageDataDB.Polling_Flag) {
fMainTmp.Polling(0x00); // iCareOffline = 0x00 (不關心離線)
}
Runtime.getRuntime().gc(); // 垃圾回收
}
}
行為:
- 每 3 秒執行一次
- 不關心離線狀態 (iCareOffline = 0x00)
- 呼叫 Polling(0x00)
- 執行 GC 垃圾回收
wkEventPolling (18秒)
class wkEventPolling extends java.util.TimerTask {
public void run() {
if (PageDataDB.Polling_Flag) {
fMainTmp.GetEvent(); // 取得事件
}
}
}
行為:
- 每 18 秒執行一次
- 呼叫 GetEvent() 取得事件日誌
- 更新 UI 事件列表
wkEventPollingForSNMP (22秒)
class wkEventPollingForSNMP extends java.util.TimerTask {
public void run() {
if (fMainTmp.jtHostList.getSelectedRow() >= 0) {
fMainTmp.EventPollingForSNMP(); // 取得事件並發送 SNMP
}
}
}
行為:
- 每 22 秒執行一次
- 需要有選中的控制器
- 呼叫 EventPollingForSNMP() 取得事件並發送 SNMP Trap
Polling() 方法
public void Polling(int iCareOffline) {
// 遍歷所有已註冊的控制器
for (int i = 0; i < jtHostList.getRowCount(); i++) {
sHostName = jtHostList.getValueAt(i, iHostList_Host).toString();
sIpAddr = jtHostList.getValueAt(i, iHostList_IP).toString();
sSN = jtHostList.getValueAt(i, iHostList_SN).toString();
if (!sSN.equals("")) {
// 檢查連線失敗次數
if (GetHostCmdFailCnt(sSN) > iMaxCommandFail) {
// 設定為離線狀態
SetHostStatus(sSN, 0x00);
jtHostList.setValueAt(imgRed, i, iHostList_Net);
jtHostList.setValueAt("Disconnected", i, iHostList_Status);
ClearEventLog(); // 清除事件日誌
}
// 根據參數決定行為
if (iCareOffline == 0x01) {
// 只關心線上的控制器
if (GetHostStatus(sSN) == 0x01) {
PageRefresh(sIpAddr, sSN, new Point(0, 0));
// 如果是 MAX Storage,刷新備援控制器
if (Check_MAX_Storage(sSN)) {
PageRefresh(sIpAddr, GetSlaveSerialNumber(sSN), new Point(0, 0));
}
}
} else {
// 刷新所有控制器
PageRefresh(sIpAddr, sSN, new Point(0, 0));
if (Check_MAX_Storage(sSN)) {
PageRefresh(sIpAddr, GetSlaveSerialNumber(sSN), new Point(0, 0));
}
}
}
}
}
iCareOffline 參數
| 值 | 行為 |
|---|---|
| 0x01 | 只刷新線上 (GetHostStatus == 0x01) 的控制器 |
| 0x00 | 刷新所有控制器 (不管離線與否) |
GetEvent() 方法
public void GetEvent() {
// 取得目前選中的控制器事件
GetEvent(sIpAddr, sSN);
}
// PageDataDB.GetEvent()
public static void GetEvent(String sIP, String sSN) {
// 避免重複執行
if (bIsGetEvent) return;
bIsGetEvent = true;
// 建立命令
CmdQueue cmdQueue = new CmdQueue();
cmdQueue.NewCommand(sIP, sSN, 1, 1, 3);
// 設定命令內容
bCmds[0] = 0xFF; // Phase Code
bCmds[1] = 0x00; // Request Code
bCmds[2] = 0x00; // Controller No
bCmds[3] = 0x02; // InBand Event Cmd Code: Get InBand Event
// 發送命令
issueCmdAgent2 ICA = new issueCmdAgent2();
ICA.IssusCmd(sIP, sSN, cmdQueue);
// 處理回應
ReplyTask(cmdQueue, iEventPollingCheckType);
bIsGetEvent = false;
}
EventPollingForSNMP() 方法
public void EventPollingForSNMP() {
// 遍歷所有控制器
for (int iHost = 0; iHost < iMaxHosts; iHost++) {
// 取得控制器資訊
sTmpHostName = jtHostList.getValueAt(iHost, iHostList_Host);
sTmpIP = jtHostList.getValueAt(iHost, iHostList_IP);
sTmpSN = jtHostList.getValueAt(iHost, iHostList_SN);
// 發送事件查詢命令 (OpCode 0xFF/0x02)
CmdQueue cmdQueue = new CmdQueue();
cmdQueue.NewCommand(sTmpIP, sTmpSN, 1, 70, 3);
bCmds[3] = 0x02; // Get InBand Event
// 發送並處理回應
issueCmdAgent2 ICA = new issueCmdAgent2();
ICA.IssusCmd(sTmpIP, sTmpSN, cmdQueue);
// 處理 SNMP 回應
ReplyTaskForSNMP(sTmpHostName, sTmpIP, sTmpSN, cmdQueue);
}
}
通訊協定
取得事件 (Get InBand Event)
Request:
Phase Code: 0xFF
Request Code: 0x00
Controller: 0x00
OpCode: 0x02 (Get InBand Event)
Response:
- 事件資料 (最多 70 bytes)
- 包含事件時間、事件 ID、事件描述
Polling 開關
// 選單功能 - Polling On/Off
public void jmPollingSwitch_actionPerformed(ActionEvent e) {
if (PageDataDB.Polling_Flag) {
PageDataDB.Polling_Flag = false;
jmPollingSwitch.setText("Polling Off");
} else {
PageDataDB.Polling_Flag = true;
jmPollingSwitch.setText("Polling On");
}
}
Polling 狀態判斷
主機狀態 (Host Status):
0x00 - 離線 (Offline)
0x01 - 線上 (Online)
0x10 - 更新中 (Updating)
連線失敗計數:
- GetHostCmdFailCnt(sSN) > iMaxCommandFail → 判定離線
- 每次命令失敗計數 +1
- 每次成功計數歸零
Code Flow
完整 Polling 流程
1. Timer 觸發
│
▼
2. 檢查 Polling_Flag
│ true → 繼續
│ false → 停止
▼
3. 選擇 Polling 任務
│
├── wkPolling1 ──► Polling(0x01) ──► PageRefresh() ──► In-Band API
│
├── wkPolling2 ──► Polling(0x00) ──► PageRefresh() ──► In-Band API
│
├── wkEventPolling ──► GetEvent() ──► In-Band API
│
└── wkEventPollingForSNMP ──► EventPollingForSNMP() ──► In-Band API ──► SNMP Trap
事件處理流程
控制器事件發生
│
▼
Polling 偵測到 (Get InBand Event)
│
▼
解析事件內容
│
├── 更新 UI Event List
│
├── 發送 SNMP Trap (EventPollingForSNMP)
│
└── 發送 Email Alert (如果啟用)
關鍵變數
// PageDataDB.java
public static boolean Polling_Flag = true; // Polling 開關
public static boolean bIsGetEvent = false; // 防止重複取得事件
public static int iMaxCommandFail = 3; // 最大允許連線失敗次數
// frmMain.java
java.util.Timer tmPolling; // Polling 計時器
效能優化
- 分層輪詢: 不同任務使用不同週期
- 離線加速: 離線後改用較短週期 (3秒 vs 12秒)
- 事件鎖:
bIsGetEvent防止重複取得 - GC 排程: wkPolling2 執行垃圾回收
Rust 實作建議
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::time::{interval, Duration};
pub struct PollingManager {
polling_flag: Arc<AtomicBool>,
// 其他狀態...
}
impl PollingManager {
pub fn new() -> Self {
Self {
polling_flag: Arc::new(AtomicBool::new(true)),
}
}
pub fn start(&self) {
let flag = self.polling_flag.clone();
tokio::spawn(async move {
let mut timer = interval(Duration::from_secs(12));
loop {
timer.tick().await;
if flag.load(Ordering::SeqCst) {
self.polling().await;
}
}
});
}
pub fn stop(&self) {
self.polling_flag.store(false, Ordering::SeqCst);
}
async fn polling(&self) {
// 取得控制器狀態
// 刷新頁面資料
}
}