4、本地日志存储:存储介质选型(eMMC/SD卡/NAND Flash)、日志文件轮转策略(大小/时间)、日志压缩与加密
大家好,我是你们的讲师。今天我们聊一个很实在的话题——日志到底存哪儿?怎么存?
很多刚入行的朋友觉得,日志嘛,随便找个地方写进去就行了。嗯,我当年也这么想。直到有一次,一台TBOX在高速上跑了三天,日志把Flash写穿了,整台设备直接变砖。从那以后,我对存储选型就格外上心。
4.1 存储介质选型:eMMC vs SD卡 vs NAND Flash
说白了,这三种介质各有各的脾气。我给大家拆开讲讲。
| 特性 | eMMC | SD卡 | NAND Flash |
|---|---|---|---|
| 接口 | 内置控制器,MMC协议 | SPI/SDIO | 裸NAND,需FTL |
| 寿命 | 高(带磨损均衡) | 中等 | 低(需软件管理) |
| 成本 | 中等 | 低 | 最低 |
| 可靠性 | 高 | 中等(易松动) | 低(坏块问题) |
| 典型场景 | 车规级TBOX | 后装/调试 | 低端设备 |
eMMC 是我个人最推荐的车规级方案。为什么?因为它自带控制器,帮你做了磨损均衡和坏块管理。我在项目中遇到过一台设备,用了三年,eMMC的寿命还剩80%以上。你想想看,这对TBOX这种7x24小时运行的设备来说,多重要。
SD卡 呢?便宜,方便。但我劝你,别用在量产车上。我曾经见过一台设备,SD卡在振动环境下接触不良,日志写到一半就断了。嗯,调试阶段用用还行,量产还是算了。
NAND Flash 是最便宜的,但也是最坑的。裸NAND没有FTL(Flash Translation Layer),你得自己处理坏块、擦写均衡。我刚开始做嵌入式时,觉得这有什么难的?结果写了一个月,发现坏块率飙升,数据全乱套了。后来我学乖了——除非成本压到极致,否则别碰裸NAND。
我的建议: 车规级TBOX首选eMMC,容量建议8GB起步。SD卡只用于调试或后装。NAND Flash?除非你团队有存储专家,否则别碰。
4.2 日志文件轮转策略:大小 vs 时间
日志不能无限写下去,对吧?你想想看,一个TBOX跑一年,日志能有多大?所以轮转策略是关键。
常见的策略有两种:按大小轮转,按时间轮转。我一般两种结合着用。
4.2.1 按大小轮转
这个逻辑很简单:每个日志文件设定一个上限,比如10MB。写满了就关掉,新建一个。我习惯保留最近10个文件,也就是总共100MB的日志空间。
// 伪代码示例:按大小轮转
#define LOG_FILE_MAX_SIZE (10 * 1024 * 1024) // 10MB
#define LOG_FILE_MAX_COUNT 10
void log_rotate_by_size() {
if (current_file_size >= LOG_FILE_MAX_SIZE) {
close_current_log();
// 删除最旧的文件
if (file_count >= LOG_FILE_MAX_COUNT) {
delete_oldest_log();
}
create_new_log();
}
}
这里有个坑:如果你只按大小轮转,万一某天日志量特别大,10个文件可能一天就写满了。所以我会加一个时间维度的保护。
4.2.2 按时间轮转
按时间轮转更直观:每天一个日志文件,或者每小时一个。我一般用每天一个,文件名带日期,比如 log_2024-01-15.txt。
// 伪代码示例:按时间轮转
void log_rotate_by_time() {
char new_filename[64];
get_current_date_string(new_filename); // 例如 "log_2024-01-15.txt"
if (strcmp(current_filename, new_filename) != 0) {
close_current_log();
// 删除超过30天的旧日志
delete_logs_older_than(30);
open_new_log(new_filename);
}
}
我个人习惯保留30天的日志。为什么是30天?因为大多数故障排查,一个月内的数据就够了。再老的日志,说实话,查到的概率很低。
避坑指南: 我曾经遇到过一个问题——按时间轮转时,如果设备断电了,当天的日志可能没写完。后来我加了一个机制:每次开机时检查上次日志文件的时间戳,如果跨天了就自动轮转。嗯,这个小细节救了我好几次。
4.3 日志压缩与加密
日志存下来了,但有两个问题:一是占空间,二是怕泄露。压缩和加密就是干这个的。
4.3.1 日志压缩
压缩算法我推荐两个:gzip 和 lz4。
- gzip:压缩率高,但慢。适合对空间敏感的场景。
- lz4:压缩率一般,但极快。适合实时性要求高的场景。
我一般这么选:如果日志是后台批量上传的,用gzip。如果日志需要实时查看,用lz4。你想想看,TBOX的CPU资源有限,压缩太狠会影响其他任务。
// 伪代码示例:使用lz4压缩日志
#include <lz4.h>
void compress_log(const char* input, size_t input_size) {
char* compressed = malloc(LZ4_compressBound(input_size));
int compressed_size = LZ4_compress_default(input, compressed, input_size, LZ4_compressBound(input_size));
// 将压缩后的数据写入文件
write_to_file(compressed, compressed_size);
free(compressed);
}
注意: 压缩后的日志文件,记得在文件名上加个后缀,比如 .gz 或 .lz4。不然云端解析时,你都不知道该用什么解压。我当年就犯过这个错,上传了一堆无后缀的二进制文件,解析时一脸懵。
4.3.2 日志加密
加密是必须的。TBOX日志里可能包含车辆位置、用户行为等敏感信息。我推荐用 AES-256-GCM,既加密又带认证,防止篡改。
// 伪代码示例:AES-256-GCM加密日志
#include <openssl/evp.h>
void encrypt_log(const char* plaintext, size_t len, unsigned char* key, unsigned char* iv) {
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv);
unsigned char ciphertext[1024];
int outlen;
EVP_EncryptUpdate(ctx, ciphertext, &outlen, plaintext, len);
unsigned char tag[16];
EVP_EncryptFinal_ex(ctx, ciphertext + outlen, &outlen);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag);
// 存储 ciphertext + tag
EVP_CIPHER_CTX_free(ctx);
}
密钥怎么管理?我习惯把密钥烧录在TBOX的SE(安全芯片)里,或者用HSM(硬件安全模块)。千万别把密钥硬编码在代码里——我见过有人这么干,结果固件被反编译,密钥直接暴露。嗯,那场面,挺尴尬的。
总结一下: 压缩和加密可以组合使用。我一般先压缩,再加密。这样既节省空间,又保证安全。顺序别搞反了——先加密再压缩,压缩率会大打折扣。
好了,这一节的内容就到这里。存储选型、轮转策略、压缩加密,这三块是本地日志存储的核心。下一节我们聊聊云端同步的协议和机制。有什么问题,欢迎在评论区留言。