2026 年 06 月 22 日 · 75 字 · 1 分钟 ·
---
一、基本思路 # 日志库的设计,抓住最核心的一条,就是日志从产生到到达最终目的地期间的处理流程。 一般而言,为了设计一个灵活可扩展,可配置的日志库,可将日志库抽象为4个部分:记录器、过滤器、格式化器、输出器四部分。
组成 描述 记录器 负责产生日志记录的原始信息,比如(原始信息,日志等级,时间,记录的位置)等信息 过滤器 负责按指定的过滤条件过滤掉我们不需要的日志(比如按日志等级过滤) 格式化器 负责对原始日志信息按照我们想要的格式去格式化 输出器 负责将将要进行记录的日志(一般经过过滤器及格式化器的处理后)记录到日志目的地(例如:输出到文件中) 通过将日志库分为4个抽象,使之成了一个较为灵活可扩展的日志库。比如你想实现输出到文件和输出到TCP中这两个功能,你只需分别实现这两个输出器的实例即可。
实现了上面的抽象就基本实现日志库的核心功能。在具体实现上,需要设计一个Logger,将上面的抽象组合起来。另外还有一些其他的工作需要完善,比如读取日志配置文件,根据配置文件中的配置条件去构建相应的代码等等其他工作。
二、一条日志的生命周期 # 可能到目前对日志库是怎么工作的还有些模糊,下面以一条日志的生命周期为例来说明日志库是怎么工作的:
产生。info!("log information."); 经过记录器。记录器去获取日志发生的时间,位置,线程信息等等信息,会有一个数据结构去存储你需要的信息(例如:msg:"log information.",time:2018-3-20 10:00:00,level:info,location:main.rs:3 lines) 经过过滤器。决定是否记录(例如,过滤条件设为info级以下的过滤掉,这里条日志信息等级是info,满足条件,继续。) 经过格式化器。假设我们想输出为2018-3-22 10:00:00 [info] log information. 到输出器。例如输出到文件中,我们就将这条信息写到文件上(File::write(....);,文件中会记录2018-3-22 10:00:00 [info] log information.). 假如你还实现了日志回滚等功能的话,在日志写入文件之后,还要判断是否触发日志回滚操作,如果满足了日志回滚的条件(比如文件Size超过某一大小),则进行日志回滚操作。 这条日志的生命周期结束了。 三、伪代码实现: # 从配置文件中读取配置(可通过序列化或其他方式),生成Config。 LoggerBuilder根据Config去构造Logger。 由Logger实现日志库的核心功能。 //配置 struct Config { level:Level, ... } //Logger建造者 struct LoggerBuilder { ... } //Logger struct Logger { record:Recorder, filter:Filter, formater:Formater, output:Output, } ...... 四、更多功能细节 # 上面是日志库设计的主干,可能我们还需要更多的功能,比如日志回滚、运行时修改配置等……
2026 年 06 月 22 日 · 7 字 · 1 分钟 ·
---
关于是否升级第三方库的思考 # 在开发中,我们经常需要很多依赖库或者使用很多第三方库,这里就有个问题了,我们什么时候需要升级依赖库或者是第三方库呢?之所以有这个问题,是因为,如果升级不当可能会对现有程序产生影响,造成目前的程序产生未知的问题。对未知影响最大是稳定性,系统稳定性在很多时候是非常非常重要的,对这个问题,有如下原则思考:
到了不得不升级的时候再升级,比如现有库功能无法满足要求,升级后的新版本可以满足功能要求。如果已有版本的功能已经满足现有要求,新版本增加的功能并不是现有需要的,则不升级。 新版本修复了已知Bug,这个没什么好说的,都有Bug了,不升版本问题解决不了。 新版本关键的性能指标有了很大的提升,而这个库又对现有程序的性能有较大的影响,即升级后对现有程序有很大的性能提升,则可以考虑升级。 所以,原则上能不升级版本就不升级,只有到不得不升级的时候再升级。
2026 年 06 月 22 日 · 4 字 · 1 分钟 ·
---
工作中,我们经常会用到线程池,通常是任务产生后放到一个任务队列,线程池中的线程不断从任务队列中取任务执行,但这样设计在一些情况下并不是最优的,更常见的实现是基于work-stealing的线程池。work-stealing从字面理解即工作窃取,工作窃取(work-stealing)算法是指某个线程从其他队列里窃取任务来执行。工作窃取的运行流程图如下:
那么为什么需要使用工作窃取算法呢?假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应,比如A线程负责处理A队列里的任务。但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。
工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。
即,基于work-strealing的线程池并不是在所有情况下都是最优的,应用它的最佳情景是线程池工作负荷比较重,外部客户大量提交任务到线程池中。而一般情况下,如果工作负荷不是很重或不是大量短任务这种情况也不一定要用线程池,所以,work-strealing算法还是很实用的。
2026 年 06 月 22 日 · 285 字 · 2 分钟 ·
---
yaml # 接触yaml是用来做配置文件的,所以首要的是学习yaml的基本编写规则,暂不做太深入的学习。学习yaml,可以参考wiki,基本各方面都讲到了。下面是摘自wiki的:
*YAML (YAML Ain’t Markup Language) is a human-readable data serialization language. It is commonly used for configuration files, but could be used in many applications where data is being stored (e.g. debugging output) or transmitted (e.g. document headers). *
需要学习更多的话,可以参考:YAML Ain’t Markup Language (YAML™) Version 1.2.
语法概要 # 在yaml里面,结构通过缩进来表示,连续的项目(如:数组元素、集合元素)通过减号“-”来表示,map结构里面的键值对(key/value)用冒号“:”来分割。yaml也有用来描述好几行相同结构数据的缩写语法,数组用“[]”包括起来,hash用“{}”来包括。
2026 年 06 月 22 日 · 142 字 · 1 分钟 ·
---
ProtocolBuffers工作原理 # 这里记录一下学习与使用Protocol Buffer的笔记,优点缺点如何使用这里不再叙述,重点关注与理解Protocol Buffers的工作原理,其大概实现。
我们经常使用Protocol Buffer进行序列化与反序列化。理解Protocol Buffer的工作原理,就要理解序列化与反序列化。
序列化:将数据结构或对象转换为二进制串的过程; 反序列化:序列化的逆过程; 如何实现呢?核心有两点:编码 + 存储。数据在计算机间通过网络进行传输时,传输的是比特流,只有0和1,并没有你所定义的各种类对象等,你如果想将一个类对象传输到对方,怎么办呢?字符是用过ASCII码编码的,这里也可以设计一套编码方案来对类似类对象这种数据进行编码,只要对方收到后能正确的解码就可以了。编码后还要确定编码后的数据存储方式,这样字节流才是有意义的字节流,这样才能知道读取的字节流有什么含义,代表什么。
好了,我们看一下Protocol Buffer是如何编码和存储的。
Protocol Buffer是如何编码的 # Varint编码 # Varint编码是一种变长的编码方式,核心思想是对数值越小的数字,用越少的字节数表示,这样可以减少数字的字节数,进行数据压缩。
举个例子:对int数据类型,一般需要4个字节来表示,而实际上,对与数值较小的数字而言,无需这么多字节,00000000 00000000 00000000 01111111 | 127,只需要一个字节就能表示,前面3个字节意义不大,浪费了许多空间。
当然,这种编码并不是所有情况下都会变小,当数值非常大时,所需的字节会增多,但因为大多数情况下数值小的数字远比数值大的多,所以整体看来,数据是被压缩了的。
具体的,Varint编码时,对每个字节的最高位赋予特殊含义:
1:表示后序的字节也是该数字的一部分; 0:表示这是最后一个字节,且剩余7bit都用来表示数字(所以Varint解码时,如果读到最高位为0的字节时,就表示已经是Varing的最后一个字节); 因为每个字节的最高位都被占用,用来表示特殊的含义,所以,当数值非常大时,原有的字节数就不够用了,所以编码时要增加字节数。
可以参考下图加深理解:
编码示例: