KHOÁ HỌC Phần 1 — Nền tảng Ghép hoàn chỉnh + chạy thử

Ghép hoàn chỉnh + chạy thử

💻 Code hoàn chỉnh

package main

import (
    "context"
    "fmt"
    "sync"
    "time"
)

// 1. INTERFACE
type Notifier interface {
    SendAlert(message string) error
}

// 2. THỰC THỂ NOTIFIER
type TelegramBot struct{ ChannelID string }

func (t TelegramBot) SendAlert(message string) error {
    fmt.Printf("🚨 [Telegram %s]: %s\n", t.ChannelID, message)
    return nil
}

// 3. STRUCT DỮ LIỆU
type Server struct {
    Name string
    IP   string
    IsUp bool
}

func (s *Server) SetStatus(status bool) { s.IsUp = status }

// 4. HÀM LOGIC CỐT LÕI
func checkServerHealth(ctx context.Context, wg *sync.WaitGroup, s *Server,
                      results chan<- string, notifier Notifier) {
    defer wg.Done()

    var processingTime time.Duration
    if s.Name == "DB-Server" {
        processingTime = 3 * time.Second
    } else {
        processingTime = 1 * time.Second
    }

    select {
    case <-time.After(processingTime):
        s.SetStatus(true)
        results <- fmt.Sprintf("✅ %s (%s) ĐANG HOẠT ĐỘNG.", s.Name, s.IP)

    case <-ctx.Done():
        s.SetStatus(false)
        err := fmt.Errorf("không thể kết nối %s: %w", s.Name, ctx.Err())
        results <- fmt.Sprintf("❌ LỖI: %s (%s) bị treo!", s.Name, s.IP)
        notifier.SendAlert(err.Error())
    }
}

// 5. MAIN
func main() {
    fmt.Println("=== KHỞI ĐỘNG HỆ THỐNG GIÁM SÁT SERVER ===")

    servers := []*Server{
        {Name: "Web-Server-01", IP: "192.168.1.10"},
        {Name: "Web-Server-02", IP: "192.168.1.11"},
        {Name: "DB-Server",     IP: "10.0.0.5"},
    }

    alertSystem := TelegramBot{ChannelID: "#sysadmin-alerts"}

    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    var wg sync.WaitGroup
    results := make(chan string, len(servers))

    for _, srv := range servers {
        wg.Add(1)
        go checkServerHealth(ctx, &wg, srv, results, alertSystem)
    }

    wg.Wait()
    close(results)

    fmt.Println("\n--- BÁO CÁO TRẠNG THÁI ---")
    for res := range results {
        fmt.Println(res)
    }
    fmt.Println("===========================================")
}

🚀 Cách chạy

# Bước 1: Tạo file main.go, dán code vào
# Bước 2: Khởi tạo module (chỉ lần đầu)
go mod init monitor

# Bước 3: Chạy
go run main.go

📤 Output mong đợi

=== KHỞI ĐỘNG HỆ THỐNG GIÁM SÁT SERVER ===
🚨 [Telegram #sysadmin-alerts]: không thể kết nối DB-Server: context deadline exceeded

--- BÁO CÁO TRẠNG THÁI ---
✅ Web-Server-01 (192.168.1.10) ĐANG HOẠT ĐỘNG.
✅ Web-Server-02 (192.168.1.11) ĐANG HOẠT ĐỘNG.
❌ LỖI: DB-Server (10.0.0.5) bị treo!
===========================================

Tổng thời gian: ~2 giây (đúng bằng timeout, chứ không phải 5 giây tuần tự).


🎓 Tổng kết tư duy 6 bước

1. DỮ LIỆU trước    → struct (khuôn mẫu)
2. HÀNH VI phụ      → interface + implementation
3. KHUNG main       → dựng "sân khấu"
4. LOGIC đơn giản   → chạy tuần tự, đảm bảo ĐÚNG
5. SONG SONG        → goroutine + WaitGroup + channel
6. TIMEOUT + LỖI    → context + select + error wrap

Bảng khái niệm Go trong bài này

Khái niệm Vai trò Ví dụ trong bài
struct Nhóm dữ liệu Server{Name, IP, IsUp}
interface Hợp đồng hành vi Notifier
* (con trỏ) Sửa được giá trị gốc *Server, &wg
func với receiver Method gắn với struct (s *Server) SetStatus
go Chạy ngầm go checkServerHealth(...)
sync.WaitGroup Đợi nhiều goroutine wg.Add/Done/Wait
chan Truyền dữ liệu giữa goroutine results chan<- string
select Chờ nhiều sự kiện case <-time.After / case <-ctx.Done
context Tín hiệu huỷ/timeout context.WithTimeout
defer Hoãn thực thi đến khi hàm thoát defer wg.Done(), defer cancel()
fmt.Errorf(%w) Bọc lỗi err := fmt.Errorf("... %w", ...)

🧭 7 nguyên tắc vàng khi học Go

  1. Viết dữ liệu (struct) trước, logic sau.
  2. Chạy tuần tự cho đúng, rồi mới làm song song.
  3. Interface = hợp đồng, đừng lạm dụng — chỉ dùng khi cần thay thế linh hoạt.
  4. Luôn defer những gì cần dọn dẹp: wg.Done(), cancel(), close(file).
  5. wg.Add() phải TRƯỚC go, không được Add trong goroutine.
  6. close(channel) sau wg.Wait(), không bao giờ trước.
  7. Mọi hàm mạng/IO phải nhận context.Context — thói quen chuyên nghiệp.

🏋️ Bài tập tự luyện

  1. Thêm SlackBot (cũng implement Notifier) — bằng chứng của đa hình.
  2. Cho phép cấu hình timeout qua biến (thay vì cứng 2s).
  3. Thêm retry: nếu server lỗi thì thử lại 3 lần trước khi cảnh báo.
  4. Ghi kết quả ra file thay vì in ra màn hình (dùng os.Create + defer file.Close()).

🎯 Bạn đã hoàn thành Phần 1 — nền tảng qua 1 dự án thực tế!

Bài này gói gọn ~80% khái niệm Go quan trọng nhất. Hiểu bài này = sẵn sàng đọc code Kubernetes, Docker, và các dự án Go lớn. Phần 2 (20% còn lại) đợi bạn ở mục lục — đọc tự do, không cần theo thứ tự, tra cứu khi cần.

← Timeout & xử lý lỗi — context, select