15. tháng 5 2025
Gần đây, tôi gặp một yêu cầu lưu trữ lượng lớn nhật ký từ các thiết bị trực tuyến. Nhật ký được tải lên qua MQTT, và phía máy chủ sử dụng golang để đăng ký các chủ đề MQTT nhằm thu thập thông tin này trước khi lưu vào MySQL. Ban đầu, vì lười biếng, tôi đã sử dụng InfluxDB 2 để lưu trữ dữ liệu thời gian liên quan đến hệ thống này. Tuy nhiên, sau khi hoàn thành một dự án, tôi nhận ra rằng việc sử dụng MySQL hiệu quả hơn nhiều so với InfluxDB 2.0. Cụ thể, cú pháp truy vấn của InfluxDB 2.0 thực sự không quen thuộc, cộng với việc tôi không có kinh nghiệm về vận hành, dẫn đến việc phải mất thêm thời gian tìm hiểu cách quản lý nó. Điều đó thật lãng phí! Vì vậy, tôi quyết định sử dụng công nghệ mà mình thạo nhất, đảm bảo tính ổn định và đáng tin cậy.
Theo đánh giá của tôi, nếu lưu trữ nhật ký theo tháng, mỗi bảng sẽ chứa ít hơn 10 triệu bản ghi. Do đó, không cần thiết phải phân chia dữ liệu theo ngày hoặc tuần.
Hôm nay, tôi đã giải quyết được vấn đề này một cách thành công, điều này thực sự là một bước đột phá trong việc lưu trữ nhật ký thiết bị.
Mỗi ngày, tôi đều học được nhiều câu lệnh SQL mới mẻ và mạnh mẽ từ AI 😅
1CREATE TABLE IF NOT EXISTS log_202503 LIKE log;
Đây là lần đầu tiên tôi nhìn thấy câu lệnh SQL như vậy.
CREATE TABLE IF NOT EXISTS %s LIKE log
hoạt động rất tốt vì ban đầu tôi sử dụng bảng log đơn giản không phân bảng. Cách này giúp dễ dàng tạo ra các bảng mới dựa trên cấu trúc bảng gốc.
1type Log struct {
2 ID uint `gorm:"primaryKey"`
3 CreatedAt time.Time
4 UpdatedAt time.Time
5}
6
7// TableName trả về tên bảng cho nhật ký hiện tại
8// Định dạng sẽ là log_YYYYMM, ví dụ: log_202401
9func (Log) TableName() string {
10 // Sử dụng thời gian hiện tại để xác định tên bảng
11 return GetLogTableName(time.Now())
12}
13
14// GetLogTableName trả về tên bảng nhật ký cho một thời điểm cụ thể
15func GetLogTableName(t time.Time) string {
16 return fmt.Sprintf("log_%d%02d", t.Year(), int(t.Month()))
17}
18
19// EnsureLogTableExists đảm bảo rằng bảng cho thời điểm cụ thể tồn tại
20func EnsureLogTableExists(t time.Time) error {
21 tableName := GetLogTableName(t)
22 // Kiểm tra xem bảng có tồn tại không
23 var count int64
24 DB.Raw("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?", tableName).Count(&count)
25 if count == 0 {
26 // Bảng không tồn tại, tạo nó bằng cách sao chép cấu trúc từ bảng log cơ sở
27 err := DB.Exec(fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s LIKE log", tableName)).Error
28 if err != nil {
29 return err
30 }
31 }
32 return nil
33}
34
35// EnsureCurrentMonthTableExists đảm bảo rằng bảng cho tháng hiện tại tồn tại
36func EnsureCurrentMonthTableExists() error {
37 return EnsureLogTableExists(time.Now())
38}
Chèn dữ liệu:
1// Lưu log vào cơ sở dữ liệu, cần lưu vào bảng log của tháng hiện tại
2DB.Table(GetLogTableName(time.Now())).Create(log)
Phía frontend khá đơn giản. Chỉ cần thêm một thành phần chọn năm-tháng, gửi yêu cầu lọc về phía backend là đủ.
Tôi đã hoàn thành việc phân bảng tự động, khóa thiết bị từ xa qua MQTT, cũng như tích hợp cả giao diện người dùng. Ngoài ra, tôi còn nghiên cứu cách truy vấn thông tin thiết bị trực tuyến từ EMQX. Đây là một ngày làm việc đầy hiệu quả! ✌️ Giờ thì tôi có thể nghỉ ngơi một chút rồi, cảm giác như não bộ đã ngừng hoạt động... 🥱
Tìm hiểu cách sử dụng API EMQX để kiểm tra trạng thái trực tuyến của thiết bị MQTT.