高性能日志:如何提升日志性能避免 IO 瓶颈?_日志性能优化-程序员宅基地

技术标签: php-小记  架构  日志  高性能  

是当系统处理大量磁盘 IO 操作的时候,由于 CPU 和内存的速度远高于磁盘,可能导致 CPU 耗费太多时间等待磁盘返回处理的结果。对于这部分 CPU 在 IO 上的开销,我们称为 “iowait”。

iowait 怎么查看呢?

如果你用的是 Linux 系统或者 Mac 系统,当你在执行一项很耗费磁盘 IO 的操作时,比如读写大文件,通过 top 命令便可以看到。如下图所示:

在这里插入图片描述

CPU 开销示意图

其中的 2.6 wa 便是 iowait 占用了 2.6% CPU。

那么,这种 CPU 开销对性能会有什么影响呢?特别是像秒杀这样的高并发系统,当秒杀服务运行的时候,会输出大量信息到日志文件,比如程序报错信息、请求参数的调试信息等,而这些写日志文件无疑会给磁盘带来更大的压力,导致更多的 CPU 开销。所以,这一讲我们就主要来探讨下这个问题。

秒杀日志面临的问题
对于并发不高的服务,我们可以把所有需要的日志写入到磁盘上的日志文件里。但是,在高峰期间,秒杀服务单节点需要处理的请求 QPS 可能达到 10 万以上。一个请求从进入秒杀服务到处理失败或者成功,至少会产生两条日志。也就是说,高峰期间,一个秒杀节点每秒产生的日志可能达到 30 万条以上。

这是什么概念?

磁盘有个性能指标:IOPS,即每秒读写次数。一块性能比较好的固态硬盘,IOPS 大概在 3 万左右。也就是说,一个秒杀节点的每秒日志条数是固态硬盘 IOPS 的 10 倍!如果这些日志每次请求时都立即写入磁盘,磁盘根本扛不住,更别说通过网络写入到监控系统中。

所以,秒杀日志会面临的第一个问题是,每秒日志量远高于磁盘 IOPS,直接写磁盘会影响服务性能和稳定性

另外,服务在输出日志前,需要先分配内存对日志信息进行拼接。日志输出完,还需要释放该日志的内存。这将会导致什么问题呢?

对于那些有内存垃圾回收器的语言,如 Java 和 Golang ,频繁分配和释放内存,可能会导致内存垃圾回收器频繁回收内存,而回收内存的时候又会导致 CPU 占用率大幅升高,进而影响服务性能和稳定性。

那些没有内存垃圾回收器的语言,如 C++ ,又会受什么影响呢?它们通常是从堆内存中分配内存,而大量的分配、释放堆内存可能会导致内存碎片,影响服务性能。

所以,秒杀日志会面临的第二个问题是,大量日志导致服务频繁分配,频繁释放内存,影响服务性能

最后,秒杀日志还会面临服务异常退出丢失大量日志的问题

我们知道,由于秒杀服务处理的请求量太大,每秒都会有很多请求的日志未写入磁盘。如果秒杀服务突然出问题挂掉了,那这批日志可能就会丢失。

对于高并发系统,这在所难免,问题是如何把控好写入日志的时间窗口,将丢失的日志条数控制在一个很小的可接受范围内。

这就是秒杀日志面临的第三个问题。通过上面的介绍,想必你也明白了,像秒杀这种大流量业务场景下,日志收集是个大难题,也是个必须要解决的性能问题。

如何优化秒杀日志性能?
前面我们了解到,秒杀日志面临着磁盘 IO 高、内存压力大、大量丢失等风险,归根结底,还是因为日志量太大,常规日志保存手段已经无法发挥作用。怎么办呢?接下来我就对这几个问题一一介绍下。

磁盘 IO 性能优化
首先,我们来看下秒杀日志量超过磁盘 IOPS 的问题。

上一讲我给你介绍了多级缓存,你是否还记得内存性能和磁盘性能的差别呢?没错,内存性能远高于磁盘性能。那我们能否利用内存来降低磁盘压力,提升写日志的性能呢?答案是可以。

Linux 有一种特殊的文件系统:tmpfs,即临时文件系统,它是一种基于内存的文件系统。当使用临时文件系统时,你以为在程序中写文件是写入到磁盘,实际上是写入到了内存中。临时文件系统中的文件虽然在内存中,但不会随着应用程序退出而丢失,因为它是由操作系统管理的。

由于云架构保障了云主机的高可用,只要操作系统正常运行,也没有人删除文件,临时文件系统中的文件就不会丢失。所以,我们可以将秒杀服务写日志的文件放在临时文件系统中。相比直接写磁盘,在临时文件系统中写日志的性能至少能提升 100 倍

当然,临时文件系统中的日志文件也不能无限制地写,否则临时文件系统的内存迟早被占满。那该怎么办呢?可以这样处理,比如,每当日志文件达到 20MB 的时候,就将日志文件转移到磁盘上,并将临时文件系统中的日志文件清空。 相比频繁的小数据写入,磁盘在顺序写入大文件的时候性能更高,也就降低了写入压力。

内存分配性能优化
不知道你学过 C 语言没?如果学过的话,你应该对 malloc 函数和 free 函数不陌生。malloc 函数主要用于从堆内存中分配内存,而 free 函数则是将使用完的内存归还到堆内存中。堆内存是由系统管理的,当堆内存中有大量碎片时,为了找到合适大小的存储空间,可能需要比对多次才能找到,这无疑让程序性能大打折扣。

而秒杀服务在输出大量日志的时候会存在频繁的内存分配和归还,如果使用常规方式分配内存,会导致高并发下性能下降。所以,我们需要使用高效的内存管理,既能快速分配内存,又能避免频繁触发垃圾回收器回收内存。

怎么做呢?

我们可以参考共享单车运营方的做法。像摩拜、哈罗、青桔等,单车的起步和归还都在人流量大的投放点,而不是运营方仓库。假如在程序中,我们也能像共享单车一样,根据实际业务自己管理内存的分配和归还,就能避免譬如内存碎片和内存垃圾回收,导致性能降低的问题。

具体怎么实现?

对于秒杀系统来说,它的日志里需要附加一些信息,以便后面排查问题或者数据统计,这些附加信息有用户 ID、来源 IP、抢购的商品 ID、时间等。但日志文件是纯文本的,而附加信息中有的是整数,有的是字符串,这就需要统一拼接成字符串才能输出到文本文件中。然而,在像 Java、Golang 这类高级语言中,字符串是一个经过封装的对象,底层是字符数组。直接用字符串拼接的话,会导致程序分配新的字符串对象来保存拼接后的结果。

比如下面的代码就会触发内存分配。

str := "hello " + userName

如何避免字符串内存分配呢?一般我们可以直接使用字符数组,基于字符数组做参数拼接。典型的例子是实现一个带字符数组缓冲区的日志对象,提供类似 AppendInt、AppendString 这样的方法拼接参数。比如下面这部分。

type Logger struct{
  data []byte
}
const maxDataSize = 65536
func NewLogger() *Logger {
  l := &Logger{
    data: make([]byte, 0, maxDataSize)
  }
}
// 整数转成字符数组并追加到缓冲区
func (l *Logger)AppendInt(data int){
  d := strconv.Itoa(data)
  l.data = append(l.data, d...)
  l.tryFlush()
}
// 字符串转成字符数组并追加到缓冲区
func (l *Logger)AppendString(data string){
  l.data = append(l.data, []byte(data)...)
  l.tryFlush()
}
// 关闭 Logger,将缓冲区中数据写入到日志文件中。通常在程序退出前调用该函数。
func (l *Logger)Close(){
  l.Flush()
}
func (l *Logger)Flush(){
  // 此处省略具体写文件的代码,大家可以自行练习
  // 将字符切片指向 l.data 的头部,清空缓冲区
  l.data = l.data[0:0]
}
func (l *Logger)tryFlush(){
  // 超过 64KB 则写入到磁盘
  if len(l.data) >= maxDataSize {
    l.Flush()
  }
}

在上面的代码实现中,每个 Append 函数中采用追加的方式拼接参数,在缓冲区足够用的情况下,不会为拼接后的数据重新分配内存。

怎么确保缓冲区足够用呢?
答案是最后面的 tryFlush 函数,它能控制缓冲区中的内容不会过大。 当 tryFlush 函数发现数据长度超过设定的最大值时,会将数据写入到日志文件中并清空缓冲区。在这个过程中,Logger 不需要归还、再分配缓冲区。

当然,以上只是个简单的示例,真正生产环境中用的 Logger 要强大很多。感兴趣的可以看看 zap、logrus 等 Logger 的实现。

如何减小丢日志的风险
前面我们了解到,秒杀服务在高并发下发生异常的时候可能导致部分日志丢失。我们还了解到,秒杀服务日志不能实时写入到日志文件。有没有发现,这两件事情是互相矛盾的?实际上,在高并发下,我们无法彻底解决丢日志的风险,只能减小丢日志的概率。为啥呢?

在高并发下,我们需要尽可能将日志先缓存到程序本地内存中,也就是 Logger 的缓冲区中。当日志到一定量后,批量写入日志文件,以便达到良好的写入性能。但是,假如程序异常退出,而缓冲区中日志大小又没达到批量写入的条件,这部分日志就可能丢弃了。

怎么办呢?

程序异常有两种:一种是能捕获的可控异常,比如 Golang 中数组越界触发 panic;一种是无法捕获的不可控异常,比如 Golang 中并发读写未加锁的 map。

这两种异常下,如何尽可能将缓冲区中的日志写入日志文件呢?

对于第一种情况,通常是捕获异常,在退出程序前执行实例代码中的 Close 函数将日志写入到日志文件。对于第二种情况,我们可以采用定时器,定时将缓冲区中的数据写入到日志文件中,比如定时 100 毫秒执行 Flush 函数
在这里插入图片描述

本文章内容为转载,如有侵权请联系作者下架。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_16142851/article/details/112511354

智能推荐

分布式光纤传感器的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告_预计2026年中国分布式传感器市场规模有多大-程序员宅基地

文章浏览阅读3.2k次。本文研究全球与中国市场分布式光纤传感器的发展现状及未来发展趋势,分别从生产和消费的角度分析分布式光纤传感器的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特点、产品规格、不同规格产品的价格、产量、产值及全球和中国市场主要生产商的市场份额。主要生产商包括:FISO TechnologiesBrugg KabelSensor HighwayOmnisensAFL GlobalQinetiQ GroupLockheed MartinOSENSA Innovati_预计2026年中国分布式传感器市场规模有多大

07_08 常用组合逻辑电路结构——为IC设计的延时估计铺垫_基4布斯算法代码-程序员宅基地

文章浏览阅读1.1k次,点赞2次,收藏12次。常用组合逻辑电路结构——为IC设计的延时估计铺垫学习目的:估计模块间的delay,确保写的代码的timing 综合能给到多少HZ,以满足需求!_基4布斯算法代码

OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版-程序员宅基地

文章浏览阅读3.3k次,点赞3次,收藏5次。OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版

关于美国计算机奥赛USACO,你想知道的都在这_usaco可以多次提交吗-程序员宅基地

文章浏览阅读2.2k次。USACO自1992年举办,到目前为止已经举办了27届,目的是为了帮助美国信息学国家队选拔IOI的队员,目前逐渐发展为全球热门的线上赛事,成为美国大学申请条件下,含金量相当高的官方竞赛。USACO的比赛成绩可以助力计算机专业留学,越来越多的学生进入了康奈尔,麻省理工,普林斯顿,哈佛和耶鲁等大学,这些同学的共同点是他们都参加了美国计算机科学竞赛(USACO),并且取得过非常好的成绩。适合参赛人群USACO适合国内在读学生有意向申请美国大学的或者想锻炼自己编程能力的同学,高三学生也可以参加12月的第_usaco可以多次提交吗

MySQL存储过程和自定义函数_mysql自定义函数和存储过程-程序员宅基地

文章浏览阅读394次。1.1 存储程序1.2 创建存储过程1.3 创建自定义函数1.3.1 示例1.4 自定义函数和存储过程的区别1.5 变量的使用1.6 定义条件和处理程序1.6.1 定义条件1.6.1.1 示例1.6.2 定义处理程序1.6.2.1 示例1.7 光标的使用1.7.1 声明光标1.7.2 打开光标1.7.3 使用光标1.7.4 关闭光标1.8 流程控制的使用1.8.1 IF语句1.8.2 CASE语句1.8.3 LOOP语句1.8.4 LEAVE语句1.8.5 ITERATE语句1.8.6 REPEAT语句。_mysql自定义函数和存储过程

半导体基础知识与PN结_本征半导体电流为0-程序员宅基地

文章浏览阅读188次。半导体二极管——集成电路最小组成单元。_本征半导体电流为0

随便推点

【Unity3d Shader】水面和岩浆效果_unity 岩浆shader-程序员宅基地

文章浏览阅读2.8k次,点赞3次,收藏18次。游戏水面特效实现方式太多。咱们这边介绍的是一最简单的UV动画(无顶点位移),整个mesh由4个顶点构成。实现了水面效果(左图),不动代码稍微修改下参数和贴图可以实现岩浆效果(右图)。有要思路是1,uv按时间去做正弦波移动2,在1的基础上加个凹凸图混合uv3,在1、2的基础上加个水流方向4,加上对雾效的支持,如没必要请自行删除雾效代码(把包含fog的几行代码删除)S..._unity 岩浆shader

广义线性模型——Logistic回归模型(1)_广义线性回归模型-程序员宅基地

文章浏览阅读5k次。广义线性模型是线性模型的扩展,它通过连接函数建立响应变量的数学期望值与线性组合的预测变量之间的关系。广义线性模型拟合的形式为:其中g(μY)是条件均值的函数(称为连接函数)。另外,你可放松Y为正态分布的假设,改为Y 服从指数分布族中的一种分布即可。设定好连接函数和概率分布后,便可以通过最大似然估计的多次迭代推导出各参数值。在大部分情况下,线性模型就可以通过一系列连续型或类别型预测变量来预测正态分布的响应变量的工作。但是,有时候我们要进行非正态因变量的分析,例如:(1)类别型.._广义线性回归模型

HTML+CSS大作业 环境网页设计与实现(垃圾分类) web前端开发技术 web课程设计 网页规划与设计_垃圾分类网页设计目标怎么写-程序员宅基地

文章浏览阅读69次。环境保护、 保护地球、 校园环保、垃圾分类、绿色家园、等网站的设计与制作。 总结了一些学生网页制作的经验:一般的网页需要融入以下知识点:div+css布局、浮动、定位、高级css、表格、表单及验证、js轮播图、音频 视频 Flash的应用、ul li、下拉导航栏、鼠标划过效果等知识点,网页的风格主题也很全面:如爱好、风景、校园、美食、动漫、游戏、咖啡、音乐、家乡、电影、名人、商城以及个人主页等主题,学生、新手可参考下方页面的布局和设计和HTML源码(有用点赞△) 一套A+的网_垃圾分类网页设计目标怎么写

C# .Net 发布后,把dll全部放在一个文件夹中,让软件目录更整洁_.net dll 全局目录-程序员宅基地

文章浏览阅读614次,点赞7次,收藏11次。之前找到一个修改 exe 中 DLL地址 的方法, 不太好使,虽然能正确启动, 但无法改变 exe 的工作目录,这就影响了.Net 中很多获取 exe 执行目录来拼接的地址 ( 相对路径 ),比如 wwwroot 和 代码中相对目录还有一些复制到目录的普通文件 等等,它们的地址都会指向原来 exe 的目录, 而不是自定义的 “lib” 目录,根本原因就是没有修改 exe 的工作目录这次来搞一个启动程序,把 .net 的所有东西都放在一个文件夹,在文件夹同级的目录制作一个 exe._.net dll 全局目录

BRIEF特征点描述算法_breif description calculation 特征点-程序员宅基地

文章浏览阅读1.5k次。本文为转载,原博客地址:http://blog.csdn.net/hujingshuang/article/details/46910259简介 BRIEF是2010年的一篇名为《BRIEF:Binary Robust Independent Elementary Features》的文章中提出,BRIEF是对已检测到的特征点进行描述,它是一种二进制编码的描述子,摈弃了利用区域灰度..._breif description calculation 特征点

房屋租赁管理系统的设计和实现,SpringBoot计算机毕业设计论文_基于spring boot的房屋租赁系统论文-程序员宅基地

文章浏览阅读4.1k次,点赞21次,收藏79次。本文是《基于SpringBoot的房屋租赁管理系统》的配套原创说明文档,可以给应届毕业生提供格式撰写参考,也可以给开发类似系统的朋友们提供功能业务设计思路。_基于spring boot的房屋租赁系统论文