当前位置: 首页 > news >正文

C++ - 基于多设计模式下的同步异步⽇志系统

1.项目介绍

项⽬介绍
本项⽬主要实现⼀个⽇志系统, 其主要⽀持以下功能:
• ⽀持多级别⽇志消息
• ⽀持同步⽇志和异步⽇志
• ⽀持可靠写⼊⽇志到控制台、⽂件以及滚动⽂件中
• ⽀持多线程程序并发写⽇志
• ⽀持扩展不同的⽇志落地⽬标地

2.开发环境

• CentOS 7
• vscode/vim
• g++/gdb
• Makefile

3.核心技术

• 类层次设计(继承和多态的应⽤)
• C++11(多线程、auto、智能指针、右值引⽤等)
• 双缓冲区
• ⽣产消费模型
• 多线程
• 设计模式(单例、⼯⼚、代理、模板等)

4.环境搭建

本项⽬不依赖其他任何第三⽅库, 只需要安装好CentOS/Ubuntu + vscode/vim环境即可开发。

5.日志系统介绍

5.1为什么需要日志系统

• ⽣产环境的产品为了保证其稳定性及安全性是不允许开发⼈员附加调试器去排查问题, 可以借助⽇志系统来打印⼀些⽇志帮助开发⼈员解决问题
• 上线客⼾端的产品出现bug⽆法复现并解决, 可以借助⽇志系统打印⽇志并上传到服务端帮助开发⼈员进⾏分析
• 对于⼀些⾼频操作(如定时器、⼼跳包)在少量调试次数下可能⽆法触发我们想要的⾏为,通过断
点的暂停⽅式,我们不得不重复操作⼏⼗次、上百次甚⾄更多,导致排查问题效率是⾮常低下, 可以借助打印⽇志的⽅式查问题
• 在分布式、多线程/多进程代码中, 出现bug⽐较难以定位, 可以借助⽇志系统打印log帮助定位bug
• 帮助⾸次接触项⽬代码的新开发⼈员理解代码的运⾏流程

5.2日志系统技术实现

⽇志系统的技术实现主要包括三种类型:
• 利⽤printf、std::cout等输出函数将⽇志信息打印到控制台
• 对于⼤型商业化项⽬, 为了⽅便排查问题,我们⼀般会将⽇志输出到⽂件或者是数据库系统⽅便查
询和分析⽇志, 主要分为同步⽇志和异步⽇志⽅式
◦ 同步写⽇志
◦ 异步写⽇志

5.2.1同步写日志

同步⽇志是指当输出⽇志时,必须等待⽇志输出语句执⾏完毕后,才能执⾏后⾯的业务逻辑语句,⽇志输出语句与程序的业务逻辑语句将在同⼀个线程运⾏。每次调⽤⼀次打印⽇志API就对应⼀次系统调⽤write写⽇志⽂件。
在这里插入图片描述
在⾼并发场景下,随着⽇志数量不断增加,同步⽇志系统容易产⽣系统瓶颈:
• ⼀⽅⾯,⼤量的⽇志打印陷⼊等量的write系统调⽤,有⼀定系统开销.
• 另⼀⽅⾯,使得打印⽇志的进程附带了⼤量同步的磁盘IO,影响程序性能

5.2.2异步写日志

异步⽇志是指在进⾏⽇志输出时,⽇志输出语句与业务逻辑语句并不是在同⼀个线程中运⾏,⽽是有专⻔的线程⽤于进⾏⽇志输出操作。业务线程只需要将⽇志放到⼀个内存缓冲区中不⽤等待即可继续执⾏后续业务逻辑(作为⽇志的⽣产者),⽽⽇志的落地操作交给单独的⽇志线程去完成(作为⽇志的消费者), 这是⼀个典型的⽣产-消费模型。
在这里插入图片描述
这样做的好处是即使⽇志没有真的地完成输出也不会影响程序的主业务,可以提⾼程序的性能:
• 主线程调⽤⽇志打印接⼝成为⾮阻塞操作
• 同步的磁盘IO从主线程中剥离出来交给单独的线程完成

6.相关技术支持补充

6.1不定参函数

6.1不定参宏函数

#include <iostream>
#include <cstdarg>
#define LOG(fmt, ...) printf("[%s:%d] " fmt "\n", __FILE__, __LINE__,##__VA_ARGS__)
int main()
{LOG("%s-%s", "hello", "dhy");return 0;
}

在这里插入图片描述

6.2c风格不定参函数

void printNum(int n, ...) 
{va_list al;va_start(al, n);//让al指向n参数之后的第⼀个可变参数for (int i = 0; i < n; i++) {int num = va_arg(al, int);//从可变参数中取出⼀个整形参数std::cout << num << std::endl;}va_end(al);//清空可变参数列表--其实是将al置空
}
int main()
{//LOG("%s-%s", "hello", "dhy");printNum(3, 11,22,33);printNum(5, 44,55,66,77,88);return 0;
}

在这里插入图片描述
vasprintf会根据format字符串和可变参数列表ap的内容动态的分配足够的内存来存储格式化后的字符串,并将地址存储在strp指针中,如果成功,就会返回格式化后的字符串的长度

void myprintf(const char *fmt, ...) 
{//int vasprintf(char **strp, const char *fmt, va_list ap);char *res;va_list al;va_start(al, fmt);int len = vasprintf(&res, fmt, al);va_end(al);std::cout << res << std::endl;free(res);
}
int main()
{myprintf("%s-%d", "dhy", 22);return 0;
}

在这里插入图片描述

6.3C++⻛格不定参函数

#include <iostream>
#include <cstdarg>
#include <memory>
#include <functional>
void xprintf() 
{std::cout << std::endl;
}
template<typename T, typename ...Args>
void xprintf(const T &value, Args &&...args) //万能引用 
{std::cout << value << " ";if ((sizeof ...(args)) > 0) {xprintf(std::forward<Args>(args)...);//完美转发}else {xprintf();}
}
int main()
{
//相当于每次都传入第一个字符串给value,然后在递归,直到打印结束xprintf("dhy");xprintf("dhy", "好困");xprintf("dhy", "每天都", "好困");return 0;
}

6.2 设计模式

设计模式是前辈们对代码开发经验的总结,是解决特定问题的⼀系列套路。它不是语法规定,⽽是⼀套⽤来提⾼代码可复⽤性、可维护性、可读性、稳健性以及安全性的解决⽅案。
六⼤原则:
单⼀职责原则(Single Responsibility Principle);
◦ 类的职责应该单⼀,⼀个⽅法只做⼀件事。职责划分清晰了,每次改动到最⼩单位的⽅法或类。
◦ 使⽤建议:两个完全不⼀样的功能不应该放⼀个类中,⼀个类中应该是⼀组相关性很⾼的函数、数据的封装
◦ ⽤例:⽹络聊天:⽹络通信 & 聊天,应该分割成为⽹络通信类 & 聊天类
• 开闭原则(Open Closed Principle);
◦ 对扩展开放,对修改封闭
◦ 使⽤建议:对软件实体的改动,最好⽤扩展⽽⾮修改的⽅式。
◦ ⽤例:超时卖货:商品价格—不是修改商品的原来价格,⽽是新增促销价格。
• ⾥⽒替换原则(Liskov Substitution Principle);
◦ 通俗点讲,就是只要⽗类能出现的地⽅,⼦类就可以出现,⽽且替换为⼦类也不会产⽣任何错误或异常。
◦ 在继承类时,务必重写⽗类中所有的⽅法,尤其需要注意⽗类的protected⽅法,⼦类尽量不要暴露⾃⼰的public⽅法供外界调⽤。
◦ 使⽤建议:⼦类必须完全实现⽗类的⽅法,孩⼦类可以有⾃⼰的个性。覆盖或实现⽗类的⽅法时,输⼊参数可以被放⼤,输出可以缩⼩
◦ ⽤例:跑步运动员类-会跑步,⼦类⻓跑运动员-会跑步且擅⻓⻓跑, ⼦类短跑运动员-会跑步且擅⻓短跑
• 依赖倒置原则(Dependence Inversion Principle)。
◦ ⾼层模块不应该依赖低层模块,两者都应该依赖其抽象. 不可分割的原⼦逻辑就是低层模式,原⼦逻辑组装成的就是⾼层模块。
◦ 模块间依赖通过抽象(接⼝)发⽣,具体类之间不直接依赖
◦ 使⽤建议:每个类都尽量有抽象类,任何类都不应该从具体类派⽣。尽量不要重写基类的⽅法。结合⾥⽒替换原则使⽤。
◦ ⽤例:奔驰⻋司机类–只能开奔驰; 司机类 – 给什么⻋,就开什么⻋; 开⻋的⼈:司机–依赖于抽象
• 迪⽶特法则(Law of Demeter),⼜叫“最少知道法则”;
◦ 尽量减少对象之间的交互,从⽽减⼩类之间的耦合。⼀个对象应该对其他对象有最少的了解。
对类的低耦合提出了明确的要求:
▪ 只和直接的朋友交流, 朋友之间也是有距离的。⾃⼰的就是⾃⼰的(如果⼀个⽅法放在本类
中,既不增加类间关系,也对本类不产⽣负⾯影响,那就放置在本类中)。
◦ ⽤例:⽼师让班⻓点名–⽼师给班⻓⼀个名单,班⻓完成点名勾选,返回结果,⽽不是班⻓点名,⽼师勾选
• 接⼝隔离原则(Interface Segregation Principle);
◦ 客⼾端不应该依赖它不需要的接⼝,类间的依赖关系应该建⽴在最⼩的接⼝上
◦ 使⽤建议:接⼝设计尽量精简单⼀,但是不要对外暴露没有实际意义的接⼝。
◦ ⽤例:修改密码,不应该提供修改⽤⼾信息接⼝,⽽就是单⼀的最⼩修改密码接⼝,更不要暴
露数据库操作
从整体上来理解六⼤设计原则,可以简要的概括为⼀句话,⽤抽象构建框架,⽤实现扩展细节,具体
到每⼀条设计原则,则对应⼀条注意事项:
• 单⼀职责原则告诉我们实现类要职责单⼀;
• ⾥⽒替换原则告诉我们不要破坏继承体系;
• 依赖倒置原则告诉我们要⾯向接⼝编程;
• 接⼝隔离原则告诉我们在设计接⼝的时候要精简单⼀;
• 迪⽶特法则告诉我们要降低耦合;
• 开闭原则是总纲,告诉我们要对扩展开放,对修改关闭。

6.2.1单例模式

⼀个类只能创建⼀个对象,即单例模式,该设计模式可以保证系统中该类只有⼀个实例,并提供⼀个访问它的全局访问点,该实例被所有程序模块共享。⽐如在某个服务器程序中,该服务器的配置信息
存放在⼀个⽂件中,这些配置数据由⼀个单例对象统⼀读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种⽅式简化了在复杂环境下的配置管理。
单例模式有两种实现模式:饿汉模式和懒汉模式

6.2.1.1饿汉模式

饿汉模式: 程序启动时就会创建⼀个唯⼀的实例对象。 因为单例对象已经确定, 所以⽐较适⽤于多线程环境中, 多线程获取单例对象不需要加锁, 可以有效的避免资源竞争, 提⾼性能。

//饿汉模式
template<typename T>
class Singleton 
{private:static Singleton _eton;private:Singleton(){}~Singleton(){}public:Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;static T& getInstance(){return _eton;}
};
Singleton Singleton::_eton;
6.2.1.2 懒汉模式

懒汉模式:第⼀次使⽤要使⽤单例对象的时候创建实例对象。如果单例对象构造特别耗时或者耗费济源(加载插件、加载⽹络资源等), 可以选择懒汉模式, 在第⼀次使⽤的时候才创建对象。
◦ 这⾥介绍的是《Effective C++》⼀书作者 Scott Meyers 提出的⼀种更加优雅简便的单例模式
Meyers’ Singleton in C++。
◦ C++11 Static local variables 特性以确保C++11起,静态变量将能够在满⾜ thread-safe 的前提下唯⼀地被构造和析构

// 懒汉模式
template <typename T>
class Singleton 
{private:Singleton(){}~Singleton(){}public:Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;static T& getInstance(){static Singleton _eton;return _eton;}
};

6.2.2 工厂模式

⼯⼚模式
⼯⼚模式是⼀种创建型设计模式, 它提供了⼀种创建对象的最佳⽅式。在⼯⼚模式中,我们创建对象时不会对上层暴露创建逻辑,⽽是通过使⽤⼀个共同结构来指向新创建的对象,以此实现创建-使⽤的分离。
⼯⼚模式可以分为:

6.2.2.1简单工厂模式

• 简单⼯⼚模式: 简单⼯⼚模式实现由⼀个⼯⼚对象通过类型决定创建出来指定产品类的实例。假设有个⼯⼚能⽣产出⽔果,当客⼾需要产品的时候明确告知⼯⼚⽣产哪类⽔果,⼯⼚需要接收⽤⼾提供的类别信息,当新增产品的时候,⼯⼚内部去添加新产品的⽣产⽅式。

//简单⼯⼚模式:通过参数控制可以⽣产任何产品
// 优点:简单粗暴,直观易懂。使⽤⼀个⼯⼚⽣产同⼀等级结构下的任意产品
// 缺点:
//      1. 所有东西⽣产在⼀起,产品太多会导致代码量庞⼤
//      2. 开闭原则遵循(开放拓展,关闭修改)的不是太好,要新增产品就必须修改⼯⼚⽅法。
#include <iostream>  
#include <string>  
#include <memory>  class Fruit   
{  
public:  Fruit(){}  virtual void show() = 0;  virtual ~Fruit() {} // 添加虚析构函数以支持多态删除  
};  class Apple : public Fruit {  
public:  Apple() {}  virtual void show() override { // 使用 override 关键字(可选,但有助于发现错误)  std::cout << "我是一个苹果" << std::endl;  }  
};  class Banana : public Fruit {  
public:  Banana() {}  virtual void show() override { // 使用 override 关键字(可选,但有助于发现错误)  std::cout << "我是一个香蕉" << std::endl;  }  
};  class FruitFactory {  
public:  static std::shared_ptr<Fruit> create(const std::string &name) {  if (name == "苹果") {  return std::make_shared<Apple>();  } else if (name == "香蕉") { // 修正了这里的字符  return std::make_shared<Banana>();  }  return std::shared_ptr<Fruit>();  }  
};  int main()  
{  std::shared_ptr<Fruit> fruit = FruitFactory::create("苹果");  fruit->show();  fruit = FruitFactory::create("香蕉"); // 修正了这里的字符  fruit->show();  return 0;  
}

在这里插入图片描述
这个模式的结构和管理产品对象的⽅式⼗分简单, 但是它的扩展性⾮常差,当我们需要新增产品的时候,就需要去修改⼯⼚类新增⼀个类型的产品创建逻辑,违背了开闭原则。

6.2.2.2工厂方法模式

⼯⼚⽅法模式: 在简单⼯⼚模式下新增多个⼯⼚,多个产品,每个产品对应⼀个⼯⼚。假设现在有A、B 两种产品,则开两个⼯⼚,⼯⼚ A 负责⽣产产品 A,⼯⼚ B 负责⽣产产品 B,⽤⼾只知道产品的⼯⼚名,⽽不知道具体的产品信息,⼯⼚不需要再接收客⼾的产品类别,⽽只负责⽣产产品。

#include <iostream>  
#include <string>  
#include <memory>  // 定义 Fruit 基类  
class Fruit {  
public:  Fruit() {}  virtual void show() = 0;  virtual ~Fruit() {} // 虚析构函数以支持多态删除  
};  
// 定义 Apple 类  
class Apple : public Fruit {  
public:  Apple() {}  virtual void show() override {  std::cout << "我是一个苹果" << std::endl;  }  
private:  std::string _color; 
};  // 定义 Banana 类  
class Banana : public Fruit {  
public:  Banana() {}  virtual void show() override {  std::cout << "我是一个香蕉" << std::endl;  }  
};  // 定义 FruitFactory 抽象基类  
class FruitFactory {  
public:  virtual std::shared_ptr<Fruit> create() = 0;  virtual ~FruitFactory() {} // 虚析构函数
};  // 定义 AppleFactory 类  
class AppleFactory : public FruitFactory {  
public:  std::shared_ptr<Fruit> create() override {  return std::make_shared<Apple>();  }  
};  // 定义 BananaFactory 类  
class BananaFactory : public FruitFactory {  
public:  std::shared_ptr<Fruit> create() override {  return std::make_shared<Banana>();  }  
};  int main() {  std::shared_ptr<Fruit> fruit; // 声明 fruit 变量  std::shared_ptr<FruitFactory> factory; // 声明 factory 变量  factory = std::make_shared<AppleFactory>(); // 创建 AppleFactory 对象  fruit = factory->create(); // 通过 AppleFactory 创建 Apple 对象  fruit->show(); // 显示 "我是一个苹果"  factory = std::make_shared<BananaFactory>(); // 创建 BananaFactory 对象  fruit = factory->create(); // 通过 BananaFactory 创建 Banana 对象  fruit->show(); // 显示 "我是一个香蕉"  return 0;  
}

在这里插入图片描述
⼯⼚⽅法模式每次增加⼀个产品时,都需要增加⼀个具体产品类和⼯⼚类,这会使得系统中类的个数成倍增加,在⼀定程度上增加了系统的耦合度。

6.2.2.3抽象工厂模式

抽象⼯⼚模式: ⼯⼚⽅法模式通过引⼊⼯⼚等级结构,解决了简单⼯⼚模式中⼯⼚类职责太重的问题,但由于⼯⼚⽅法模式中的每个⼯⼚只⽣产⼀类产品,可能会导致系统中存在⼤量的⼯⼚类,势必会增加系统的开销。此时,我们可以考虑将⼀些相关的产品组成⼀个产品族(位于不同产品等级结构中功能相关联的产品组成的家族),由同⼀个⼯⼚来统⼀⽣产,这就是抽象⼯⼚模式的基本思想。

#include <iostream>  
#include <string>  
#include <memory>  // Fruit 基类及其派生类  
class Fruit {  
public:  Fruit() {}  virtual void show() = 0;  virtual ~Fruit() {}  
};  class Apple : public Fruit {  
public:  Apple() {}  void show() override {  std::cout << "我是一个苹果" << std::endl;  }  
private:  std::string _color; // 这里_color未使用,但保留作为示例  
};  class Banana : public Fruit {  
public:  Banana() {}  void show() override {  std::cout << "我是一个香蕉" << std::endl;  }  
};  // Animal 基类及其派生类  
class Animal {  
public:  virtual void voice() = 0;  virtual ~Animal() {}  
};  class Sheep : public Animal { // 替换Lamp为Sheep  
public:  void voice() override {  std::cout << "咩咩咩\n";  }  
};  class Dog : public Animal {  
public:  void voice() override {  std::cout << "汪汪汪\n";  }  
};  // Factory 抽象基类及其派生类  
class Factory {  
public:  virtual std::shared_ptr<Fruit> getFruit(const std::string &name) = 0;  virtual std::shared_ptr<Animal> getAnimal(const std::string &name) = 0;  virtual ~Factory() {}  
};  class FruitFactory : public Factory {  
public:  std::shared_ptr<Fruit> getFruit(const std::string &name) override {  if (name == "苹果") {  return std::make_shared<Apple>();  } else if (name == "香蕉") { // 修正字符串  return std::make_shared<Banana>();  }  return std::shared_ptr<Fruit>();  }  std::shared_ptr<Animal> getAnimal(const std::string &name) override {  return std::shared_ptr<Animal>(); // 默认不返回Animal对象  }  
};  class AnimalFactory : public Factory {  
public:  std::shared_ptr<Animal> getAnimal(const std::string &name) override {  if (name == "小羊") { // 修正字符串  return std::make_shared<Sheep>();  } else if (name == "小狗") {  return std::make_shared<Dog>();  }  return std::shared_ptr<Animal>();  }  std::shared_ptr<Fruit> getFruit(const std::string &name) override {  return std::shared_ptr<Fruit>(); // 默认不返回Fruit对象  }  
};  class FactoryProducer {  
public:  static std::shared_ptr<Factory> getFactory(const std::string &name) {  if (name == "动物") {  return std::make_shared<AnimalFactory>();  } else {  return std::make_shared<FruitFactory>();  }  }  
};  int main() {  std::shared_ptr<Factory> fruit_factory = FactoryProducer::getFactory("水果"); // 修正为"水果"  std::shared_ptr<Fruit> fruit = fruit_factory->getFruit("苹果");  fruit->show();  fruit = fruit_factory->getFruit("香蕉"); // 修正为"香蕉"  fruit->show();  // 示例使用AnimalFactory  std::shared_ptr<Factory> animal_factory = FactoryProducer::getFactory("动物");  std::shared_ptr<Animal> animal = animal_factory->getAnimal("小羊");  animal->voice();  animal = animal_factory->getAnimal("小狗");  animal->voice();  return 0;
}

在这里插入图片描述
抽象⼯⼚模式适⽤于⽣产多个⼯⼚系列产品衍⽣的设计模式,增加新的产品等级结构复杂,需要对原有系统进⾏较⼤的修改,甚⾄需要修改抽象层代码,违背了“开闭原则”。

6.2.3建造者模式

建造者模式是⼀种创建型设计模式, 使⽤多个简单的对象⼀步⼀步构建成⼀个复杂的对象,能够将⼀个复杂的对象的构建与它的表⽰分离,提供⼀种创建对象的最佳⽅式。主要⽤于解决对象的构建过于复杂的问题。
建造者模式主要基于四个核⼼类实现:
• 抽象产品类:
• 具体产品类:⼀个具体的产品对象类
• 抽象Builder类:创建⼀个产品对象所需的各个部件的抽象接⼝
• 具体产品的Builder类:实现抽象接⼝,构建各个部件
• 指挥者Director类:统⼀组建过程,提供给调⽤者使⽤,通过指挥者来构造产品

#include <iostream>  
#include <memory>  /* 抽象电脑类 */  
class Computer {  
public:  using ptr = std::shared_ptr<Computer>;  Computer() {}  void setBoard(const std::string &board) {_board = board;}  void setDisplay(const std::string &display) {_display = display;}  virtual void setOs() = 0;  std::string toString() {  std::string computer = "Computer:{\n";  computer += "\tboard=" + _board + ",\n";  computer += "\tdisplay=" + _display + ",\n";  computer += "\tOs=" + _os + ",\n";  computer += "}\n";  return computer;  }  
protected:  std::string _board;  std::string _display;  std::string _os;  
};  /* 具体产品类 */  
class MacBook : public Computer {  
public:  using ptr = std::shared_ptr<MacBook>;  MacBook() {}  void setOs() override {  _os = "Mac OS X12";  }  
};  /* 抽象建造者类 */  
class Builder {  
public:  using ptr = std::shared_ptr<Builder>;  virtual void buildBoard(const std::string &board) = 0;  virtual void buildDisplay(const std::string &display) = 0;  virtual void buildOs() = 0;  virtual Computer::ptr build() = 0;  virtual ~Builder() {}  
};  /* 具体建造者类 */  
class MacBookBuilder : public Builder {  
public:  using ptr = std::shared_ptr<MacBookBuilder>;  MacBookBuilder() : _computer(std::make_shared<MacBook>()) {}  void buildBoard(const std::string &board) override {  _computer->setBoard(board);  }  void buildDisplay(const std::string &display) override {  _computer->setDisplay(display);  }  void buildOs() override {  _computer->setOs();  }  Computer::ptr build() override {  return _computer;  }  
private:  Computer::ptr _computer;  
};  /* 指挥者类 */  
class Director {  
public:  Director(Builder::ptr builder) : _builder(builder) {}  void construct(const std::string &board, const std::string &display) {  _builder->buildBoard(board);  _builder->buildDisplay(display);  _builder->buildOs();  }  
private:  Builder::ptr _builder;  
};  int main() {  auto builder = std::make_shared<MacBookBuilder>();  std::unique_ptr<Director> pd(new Director(builder));  pd->construct("英特尔主板", "VOC显示器");  Computer::ptr computer = builder->build();  std::cout << computer->toString();  return 0;  
}

6.2.4代理模式

代理模式指代理控制对其他对象的访问, 也就是代理对象控制对原对象的引⽤。在某些情况下,⼀个对象不适合或者不能直接被引⽤访问,⽽代理对象可以在客⼾端和⽬标对象之间起到中介的作⽤。
代理模式的结构包括⼀个是真正的你要访问的对象(⽬标类)、⼀个是代理对象。⽬标对象与代理对象实现同⼀个接⼝,先访问代理类再通过代理类访问⽬标对象。代理模式分为静态代理、动态代理
静态代理指的是,在编译时就已经确定好了代理类和被代理类的关系。也就是说,在编译时就已经确定了代理类要代理的是哪个被代理类。
动态代理指的是,在运⾏时才动态⽣成代理类,并将其与被代理类绑定。这意味着,在运⾏时才能确定代理类要代理的是哪个被代理类。
以租房为例,房东将房⼦租出去,但是要租房⼦出去,需要发布招租启⽰, 带⼈看房,负责维修,这些⼯作中有些操作并⾮房东能完成,因此房东为了图省事,将房⼦委托给中介进⾏租赁。 代理模式实现: 、

/*房东要把⼀个房⼦通过中介租出去理解代理模式*/
#include <iostream>
#include <string>
class RentHouse 
{public:virtual void rentHouse() = 0;
};
/*房东类:将房⼦租出去*/
class Landlord : public RentHouse 
{public:void rentHouse() {std::cout << "将房⼦租出去\n";
}
};
/*中介代理类:对租房⼦进⾏功能加强,实现租房以外的其他功能*/
class Intermediary : public RentHouse 
{public:void rentHouse() {std::cout << "发布招租启⽰\n";std::cout << "带⼈看房\n";_landlord.rentHouse();std::cout << "负责租后维修\n";}private:Landlord _landlord;
};
int main()
{Intermediary intermediary;intermediary.rentHouse();return 0;
}

在这里插入图片描述

7.⽇志系统框架设计

本项⽬实现的是⼀个多⽇志器⽇志系统,主要实现的功能是让程序员能够轻松的将程序运⾏⽇志信息落地到指定的位置,且⽀持同步与异步两种⽅式的⽇志落地⽅式。
项⽬的框架设计将项⽬分为以下⼏个模块来实现。

7.1模块划分

  1. ⽇志等级模块:对输出⽇志的等级进⾏划分,以便于控制⽇志的输出,并提供等级枚举转字符串功能。
    ◦ OFF:关闭
    ◦ DEBUG:调试,调试时的关键信息输出。
    ◦ INFO:提⽰,普通的提⽰型⽇志信息。
    ◦ WARN:警告,不影响运⾏,但是需要注意⼀下的⽇志。
    ◦ ERROR:错误,程序运⾏出现错误的⽇志
    ◦ FATAL:致命,⼀般是代码异常导致程序⽆法继续推进运⾏的⽇志

  2. ⽇志消息模块:中间存储⽇志输出所需的各项要素信息
    ◦ 时间:描述本条⽇志的输出时间。
    ◦ 线程ID:描述本条⽇志是哪个线程输出的。
    ◦ ⽇志等级:描述本条⽇志的等级。
    ◦ ⽇志数据:本条⽇志的有效载荷数据。
    ◦ ⽇志⽂件名:描述本条⽇志在哪个源码⽂件中输出的。
    ◦ ⽇志⾏号:描述本条⽇志在源码⽂件的哪⼀⾏输出的。

  3. ⽇志消息格式化模块:设置⽇志输出格式,并提供对⽇志消息进⾏格式化功能。
    ◦ 系统的默认⽇志输出格式:[%d{%H:%M:%S}][%t][%p][%c][%f:%l]%m%n
    ◦ %d{%H:%M:%S}:表⽰⽇期时间,花括号中的内容表⽰⽇期时间的格式。
    ◦ %T:表⽰制表符缩进。
    ◦ %t:表⽰线程ID
    ◦ %p:表⽰⽇志级别
    ◦ %c:表⽰⽇志器名称,不同的开发组可以创建⾃⼰的⽇志器进⾏⽇志输出,⼩组之间互不影
    响。
    ◦ %f:表⽰⽇志输出时的源代码⽂件名。
    ◦ %l:表⽰⽇志输出时的源代码⾏号。
    ◦ %m:表⽰给与的⽇志有效载荷数据
    ◦ %n:表⽰换⾏
    ◦ 设计思想:设计不同的⼦类,不同的⼦类从⽇志消息中取出不同的数据进⾏处理。

  4. ⽇志消息落地模块:决定了⽇志的落地⽅向,可以是标准输出,也可以是⽇志⽂件,也可以滚动⽂件输出…
    ◦ 标准输出:表⽰将⽇志进⾏标准输出的打印。
    ◦ ⽇志⽂件输出:表⽰将⽇志写⼊指定的⽂件末尾。
    ◦ 滚动⽂件输出:当前以⽂件⼤⼩进⾏控制,当⼀个⽇志⽂件⼤⼩达到指定⼤⼩,则切换下⼀个⽂件进⾏输出
    ◦ 后期,也可以扩展远程⽇志输出,创建客⼾端,将⽇志消息发送给远程的⽇志分析服务器。
    ◦ 设计思想:设计不同的⼦类,不同的⼦类控制不同的⽇志落地⽅向。

  5. ⽇志器模块:
    ◦ 此模块是对以上⼏个模块的整合模块,⽤⼾通过⽇志器进⾏⽇志的输出,有效降低⽤⼾的使⽤难度。
    ◦ 包含有:⽇志消息落地模块对象,⽇志消息格式化模块对象,⽇志输出等级

  6. ⽇志器管理模块:
    ◦ 为了降低项⽬开发的⽇志耦合,不同的项⽬组可以有⾃⼰的⽇志器来控制输出格式以及落地⽅向,因此本项⽬是⼀个多⽇志器的⽇志系统。
    ◦ 管理模块就是对创建的所有⽇志器进⾏统⼀管理。并提供⼀个默认⽇志器提供标准输出的⽇志输出。

  7. 异步线程模块:
    ◦ 实现对⽇志的异步输出功能,⽤⼾只需要将输出⽇志任务放⼊任务池,异步线程负责⽇志的落地输出功能,以此提供更加⾼效的⾮阻塞⽇志输出。

7.2模块关系图

在这里插入图片描述

8.代码设计

8.1 实⽤类设计

提前完成⼀些零碎的功能接⼝,以便于项⽬中会⽤到。
• 获取系统时间
• 判断⽂件是否存在
• 获取⽂件的所在⽬录路径
• 创建⽬录

#ifndef __M_UTIL_H__
#define __M_UTIL_H__
/*
通⽤功能类,与业务⽆关的功能实现
1. 获取系统时间
2. 获取⽂件⼤⼩
3. 创建⽬录
4. 获取⽂件所在⽬录
*/
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <ctime>
#include <cassert>
#include <sys/stat.h>
namespace dhylog
{namespace util{class date{public:static size_t now(){return size_t(time(nullptr));}};class File{public:static bool exists(const std::string& name){struct stat st;return stat(name.c_str(),&st)==0;}static std::string path(const std::string& name){if(name.empty()) return ".";size_t pos=name.find_last_of("/\\");if(pos==std::string::npos){return ".";}return name.substr(0,pos+1);}static void create_directory(const std::string &path) {if(path.empty()) return;if(exists(path)) return;size_t pos=0,idx=0;while(idx<path.size()){pos=path.find_first_of("/\\",idx);if(pos==std::string::npos){mkdir(path.c_str(), 0777);return;}std::string parent_dir=path.substr(0,pos+1);if(exists(parent_dir)) {idx=pos+1; continue;}mkdir(parent_dir.c_str(),0777);idx=pos+1;}}              };};
};
#endif

8.2⽇志等级类设计

⽇志等级总共分为7个等级,分别为:
• OFF 关闭所有⽇志输出
• DRBUG 进⾏debug时候打印⽇志的等级
• INFO 打印⼀些⽤⼾提⽰信息
• WARN 打印警告信息
• ERROR 打印错误信息
• FATAL 打印致命信息- 导致程序崩溃的信息

#ifndef __M_LEVEL_H__
#define __M_LEVEL_H__
namespace dhylog
{class loglevel{public:enum class value{UNKNOW=0,DEBUG,INFO,WARN,ERROR,FATAL,OFF,};static const char *toString(loglevel::value level){switch(level){case loglevel::value::DEBUG: return "DEBUG";case loglevel::value::INFO: return "INFO";case loglevel::value::WARN: return "WARN";case loglevel::value::ERROR: return "ERROR";case loglevel::value::FATAL: return "FATAL";case loglevel::value::OFF: return "OFF";}return "UNKNOW";}};
}
#endif

8.3 ⽇志消息类设计

⽇志消息类主要是封装⼀条完整的⽇志消息所需的内容,其中包括⽇志等级、对应的logger name、打印⽇志源⽂件的位置信息(包括⽂件名和⾏号)、线程ID、时间戳信息、具体的⽇志信息等内容。

#ifndef __M_MESSAGE_H__
#define __M_MESSAGE_H__#include<iostream>
#include"level.hpp"
#include"util.hpp"
#include<thread>
namespace dhylog
{struct LogMsg{time_t _ctime;//日志产生的时间戳loglevel::value _level;//日志等级size_t _line;//行号std::thread::id _tid;//线程idstd::string _file;//源码文件名std::string _logger;//日志器名称std::string _payload;//有效消息数据LogMsg(loglevel::value level,size_t line,std::string file,std::string logger,std::string msg):_ctime(util::date::now()),_line(line),_level(level),_tid(std::this_thread::get_id()),_file(file),_logger(logger),_payload(msg) {}       };
}
#endif

8.4 ⽇志输出格式化类设计

⽇志格式化(Formatter)类主要负责格式化⽇志消息。其主要包含以下内容
• pattern成员:保存⽇志输出的格式字符串。
◦ %d ⽇期
◦ %T 缩进
◦ %t 线程id
◦ %p ⽇志级别
◦ %c ⽇志器名称
◦ %f ⽂件名
◦ %l ⾏号
◦ %m ⽇志消息
◦ %n 换⾏

#ifndef __M_FMT_H__
#define __M_FMT_H__#include "util.hpp"
#include "message.hpp"
#include "level.hpp"
#include <memory>
#include <vector>
#include <tuple>
#include <ctime>namespace dhylog
{class  FormatItem{public:using ptr=std::shared_ptr<FormatItem>;virtual void format(std::ostream &out, const LogMsg &msg) = 0;};class  MsgFormatItem :public FormatItem{public:void format(std::ostream &out, const LogMsg &msg)override{out<<msg._payload;}};class LevelFormatItem :public FormatItem{public:void format(std::ostream &out, const LogMsg &msg)override{out<<loglevel::toString(msg._level);}};class TimeFormatItem :public FormatItem{public:TimeFormatItem(const std::string& fmt="%H:%M:%S"):_time_fmt(fmt){}void format(std::ostream &out, const LogMsg &msg)override{struct tm t;localtime_r(&msg._ctime,&t);char tmp[128];strftime(tmp,127,_time_fmt.c_str(),&t);out<<tmp;}private:std::string _time_fmt;};class FileFormatItem :public FormatItem{public:void format(std::ostream &out, const LogMsg &msg)override{out<<msg._file;}};class LineFormatItem :public FormatItem{public:void format(std::ostream &out, const LogMsg &msg)override{out<<msg._line;}};class ThreadFormatItem :public FormatItem{public:void format(std::ostream &out, const LogMsg &msg)override{out<<msg._tid;}};class LoggerFormatItem :public FormatItem{public:void format(std::ostream &out, const LogMsg &msg)override{out<<msg._logger;}};class TabFormatItem :public FormatItem{public:void format(std::ostream &out, const LogMsg &msg)override{out<<"\t";}};class NlineFormatItem :public FormatItem{public:void format(std::ostream &out, const LogMsg &msg)override{out<<"\n";}};class OtherFormatItem :public FormatItem{public:OtherFormatItem(const std::string& str="" ):_str(str){}void format(std::ostream &out, const LogMsg &msg)override{out<<_str;}private:std::string _str;};class Formatter{public:using ptr=std::shared_ptr<Formatter>;/*%d 日期%T 缩进%t 线程id%p 日志级别%c 日志器名称%f 文件名%l 行号%m 日志消息%n 换行*///时间{年-月-日 时:分:秒}缩进 线程ID 缩进 [日志级别] 缩进 [日志名称] 缩进 文件名:行号 缩进 消息换行 [%d{%H:%M:%S}][%t][%p][%c][%f:%l]%m%nFormatter(const std::string &pattern = "[%d{%H:%M:%S}][%t][%p][%c][%f:%l]%m%n"):_pattern(pattern){assert(parsePattern());}const std::string pattern() { return _pattern; }std::string format(const LogMsg &msg){std::stringstream ss;for(auto &it :_items){it->format(ss,msg);}return ss.str();}std::ostream& format(std::ostream &out,const LogMsg&msg){for(auto &it :_items){it->format(out,msg);}return out;}FormatItem::ptr createItem(const std::string &key, const std::string &val) {if(key=="d") return std::make_shared<TimeFormatItem>(val);if(key=="T") return std::make_shared<TabFormatItem>();if(key=="c") return std::make_shared<LoggerFormatItem>();if(key=="f") return std::make_shared<FileFormatItem>();if(key=="l") return std::make_shared<LineFormatItem>();if(key=="p") return std::make_shared<LevelFormatItem>();if(key=="t") return std::make_shared<ThreadFormatItem>();if(key=="m") return std::make_shared<MsgFormatItem>();if(key=="n") return std::make_shared<NlineFormatItem>();if(key.empty()) return std::make_shared<OtherFormatItem>(val);std::cout<<"没有对应的格式化字符%"<<key<<std::endl;abort();}private:bool parsePattern() {//[%d{%H:%M:%S}][%t][%p][%c][%f:%l] %m%n//sg{}fsg%d{%H:%M:%S}%Tsdf%t%T[%p]%T[%c]%T%f:%l%T%m%n std::vector<std::pair<std::string,std::string>> fmt_order;size_t pos=0;std::string key,value;while(pos<_pattern.size()){//寻找%if(_pattern[pos]!='%')  {value.push_back(_pattern[pos++]);continue;}//当前位置为%,判断后面是否为%if(pos+1<_pattern.size()&&_pattern[pos+1]=='%'){value.push_back('%');pos+=2;continue;};//当前位置为%,且pos+1不为%,剔除前两下项为%d的情况if(!value.empty()){fmt_order.push_back(std::make_pair("",value));value.clear();}//当前位置为%,后面为格式化字符,接下来处理格式化字符pos+=1;//当前位置为格式化字符,判断越界if(pos==_pattern.size()){std::cout<<"%之后,没有对相应的格式化字符!\n";return false;}key=_pattern[pos];pos+=1;//格式化字符完整,当前位置为格式化字符后一位,判断是否为{if(pos<_pattern.size()&&_pattern[pos]=='{'){pos++;while(pos<_pattern.size()&&_pattern[pos]!='}'){value.push_back(_pattern[pos++]);}if(pos==_pattern.size()){std::cout<<"{不匹配";return false;}pos+=1;}    fmt_order.push_back(std::make_pair(key,value));key.clear();value.clear();}for(auto &it:fmt_order){_items.push_back(createItem(it.first,it.second));}return true;}std::string _pattern;std::vector<FormatItem::ptr> _items;};
}#endif

8.5 ⽇志落地(LogSink)类设计(简单⼯⼚模式)

⽇志落地类主要负责落地⽇志消息到⽬的地。
它主要包括以下内容:
• Formatter⽇志格式化器:主要是负责格式化⽇志消息,
• mutex互斥锁:保证多线程⽇志落地过程中的线程安全,避免出现交叉输出的情况。
这个类⽀持可扩展,其成员函数log设置为纯虚函数,当我们需要增加⼀个log输出⽬标, 可以增加⼀
个类继承⾃该类并重写log⽅法实现具体的落地⽇志逻辑。
⽬前实现了三个不同⽅向上的⽇志落地:
• 标准输出:StdoutSink
• 固定⽂件:FileSink
• 滚动⽂件:RollSink
◦ 滚动⽇志⽂件输出的必要性:
▪ 由于机器磁盘空间有限, 我们不可能⼀直⽆限地向⼀个⽂件中增加数据 ▪ 如果⼀个⽇志⽂件体积太⼤,⼀⽅⾯是不好打开,另⼀⽅⾯是即时打开了由于包含数据巨⼤,也不利于查找我们需要的信息
▪ 所以实际开发中会对单个⽇志⽂件的⼤⼩也会做⼀些控制,即当⼤⼩超过某个⼤⼩时(如1GB),我们就重新创建⼀个新的⽇志⽂件来滚动写⽇志。 对于那些过期的⽇志, ⼤部分企业内部都有专⻔的运维⼈员去定时清理过期的⽇志,或者设置系统定时任务,定时清理过期⽇志。
◦ ⽇志⽂件的滚动思想:
⽇志⽂件滚动的条件有两个:⽂件⼤⼩ 和 时间。我们可以选择:
▪ ⽇志⽂件在⼤于 1GB 的时候会更换新的⽂件
▪ 每天定点滚动⼀个⽇志⽂件
本项⽬基于⽂件⼤⼩的判断滚动⽣成新的⽂件

#ifndef __M_SINK_H__
#define __M_SINK_H__
#include "util.hpp"
#include "message.hpp"
#include "formatter.hpp"
#include <memory>
#include <mutex>namespace dhylog
{class LogSink{public:using ptr = std::shared_ptr<LogSink>;LogSink() {}virtual void log(const char *data, size_t len) = 0;};class StdoutSink : public LogSink {public:using ptr = std::shared_ptr<StdoutSink>;void log(const char *data, size_t len)override{std::cout.write(data,len);std::cout<<std::endl;}};class FileSink : public LogSink {public:using ptr = std::shared_ptr<FileSink>;FileSink(const std::string &filename):_Filename(filename) {util::File::create_directory(util::File::path(filename));_ofs.open(filename, std::ios::binary | std::ios::app);assert(_ofs.is_open());}void log(const char *data, size_t len)override{_ofs.write(data,len);if(_ofs.good()==false){std::cout<<"日志文件输出失败"<<std::endl;}}private:std::string _Filename;std::ofstream _ofs;};class RollSink : public LogSink{public:using ptr = std::shared_ptr<RollSink>;RollSink(std::string basename,size_t max_fsize):_basename(basename),_max_fsize(max_fsize),_cur_fsize(0){util::File::create_directory(util::File::path(basename));    }void log(const char *data, size_t len)override{initLogFile();_ofs.write(data,len);_ofs<<'\n';if (_ofs.good() == false) {std::cout << "日志输出文件失败!\n";}_cur_fsize += len;}private:void initLogFile() {if(_ofs.is_open()==false||_cur_fsize>=_max_fsize){_ofs.close();std::string name = createFilename();_ofs.open(name, std::ios::binary | std::ios::app);assert(_ofs.is_open());_cur_fsize = 0;return;}return;}std::string createFilename(){time_t t=util::date::now();struct tm lt;localtime_r(&t, &lt);std::stringstream ss;ss << _basename;ss << lt.tm_year + 1900;ss << "-";ss << lt.tm_mon + 1;ss << "-";ss << lt.tm_mday;ss << "-";ss << lt.tm_hour;ss << "-";ss << lt.tm_min;ss << "-";ss << lt.tm_sec;ss << ".log";return ss.str();}std::string _basename;std::ofstream _ofs;size_t _max_fsize;size_t _cur_fsize;};class SinkFactory {public:template<typename SinkType, typename ...Args>static LogSink::ptr create(Args &&...args) {return std::make_shared<SinkType>(std::forward<Args>(args)...);}};
}
#endif

8.6 ⽇志器类(Logger)设计(建造者模式)

⽇志器主要是⽤来和前端交互, 当我们需要使⽤⽇志系统打印log的时候, 只需要创建Logger对象,调⽤该对象debug、info、warn、error、fatal等⽅法输出⾃⼰想打印的⽇志即可,⽀持解析可变参数列表和输出格式, 即可以做到像使⽤printf函数⼀样打印⽇志。

当前⽇志系统⽀持同步⽇志 & 异步⽇志两种模式,两个不同的⽇志器唯⼀不同的地⽅在于他们在⽇志的落地⽅式上有所不同:

同步⽇志器:直接对⽇志消息进⾏输出。

异步⽇志器:将⽇志消息放⼊缓冲区,由异步线程进⾏输出。

因此⽇志器类在设计的时候先设计出⼀个Logger基类,在Logger基类的基础上,继承出SyncLogger同步⽇志器和AsyncLogger异步⽇志器。
且因为⽇志器模块是对前边多个模块的整合,想要创建⼀个⽇志器,需要设置⽇志器名称,设置⽇志输出等级,设置⽇志器类型,设置⽇志输出格式,设置落地⽅向,且落地⽅向有可能存在多个,整个⽇志器的创建过程较为复杂,为了保持良好的代码⻛格,编写出优雅的代码,因此⽇志器的创建这⾥采⽤了建造者模式来进⾏创建。

#ifndef _M_LOGGER_H_
#define _M_LOGGER_H_
//#define _GNU_SOURCE
#include"util.hpp"
#include"level.hpp"  
#include"formatter.hpp"
#include"sink.hpp"
#include"looper.hpp"
#include<atomic>
#include<cstdarg>
#include<unordered_map>namespace dhylog {class Logger{public:using ptr=std::shared_ptr<Logger>;Logger(const std::string &logger_name,loglevel::value level,Formatter::ptr &formatter,std::vector<LogSink::ptr> sinks):_logger_name(logger_name),_limit_level(level),_formatter(formatter),_sinks(sinks.begin(),sinks.end()){}const std::string &name(){return _logger_name;}void debug(const std::string &file,size_t line,const std::string &fmt,...){if(loglevel::value::DEBUG<_limit_level) {return;}va_list ap;va_start(ap,fmt);char *res;int ret=vasprintf(&res,fmt.c_str(),ap);if(ret==-1){std::cout<<"vasprintf failed!!\n";return;}va_end(ap);serialize(loglevel::value::DEBUG,file,line,res);             free(res);}void info(const std::string &file,size_t line,const std::string &fmt,...){if(loglevel::value::INFO<_limit_level) {return;}va_list ap;va_start(ap,fmt);char *res;int ret=vasprintf(&res,fmt.c_str(),ap);if(ret==-1){std::cout<<"vasprintf failed!!\n";return;}va_end(ap);serialize(loglevel::value::INFO,file,line,res);             free(res);}void warn(const std::string &file,size_t line,const std::string &fmt,...){if(loglevel::value::WARN<_limit_level) {return;}va_list ap;va_start(ap,fmt);char *res;int ret=vasprintf(&res,fmt.c_str(),ap);if(ret==-1){std::cout<<"vasprintf failed!!\n";return;}va_end(ap);serialize(loglevel::value::WARN,file,line,res);             free(res);}void error(const std::string &file,size_t line,const std::string &fmt,...){if(loglevel::value::ERROR<_limit_level) {return;}va_list ap;va_start(ap,fmt);char *res;int ret=vasprintf(&res,fmt.c_str(),ap);if(ret==-1){std::cout<<"vasprintf failed!!\n";return;}va_end(ap);serialize(loglevel::value::ERROR,file,line,res);             free(res);}void fatal(const std::string &file,size_t line,const std::string &fmt,...){if(loglevel::value::FATAL<_limit_level) {return;}va_list ap;va_start(ap,fmt);char *res;int ret=vasprintf(&res,fmt.c_str(),ap);if(ret==-1){std::cout<<"vasprintf failed!!\n";return;}va_end(ap);serialize(loglevel::value::FATAL,file,line,res);             free(res);}protected:void serialize(loglevel::value level,const std::string &file,size_t line,char*str){LogMsg msg(level,line,file,_logger_name,str);std::stringstream ss;_formatter->format(ss,msg);log(ss.str().c_str(),ss.str().size()); }virtual void log(const char*data,size_t len)=0;protected:std::mutex _mutex;std::string _logger_name;std::atomic<loglevel::value> _limit_level;Formatter::ptr _formatter;std::vector<LogSink::ptr> _sinks;};//同步日志器class SyncLogger : public Logger {public:SyncLogger(const std::string &logger_name,loglevel::value level,Formatter::ptr &formatter,std::vector<LogSink::ptr> sinks):Logger(logger_name,level,formatter,sinks) {}protected:void log(const char*data,size_t len){std::unique_lock<std::mutex> lock(_mutex);if(_sinks.empty()) return;for(auto &sink :_sinks){sink->log(data,len);}              }};//异步日志器class AsyncLogger : public Logger {public://using ptr = std::shared_ptr<AsyncLogger>;AsyncLogger(const std::string &logger_name,loglevel::value level,Formatter::ptr &formatter,std::vector<LogSink::ptr> sinks,AsyncType looper_type):Logger(logger_name,level,formatter,sinks),_looper(std::make_shared<AsyncLooper>(std::bind(&AsyncLogger::reallog,this,std::placeholders::_1),looper_type)){}protected:void log(const char*data,size_t len)//将数据写入缓冲区{_looper->push(data,len);}//设计一个实际落地函数将缓冲区中的数据落地void reallog(Buffer& buf){if(_sinks.empty()) return;for(auto &sink:_sinks){sink->log(buf.begin(),buf.readAbleSize());}}private:AsyncLooper::ptr _looper;};enum class LoggerType {LOGGER_SYNC = 0,LOGGER_ASYNC};class LoggerBuilder{public:LoggerBuilder():_logger_type(LoggerType::LOGGER_SYNC),_limit_level(loglevel::value::DEBUG),_looper_Type(AsyncType::ASYNC_SAFE){}void buildLoggerType(LoggerType type){_logger_type=type;}void buildEnableUnSafeAsync(){_looper_Type=AsyncType::ASYNC_UNSAFE;}void buildLoggerName(const std::string &name){_logger_name=name;}void buildLoggerLevel(loglevel::value level){_limit_level=level;}void buildFormatter(const std::string &pattern){_formatter=std::make_shared<Formatter>(pattern);}template<typename SinkType,typename ...Args>void buildSink(Args &&...args){LogSink::ptr psink=SinkFactory::create<SinkType>(std::forward<Args>(args)...);_sinks.push_back(psink);}virtual Logger::ptr build()=0;protected:AsyncType _looper_Type;LoggerType _logger_type;std::string _logger_name;loglevel::value _limit_level;Formatter::ptr _formatter;std::vector<LogSink::ptr> _sinks;};class LocalLoggerBuilder :public LoggerBuilder{public:Logger::ptr build() override{assert(_logger_name.empty()==false);if(_formatter.get()==nullptr){_formatter=std::make_shared<Formatter>();}if(_sinks.empty()){buildSink<StdoutSink>();}if(_logger_type==LoggerType::LOGGER_ASYNC){//std::cout<<(_looper_Type==AsyncType::ASYNC_UNSAFE);return std::make_shared<AsyncLogger>(_logger_name,_limit_level,_formatter,_sinks,_looper_Type);}return std::make_shared<SyncLogger>(_logger_name,_limit_level,_formatter,_sinks);}};class LoggerManager{public:static LoggerManager &getInstance(){static LoggerManager eton;return eton;}void addLogger(Logger::ptr &Logger){if(hasLogger(Logger->name())) return ;std::unique_lock<std::mutex> lock(_mutex);_loggers.insert(make_pair(Logger->name(),Logger));           }bool hasLogger(std::string name){std::unique_lock<std::mutex> lock(_mutex);auto it=_loggers.find(name);if(it==_loggers.end()) return false;else return true;}Logger::ptr getLogger(const std::string& name){std::unique_lock<std::mutex> lock(_mutex);auto it=_loggers.find(name);if(it==_loggers.end()) return Logger::ptr();else return it->second;}Logger::ptr rootLogger(){return _root_logger;}private:LoggerManager(){std::unique_ptr<dhylog::LoggerBuilder> builder(new dhylog::LocalLoggerBuilder());builder->buildLoggerName("root");_root_logger=builder->build();_loggers.insert(make_pair("root",_root_logger));}private:std::mutex _mutex;Logger::ptr _root_logger;//默认日志器std::unordered_map<std::string,Logger::ptr> _loggers;};class GlobalLoggerBuilder :public LoggerBuilder{public:Logger::ptr build() override{assert(_logger_name.empty()==false);if(_formatter.get()==nullptr){_formatter=std::make_shared<Formatter>();}if(_sinks.empty()){buildSink<StdoutSink>();}Logger::ptr logger;if(_logger_type==LoggerType::LOGGER_ASYNC){               logger=std::make_shared<AsyncLogger>(_logger_name,_limit_level,_formatter,_sinks,_looper_Type);}else{logger=std::make_shared<SyncLogger>(_logger_name,_limit_level,_formatter,_sinks);}LoggerManager::getInstance().addLogger(logger);return logger;}};
}  
#endif      

8.7双缓冲区异步任务处理器(AsyncLooper)设计

设计思想:异步处理线程 + 数据池
使⽤者将需要完成的任务添加到任务池中,由异步线程来完成任务的实际执⾏操作。
任务池的设计思想:双缓冲区阻塞数据池
优势:避免了空间的频繁申请释放,且尽可能的减少了⽣产者与消费者之间锁冲突的概率,提⾼了任务处理效率。

在任务池的设计中,有很多备选⽅案,⽐如循环队列等等,但是不管是哪⼀种都会涉及到锁冲突的情况,因为在⽣产者与消费者模型中,任何两个⻆⾊之间都具有互斥关系,因此每⼀次的任务添加与取出都有可能涉及锁的冲突,⽽双缓冲区不同,双缓冲区是处理器将⼀个缓冲区中的任务全部处理完毕后,然后交换两个缓冲区,重新对新的缓冲区中的任务进⾏处理,虽然同时多线程写⼊也会冲突,但是冲突并不会像每次只处理⼀条的时候频繁(减少了⽣产者与消费者之间的锁冲突),且不涉及到空间的频繁申请释放所带来的消耗。

在这里插入图片描述

#ifndef M_BUF_H
#define M_BUF_H
#include"util.hpp"
#include<vector>
namespace dhylog{#define DEFAULT_BUFFER_SIZE (100*1024*1024)#define THRESHOLD_BUFFER_SIZE (80*1024*1024)#define INCREMENNT_BUFFER_SIZE (10*1024*1024)class Buffer{public:Buffer():_buffer(DEFAULT_BUFFER_SIZE),_writer_idx(0),_reader_idx(0){}//向缓冲区写入数据void push(const char*data,size_t len){//缓冲区剩余空间不够的情况:1.扩容//1.固定大小,则直接返回//if(len>writeAbleSize()) return;//2.动态控件,用于极限性能测试--扩容ensureEnoughSize(len);//1.将数据拷贝到缓冲区std::copy(data,data+len,&_buffer[_writer_idx]);//2.将当前写入位置向后偏移moveWriter(len);  }size_t writeAbleSize(){return (_buffer.size()-_writer_idx);}//返回可读数据得起始地址const char*begin(){return &_buffer[_reader_idx];}//返回可读数据的长度size_t readAbleSize(){return (_writer_idx-_reader_idx);}void moveReader(size_t len){assert(len<=readAbleSize());_reader_idx+=len;}//重置读写位置,初始化缓冲区void reset(){_writer_idx=0;_reader_idx=0;}//对Buffer实现交换操作void swap(Buffer &buffer){_buffer.swap(buffer._buffer);std::swap(_reader_idx,buffer._reader_idx);std::swap(_writer_idx,buffer._writer_idx);}//判断缓冲区是否为空bool empty(){{return (_reader_idx==_writer_idx);}}private://对空间进行扩容void ensureEnoughSize(size_t len){if(len<=writeAbleSize()) return ;size_t new_size=0;if(_buffer.size()<THRESHOLD_BUFFER_SIZE){new_size=_buffer.size()*2;}else {new_size=_buffer.size()+INCREMENNT_BUFFER_SIZE;}_buffer.resize(new_size);}//对读写指针进行向后偏移操作void moveWriter(size_t len){assert((len+_writer_idx)<=_buffer.size());_writer_idx+=len;}private:std::vector<char> _buffer;size_t _reader_idx;//当前可读数据的指针--本质是下标size_t _writer_idx;//当前可写的指针};
}
#endif

8.8 异步⽇志器(AsyncLogger)设计

异步⽇志器类继承⾃⽇志器类, 并在同步⽇志器类上拓展了异步消息处理器。当我们需要异步输出⽇志的时候, 需要创建异步⽇志器和消息处理器, 调⽤异步⽇志器的log、error、info、fatal等函数输出不同级别⽇志。
• log函数为重写Logger类的函数, 主要实现将⽇志数据加⼊异步队列缓冲区中
• realLog函数主要由异步线程进⾏调⽤(是为异步消息处理器设置的回调函数),完成⽇志的实际落地⼯作。

//异步日志器class AsyncLogger : public Logger {public://using ptr = std::shared_ptr<AsyncLogger>;AsyncLogger(const std::string &logger_name,loglevel::value level,Formatter::ptr &formatter,std::vector<LogSink::ptr> sinks,AsyncType looper_type):Logger(logger_name,level,formatter,sinks),_looper(std::make_shared<AsyncLooper>(std::bind(&AsyncLogger::reallog,this,std::placeholders::_1),looper_type)){}protected:void log(const char*data,size_t len)//将数据写入缓冲区{_looper->push(data,len);}//设计一个实际落地函数将缓冲区中的数据落地void reallog(Buffer& buf){if(_sinks.empty()) return;for(auto &sink:_sinks){sink->log(buf.begin(),buf.readAbleSize());}}private:AsyncLooper::ptr _looper;};

8.9 单例⽇志器管理类设计(单例模式)

⽇志的输出,我们希望能够在任意位置都可以进⾏,但是当我们创建了⼀个⽇志器之后,就会受到⽇志器所在作⽤域的访问属性限制。
因此,为了突破访问区域的限制,我们创建⼀个⽇志器管理类,且这个类是⼀个单例类,这样的话,我们就可以在任意位置来通过管理器单例获取到指定的⽇志器来进⾏⽇志输出了。
基于单例⽇志器管理器的设计思想,我们对于⽇志器建造者类进⾏继承,继承出⼀个全局⽇志器建造者类,实现⼀个⽇志器在创建完毕后,直接将其添加到单例的⽇志器管理器中,以便于能够在任何位置通过⽇志器名称能够获取到指定的⽇志器进⾏⽇志输出。

class LoggerManager{public:static LoggerManager &getInstance(){static LoggerManager eton;return eton;}void addLogger(Logger::ptr &Logger){if(hasLogger(Logger->name())) return ;std::unique_lock<std::mutex> lock(_mutex);_loggers.insert(make_pair(Logger->name(),Logger));           }bool hasLogger(std::string name){std::unique_lock<std::mutex> lock(_mutex);auto it=_loggers.find(name);if(it==_loggers.end()) return false;else return true;}Logger::ptr getLogger(const std::string& name){std::unique_lock<std::mutex> lock(_mutex);auto it=_loggers.find(name);if(it==_loggers.end()) return Logger::ptr();else return it->second;}Logger::ptr rootLogger(){return _root_logger;}private:LoggerManager(){std::unique_ptr<dhylog::LoggerBuilder> builder(new dhylog::LocalLoggerBuilder());builder->buildLoggerName("root");_root_logger=builder->build();_loggers.insert(make_pair("root",_root_logger));}private:std::mutex _mutex;Logger::ptr _root_logger;//默认日志器std::unordered_map<std::string,Logger::ptr> _loggers;};

8.10 ⽇志宏&全局接⼝设计(代理模式)

提供全局的⽇志器获取接⼝。

使⽤代理模式通过全局函数或宏函数来代理Logger类的log、debug、info、warn、error、fatal等接⼝,以便于控制源码⽂件名称和⾏号的输出控制,简化⽤⼾操作。

当仅需标准输出⽇志的时候可以通过主⽇志器来打印⽇志。 且操作时只需要通过宏函数直接进⾏输出
即可。

#ifndef _MY_LOG_
#define _MY_LOG_
#include"logger.hpp"namespace dhylog
{//1.提供获取指定日志器的全局接口(避免用户自己操作单例对象)Logger::ptr getLogger(const std::string &name){return dhylog::LoggerManager::getInstance().getLogger(name);}Logger::ptr rootLogger(){return dhylog::LoggerManager::getInstance().rootLogger();}//2.使用宏函数对日志器的接口进行代理(代理模式)#define debug(fmt, ...) debug(__FILE__, __LINE__, fmt, ##__VA_ARGS__)#define info(fmt, ...) info(__FILE__, __LINE__, fmt, ##__VA_ARGS__)#define warn(fmt, ...) warn(__FILE__, __LINE__, fmt, ##__VA_ARGS__)#define error(fmt, ...) error(__FILE__, __LINE__, fmt, ##__VA_ARGS__)#define fatal(fmt, ...) fatal(__FILE__, __LINE__, fmt, ##__VA_ARGS__)//3.提供宏函数,直接通过默认日志器进行日志标准输出打印(不用获取日志器了)#define DEBUG(fmt,...) dhylog::rootLogger()->debug(fmt, ##__VA_ARGS__)#define INFO(fmt,...)  dhylog::rootLogger()->info(fmt, ##__VA_ARGS__)#define WARN(fmt,...)  dhylog::rootLogger()->warn(fmt, ##__VA_ARGS__)#define ERROR(fmt,...) dhylog::rootLogger()->error(fmt, ##__VA_ARGS__)#define FATAL(fmt,...) dhylog::rootLogger()->fatal(fmt, ##__VA_ARGS__)
}
#endif

9. 功能⽤例

#include "../logs/dhylog.hpp"
#include"unistd.h"
void test_log(const std::string &name)
{INFO("%s", "测试开始");dhylog::Logger::ptr logger=dhylog::LoggerManager::getInstance().getLogger(name);logger->debug( "%s", "测试日志");logger->info( "%s", "测试日志");logger->error("%s", "测试日志");logger->warn( "%s", "测试日志");logger->fatal("%s", "测试日志");INFO( "%s", "测试结束");
}
int main()
{std::unique_ptr<dhylog::LoggerBuilder> builder(new dhylog::GlobalLoggerBuilder());builder->buildLoggerName("async_logger");builder->buildLoggerLevel(dhylog::loglevel::value::WARN);builder->buildFormatter("[%c][%f:%l]%m%n");builder->buildLoggerType(dhylog::LoggerType::LOGGER_ASYNC);//builder->buildEnableUnSafeAsync();builder->buildSink<dhylog::FileSink>("./logfile/async.log");builder->buildSink<dhylog::StdoutSink>();builder->buildSink<dhylog::RollSink>("./logfile/roll-async-by-size",1024*1024);builder->build();test_log("async_logger");return 0;
}

10.性能测试

下⾯对⽇志系统做⼀个性能测试,测试⼀下平均每秒能打印多少条⽇志消息到⽂件。
主要的测试⽅法是:每秒能打印⽇志数 = 打印⽇志条数 / 总的打印⽇志消耗时间
主要测试要素:同步/异步 & 单线程/多线程
• 100w+条指定⻓度的⽇志输出所耗时间
• 每秒可以输出多少条⽇志
• 每秒可以输出多少MB⽇志
测试环境
在这里插入图片描述

#include "../logs/dhylog.hpp"
#include<vector>
#include<thread>
#include<chrono>void bench(const std::string &logger_name,size_t thr_count,size_t msg_count,size_t msg_len)
{dhylog::Logger::ptr logger=dhylog::getLogger(logger_name);if(logger.get()==nullptr){return;}std::cout<<"测试日志:"<<msg_count<<"条,总大小:"<<(msg_count*msg_len)/1024<<"kb\n";std::string msg(msg_len-1,'A');std::vector<std::thread> threads;std::vector<double> cost_arry(thr_count);size_t msg_per_thr=msg_count/thr_count;for(int i=0;i<thr_count;i++){threads.emplace_back([&,i](){auto start=std::chrono::high_resolution_clock::now();for(int j=0;j<msg_per_thr;j++){logger->fatal("%s",msg.c_str());}auto end=std::chrono::high_resolution_clock::now();std::chrono::duration<double> cost=end-start;cost_arry[i]=cost.count();std::cout<<"线程"<<i<<":"<<"\t输出数量"<<msg_per_thr<<",耗时:"<<cost.count()<<"s"<<std::endl;});}for(int i=0;i<thr_count;i++){threads[i].join();}double max_cost=cost_arry[0];for(int i=0;i<thr_count;i++) max_cost=max_cost<cost_arry[i]? cost_arry[i]:max_cost;size_t msg_per_sec=msg_count/max_cost;size_t size_per_sec=(msg_count*msg_len)/(max_cost*1024);std::cout<<"\t总耗时:"<<max_cost<<"s\n";std::cout<<"\t每秒输出日志数量:"<<msg_per_sec<<"条\n";std::cout<<"\t每秒输出日志大小:"<<size_per_sec<<"KB\n";
}
void sync_bench()
{std::unique_ptr<dhylog::LoggerBuilder> builder(new dhylog::GlobalLoggerBuilder());builder->buildLoggerName("sync_logger");builder->buildFormatter("%m%n");builder->buildLoggerType(dhylog::LoggerType::LOGGER_SYNC);builder->buildSink<dhylog::FileSink>("./logfile/sync.log");builder->build();bench("sync_logger",1,1000000,100);}
void async_bench()
{std::unique_ptr<dhylog::LoggerBuilder> builder(new dhylog::GlobalLoggerBuilder());builder->buildLoggerName("async_logger");builder->buildFormatter("%m%n");builder->buildLoggerType(dhylog::LoggerType::LOGGER_ASYNC);builder->buildEnableUnSafeAsync();builder->buildSink<dhylog::FileSink>("./logfile/async.log");builder->build();bench("async_logger",3,1000000,100);}
int main()
{sync_bench();//async_bench();return 0;}

在这里插入图片描述

在这里插入图片描述

11.参考文献

参考资料
https://www.imangodoc.com/174918.html
https://blog.csdn.net/w1014074794/article/details/125074038
https://zhuanlan.zhihu.com/p/472569975
https://zhuanlan.zhihu.com/p/460476053
https://gitee.com/davidditao/DDlog
https://www.cnblogs.com/ailumiyana/p/9519614.html
https://gitee.com/lqk1949/plog/
https://www.cnblogs.com/horacle/p/15494358.html
https://blog.csdn.net/qq_29220369/article/details/127314390

相关文章:

C++ - 基于多设计模式下的同步异步⽇志系统

1.项目介绍 项⽬介绍 本项⽬主要实现⼀个⽇志系统&#xff0c; 其主要⽀持以下功能: • ⽀持多级别⽇志消息 • ⽀持同步⽇志和异步⽇志 • ⽀持可靠写⼊⽇志到控制台、⽂件以及滚动⽂件中 • ⽀持多线程程序并发写⽇志 • ⽀持扩展不同的⽇志落地⽬标地 2.开发环境 • Cent…...

git 相关内容

...

ElasticSearch(es)倒排索引

目录 一、ElasticSearch 二、倒排索引 1. 正向索引 2. 倒排索引 具体细节 1. 文档分析 2. 索引构建 3. 索引存储 4. 词条编码 5. 索引优化 6. 查询处理 示例 总结 3. 正向和倒排 三、总结 倒排索引的基本概念 为什么倒排索引快 一、ElasticSearch Elasticsear…...

【自然语言处理】概论(一):自然语言处理概要

1.1 概论&#xff1a;&#xff08;一&#xff09;自然语言处理概要 知识点 自然语言的定义&#xff1a;人类交流使用的&#xff0c;包括口语和书面语的信息交流方式。AI的终极目标&#xff1a;使计算机具备理解&#xff08;听、读&#xff09;和生成&#xff08;说、写&#…...

flask 开始

# 导入flask类 from flask import Flask,request,render_template # 使用flask类来创建一个app对象 # __name__ 代表当前app.py 这个模块 app Flask(__name__) # 创建一个路由和视图函数的映射 url http://127.0.0.1:5000/ app.route("/") def hello_word():return …...

仕考网:公务员可以报考军队文职吗?

公务员可以报考军队文职考试&#xff0c;但是需要满足前提条件。 对于已经与国家、地方的用人单位建立劳动关系的社会人才&#xff0c;在获得当前用人单位的许可后才可以申请报考。 在面试过程中&#xff0c;考生必须出示一份由其用人单位出具的且加盖公章的同意报考证明。一…...

Java整理22

1、动态sql 多条件查询 .xml配置文件中sql语句书写<select id"getEmpByCondition",resultType"Emp">select * from t_emp where <if test"empName ! null and empName! ">empName#{empName}</if><if test"age ! nul…...

leetcode 408周赛 3234. 统计 1 显著的字符串的数量

3234. 统计 1 显著的字符串的数量 题目描述 给你一个二进制字符串 s。 请你统计并返回其中 1 显著 的子字符串的数量。 如果字符串中 1 的数量 大于或等于 0 的数量的 平方&#xff0c;则认为该字符串是一个 1 显著 的字符串 。 思路 一个很显然的思路是&#xff0c;我们…...

容器对比虚拟机有哪些不足?

引言 在当今的云计算和微服务架构中&#xff0c;容器技术已成为不可或缺的一部分。它以其轻量级、高效和快速部署的特性&#xff0c;赢得了广大开发者和运维人员的青睐。然而&#xff0c;正如任何技术都有其两面性&#xff0c;容器技术也不例外。本文将对容器技术在安全性、隔离…...

C# 归并排序

栏目总目录 概念 归并排序是一种分而治之的排序算法。它将一个大数组分成两个小数组&#xff0c;递归地对这两个小数组进行排序&#xff0c;然后将排序好的小数组合并成一个有序的大数组。这个过程一直递归进行&#xff0c;直到数组被拆分成只有一个元素的数组&#xff08;自然…...

【请求代理】springboot单机服务基于过滤器Filter实现第三方服务器接口请求代理功能

springboot单机服务基于过滤器Filter实现第三方服务器接口请求代理功能 一、前言二、解决思路三、基于gateway实现四、基于过滤器Filter实现五、问题总结 **注&#xff1a;本文源码获取或者更多资料&#xff0c;关注公众号&#xff1a;技术闲人**一、前言 在项目开发时会遇到w…...

.NET Core异步编程与多线程解析:提升性能与响应能力的关键技术

在.NET Core中&#xff0c;异步编程和多线程是构建高性能应用程序的核心技能。理解这两个概念不仅可以提升应用程序的响应能力&#xff0c;还能优化资源使用。本文将深入剖析异步编程和多线程的关键知识点&#xff0c;提供代码示例&#xff0c;并附上步骤以帮助理解。 1. 异步…...

Photoshop(PS) 抠图简单教程

目录 快速选择 魔棒 钢笔 橡皮擦 蒙版 通道 小结 可以发现&#xff0c;ps逐渐成为必备基础的办公软件。本文让ps新手轻松学会抠图。 快速选择 在抠图之前&#xff0c;先了解下选区的概念。ps中大多数的抠图操作都是基于选区的&#xff0c;先选区再Ctrl J提取选区。而快…...

项目管理中的常用工件(二):可视化工件

项目管理中的常用工件&#xff08;二&#xff09;&#xff1a;可视化工件 亲和图&#xff08;affinity diagram&#xff09;因果图&#xff08;cause-and-effect diagram&#xff09;直方图&#xff08;histogram&#xff09;流程图&#xff08;flowchart&#xff09;散点图&am…...

Git入门与实战:版本控制的艺术

&#x1f341; 作者&#xff1a;知识浅谈&#xff0c;CSDN签约讲师&#xff0c;CSDN博客专家&#xff0c;华为云云享专家&#xff0c;阿里云专家博主 &#x1f4cc; 擅长领域&#xff1a;全栈工程师、爬虫、ACM算法 &#x1f525; 微信&#xff1a;zsqtcyw 联系我领取学习资料 …...

[Mysql-DML数据操作语句]

目录 数据增加&#xff1a;INSERT 全字段插入&#xff1a; 部分字段插入&#xff1a; 一次性添加多条&#xff1a; 数据修改&#xff1a;UPDATE 数据删除&#xff1a;DELECT delete truncate drop 区别 数据增加&#xff1a;INSERT 总体格式&#xff1a;insert into 表…...

Tableau入门|数据可视化与仪表盘搭建

原视频链接&#xff08;up:戴戴戴师兄&#xff09;&#xff0c;文章为笔者的自学笔记&#xff0c;用于复习回顾&#xff0c;原视频下方有原up整理的笔记&#xff0c;更加直观便捷。因为视频中间涉及的细节较多&#xff0c;建议一边操作&#xff0c;一边学习。 整体介绍 可视化…...

API 技术开发分享:连接电商平台数据获取的桥梁

在当今数字化的时代&#xff0c;API&#xff08;Application Programming Interface&#xff0c;应用程序编程接口&#xff09;技术成为了实现不同系统之间通信和数据交换的关键。它就像是一座无形的桥梁&#xff0c;使得各种应用能够相互协作&#xff0c;共享资源&#xff0c;…...

区块链如何助力数字版权保护和内容创作者的权益?

区块链技术可以助力数字版权保护和内容创作者的权益&#xff0c;主要有以下几个方面&#xff1a; 去中心化的版权登记和溯源&#xff1a;区块链可作为一个可信的去中心化数据库&#xff0c;记录并验证数字内容的版权信息。内容创作者可以将自己的作品信息存储在区块链上&#x…...

记一次老旧项目的整体技术升级

最近给公司采购的老旧的 node8 vue2.6 webpack3 npm 项目做构建优化 背景&#xff1a;整个项目 build 一次 20 min &#xff0c;本地冷启动和热更新也忒慢&#xff0c;依赖 npm i 一下也得装个 20 min 众所周知&#xff0c;Node 版本&#xff0c;依赖包管理工具 和 构建工…...

2024年最受欢迎的五大上网审计设备和软件

在2024年的市场上&#xff0c;上网行为审计设备和软件种类繁多&#xff0c;它们帮助企业监控和管理员工的网络活动&#xff0c;确保网络安全并提高工作效率。下面是一些受欢迎的上网行为审计设备和软件。 2024年最受欢迎的上网行为审计设备和软件如下 1.安企神软件&#xff1a…...

sed利用脚本处理文件

一、sed是什么 sed 命令是利用脚本来处理文本文件。它可以依照脚本的指令来处理、编辑文本文件。主要用来自动编 辑一个或多个文件、简化对文件的反复操作、编写转换程序等。 二、sed的原理 读入新的一行内容到缓存空间&#xff1b; 从指定的操作指令中取出第一条指令&…...

泰山派RK3566开发板800x1280MIPI屏设备树补丁

泰山派RK3566开发板800x1280MIPI屏设备树补丁 泰山派下800 X 1280分辨率MIPI屏调试&#xff0c;设备树补丁如下&#xff1a; https://download.csdn.net/download/qq_45143522/89584066 用kernel.patch文件&#xff0c;在泰山派内核源码下打补丁即可完成更新&#xff0c;或者…...

informer中的indexer机制的实现分析与源码解读

1. 背景 client-go工具下的tools/cache.indexer为informer提供缓存与索引的能力。可以实现快速通过索引找到对应的对象(pod, deployment,secret,configmap等)。 indexer再informer机制中的使用图示&#xff1a; indexer包括2部分: 一部分是store用于实际数据的存储&#xff0c;…...

英特尔宣布针对对Llama 3.1进行优化 以提升所有产品的性能

日前Meta正式发布了Llama 3.1开源大模型&#xff0c;以其庞大的参数量和卓越性能&#xff0c;首次在多项基准测试中击败了GPT-4o等业界领先的闭源模型。允许开发者自由地进行微调、蒸馏&#xff0c;甚至在任何地方部署&#xff0c;这种开放性为AI技术的普及和创新提供了无限可能…...

Python3网络爬虫开发实战(1)爬虫基础

一、URL 基础 URL也就是网络资源地址&#xff0c;其满足如下格式规范 scheme://[username:password]hostname[:port][/path][;parameters][?query][#fragment] scheme&#xff1a;协议&#xff0c;常用的协议有 Http&#xff0c;https&#xff0c;ftp等等&#xff1b;usern…...

Redis的五种数据类型与命令

目录 引言 一 Redis的特性 二 Redis的安装 三 Redis的优点 四 Redis的五种数据类型与命令 五 Redis的配置文件 引言 Redis是什么&#xff1f; Remote Dictionary Service(远程字典服务器) Redis 是一个开源的(BSD许可)的&#xff0c;C语言编写的&#xff0c;高性能的数…...

RocketMQ的详细讲解(四种mq的对比(activeMq、rabbitmq、rocketmq、kafka))

20240729 RocketMQ1 mq的三大作用 异步、削峰限流、解耦合2. 四种mq的对比&#xff08;activeMq、rabbitmq、rocketmq、kafka&#xff09;3 rocketmq特点1. 平台无关2. 能提供什么样的功能 4 rocketMq4.1 broker中的标题&#xff0c;来约束读和写4.2 rocketmq的结构4.3 读和写的…...

除了GPT,还有哪些好用的AI工具?

最强AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频百万播放量https://aitools.jurilu.com/ 多得很&#xff0c;这20个免费的国产AI工具&#xff0c;打工人必备&#xff0c;除了比chatGPT好用&#xff0c;甚至还可以用来变现…...

04 | 深入浅出索引(上)

此系列文章为极客时间课程《MySQL 实战 45 讲》的学习笔记&#xff01; 索引的常见模型 可以提供查询效率的数据结构有很多&#xff0c;常见的有三种&#xff1a;哈希表、有序数组、搜索数。 哈希表是一种以 key-value 形式存储的数据结构。输入一个 key&#xff0c;通过固定…...

Linux的yum源安装MySQL5.7

linux的yum源安装MySQL5.7 一、MySQL 1、简介 MySQL 是一种流行的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;由瑞典公司 MySQL AB 开发&#xff0c;后来被 Oracle Corporation 收购。它是一个开源软件&#xff0c;提供了高效、稳定和可靠的数据管理解决…...

基于深度学习的音频自监督学习

基于深度学习的音频自监督学习&#xff08;Self-Supervised Learning, SSL&#xff09;是一种利用未标注的音频数据&#xff0c;通过设计自监督任务进行特征学习的方法。这种方法在需要大量标注数据的音频处理任务&#xff08;如语音识别、情感分析等&#xff09;中&#xff0c…...

用uniapp 及socket.io做一个简单聊天app1

####相关的表结构&#xff0c;用的是mysql 用户表&#xff08;Users&#xff09; 存储用户的基本信息。 CREATE TABLE Users (id INT AUTO_INCREMENT PRIMARY KEY,username VARCHAR(50) NOT NULL UNIQUE,password VARCHAR(100) NOT NULL,email VARCHAR(100) UNIQUE,created_a…...

在Postman中引用JS库

前言 在做接口测试时&#xff0c;出于安全因素&#xff0c;请求参数需要做加密或者加上签名才能正常请求&#xff0c;例如&#xff1a;根据填写的请求参数进行hash计算进行签名。postman作为主流的接口调试工具也是支持请求预处理的&#xff0c;即在请求前使用JavaScript脚本对…...

学习笔记-系统框图简化求传递函数公式例题

简化系统结构图求系统传递函数例题 基础知识回顾 第四讲 控制系统的方框图 (zhihu.com) 「自控原理」2.3 方框图的绘制及化简_方框图化简-CSDN博客 自动控制原理笔记-结构图及其等效变换_结构图等效变换-CSDN博客 例子一 「自控原理」2.3 方框图的绘制及化简_方框图化简-CS…...

postgrsql——事务概述

事务概述 事务的特性 原子性&#xff08;Atomicity&#xff09;&#xff1a; 事务被视为一个整体&#xff0c;其中的操作要么全部执行成功&#xff0c;要么全部不执行&#xff0c;即不存在部分执行的情况。这确保了事务的完整性和一致性。一致性&#xff08;Consistency&…...

1.Spring Boot 简介(Spring MVC+Mybatis-plus)

文章目录 一&#xff0c;Spring Boot 简介二&#xff0c;搭建springboot项目并整合mybatis-plus框架1.pom导依赖2.添加启动项3.配置文件.yml 三&#xff0c;springboot集成 Spring MVC1.springmvc定义2.应用注解 一&#xff0c;Spring Boot 简介 SpringBoot是Spring的子工程(或…...

《计算机网络》(学习笔记)

目录 一、计算机网络体系结构 1.1 计算机网络概述 1.1.1 计算机网络的概念 1.1.2 计算机网络的组成 1.1.3 计算机网络的功能 1.1.4 电流交换、报文交换和分组交换 1.1.5 计算机网络的分类 1.1.6 计算机网络的性能指标 1.2 计算机网络体系结构与参考模型 1.2.1 计算机…...

指针函数和函数指针

函数名在表达式中应该如何被解读&#xff1f;答&#xff1a;函数名可以在表达式中被解读成“指向该函数的指针”。 函数指针和指针函数有什么区别&#xff1f;答&#xff1a;函数指针是一个指向函数的指针&#xff1b;指针函数是一个返回指针变量的函数。 一个函数能否有时候…...

Elasticsearch跨集群搜索

Elasticsearch&#xff08;简称ES&#xff09;是一种基于Lucene的搜索引擎&#xff0c;以其高性能、可扩展性和实时搜索能力而广受欢迎。在大型分布式系统中&#xff0c;跨集群搜索成为了一个重要的需求&#xff0c;它允许用户从多个Elasticsearch集群中联合查询数据&#xff0…...

基于FPGA的数字信号处理(19)--行波进位加法器

1、10进制加法是如何实现的&#xff1f; 10进制加法是大家在小学就学过的内容&#xff0c;不过在这里我还是帮大家回忆一下。考虑2个2位数的10进制加法&#xff0c;例如&#xff1a;15 28 43&#xff0c;它的运算过程如下&#xff1a; 个位两数相加&#xff0c;结果为5 8 1…...

树莓派下,centos7操作系统, TensorFlow java版实现植物分类功能

在树莓派上运行CentOS 7,并使用TensorFlow Java版本实现植物分类功能可以通过以下步骤实现。以下是详细的指导: 一、安装和设置环境 1. 更新系统并安装基本工具 确保你的CentOS 7系统是最新的,并安装必要的工具: sudo yum update -y sudo yum install -y wget unzip gi…...

开源一个react路由缓存库

Github仓库 背景 产品希望可以像浏览器那样每打开一个路由&#xff0c;会多一个tab&#xff0c;用户可以切换tab访问之前加载过的页面&#xff0c;且不会重新加载。真就产品一句话…… Github上有轮子了吗 Github上开箱即用的轮子是基于react-router-dom V5实现的&#xff…...

go-kratos 学习笔记(7) 服务发现服务间通信grpc调用

服务发现 Registry 接口分为两个&#xff0c;Registrar 为实例注册和反注册&#xff0c;Discovery 为服务实例列表获取 创建一个 Discoverer 服务间的通信使用的grpc&#xff0c;放到data层&#xff0c;实现的是从uses服务调用orders服务 app/users/internal/data.go 加入 New…...

SPSS个人版是什么软件

SPSS是一款数据统计、分析软件&#xff0c;它由IBM公司出品&#xff0c;这款软件平台提供了文本分析、大量的机器学习算法、数据分析模型、高级统计分析功能等&#xff0c;软件易学且功能非常强大&#xff0c;可以使用SPSS制作图表&#xff0c;例如柱状、饼状、折线等图表&…...

Minos 多主机分布式 docker-compose 集群部署

参考 docker-compose搭建多主机分布式minio - 会bk的鱼 - 博客园 (cnblogs.com) 【运维】docker-compose安装minio集群-CSDN博客 Minio 是个基于 Golang 编写的开源对象存储套件&#xff0c;虽然轻量&#xff0c;却拥有着不错的性能 中文地址&#xff1a;MinIO | 用于AI的S3 …...

Unity + Hybridclr + Addressable + 微信小程序 热更新报错

报错时机&#xff1a; Generate All 怎么All 死活就是报错 生成微信小程序&#xff0c;并启动后 报错内容&#xff1a; MissingMethodException:AoT generic method notinstantiated in aot.assembly:Unity.ResourceManager:dll, 原因&#xff1a; Hybridclr 开发文档 解…...

鸿蒙开发—黑马云音乐之Music页面

目录 1.外层容器效果 2.信息区-发光效果 3.信息区-内容布局 4.播放列表布局 5.播放列表动态化 6.模拟器运行并配置权限 效果&#xff1a; 1.外层容器效果 Entry Component export struct MuiscPage {build() {Column() {// 信息区域Column() {}.width(100%)// .backgroun…...

IsaacLab | 如何在Manipulation任务中添加新的目标(target)

如是我闻&#xff1a; 终于让我给摸索出来了&#xff0c;在这里描述一下问题场景。 假使说我们有一个机械臂操作的任务&#xff0c;这样婶的 Isaac Lab | Push 我们想做多目标的任务&#xff0c;这时候需要向环境中添加第二个目标&#xff0c;像这样 Isaac Lab | Add target 那…...

【Python从入门到进阶】61、Pandas中DataFrame对象的操作(二)

接上篇《60、Pandas中DataFrame对象的操作&#xff08;一&#xff09;》 上一篇我们讲解了DataFrame对象的简介、基本操作及数据清洗相关的内容。本篇我们来继续讲解DataFrame对象的统计分析、可视化以及数据导出与保存相关内容。 一、DataFrame的统计分析 在数据分析和处理中…...