Perl Net::Server日志缓冲4096字符截断、多进程日志穿插问题求助
问题根因
- 你之前在
post_configure里设置local $/ = 8192不解决问题:$/是Perl的输入记录分隔符,和输出缓冲、写入原子性没有关联。 - 多进程同时追加写同一个文件时,内核仅保证长度小于
PIPE_BUF(绝大多数Linux/Unix系统默认值为4096字节)的单次写入操作是原子的,不会被其他进程的写入打断;如果单次写入长度超过4096,内核会将写入拆分为多次执行,中间就会插入其他进程的日志内容,这就是你遇到问题的核心原因。 - 你已经开启的
autoflush(1)仅能保证Perl层不缓存输出、直接调用系统write接口,但无法解决内核层面的写入拆分问题。
解决方案:加跨进程文件锁保证单条日志原子写入
通过对日志文件加内核级排他锁,保证单条日志完整写入完成前,其他进程无法写入日志文件,修改代码如下:
- 先在脚本头部引入fcntl锁相关常量:
use Fcntl qw(:flock SEEK_END);
- 修改
write_to_log_hook方法,写入前加锁、写完释放锁:
sub write_to_log_hook { my $self = shift; my $prop = $self->{server}; my $level = shift; local $_ = shift || ''; chomp; s/([^\n\ -\~])/sprintf("%%%02X",ord($1))/eg; # 提前拼接换行符,保证单次写入完整日志内容 my $log_line = $_ . "\n"; if( $prop->{log_file} ){ # 加排他锁,阻塞直到拿到锁权限 flock(_SERVER_LOG, LOCK_EX) or die "无法锁定日志文件: $!"; # 写入前强制定位到文件末尾,避免其他进程已经修改了文件长度导致位置偏移 seek(_SERVER_LOG, 0, SEEK_END) or die "无法定位到日志文件末尾: $!"; # 写入完整日志 print _SERVER_LOG $log_line; # 强制刷出内核缓冲 _SERVER_LOG->flush(); # 释放锁,允许其他进程写入 flock(_SERVER_LOG, LOCK_UN) or die "无法释放日志文件锁: $!"; } }
补充说明
如果你的服务并发量很高,担心锁带来性能损耗,也可以选择封装了原子写入逻辑的第三方日志组件替代自研日志逻辑,不需要自己实现锁操作。
内容的提问来源于stack exchange,提问作者Joseph Batson
相关产品推荐
相关产品推荐

