3.nginx架构-日志

一:日志
日志对于运行环境中系统的监控和问题定位是至关重要的,在系统设计、开发和实现的过程中必须时刻注意着log的输出,这将会对于日后的系统监控和异常分析起至关重要的作用。在系统日志实现上应该注意哪些问题呢?如何做到不滥用日志、减少大量无用信息,让日志记录足够精简明了?
1)系统的哪些运行信息需要进行日志记录?
   1、功能模块的启动和结束(完整的系统由多个功能模块组成,每个模块负责不同的功能,因此需要对模块的启动和结束进行监控。是否在需要的时机正常加载该模块?又是否在退出结束的时候正常完成结束操作,正常退出?)
   2、用户的登录和退出(哪位用户在什么时间通过什么IP登录或退出了系统)
   3、系统的关键性操作(数据库链接信息、网络通信的成功与失败等)
   4、系统运行期间的异常信息(NPE、OOM以及其他的超时、转换异常等)
   5、关键性方法的进入和退出(一些重要业务处理的方法,在进入和结束的时候需要有日志信息进行输出)

2):什么样的日志格式有助于开发者进行明确的分析?
  日志信息要求必须精简,过多的无用信息不但对系统分析起不到什么作用,反而会增加系统的运行压力、消耗系统的运行资源。这里有个日志模板,可供参考。
   时间-[线程名][日志等级]-日志输出位置(全类名,可以精确到方法名):日志信息
  2013-09-04 10:49:20.296-[Thread-initRedis21504][INFO]-com.shanghai.LoginController.initLogInfo:LingMing[User] is logining
  日志信息的内容可以根据不同的情况进行设计,但是前面的时间到日志输出位置必须要保证完整性,这样才有利于日志的分析。
  
3):如何对不同的日志信息进行等级划分?
  日志等级通常分为四种:DEBUG、INFO、WARN、ERROR
  DEBUG:系统调试信息,通常用于开发过程中对系统运行情况的监控,在实际运行环境中不进行输出。
  INFO:系统运行的关键性信息,通常用于对系统运行情况的监控。
  WARN:告警信息,系统存在潜在的问题,有可能引起运行异常,但此时并未产生异常。
  ERROR:系统错误信息,需要进行及时处理和优化。
 这里列出来了各种等级的日志信息,在开发过程中哪些信息需要设置为哪种等级有赖于开发者的自己判断,这里只是给个建议。

二:日志输出信息
1)printf()函数不加\n无法及时输出的解释
\r:回车符,定位到本行开头
\n:换行符,定位到下一行
在老式的机械打字机,如果你想在下一行最左端开始继续打印,需要做两个动作,先把机头重新推回最左侧,这就是回车,但是他还没有换行,然后再按一下换行键,这样才到下一行。但现在\n具备了回车换行的功能,这样的目的是为节省一个字符。
我们发现printf末尾不加\n就无法及时的将信息显示到屏幕 ,这是因为需要输出的数据不直接显示到终端,而是首先缓存到某个地方,当遇到行刷新标识或者该缓存已满的情况下,才会把缓存的数据显示到终端设备;
ANSI C中定义\n是行刷新标记,所以,printf函数没有带\n是不会自动刷新输出流,直至行缓存被填满才显示到屏幕上;
所以大家用printf的时候,注意末尾要用\n;
或者:fflush(stdout);
或者:setvbuf(stdout,NULL,_IONBF,0); //这个函数. 直接将printf缓冲区禁止, printf就直接输出了。

//验证printf函数带'\n'会输出结果到终端;
#include<stdio.h>
#include<unistd.h>

int main()
{
	printf("hello world.");
	sleep(1);
	printf("\n");
}
//验证fflush函数输出结果到终端;
#include<stdio.h>
#include<unistd.h>

int main()
{
	printf("hello world.");
	fflush(stdout);
}
//验证setvbuf函数输出结果到终端;
#include<stdio.h>
#include<unistd.h>

int main()
{
	setvbuf(stdout,NULL,_IONBF,0); //直接将缓冲区禁止了. 它就直接输出了
	printf("hello world.");
}

2)write()函数思考
多进程下同时写入一个文件,用write不会出现内容交叉,而使用fwrite却出现了内容交叉?这是为什么?

//验证多进程下同时写入一个文件,write系统调用的情况下,不会出现内容交叉;
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include<sys/wait.h>

int main(int argc,char * argv[])
{
    struct timeval start,end;
        int times = argc > 1 ? atoi(argv[1]):10000;  //通过参数传入需要写入的字节数
        int stat;
        int fd;
        int childpid;
        int i;


        for(i=0 ;i<1; i++){
                if(childpid = fork())
                        break;
        }

        if(childpid == -1){
                perror("failed to fork\n");
                return 1;
        }

        fd = open("tmp.dat",O_WRONLY|O_CREAT|O_APPEND,0666);

        if(fd < 0){
                perror("failed to open\n");
                return 1;
        }

        gettimeofday(&start,NULL);          //测试下时间
        if(childpid > 0){
                char *buf = (char*)malloc(times);
                for(int i = 0;i < times;++i) {
                    buf[i] = 'a';
                }
                strcat(buf,"\n");
                for(i=0; i<10; i++){
                        usleep(1000);
                        write(fd,buf,strlen(buf));
                }
                wait(&stat);
        }else{
                char *buf = (char*)malloc(times);
                for(int i = 0;i < times;++i) {
                    buf[i] = 'b';
                }
                strcat(buf,"\n");
                for(i=0; i<10; i++){
                        usleep(1000);
                        write(fd,buf,strlen(buf));
                }
        }
        close(fd);
        gettimeofday(&end,NULL);

        int timeuse = 1000000 * (end.tv_sec - start.tv_sec) + end.tv_usec - start.tv_usec;
        printf("UseTime: MicroSeconds:%d us and  Seconds:%d s\n",timeuse,end.tv_sec-start.tv_sec);
        return 0;
}
//验证多进程下同时写入一个文件,fwrite系统调用的情况下,会出现内容交叉;
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/wait.h>

int main(int argc,char * argv[])
{
        struct timeval start,end;
        int times = argc > 1 ? atoi(argv[1]):10000;
        int stat;
        int fd;
        int childpid;
        int i;


        for(i=0 ;i<1; i++){
                if(childpid = fork())
                        break;
        }

        if(childpid == -1){
                perror("failed to fork\n");
                return 1;
        }

        FILE *fp = NULL;
        fp = fopen("tmpfp.dat","ab");
        if(fp == NULL) {
            system("touch tmpfp.dat");
        }

        gettimeofday(&start,NULL); 
        if(childpid > 0){
                char *buf = (char*)malloc(times);
                for(int i = 0;i < times;++i) {
                    buf[i] = 'a';
                }
                strcat(buf,"\n");

                for(i=0; i<10; i++){
                        usleep(1000);
                        fwrite(buf,strlen(buf),1,fp);
                }
                wait(&stat);
        }else{
                char *buf = (char*)malloc(times);
                for(int i = 0;i < times;++i) {
                    buf[i] = 'b';
                }
                strcat(buf,"\n");
                for(i=0; i<10; i++){
                        usleep(1000);
                        fwrite(buf,strlen(buf),1,fp);
                }
        }
        fclose(fp);

        gettimeofday(&end,NULL); 
        int timeuse = 1000000 * (end.tv_sec - start.tv_sec) + end.tv_usec - start.tv_usec;
        printf("UseTime: MicroSeconds:%d us and Seconds:%d s\n",timeuse,end.tv_sec-start.tv_sec);
        return 0;
}

为了解释这个问题,我们应该先清楚的知道write是系统调用函数,fwrite是标准库函数,它们的区别就在于,标准库函数在用户态中会自动分配缓存,然后才与内核空间进行交互,而write是直接和内核空间进行交互的,也就是说fwrite包含了write的过程,如下图所示:
在这里插入图片描述
我们回到处理多进程同时写入文件的问题上来,就会发现fwrite写入的数据会在用户态的缓冲区满后,才交给内核空间,而write则是调用一次就立即交给内核空间,所以fwrite就会有这种日志混乱的问题。

系统为什么要引入fwrite和fread等标准库函数呢?
缓冲文件系统的IO函数主要包括:fopen, fclose, fread, fwrite, fgetc, fgets, fputc, fputs, freopen, fseek, ftell, rewind等。非缓冲文件系统的IO函数主要包括:open, close, read, write, getc, getchar, putc, putchar等,也就是系统调用函数。通过“缓冲”区,可以解决效率问题。
举个例子来说明open系列函数与fopen系列函数的效率问题:如果文件的大小是8k。你如果用read/write,且只分配了2K的缓存,则要将此文件读出需要做4次系统调用来实际从磁盘上读出。如果你用fread/fwrite,则系统自动分配缓存,则读出此文件只要一次系统调用从磁盘上读出。也就是用read/write要读4次磁盘,而用fread/fwrite则只要读1次磁盘。效率比read/write要高4倍。
因此总结了一下他们的区别:
1,fread是带缓冲的,read不带缓冲,如果程序对内存有限制,则用read/write比较好
2,fopen是标准c里定义的也就是stdio.h,open是POSIX中定义的.
3,fread可以读一个结构. read在linux/unix中读二进制与普通文件没有区别.
4,fopen不能指定要创建文件的权限.open可以指定权限.
5,fopen返回指针,open返回文件描述符(整数).
6,linux/unix中任何设备都是文件,都可以用open,read.

3)缓存同步问题
缓存同步:尽量保证缓存数据和写道磁盘上的数据一致。如何解决这个问题?有以下几种方法,但优选fsync(fd);
sync(void):将所有修改过的块缓冲区排入写队列;然后返回,并不等待实际写磁盘操作结束,数据是否写入磁盘并没有保证;
fsync(int fd):将fd对应的文件的块缓冲区立即写入磁盘,并等待实际写磁盘操作结束返回;
fdatasync(int fd):类似于fsync,但只影响文件的数据部分。而fsync不一样,fsync除数据外,还会同步更新文件属性;

fsync()的正确用法
假如要处理4M的文件,write每次只能处理4k,正确使用fsync方法是等到write执行了1K次后,才调用一次ffsync,因为从内核空间写入到磁盘的效率是很低的,应该尽量减少这种的执行次数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值