跟着 iLogtail 学习设计模式

2023-10-29

设计模式是软件开发中的重要经验总结,Gang of Four (GoF) 提出的经典设计模式则被誉为设计模式中的“圣经”。但是设计模式往往是以抽象和理论化的方式呈现,对于初学者或者没有太多实战经验的开发者来说,直接学习设计模式往往会显得枯燥乏味。

市面上或者网上也经常有一些书籍或者文章,尝试以实际的应用场景深入浅出地介绍设计模式。但是这些资料所列举的样例或应用实践,往往都是一些构造的虚拟场景,缺乏生产级软件的真实应用。而软件理论最重要的是学以致用,那是否有真实生产级代码的学习机会呢?

iLogtail 作为一款阿里云日志服务(SLS)团队自研的可观测数据采集器,目前已经在 Github 开源,其核心定位是帮助开发者构建统一的数据采集层。iLogtail 在多年的技术演进过程中,也一直在尝试进行各种设计模式的应用,这些设计模式的应用大大提升了软件的质量与可维护性。本文我们将结合 iLogtail 项目,从实践角度探讨一些常见设计模式的技术原理。在这里也要感谢字节跳动多位同学对 iLogtail Golang 部分架构的一些升级优化。

如果你曾经感到学习设计模式枯燥无味,那么来学习 iLogtail 吧!欢迎参与任何形式的社区讨论交流,相信你会发现学习设计模式也可以是一件非常有趣的事情!

创建型模式

创建型模式的作用是提供一个通用的解决方案来创建对象,并隐藏创建的细节创建对象。说到创建一个对象,最熟悉的就是 New 一个对象,然后设置相关属性。但是,在很多场景下,我们需要给应用方提供更加友好的创建对象的方式,尤其在创建各种复杂类的场景下。

单例模式

模式简介

单例模式是指在整个系统生命周期内,保证一个类只能产生一个实例,确保该类的唯一性。对于一些资源管理类的场景(例如配置管理),往往需要拥有一个全局对象,这样有利于协调系统整体的行为。

iLogtail实践

在 iLogtail 中,采集配置管理扮演着衔接用户采集配置和内部采集任务的重要角色,通过加载与解析用户采集配置,建立具体的采集任务。

作为一个进程级的管理机制,ConfigManager 非常适合采用单例模式。iLogtail 启动时会初始加载所有采集配置,并支持运行过程中动态加载变更的采集配置。通过单例模式,可以有效避免多个实例间状态同步的问题;也提供了统一的全局接口,方便各个模块进行调用。

class ConfigManager : public ConfigManagerBase {
public:
    static ConfigManager* GetInstance() {
        static ConfigManager* ptr = new ConfigManager();
        return ptr;
    }

// 构造、析构、拷贝构造、赋值构造等均为私有,防止构造多个对象
private:
    ConfigManager();
    virtual ~ConfigManager(); 
    ConfigManager(const ConfigManager&) = delete;
    ConfigManager& operator=(const ConfigManager&) = delete;
    ConfigManager(ConfigManager&&) = delete;
    ConfigManager& operator=(ConfigManager&&) = delete;
};

GetInstance() 函数是单例模式的关键,该函数内使用了静态变量、静态函数的方式,以确保在应用程序中只有一个 ConfigManager 类的实例。为了防止通过拷贝或赋值实例化多个 ConfigManager 对象,将拷贝构造函数和赋值运算符定义为私有,并将其标记为删除。

同时,利用 C++11标准中的Magic Static特性:若变量在初始化时,并发同时进入声明语句,并发线程将会阻塞等待初始化结束,保证了并发程序中的线程安全。

If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.

工厂模式

模式简介

工厂模式提供了一种创建对象的最佳方式。创建对象时不会对客户端暴露创建逻辑,客户端仅需要告诉工厂类要创建的对象,其余工作由工厂类完成。

iLogtail实践

为了应对众多可观测数据类型的采集、处理需求,在 iLogtail C++ Pipeline 中定义了 Log、Metric、Trace,并抽象出了 Pipeline Event 作为 Pipeline 数据流的通用格式。Pipeline Event 作为 Pipeline 中的数据流转的基本单元,往往会涉及大量的 Event 申请,因此在core/models中定义了 Pipeline Event 工厂,提供 Log、Metric、Span 等对象的创建,便于数据流灵活调用,降低了业务场景上的耦合,同时提高了数据模型新增时可扩展性。

生成器模式

模式简介

生成器模式又称建造者模式,该模式能够分步骤创建复杂对象,允许使用相同的创建代码生成不同类型和形式的对象。生成器模式所构建的对象一定是庞大而复杂的,并且一定是按照既定的制造工序将组件组装起来的,例如汽车生产线等。

生成器模式由四个角色组成:

  • Product(产品):复杂对象,它由多个部件组成,每个部件都有自己的构建方法和表示。
  • Builder(抽象生成器):负责定义构建复杂对象的抽象接口,包括构建每个部件的方法。
  • ConcreteBuilder(具体生成器):实现 Builder 接口,负责实现各个部件的构建方法,并最终组合成一个完整的复杂对象。
  • Director(指挥者):负责管理 Builder 对象,调用 Builder 对象的方法来构建复杂对象。它不直接创建复杂对象,而是通过 Builder 对象来构建复杂对象。

iLogtail实践

iLogtail 的 Go Pipeline 可以视作一个复杂的生产线,是一种典型的生成器模式应用场景。首先,Pipeline 管理器(Director)将 Pipeline 的构建过程分解为多个插件的构建步骤,并由 PipeBuilder 完成各阶段插件的创建和初始化;最后将这些插件组合成一个完整的Pipeline对象(Product)。

通过生成器模式的应用,大大提高 iLogtail 插件机制的可扩展性和可维护性,方便用户根据实际需求进行扩展各类采集和处理场景。

原型模式

模式简介

原型模式允许通过复制现有对象来创建新的对象,而不是通过显式的实例化来创建。

iLogtail实践

原型模式通常用于创建大量相似对象的场景。在 iLogtail 数据处理过程中,使用原型模式创建多个相似的 PipelineEvent 对象可以有效提高数据处理的效率和可维护性。

总结

创建型模式总体上比较简单,它们的作用就是为了产生实例对象。

  • 单例模式:保证一个类只有一个实例,并提供一个访问该实例的全局点。适用于管理一些全局的共享资源,避免多个实例之间的竞争和冲突,但是需要注意实现上的问题。
  • 工厂模式:定义一个用于创建对象的接口,但让子类决定将哪一个类实例化。适用于具有相似性质的对象的创建,更加灵活。
  • 生成器模式:将一个复杂对象的构建过程分成多个步骤来完成。适用于创建一些复杂的对象,方便代码的维护和扩展。
  • 原型模式:利用拷贝对象的方法,减少一些复杂的创建过程。

结构型模式

结构型模式的作用是提供一种组织对象的方式,以便实现对象之间的关系和交互。

适配器模式

模式简介

适配器模式将一种类型的接口转换成希望的另一类接口,使得原本接口不兼容对象能够一起配合工作。

iLogtail应用

iLogtail 进程由两部分组成,一是 C++ 编写的主体二进制进程,提供了管控、文件采集、C++加速处理、SLS 发送等功能;二是 Golang 编写的插件部分(libPluginBase.so),通过插件系统实现了处理能力的扩展以及更丰富的上下游生态支持。

在 iLogtail 中,SLS 发送场景主要的实现逻辑在 C++ Sender.cpp,提供了完善的发送可靠性增强能力(异常处理、重试、反压等)。而对于 Go Pipeline 中 SlsFlusher 也需要将采集、处理后的数据发送到 SLS,如果在 Go 插件侧也实现相同的逻辑,会造成代码的冗余。因此,Go SlsFlusher 的实现原理是将处理后的数据转发到 C++ 部分完成最终数据发送。但是跨语言场景必然存在不适配的因素,此时 libPluginAdaptor.so 充当一个适配器层,实现了 Golang 发送接口与 C++ 发送接口之间的衔接。

外观模式

模式简介

外观模式旨在为程序库、 框架或其他复杂类提供一个简单的接口。 外观类通常会屏蔽一些子系统的复杂交互,提供一个简单的接口,使得客户端聚焦在真正关心的功能上。

iLogtail应用

在 K8s 日志采集到 SLS 场景下,iLogtail 通过支持环境变量( aliyun_logs_{key} )的方式自动完成采集配置,包括创建 Project、Logstore、机器组、采集配置等 SLS 相关资源。整体操作较多,需要考虑配置详情、容器过滤项、操作顺序、失败等众多因素。

而对于 iLogtail Env 采集场景来说,仅需关心少数几个核心的配置项即可。因此,实现了一个封装所需功能并隐藏代码细节的外观类,不仅简化了当前的调用关系;还能将未来后端 API 升级所造成的影响最小化, 因为只需修改程序中外观方法的实现即可。

桥接模式

模式简介

桥接模式(Bridge Pattern)可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构, 从而能在开发时分别使用。概念比较晦涩,换一种理解方式:一个类存在两个(或多个)独立变化的维度,可以通过组合的方式,让这两个(或多个)维度可以独立进行扩展。

iLogtail应用

在 iLogtail 中,使用 flusher_http 发送到不同后端系统时,往往需要支持请求加签、追加auth header,请求的加签算法可能因后端平台而异。为了实现更好的可扩展性,iLogtail 提供了 extensions 机制,将 flusher_http 插件的实现与具体的发送策略的实现分离,进而实现了Authenticator、FlushInterceptor、RequestInterceptors的可扩展性。

代理模式

模式简介

代理模式就是使用一个代理类来隐藏具体实现类的实现细节,通常还用于在真实的实现的前后添加一部分逻辑。既然说是代理 ,那就要对客户端隐藏真实实现,由代理来负责客户端的所有请求。

iLogtail应用

在 iLogtail 中,最核心的步骤就是保证数据准确地发送到后端服务。在将数据发送到 SLS 场景下,最根本的就是调用 SDK 将打包好的数据发送,整个过程看似简单却蕴含着大智慧。因为后端服务是复杂多变的,往往会存在着这种不确定因素,例如网络不稳定、后端Quota满、鉴权失败、偶尔服务不可用、流控、进程重启等。如果每个数据发送方独立处理直接调用 SLS SDK 进行发送,必然导致大量重复代码,造成代码复杂度增加。因此,iLogtail 引入了 Sender 代理类,增强了直接 SDK 发送的可靠性。数据发送方仅需要调用 Sender::Instance()->Send 即可认为已经完成了数据发送,剩下的复杂场景处理全都交给 Sender 类完成,由 Sender 类保证将数据成功发送到后端系统。

总结

代理模式用来做方法的增强;适配器模式实现了类似“把鸡包装成鸭”的接口适配;桥梁模式通过组合,实现系统的解耦;外观模式可以让客户端不需要关心实例化过程,只要调用需要的方法即可。

此外,还有组合模式用于描述具有层次结构的数据;享元模式为了在特定的场景中缓存已经创建的对象,用于提高性能。

行为型模式

行为模式负责对象间的高效沟通和职责委派,它关注的是各个类之间的相互作用,将职责划分清楚,使得我们的代码更加地清晰。

观察者模式

模式简介

观察者模式定义了一种对象间的一对多的依赖关系,类似于订阅和发布的机制。当可观察对象的状态发生改变时, 所有依赖于它的对象都得到通知并自动进行事件处理。通过观察者模式可以实现灵活的事件处理,使对象间的关系更加松散,便于系统的扩展和维护。

iLogtail实践

文件采集场景可以认为是观察者模式比较典型的应用场景。为了兼顾采集效率以及跨平台的支持,iLogtail 采用了轮询(polling)与事件(inotify)并存的模式,既借助了inotify的低延迟与低性能消耗的特点,也通过轮询的方式兼顾了运行环境的全面性。

iLogtail 内部以事件的方式触发日志读取行为。其中,polling 和 inotify 作为两个独立模块,分别将各自产生的 Create/Modify/Delete 事件,存入 Polling Event Queue和 Inotify Event Queue 中,并最终合并成一个统一的 Event Queue。

  • 轮询模块由 DirFilePolling 和 ModifyPolling 两个线程组成,DirFilePolling 负责根据用户配置定期遍历文件夹,将符合日志采集配置的文件加入到 modify cache 中;ModifyPolling 负责定期扫描modify cache 中文件状态,对比上一次状态(Dev、Inode、Modify Time、Size),若发现更新则生成modify event。
  • inotify 属于事件监听方式,根据用户配置监听对应的目录以及子目录,当监听目录存在变化,内核会产生相应的通知事件。

最终,LogInput 模块完成对 Event Queue 消费的消费,并交由 Event Handler 处理Create/Modify/Delete 等事件,进而进行实际的日志采集。

责任链模式

模式简介

责任链模式允许你将请求沿着处理者链进行发送。收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。

责任链会将特定行为转换为被称作处理者的独立对象。在一个冗长的流程中,每个步骤都可被抽取为仅有单个方法的类, 并执行操作,请求及其数据则会被作为参数传递给该方法。

iLogtail实践

iLogtail 中的数据处理 Pipeline,是非常经典的责任链模式。插件系统目前的主体由 Input、Processor、Aggregator 和 Flusher 四部分组成,其中 Processor 作为处理层,可以对输入的数据进行过滤,比如检查特定字段是否符合要求或是对字段进行增删改。每一个配置可以同时配置多个 Processor,它们之间采用串行结构,即上一个 Processor 的输出作为下一个 Processor 的输入,最后一个 Processor 的输出会传递到 Aggregator。

备忘录模式

模式简介

备忘录模式允许在不暴露对象实现细节的情况下,捕获一个对象的内部状态,并在该对象之外保存这个状态,便于后来将该对象恢复到原先保存的状态。

备忘录模式主要有以下几个组成部分:

  • 发起人类(Originator):主要记录当前时刻的内部状态,并且负责定义哪些是属于备份范围的状态,负责创建和恢复备忘录数据。
  • 备忘录类(Memento):负责存储发起人对象的内部状态,并且在需要的时候向发起人提供需要的内部状态。
  • 管理类(Caretaker):备忘录的管理类,保存和提供备忘录。但不能对备忘录的内容进行访问与修改。

iLogtail实践

日志采集场景下最重要的特性是保证日志不丢。iLogtail 通过 Checkpoint 机制,及时将文件采集的状态备份到本地磁盘,保证在极端场景下数据的可靠性。两个比较典型的应用场景:

  • 采集配置更新/进程升级

配置更新或进行升级时需要中断采集并重新初始化采集上下文,iLogtail需要保证在配置更新/进程升级时,即使日志发生轮转也不会丢失日志。

解决思路:为保证配置更新/升级过程中日志数据不丢失,在 iLogtail 在配置重新加载前或进程主动退出前,会将当前所有采集的状态保存到本地的 checkpoint 文件中;当新配置应用/进程启动后,会加载上一次保存的 checkpoint,并通过 checkpoint 恢复之前的采集状态。

  • 进程crash、宕机等异常情况

在进程crash或宕机时,iLogtail需要提供容错机制,不丢数据,尽可能地少重复采集。

解决思路:进程 crash 或宕机没有退出前记录 checkpoint 的时机,因此 iLogtail 还会定期将采集进度dump到本地:除了恢复正常日志文件状态外,还会查找轮转后的日志,尽可能降低日志丢失风险。

迭代器模式

模式简介

迭代器模式提供一种在不暴露对象的内部细节的前提下,访问对象中各个元素的方法。

iLogtail实践

Golang 插件使用 LevelDB 进行一些上下文资源的备份,并基于迭代器模式恢复数据。

// Iterator iterates over a DB's key/value pairs in key order.
type Iterator interface {
  CommonIterator
  
  // Key returns the key of the current key/value pair, or nil if done.
  // The caller should not modify the contents of the returned slice, and
  // its contents may change on the next call to any 'seeks method'.
  Key() []byte

  // Value returns the key of the current key/value pair, or nil if done.
  // The caller should not modify the contents of the returned slice, and
  // its contents may change on the next call to any 'seeks method'.
  Value() []byte
}

总结

行为模式主要关注对象之间的通信和交互的方式和模式。

  • 观察者模式:定义了一种一对多的依赖关系,当一个对象的状态发生变化时,其所有依赖者都会得到通知并自动更新。
  • 职责链模式:将请求的发送者和接收者解耦,使多个对象都有机会处理该请求,直到其中一个对象处理成功为止。
  • 备忘录模式:允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态。
  • 迭代器模式:提供一种统一的方式来访问聚合对象中的各个元素,而不需要暴露其内部结构。

参考:

谈谈我工作中的23个设计模式

一文讲透设计模式(C++版)

如何理解这6种常见设计模式?

25000 字详解 23 种设计模式

C++ 常用设计模式:https://refactoringguru.cn/desi

作者|烨陌

点击立即免费试用云产品 开启云上实践之旅!

原文链接

本文为阿里云原创内容,未经允许不得转载。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

跟着 iLogtail 学习设计模式 的相关文章

  • 13-Error接口错误处理以及go Module

    Error接口和错误处理 Go语言中的错误处理和其他语言不一样 它把错误当成一种值来处理 更强调判断错误 处理错误 而不是一股脑的catch错误 error接口 Go语言中把错误当成一种特殊的值来处理 不支持其他语言的try catch 捕
  • 【matlab】常用快捷键汇总

    记录自己常用的快捷键 冲冲冲 命令行窗口的常用快捷键 上下光标键 调用Matlab最近使用过的历史命令 便于快速重新执行 编辑器的常用快捷键 Ctrl R 快速注释代码段 拖动鼠标选中需要注释的代码行 Ctrl T 快速取消注释代码段 ht
  • Linux命令之tftp常用参数说明

    tftp 文件传输 实际操作 put 上传 get 下载 mode 文件传输模式 connect 连接 quit 退出 参数 g 下载文件 p 上传文件 l 本地文件名 r 远程主机文件名 常用命令 tftp g r a 10 0 0 1
  • ROS2 驱动思岚G4雷达(ydlidar)- Rviz显示

    记录G4雷达的配置 系统环境为 Ubuntu22 04 配置步骤 1 安装雷达SDK 2 构建 G4 雷达 ROS2 项目工程文件 3 使用Rviz可视化界面显示 1 安装雷达SDK 1 1 安装CMake YDLidar SDK需要CMa

随机推荐

  • 从服务器上复制文件复制不出来的,从远程服务器上复制文件

    从远程服务器上复制文件 内容精选 换一换 问题描述 将在x86平台上经过sh加密的XXX sh x文件复制到基于鲲鹏的服务器中 无法执行 例如 将test sh x从x86平台复制鲲鹏平台云服务器执行 test sh x回显内容如下 问题原
  • 毕设分享javaWeb的网络考试系统的设计与实现

    文章目录 1 项目简介 2 实现效果 3 系统设计 4 关键代码 5 论文概览 6 最后 1 项目简介 Hi 各位同学好呀 这里是L学长 今天向大家分享一个今年 2022 最新完成的毕业设计项目作品 毕设分享javaWeb的网络考试系统的设
  • C#this关键字的四种用法

    用法一 this代表当前类的实例对象 namespace Demo public class Test private string scope 全局变量 public string getResult string scope 局部变量
  • sqli-labs:less-32(宽字节注入)

    宽字节注入 产生原因 1 mysql 在使用 GBK 编码的时候 会认为两个字符为一个汉字 例如 aa 5c 就是一个汉字 前一个 ascii 码大于 128 才能到汉字的范围 2 mysqli real escape string 函数转
  • Helix QAC — 软件静态测试工具

    Helix QAC 是Perforce 公司 原PRQA 公司 产品 主要用于C C 代码的完全自动化静态分析工作 可以提供编码规则检查 代码质量度量 软件结构分析 测试结果管理等功能 Helix QAC 能够全面而正确地发现软件中潜在的问
  • Jmeter系列(33)- 跨平台运行 Jmeter,CSV 文件路径如何设置?

    抛出问题 上一篇文章中详细讲解了 CSV 数据文件设置的用法 https www cnblogs com poloyy 通常 我们编写 调试脚本都是在 Window 机器上 而真正性能测试时 脚本几乎都在 Linux 下运行 使用 CSV
  • 图像处理中 光场(Light Field)简介及理解

    1 光场 Light Field 是一个四维的参数化表示 是空间中同时包含位置和方向信息的四维光辐射场 简单地说 涵盖了光线在传播中的所有信息 光线携带二维位置信息 u v 和二维方向信息 x y 在光场中传递 光场是光线在空间传播中四维的
  • 语法怎么学

    为什么要学非谓语动词 太常见了 非谓语动词可以做句子中 除了谓语的任何成分 是一个非常重要课题 弄懂了这个 就相当于弄懂了语法的90 特别是语法题 怎样学习 我在小破站上查了不少教程 大多数都很好 但不太适合新手 讲得很复杂 我看到一个视频
  • 【Bugly干货分享】手把手教你逆向分析 Android 程序

    很多人写文章 喜欢把什么行业现状啊 研究现状啊什么的写了一大通 感觉好像在写毕业论文似的 我这不废话 先直接上几个图 感受一下 第一张图是在把代码注入到地图里面 启动首页的时候弹出个浮窗 下载网络的图片 苍老师你们不会不认识吧 第二张图是微
  • Pygame实战:Python做一款超好玩的滑雪大冒险小游戏,超会玩【附源码】

    导语 冬日当然要和心爱的人一起去滑雪 徜徉在雪白的世界 浪漫又刺激 唯有爱和滑雪不可辜负 不但风景绝美 而且还超 会 玩 现在还不是时候 但秋天已过半动冬天还会远吗 既然不能现在去滑雪 但是小编可以先让大家身临其境 带大家做一款超好玩的滑雪
  • Kafka3.0.0版本——消费者(RoundRobin分区分配策略以及再平衡)

    目录 一 RoundRobin 分区分配策略原理 二 RoundRobin分区分配策略代码案例 2 1 创建带有7个分区的sixTopic主题 2 3 创建三个消费者 组成 消费者组 2 3 创建生产者 2 4 测试 2 5 RoundRo
  • Latex中一些特殊常用符号的输入

    搞学术的童鞋们很有可能会接触到Latex这种论文格式编辑工具 一般在论文投稿的时候 大多数期刊和会议会给一个Latex模板 要求将你的论文用Latex编辑成 pdf版本 一般的word文字部分是可以直接复制粘贴到latex的 tex文档中的
  • QT子线程中使用主线程的方法

    QT子线程中使用主线程的方法 QMetaObject invokeMethod p setText Q ARG const QString strNum 使用QMetaObject invokeMethod方法将setText函数在主线程运
  • 机器学习——信息熵与信息增益

    问 信息熵越大说明其纯度越高 答 错误 信息熵越小 说明数据集的纯度越高 信息熵是衡量数据集纯度的指标之一 它是对数据集中所有类别出现概率进行加权平均所得到的一个值 信息熵是度量样本集合纯度 不确定度最常用的指标之一 但要注意 信息熵越小
  • 【Mac】mac安装redis客户端 Error: Cask ‘rdm‘ is unavailable: No Cask with this name exist

    1 概述 mac安装redis客户端 rdm 报错如下 lcc lcc brew cask install rdm Updating Homebrew Error Cask rdm is unavailable No Cask with t
  • java——线程池

    一 线程池 线程池可以看做是线程的集合 它的工作主要是控制运行的线程的数量 处理过程中将任务放入队列 然后在线程创建后 启动这些任务 如果线程数量超过了最大数量超出数量的线程排队等候 等其它线程执行完毕 再从队列中取出任务来执行 他的主要特
  • 重写equal()时为什么也得重写hashCode()之深度解读equal方法与hashCode方法渊源

    今天这篇文章我们打算来深度解读一下equal方法以及其关联方法hashCode 我们准备从以下几点入手分析 1 equals 的所属以及内部原理 即Object中equals方法的实现原理 说起equals方法 我们都知道是超类Object
  • Android Hierarchy Viewer

    Android的SDK工具包中 有很多十分有用的工具 可以帮助程序员开发和测试Android应用程序 大大提高其工作效率 其中的一款叫Hierachy Viewer的可视化调试工具 可以很方便地帮助开发者分析 设计 调试和调整UI界面 提高
  • 高斯低通频率域滤波

    基本原理 频率域滤波 即将原图像通过傅里叶变换 转换至频率域 在频率域利用该域特有的性质进行处理 再通过傅里叶反变换把处理后的图像返回至空间域 所以 频率域的操作是在图像的傅里叶变换上执行 而不是在图像本身上执行 高斯低通滤波器传递函数表达
  • 跟着 iLogtail 学习设计模式

    设计模式是软件开发中的重要经验总结 Gang of Four GoF 提出的经典设计模式则被誉为设计模式中的 圣经 但是设计模式往往是以抽象和理论化的方式呈现 对于初学者或者没有太多实战经验的开发者来说 直接学习设计模式往往会显得枯燥乏味