WebServer -- 日志系统(下)

目录

🌼整体思路

🎂基础API

fputs

可变参数宏 __VA_ARGS__

fflush

🚩流程图与日志类定义

流程图

日志类定义

🌼功能实现

生成日志文件 && 判断写入方式

日志分级与分文件


🌼整体思路

日志系统分两部分,

一:单例模式与阻塞队列的定义

二:日志类的定义与使用

这里介绍日志类的定义与使用,具体涉及基础API,流程图与日志类的定义,功能实现

  • 基础API
    描述 fputs,可变参数宏__VA__ARGS__,fflush
  • 流程图与日志类定义
    描述日志系统整体运行流程,介绍日志类的具体定义
  • 功能实现
    结合代码分析同步 / 异步写文件逻辑,分析超行,按天分文件和日志分级的具体实现

🎂基础API

更好的源码阅读体验~

fputs

#include<stdio.h>
int fputs(const char *str, FILE *stream);
  • str,一个数组,包含要写入的   以空字符终止的字符序列
  • stream,指向FILE对象的指针,该FILE对象标识了要被写入字符串的流

可变参数宏 __VA_ARGS__

__VA_ARGS__,可变参数的宏,定义宏时,定义中参数列表的最后一个参数为省略号,实际使用中有时会加##,有时不加

myprintf("CSDN zhanghm1995")
-> printf("CSDN zhanghm1995")
myprintf("CSDN zhanghm1995 is %d years", 2)
-> printf("CSDN zhanghm1995 is %d years", 2)
// 最简单的定义
#define my_print1(...) printf(__VA_ARGS__)// 搭配va_list的format使用
#define my_print2(format, ...) printf(format, __VA_ARGS__)
#define my_print3(format, ...) printf(format, ##__VA_ARGS__)

__VA_ARGS__ 宏前面加上 ## 的作用:

当可变参数个数为 0,这里的 printf 参数列表中的 ## 回把前面多余的 , 去掉

否则会编译出错

建议用第二种带 ## 的,程序更加健壮

补充解释👇

C++中可变参数宏定义用法实践_c++可变宏-CSDN博客

  1. 处理任意数量的参数:可变参数宏可以接受不定数量的参数,使得宏在调用时可以传入任意数量的参数。

  2. 使用省略号表示:在宏定义中,使用省略号...表示可变数量的参数,这样就可以在宏中处理不确定数量的实参。

  3. 利用递归或辅助函数处理参数:为了处理可变数量的参数,通常会结合使用递归函数或辅助函数来逐个处理每个参数。

  4. 宏展开后形成单一代码块:为避免在宏展开时产生意外的行为,通常在宏定义中使用do...while(0)结构,将宏展开后形成单一的代码块。

  5. 提高代码复用性:通过可变参数宏,可以实现一次宏定义,多处调用,提高代码的复用性和可维护性。

#include <iostream>  // 包含输入输出流库
#include <sstream>   // 包含字符串流库// 定义一个可变参数宏 PRINT,用于打印任意数量的参数
/*
定义 PRINT 宏,使用反斜杠表示换行继续宏定义
定义宏展开后的代码块开始
创建一个字符串流对象
用辅助函数处理可变参数并将结果存入字符串流
将字符串流中的内容输出到标准输出流
宏展开后的代码块结束,用 do...while(0) 结构避免宏在使用时产生意外的行为
*/
#define PRINT(...) \ 
do { \  std::stringstream ss; \  print_helper(ss, __VA_ARGS__); \  std::cout << ss.str() << std::endl; \  
} while(0)  // 辅助函数,用于将可变参数格式化为字符串
template <typename T>
void print_arg(std::ostream& os, const T& arg) {  // 辅助函数,将单个参数输出到指定输出流os << arg;  // 输出参数到指定输出流
}// 递归模板函数,将多个参数格式化为字符串
template <typename T, typename... Args>
void print_arg(std::ostream& os, const T& firstArg, const Args&... args) {  // 递归模板函数,处理多个参数的输出os << firstArg << " ";  // 输出第一个参数到指定输出流,并添加空格分隔print_arg(os, args...);  // 递归调用自身,处理剩余参数
}// 模板函数,调用递归函数开始处理多个参数
template <typename... Args>
void print_helper(std::ostream& os, const Args&... args) {  // 模板函数,调用递归函数开始处理多个参数print_arg(os, args...);  // 调用递归函数开始处理多个参数
}int main() {// 使用 PRINT 宏输出多个参数PRINT("Hello, ", "World!");  // 使用 PRINT 宏输出多个参数PRINT("The answer is", 42, "and", 3.14);  // 使用 PRINT 宏输出多个参数return 0;  // 返回执行成功
}
Hello,  World!
The answer is 42 and 3.14

fflush

#include<stdio.h>
int fflush(FILE *stream);

fflush() 会强迫将 缓冲区 的数据,写回参数 stream 指定的文件中,如果参数 stream 为 NULL,fflush() 会将所有打开的文件数据更新

在使用多个输出函数,连续进行多次输出到控制台时,有可能下一个数据的上一数据还没输出完毕,还在输出缓冲区中时,下一个 printf 就把另一个数据加入输出缓冲区,结果冲掉了原来的数据,出现错误

在 printf() 后加上 fflush(stdout); 强制马上输出到控制台,可以避免上述错误

🚩流程图与日志类定义

流程图

  • 日志文件
    • 局部变量的懒汉模式获取实例
    • 生成日志文件,并判断同步和异步写入方式
  • 同步
    • 判断是否分文件
    • 直接格式化输出内容,将信息写入日志文件
  • 异步
    • 判断是否分文件
    • 格式化输出内容,将内容写入阻塞队列,创建一个写线程,从阻塞队列取出内容写入日志文件

下面的图,是我在这个网站上画的👇 比语雀好用一丢丢

Excalidraw | Hand-drawn look & feel • Collaborative • Secure

日志类定义

通过局部变量的懒汉单例模式,创建日志实例,对其进行初始化生成日志文件后,格式化输出内容,并根据不同的写入方式,完成对应逻辑,写入日志文件

日志类包括以下方法👇

  • 公有的实例获取方法
  • 初始化日志文件方法
  • 异步日志写入方法,内部调用私有异步方法
  • 内容格式化方法
  • 刷新缓冲区
  • ....
class Log
{
public:// C++11以后,使用局部变量懒汉不用加锁static Log *get_instance() // 获取Log类的唯一实例{static Log instance; // 静态局部变量,保证线程安全的懒汉单例模式return &instance; // 返回实例指针}// 可选的参数有 日志文件,日志缓冲区大小,最大行数,// 和 最长日志条队列bool init(const char *file_name, int log_buf_size = 8192,int split_lines = 5000000, int max_queue_size = 0); // 初始化日志系统// 异步写日志公有方法,调用私有方法 async_write_logstatic void *flush_log_thread(void *args) // 刷新日志的线程函数{// Log类的唯一实例指针// 类内访问静态成员,也需要声明作用域 ::Log::get_instance()->async_write_log(); // 调用日志实例的异步写日志方法}// 将输出内容按照标准格式整理// ... 表示 可变参数列表void write_log(int level, const char *format, ...); // 写日志方法// 强制刷新缓冲区void flush(void); // 强制刷新日志缓冲private:Log(); // 构造函数virtual ~Log(); // 虚析构函数// 异步写日志方法void *async_write_log() // 异步写日志方法{string single_log; // 单条日志字符串// 从阻塞队列中取出一条日志内容,写入文件while (m_log_queue->pop(single_log)) // 从阻塞队列中取出日志{m_mutex.lock(); // 加锁fputs(single_log.c_str(), m_fp); // 将日志内容写入文件m_mutex.unlock(); // 解锁}}private:char dir_name[128]; // 路径名char log_name[128]; // log文件名int m_split_lines; // 日志最大行数int m_log_buf_size; // 日志缓冲区大小long long m_count; // 日志行数记录int m_today; // 按天分文件,记录当前时间哪一天FILE *m_fp; // 打开log的文件指针char *m_buf; // 要输出的内容block_queue<string> *m_log_queue; // 阻塞队列bool m_is_async; // 是否同步标志位locker m_mutex; // 同步类
};// 可变参数宏定义
// 以下,4 个宏定义在其他文件使用,主要用于不同类型的日志输出// 调试日志输出宏
#define LOG_DEBUG(format, ...) Log::get_instance()->write_log(0, format, __VA_ARGS__)
// 信息日志输出宏
#define LOG_INFO(format, ...) Log::get_instance()->write_log(1, format, __VA_ARGS__)
// 警告日志输出宏
#define LOG_WARN(format, ...) Log::get_instance()->write_log(2, format, __VA_ARGS__)
// 错误日志输出宏
#define LOG_ERROR(format, ...) Log::get_instance()->write_log(3, format, __VA_ARGS__)#endif

日志类中的方法都不会被其他程序直接调用,末尾的四个可变参数宏,提供了其他程序的调用方法

前述方法对日志等级进行分类,包括DEBUG,INFO,WARN 和 ERROR 四种级别的日志

🌼功能实现

init() 函数实现日志创建,写入方式的判断

write_log() 函数完成写入日志文件中的具体内容,实现  日志分级,分文件,格式化输出内容

生成日志文件 && 判断写入方式

通过单例模式获取唯一的日志类,调用 init() 方法,初始化生成日志文件,服务器启动按当前时刻创建日志,前缀为时间,后缀为自定义 log 文件名,并记录创建日志的时间 day 和 行数 count

写入方式通过初始化时,是否设置队列大小(表示在队列中可以放几条数据)来判断,若队列大小为 0,则为同步,否则为异步

// 异步需要设置阻塞队列的长度,同步不需要设置
bool Log::init(const char *file_name, int log_buf_size,int split_lines, int max_queue_size)
{// 如果设置了 max_queue_size,则设置为 异步if (max_queue_size >= 1) {// 设置写入方式 flagm_is_async = true;// 创建并设置阻塞队列长度m_log_queue = new block_queue<string>(max_queue_size);pthread_t tid;// flush_log_thread 为回调函数,这里表示创建线程异步写日志pthread_create(&tid, NULL, flush_log_thread, NULL);}// 输出内容长度m_log_buf_size = log_buf_size;m_buf = new char[m_log_buf_size];memset(m_buf, '/0', sizeof(m_buf));// 日志最大行数m_split_lines = split_lines;time_t t = time(NULL);  // 获取当前时间struct tm *sys_tm = localtime(&t);  // 获取本地时间struct tm my_tm = *sys_tm;  // 复制本地时间// 从后往前找到第一个 / 的位置const char *p = strrchr(file_name, '/');  // 查找最后一个斜杠的位置char log_full_name[256] = {0};  // 初始化日志文件名数组// 相当于自定义日志名// 若输入的文件名没有 /,则直接将时间 + 文件名作为日志名if (p == NULL) snprintf(log_full_name, 255, "%d_%02d_%02d_%s",  // 格式化生成日志文件名my_tm.tm_year + 1900, my_tm.tm_mon + 1,my_tm.tm_mday, file_name);else {// 将 / 的位置向后移动一个位置,然后复制到 logname 中// p - file_name + 1 是文件所在路径文件夹的长度// dirname 相当于 ./strcpy(log_name, p + 1);  // 复制文件名部分strncpy(dir_name, file_name, p - file_name + 1);  // 复制路径部分// 后面的参数跟 format 有关snprintf(log_full_name, 255, "%s%d_%02d_%02d_%s",  // 格式化生成日志文件名dir_name, my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday, log_name);}m_today = my_tm.tm_mday;  // 记录今天日期m_fp = fopen(log_full_name, "a");  // 以追加模式打开日志文件if (m_fp == NULL)  // 如果打开文件失败return false;return true;
}

代码解释👇

这段代码是日志系统的初始化函数,根据传入的参数初始化日志系统

  1. 异步和同步模式选择

    • 通过判断传入的 max_queue_size 参数是否大于等于1 来确定日志系统是以同步模式还是异步模式运行
    • 如果 max_queue_size 大于等于1,则表示使用异步模式,会创建一个阻塞队列用于存储日志消息,并启动一个线程异步写日志
  2. 日志文件相关设置

    • 初始化日志系统的缓冲区大小为 log_buf_size
    • 设置日志分割的最大行数为 split_lines
  3. 生成日志文件名

    • 获取当前时间 t,并将其转换成本地时间 sys_tm
    • 判断传入的 file_name 是否包含路径分隔符 /,若无则直接以当前日期和传入的文件名作为日志文件名;若有,则从路径中提取出文件名和路径部分
    • 根据日期、文件名和路径信息构建完整的日志文件名 log_full_name
  4. 记录今天的日期

    • 将当天的日期存储在变量 m_today 中,用于后续判断是否需要切换日志文件
  5. 打开日志文件

    • 使用追加模式打开生成的日志文件,如果打开失败则返回 false

日志分级与分文件

日志分级的实现,一般提供 5 种级别👇

  • Debug,调试代码时输出,系统实际运行时一般不使用
  • Warn,这种警告与调试时终端的 warning 类似,同样是调试代码时使用
  • Info,报告系统当前状态,当前执行的流程或接收的信息
  • Error 和 Fatal,输出系统的错误信息

上述方法,会根据开发具体情况改变

TinyWebServer 中,给出了除Fatal外的四种分级,实际使用了 Debug,Info 和 Error

超行,按天分文件 的逻辑具体是👇

  • 日志写入前会判断当前 day 是否为创建日志的时间,行数是否超过最大行限制
    • 若为创建日志时间,写入日志,否则按当前时间创建心 log,更新创建时间和行数
    • 若行数超过最大行限制,在当前日志的末尾加 count / max_lines 为后缀,创建新log

将系统信息格式化后输出,具体为:格式化时间 + 格式化内容

void Log::write_log(int level, const char *format, ...)
{struct timeval now = {0, 0};gettimeofday(&now, NULL); // 获取当前时间time_t t = now.tv_sec; // 获取秒数struct tm *sys_tm = localtime(&t); // 转换为本地时间struct tm my_tm = *sys_tm; // 复制本地时间char s[16] = {0}; // 存储日志级别字符串// 日志分级switch (level) {case 0:strcpy(s, "[debug]:"); // 调试级别break;case 1:strcpy(s, "[info]:"); // 信息级别break;case 2:strcpy(s, "[warn]:"); // 警告级别break;case 3:strcpy(s, "[error]:"); // 错误级别break;case 4:strcpy(s, "[fatal]:"); // 致命错误级别break;} m_mutex.lock(); // 加锁,保证线程安全// 更新现有行数m_count++;// 如果日志不是今天或者写入的日志行数是最大行的倍数// m_split_lines 为最大行数if (m_today != my_tm.tm_mday || m_count % m_split_lines == 0){char new_log[256] = {0}; // 存储新的日志文件名fflush(m_fp); // 刷新文件流fclose(m_fp); // 关闭文件char tail[16] = {0}; // 存储时间部分的字符串// 格式化日志名中的时间部分snprintf(tail, 16, "%d_%02d_%02d_", my_tm.tm_year + 1900,my_tm.tm_mon + 1, my_tm.tm_mday);// 如果时间不是今天,就创建今天的日志,更新 m_today 和 m_countif (m_today != my_tm.tm_mday) {snprintf(new_log, 255, "%s%s%s", dir_name, tail, log_name);m_today = my_tm.tm_mday; // 更新为今天日期m_count = 0; // 重置行数为0}else {// 超过最大行,在之前的日志名基础上,加后缀// m_count / m_split_linessnprintf(new_log, 255, "%s%s%s.%lld", dir_name, tail,log_name, m_count / m_split_lines);}m_fp = fopen(new_log, "a"); // 打开新的日志文件}m_mutex.unlock(); // 解锁va_list valst; // 创建可变参数列表va_start(valst, format); // 初始化可变参数列表string log_str; // 存储日志内容的字符串m_mutex.lock(); // 加锁// 写入内容格式:时间 + 内容// 时间格式化int n = snprintf(m_buf, 48, "%d-%02d-%02d %02d:%02d:%02d.%06ld %s ",my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday,my_tm.tm_hour, my_tm.tm_min, my_tm.tm_sec, now.tv_usec, s);// 内容格式化int m = vsnprintf(m_buf + n, m_log_buf_size - 1, format, valst);m_buf[n + m] = '\n'; // 添加换行符m_buf[n + m + 1] = '\0'; // 添加字符串终止符log_str = m_buf; // 将格式化后的内容存入字符串m_mutex.unlock(); // 解锁// 若 m_is_async 为 true 表示异步,默认同步// 若异步,就将日志信息加入阻塞队列,同步就加锁向文件中写if (m_is_async && !m_log_queue->full()) {m_log_queue->push(log_str); // 异步写入日志队列} else {m_mutex.lock(); // 加锁fputs(log_str.c_str(), m_fp); // 同步写入日志文件m_mutex.unlock(); // 解锁}va_end(valst); // 结束可变参数列表
}

补充解释

1)

char *strcpy(char *dest, const char *src);

dest表示目标字符串的地址,src表示源字符串的地址。strcpy会把源字符串(src指向的字符串)的内容复制到目标字符串(dest指向的字符串)中,并以'\0'(空字符)作为结束符

上面的代码中,通过strcpy函数将不同日志级别对应的字符串(比如"[debug]:"、"[info]:")复制到变量s中,以便后续拼接成完整的日志信息

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/265835.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Figma 最新版下载:无需激活码,轻松安装!

从事设计工作&#xff0c;怎么能没有设计工具呢&#xff1f;我相信许多设计师也必须使用Figma这样的软件&#xff0c;真的可以让我们的设计工作更有效率&#xff0c;但我相信你也发现Figma属于外国软件&#xff0c;自然语言也是英语&#xff0c;直到现在没有中文版本&#xff0…

【Kubernetes】安装K3S

目录 前言一、原理单体架构高可用架构 二、初始化1.配置yum源2.关掉防火墙3.关掉selinux4. 修改内核参数5.关掉swap交换分区 三、安装master节点1. 安装container2.启动master服务 四、安装node节点五、卸载六、总结 前言 各位小伙伴们&#xff0c;大家好&#xff0c;小涛又来…

自定义神经网络四之编写自定义神经网络

文章目录 前言神经网络组件代码整体的项目结构Tensor张量Layers层NeuralNet神经网络Loss损失函数Optim优化器data数据处理train训练 神经网络解决实际问题实际问题训练和推理代码 总结 前言 自定义神经网络一之Tensor和神经网络 自定义神经网络二之模型训练推理 自定义神经网络…

关于电脑一天24小时多少度电电脑的一天用电量计算

随着这几年物价的上涨&#xff0c;一些地区的电价越来越高&#xff0c;而我们经常需要使用电脑&#xff0c;那么一台电脑一天24小时用多少度电呢&#xff1f; 如何计算电脑一天的用电量&#xff1f; 让我们跟随小编来了解更多吧。 1、功耗、主机箱功耗 现在的计算机中&#xf…

(Linux学习一):Mac安装vmWare11.5,centOS 7安装步骤教程

一。下载vmware 官网地址&#xff1a;下载地址 由于我的电脑系统是Mac 10.15.6版本系统&#xff0c;我下载的是VMware Fusion 11.5版本&#xff0c;13是最新版本不支持安装需要系统在11以上。 百度网盘下载地址: VMware Fusion 11 VMware Fusion 12 VMware Fusion 13 下载需要…

插值、逼近、拟合、光顺

插值 插值&#xff08;Interpolation&#xff09;是数学和计算科学中的一个重要概念&#xff0c;它指的是通过已知的一系列数据点&#xff0c;构造一个函数或曲线&#xff0c;并据此估计未知数据点的值。这个过程通常发生在已知数据点之间&#xff0c;用于预测或估算在这些已知…

Android Duplicate class 排除重复类

一、起因&#xff1a; 在迭代开发的时候&#xff0c;发现2个ijk很多类重复。但又2个库实现的功能是不一样&#xff0c;目前不能合并。但又想保留2个功能。需要排除其中一个库。 二、报错如何下图&#xff1a; 三、解决方法&#xff1a; 3.1 在terminal 也就是命令行处输入 …

Project_Euler-29 题解

Project_Euler-29 题解 题目 思路 如果暴力破解的话会有一个问题&#xff0c;那就是数值过大的问题&#xff0c;那我们就需要通过对数据结构来进行操作&#xff0c;这样的话会让代码变得很臃肿。 优化思路 其实&#xff0c;对于一个数值&#xff0c;我们并不需要要将其计算出…

Mysql数据库管理系统学习笔记1——sql语句,DBMS,数据库的分类

mysql是一种数据库管理系统&#xff08;DBMS&#xff09;&#xff0c;data base manage system sql语句即为“structured query language”&#xff0c;结构化查询语言 数据库的分类&#xff1a;关系型数据库&#xff08;RDBMS&#xff09;与非关系型数据库 对于一些具有相同…

如何在aws服务器上部署mysql

在AWS服务器上部署 MySQL 数据库可以通过以下步骤完成&#xff1a; 启动 EC2 实例&#xff1a; 在 AWS 控制台中启动一个 EC2 实例&#xff0c;选择适合你需求的实例类型和配置。 安全组配置&#xff1a; 确保你的 EC2 实例的安全组配置允许来自你的 IP 地址的 MySQL 连接。默…

【全志D1-H 哪吒开发板】Debian系统安装调教和点灯指南

全志D1-H开发板【哪吒】使用Deabian系统入门 特别说明&#xff1a; 因为涉及到操作较多&#xff0c;博文可能会导致格式丢失 其中内容&#xff0c;会根据后续使用做优化调整 目录&#xff1a; 参考资料固件烧录启动调教点灯问题 〇、参考资料 官方资料 开发板-D1开发板【…

VR元宇宙的概念|VR体验店加盟|虚拟现实设备销售

VR元宇宙是一个结合了虚拟现实&#xff08;Virtual Reality&#xff09;和增强现实&#xff08;Augmented Reality&#xff09;等技术的概念&#xff0c;代表着一个虚拟的多维度世界。它是一个由数字化的空间构成的虚拟环境&#xff0c;可以通过虚拟现实设备进行交互和探索。 元…

Linux安装Nginx详细步骤

1、创建两台虚拟机&#xff0c;分别为主机和从机&#xff0c;区别两台虚拟机的IP地址 2、将Nginx素材内容上传到/usr/local目录&#xff08;pcre,zlib,openssl,nginx&#xff09; 附件 3、安装pcre库   3.1 cd到/usr/local目录 3.2 tar -zxvf pcre-8.36.tar.gz 解压 3.3 cd…

goland配置新增文件头

参考&#xff1a; goland函数注释生成插件 goland函数注释生成插件_goland自动加函数说明-CSDN博客 GoLand 快速添加方法注释 GoLand 快速添加方法注释_goland批量注释-CSDN博客 goland 如何设置头注释&#xff0c;自定义author和data goland 如何设置头注释&#xff0c;自定…

【北京迅为】《iTOP-3588开发板网络环境配置手册》第2章 电脑、开发板直连交换机或路由器

RK3588是一款低功耗、高性能的处理器&#xff0c;适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用&#xff0c;RK3588支持8K视频编解码&#xff0c;内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP&…

通过大语言模型理解运维故障:评估和总结

张圣林 南开大学软件学院副教授、博士生导师 第六届CCF国际AIOps挑战赛程序委员会主席 在ATC、WWW、VLDB、KDD、SIGMETRICS等国际会议和JSAC、TC、TSC等国际期刊发表高水平论文50余篇。主持国家自然科学基金项目2项&#xff0c;横向项目13项&#xff08;与华为、字节跳动、腾讯…

resilience4j 2.0.0版本使用要求最低JDK17(使用踩坑记录)

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容&#x1f4e2;文章总结&#x1f4e5;博主目标 &#x1f50a;博主介绍 &#x1f31f;我是廖志伟&#xff0c;一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专家博主、阿里云专家博主、清华大学出版社签约作…

非线性优化资料整理

做课题看了一些非线性优化的资料&#xff0c;整理一下&#xff0c;以方便查看&#xff1a; 优化的中文博客 数值优化|笔记整理&#xff08;8&#xff09;——带约束优化&#xff1a;引入&#xff0c;梯度投影法 (附代码)QP求解器对比对于MPC的QP求解器 数值优化| 二次规划的…

MYSQL--JDBC*

一.介绍: 1.JDBC是一种用于执行SQL于语句的JAVA API,JDBC是一种使用JAVA访问数据库的执行规范标准,能够为不同的数据库提供统一的访问!由一组使用JAVA语言编写的接口以及类组成的 2.JDBC核心的类以及相关的接口主要有: DriverManager 注册驱动 Connection 使用…

DolphinScheduler——介绍及架构设计

目录 一、DolphinScheduler介绍 1.1 概述 1.2 特性 1.2.1 简单易用 1.2.2 丰富的使用场景 1.2.3 High Reliability 1.2.4 High Scalability 1.3 名词解释 1.3.1 名词解释 1.3.2 模块介绍 二、DolphinScheduler架构原理 2.1 系统架构图 2.2 架构说明 2.2.1 Maste…