【sylar-webserver】重构日志系统
文章目录
- 主要工作
- 流程图
- FiberCondition
- Buffer
- BufferManager
- LogEvent 序列化 & 反序列化
- Logger
- RotatingFileLogAppender
主要工作
- 实现, LogEvent 序列化和反序列化 (使用序列化是为了更标准,如果转成最终的日志格式再存储(确实会快点,但是对于缓冲区的利用就不够了)
- 优化,使用 LoggerBuild 建造者 实现和管理日志器;
- 双缓冲区设计,使用条件变量等同步技术实现日志异步处理器。支持定时检查缓冲区 和 生产者缓冲区 唤醒;
- 使用 WorkerManager 管理多个调度器;
- 增加 循环日志写入,按时间分片的 LoggerAppender
支持上传下载和展示功能,支持备份重要日志;(待实现网络库后实现)- 性能测试,2h4g 服务器下,异步日志器每秒输出 130MB 日志器。
流程图
FiberCondition
仿照 std::condition_variable
存在的问题,由于是 notify_one 是把 fiber 重新添加调度,并且调度策略是先来先服务。
所以,存在日志时序性错误问题。(解决方法,后期修改调度策略,增加优先级。)⭐
class FiberCondition{
public:using MutexType = Spinlock;void wait(MutexType::Lock& lock);template <typename Predicate>void wait(MutexType::Lock& lock, Predicate pred){while(!pred()){wait(lock);}}void notify_one();void notify_all();private:void printWaiters() const;private:MutexType m_mutex;std::list<std::pair<Scheduler*, Fiber::ptr>> m_waiters;
};
void FiberCondition::wait(MutexType::Lock& lock){SYLAR_ASSERT(Scheduler::GetThis());{MutexType::Lock lock(m_mutex);m_waiters.push_back(std::make_pair(Scheduler::GetThis(), Fiber::GetThis()));printWaiters();}lock.unlock();Fiber::GetThis()->yield();lock.lock();
}void FiberCondition::notify_one(){MutexType::Lock lock(m_mutex);if (!m_waiters.empty()) {auto next = m_waiters.front();m_waiters.pop_front();next.first->schedule(next.second);}
}void FiberCondition::notify_all() {MutexType::Lock lock(m_mutex);for (auto& waiter : m_waiters) {waiter.first->schedule(waiter.second);}m_waiters.clear();
}
Buffer
class Buffer {public:using ptr = std::shared_ptr<Buffer>;using MutexType = Spinlock;Buffer(size_t buffer_size);Buffer(size_t buffer_size, size_t threshold, size_t linear_growth);void push(const char* data, size_t len);void push(const std::string& str);char* readBegin(int len);bool isEmpty();void swap(Buffer& buf);size_t writeableSize();size_t readableSize() const;const char* Begin() const;void moveWritePos(int len);void moveReadPos(int len);void Reset();protected:void ToBeEnough(size_t len);private:MutexType m_mutex;size_t m_buffer_size;size_t m_threshold;size_t m_linear_growth;std::vector<char> m_buffer;size_t m_write_pos = 0;size_t m_read_pos = 0;
};
BufferManager
重点操作:
BufferManager::BufferManager(const functor& cb, // 消费者缓冲区写入的回调函数 ⭐AsyncType::Type asyncType,size_t buffer_size,size_t threshold,size_t linear_growth,size_t swap_time,IOManager* iom): m_stop(false),m_swap_status(false),m_asyncType(asyncType),m_buffer_productor(std::make_shared<Buffer>(buffer_size, threshold, linear_growth)),m_buffer_consumer(std::make_shared<Buffer>(buffer_size, threshold, linear_growth)),m_callback(cb),m_swap_time(swap_time)
{assert(iom != nullptr);iom->schedule(std::bind(&BufferManager::ThreadEntry, this));m_timer = iom->addTimer(m_swap_time, std::bind(&BufferManager::TimerThreadEntry, this), true);
}// 写入线程,生产者
void BufferManager::push(const char* data, size_t len) {MutexType::Lock lock(m_mutex);if(m_asyncType == AsyncType::ASYNC_SAFE){if (len > m_buffer_productor->writeableSize()) {SYLAR_LOG_DEBUG(g_logger) << "notify consumer";m_cond_consumer.notify_one();}m_cond_producer.wait(lock, [&](){return (m_stop || (len <= m_buffer_productor->writeableSize()));});}if(m_stop){throw std::runtime_error("BufferManager is stopped");}m_buffer_productor->push(data, len);
}// 使用Timer,按照频率访问缓冲区
// 如果生产者没有就退出
void BufferManager::TimerThreadEntry(){{MutexType::Lock lock(m_mutex);if ((!m_buffer_productor->isEmpty() && m_buffer_consumer->isEmpty()) || m_stop) {swap_buffers();if(m_asyncType == AsyncType::ASYNC_SAFE){m_cond_producer.notify_all();}}else{return;}}{MutexType::Lock lock(m_swap_mutex);m_callback(m_buffer_consumer);m_buffer_consumer->Reset();}
}void BufferManager::ThreadEntry() {while(true){{MutexType::Lock lock(m_mutex);SYLAR_LOG_DEBUG(g_logger) << "ThreadEntry started.";m_cond_consumer.wait(lock, [&](){return m_stop || (!m_buffer_productor->isEmpty() && m_buffer_consumer->isEmpty());});swap_buffers();if(m_asyncType == AsyncType::ASYNC_SAFE){m_cond_consumer.notify_all();}}{MutexType::Lock lock(m_swap_mutex);m_callback(m_buffer_consumer);m_buffer_consumer->Reset();if(m_stop && m_buffer_productor->isEmpty()) return;}}
}
LogEvent 序列化 & 反序列化
#pragma pack(push, 1)
struct LogMeta {uint64_t timestamp; // 时间戳uint32_t threadId; // 线程IDuint32_t fiberId; // 协程IDint32_t line; // 行号uint32_t elapse;LogLevel::Level level; // 日志级别uint16_t fileLen; // 文件名长度uint32_t threadNameLen;// 线程名长度uint32_t msgLen; // 消息内容长度
};
#pragma pack(pop)
Buffer::ptr LogEvent::serialize() const {LogMeta meta{ // ⭐.timestamp = m_time,.threadId = m_threadId,.fiberId = m_fiberId,.line = m_line,.elapse = m_elapse,.level = m_level,.fileLen = static_cast<uint16_t>(m_file.size()),.threadNameLen = static_cast<uint16_t>(m_threadName.size()),.msgLen = static_cast<uint32_t>(m_ss.str().size())};const size_t total_need = sizeof(LogMeta) + meta.fileLen + meta.threadNameLen + meta.msgLen;auto buffer = std::make_shared<Buffer>(total_need); // 使用 shared_ptr 管理内存// 序列化元数据buffer->push(reinterpret_cast<const char*>(&meta), sizeof(meta));// 序列化变长数据(包含终止符)buffer->push(m_file.c_str(), meta.fileLen);buffer->push(m_threadName.c_str(), meta.threadNameLen);buffer->push(m_ss.str().c_str(), meta.msgLen);return buffer; // 返回 shared_ptr}// 每次调用,解析一个LogEventstatic LogEvent::ptr LogEvent::deserialize(Buffer& buffer) {if(buffer.readableSize() < sizeof(LogMeta)) {return nullptr;}LogMeta meta;memcpy(&meta, buffer.Begin(), sizeof(LogMeta));const size_t total_need = sizeof(LogMeta) + meta.fileLen + meta.threadNameLen + meta.msgLen;if(buffer.readableSize() < total_need){return nullptr;}// 4. 提取各字段数据(使用临时指针操作)const char* data_ptr = buffer.Begin() + sizeof(LogMeta);// 文件名处理std::string file(data_ptr, meta.fileLen);data_ptr += meta.fileLen;// 线程名std::string thread_name(data_ptr, meta.threadNameLen);data_ptr += meta.threadNameLen;// 消息内容处理std::string message(data_ptr, meta.msgLen);// 5. 统一移动读指针(原子操作保证数据一致性)buffer.moveReadPos(total_need);// 6. 构建日志事件对象auto event = std::make_shared<LogEvent>(std::move(file),meta.line,meta.elapse,meta.threadId,std::move(thread_name),meta.fiberId,meta.timestamp,meta.level);event->getSS() << message;return event;}
Logger
重构时,出现的问题:Logger 对 BufferManager 的依赖,并且 BufferManger 也依赖 IOMgr调度器。
简单说,就说 全局静态变量的初始化顺序问题
解决方法:Logger默认构造的时候,不提供BufferParams,就使用同步方式创建。
导入 yaml 配置后,重置 logger,再创建 异步日志器
Logger(const std::string name, LogLevel::Level level,std::vector<LogAppender::ptr>& appenders, const BufferParams& bufParams) :m_name(name), m_level(level), m_appenders(appenders.begin(), appenders.end()){if(bufParams.isValid()){m_bufMgr = std::make_shared<BufferManager>(std::bind(&Logger::realLog, this, std::placeholders::_1), bufParams);}else{m_bufMgr = nullptr;}}// 由 iom_log 写入真正的文件。void realLog(Buffer::ptr buffer) {MutexType::Lock lock(m_log_mutex); // 强制 只能 一个线程写入。if (!buffer) {std::cerr << "realLog: invalid buffer pointer" << std::endl;return;}std::vector<LogEvent::ptr> events;while (true) { // 解析 bufferLogEvent::ptr event = LogEvent::deserialize(*buffer);// 理论上 buffer 里是多个Event的数据,不存在处理失败。if (event) {events.push_back(event);} else {if (buffer->readableSize() == 0) { // 读完了break;} else {// 处理失败但数据未读完(说明发生严重错误)std::cout << "Log deserialization error, remaining data: " << buffer->readableSize() << std::endl;break;}}}auto self = shared_from_this();for (auto& appender : m_appenders) {appender->log(self, events);}}// 写入缓冲区// 多个线程的 写日志,写入缓存区void log(LogEvent::ptr event){if(event->getLevel() >= m_level){if(m_bufMgr != nullptr){// MutexType::Lock lock(m_mutex); 当协程阻塞,这个锁就一直没释放。搞半天,给我整的怀疑人生了。Buffer::ptr buf = event->serialize();m_bufMgr->push(buf);}else{// 如果没有配置iom,直接同步输出日志auto self = shared_from_this();for(auto& appender : m_appenders) {appender->log(self, event);}}}}
RotatingFileLogAppender
FileLogAppeneder 改为使用 FILE 库函数
支持功能:
- max_size,限制单个日志文件大小(按照时间片创建文件名)
- m_maxFile = 0,无限增加日志
- m_maxFile > 0,限制日志文件的个数。当超过,从第一个文件循环写入。
class RotatingFileLogAppender : public LogAppender{
public:typedef std::shared_ptr<RotatingFileLogAppender> ptr;RotatingFileLogAppender(const std::string& filename, LogLevel::Level level, LogFormatter::ptr formatter,size_t max_size,size_t max_file = 0, // 默认是无限增加FlushRule::Rule flush_rule = FlushRule::Rule::FFLUSH // 默认是普通日志 );~RotatingFileLogAppender(){if(m_curFile){fclose(m_curFile);m_curFile = NULL;}}std::string toYamlString();void log(std::shared_ptr<Logger> logger, LogEvent::ptr event) override;void log(std::shared_ptr<Logger> logger, std::vector<LogEvent::ptr> events) override; private:void initLogFile(size_t len = 0);/*** 判断是否写的下,如果写的下就 ss<<str,缓存* 如果写不写了,就把 ss 缓存一次性写入。重置ss */bool checkLogFile(const std::string& str);std::string createFilename();
private:std::string m_filename;FILE* m_curFile = NULL;std::vector<std::string> m_fileNames;size_t m_maxSize;size_t m_maxFile;FlushRule::Rule m_flushRule;size_t m_curFilePos = 0;size_t m_curFileIndex = 0;Buffer m_buffer;
};
void RotatingFileLogAppender::initLogFile(size_t len){if(m_curFile == NULL || (m_curFilePos + len) > m_maxSize){// 写不下了,保证日志的完整性,直接新建文件。if(m_curFile != NULL){fflush(m_curFile);fclose(m_curFile);if(m_maxFile == 0){// 无限增加日志文件m_curFileIndex++;}else{m_curFileIndex = (m_curFileIndex + 1) % m_maxFile;if(!m_fileNames[m_curFileIndex].empty()){ // 说明 循环到 已有的文件了。std::string newfilename = createFilename();if(rename(m_fileNames[m_curFileIndex].c_str(), newfilename.c_str()) != 0){ // 文件 改新名字perror("rename failed");} m_fileNames[m_curFileIndex] = newfilename;m_curFile = fopen(newfilename.c_str(), "r+b");fseek(m_curFile, 0, SEEK_SET); // 从头 覆盖,不考虑 日志文件名了。默认最后一个文件可能会存在过往的日志信息。m_curFilePos = 0;return;}}}std::string filename = createFilename();m_fileNames[m_curFileIndex] = filename;m_curFile = fopen(filename.c_str(), "ab");if(m_curFile==NULL){std::cout <<__FILE__<<__LINE__<<"open file failed"<< std::endl;perror(NULL);}m_curFilePos = 0;return;}
}std::string RotatingFileLogAppender::createFilename() {time_t now = time(nullptr);struct tm tm;localtime_r(&now, &tm);char time_buf[64];strftime(time_buf, sizeof(time_buf), "%Y%m%d_%H%M%S", &tm);return m_filename + "_" + time_buf + "_" + std::to_string(m_curFileIndex) + ".log";
}void RotatingFileLogAppender::log(std::shared_ptr<Logger> logger, LogEvent::ptr event){MutexType::Lock lock(m_mutex);if(event->getLevel() >= m_level){std::string data = m_formatter->format(logger , event);initLogFile(data.size());fwrite(data.c_str(), 1, data.size() , m_curFile);if(ferror(m_curFile)){std::cout <<__FILE__<<__LINE__<<"write log file failed"<< std::endl;perror(NULL);}m_curFilePos += data.size();if(m_flushRule == FlushRule::Rule::FFLUSH){if(fflush(m_curFile)==EOF){ // 刚好最后一个日志把文件写满了。std::cout <<__FILE__<<__LINE__<<"fflush file failed"<< std::endl;perror(NULL);}}else if(m_flushRule == FlushRule::Rule::FSYNC){fflush(m_curFile);fsync(fileno(m_curFile));}}
}bool RotatingFileLogAppender::checkLogFile(const std::string& data){if(m_curFile == NULL || (m_curFilePos + data.size()) > m_maxSize){// 写不下了,保证日志的完整性,直接新建文件。if(m_curFile != NULL){// 把 ss 缓存一次性写入fwrite(m_buffer.Begin(), 1, m_buffer.readableSize(), m_curFile);m_buffer.Reset();// 判断错误信息if(ferror(m_curFile)){std::cout <<__FILE__<<__LINE__<<"write log file failed"<< std::endl;perror(NULL);}if(m_flushRule == FlushRule::Rule::FFLUSH){if(fflush(m_curFile)==EOF){ // 刚好最后一个日志把文件写满了。std::cout <<__FILE__<<__LINE__<<"fflush file failed"<< std::endl;perror(NULL);}}else if(m_flushRule == FlushRule::Rule::FSYNC){fflush(m_curFile);fsync(fileno(m_curFile));}fclose(m_curFile);if(m_maxFile == 0){// 无限增加日志文件m_curFileIndex++;}else{m_curFileIndex = (m_curFileIndex + 1) % m_maxFile;if(!m_fileNames[m_curFileIndex].empty()){ // 说明 循环到 已有的文件了。std::string newfilename = createFilename();if(rename(m_fileNames[m_curFileIndex].c_str(), newfilename.c_str()) != 0){ // 文件 改新名字std::cout << "rename failed" << std::endl;perror(NULL);}m_fileNames[m_curFileIndex] = newfilename;m_curFile = fopen(newfilename.c_str(), "r+b");if (m_curFile == NULL) {std::cout << __FILE__ << __LINE__ << "open file failed" << std::endl;perror(NULL);}// 从头 覆盖,不考虑 日志文件名了。默认最后一个文件可能会存在过往的日志信息。fseek(m_curFile, 0, SEEK_SET); m_curFilePos = 0;// 模拟写入文件,实际上写入缓存。m_buffer.push(data);m_curFilePos += data.size();return true;}}}// m_curFile为空,创建新文件 std::string filename = createFilename();if(m_maxFile > 0){m_fileNames[m_curFileIndex] = filename; // 只有限制最大文件数,记录文件名}m_curFile = fopen(filename.c_str(), "ab");if(m_curFile==NULL){std::cout <<__FILE__<<__LINE__<<"open file failed"<< std::endl;perror(NULL);}m_curFilePos = 0;}// 模拟写入文件,实际上写入缓存。m_buffer.push(data);m_curFilePos += data.size();return false;
}void RotatingFileLogAppender::log(std::shared_ptr<Logger> logger, std::vector<LogEvent::ptr> events){// 这个时候,m_curFilePos 转变成对 m_buffer 写入数据的 pos 长度。 ⭐MutexType::Lock lock(m_mutex);for(auto& event : events){if(event->getLevel() >= m_level){std::string data = m_formatter->format(logger , event);checkLogFile(data);}}// 最后再次,把缓存里的写入if(m_buffer.readableSize() > 0 && m_curFile != NULL){fwrite(m_buffer.Begin(), 1, m_buffer.readableSize(), m_curFile);m_buffer.Reset();// 判断错误信息if(ferror(m_curFile)){std::cout <<__FILE__<<__LINE__<<"write log file failed"<< std::endl;perror(NULL);}if(m_flushRule == FlushRule::Rule::FFLUSH){if(fflush(m_curFile)==EOF){ // 刚好最后一个日志把文件写满了。std::cout <<__FILE__<<__LINE__<<"fflush file failed"<< std::endl;perror(NULL);}}else if(m_flushRule == FlushRule::Rule::FSYNC){fflush(m_curFile);fsync(fileno(m_curFile));}}
}
剩下的就是:
works,多个调度器的的管理~
对Config监听函数的修改,补充BufferParams
分离works.yml和log.yml,分别导入。提前导入works.yml 保证 调度器创建完成。
详见 代码 https://github.com/star-cs/webserver
相关文章:
【sylar-webserver】重构日志系统
文章目录 主要工作流程图FiberConditionBufferBufferManagerLogEvent 序列化 & 反序列化LoggerRotatingFileLogAppender 主要工作 实现, LogEvent 序列化和反序列化 (使用序列化是为了更标准,如果转成最终的日志格式再存储(确…...
图形编辑器基于Paper.js教程27:对图像描摹的功能实现,以及参数调整
本篇文章来讲一下 图像描摹的功能的实现。 我们知道要雕刻图片可以通过分析图片的像素来生成相应的gcode进行雕刻,但如果你想要将图片转换为线稿进行雕刻,这个时候就要从图片中提取出 线稿。 例如下面的图片: 你想要获取到这个图片的线稿&…...
使用Mybaitis-plus提供的各种的免写SQL的Wrapper的使用方式
文章目录 内连接JoinWrappers.lambda和 new MPJLambdaWrapper 生成的MPJLambdaWrapper对象有啥区别?LambdaQueryWrapper 和 QueryWrapper的区别?LambdaQueryWrapper和MPJLambdaQueryWrapper的区别?在作单表更新时建议使用:LambdaU…...
SQL_连续登陆问题
文章目录 方案1:使用ROW_NUMBER函数1、针对对数据user_id分组,根据用户的活动日期排序2、用登录日期与rn求date_sub,得到的差值日期如果是相等的,则说明这两天肯定是连续的3、根据user_id和日期差sub_date分组,登录次数…...
【解决】Vue + Vite + TS 配置路径别名成功仍爆红
目录 前言 一.vite.config.ts 二.tsconfig.json 三. 别名配置成功,但语法提示爆红问题 四、可能遇到的问题 前言 在项目中设置路径别名后仍然出现爆红问题,通常是由于配置不完整或配置错误导致的。Vite 中配置 alias 总共需要配置两个地方: vite…...
Discuz!与DeepSeek结合:打造智能论坛,提升用户体验与运营效率
引言 随着互联网技术的飞速发展,社区论坛作为用户交流、分享信息的重要平台,其用户体验和运营效率成为了影响平台竞争力的关键因素。Discuz!作为国内领先的社区论坛软件系统,以其强大的功能和广泛的用户基础,在社区论坛领域占据着…...
Federated Feature Augmentation and Alignment
TPAMI2025,说是解决特征偏移问题,但从实验看其他数据异构也一并改善了。文章和题目一样干了特征增强和特征对齐两件事。特征增强部分套了两次高斯拟合,搞得很复杂,没法一句话说清楚。特征对齐部分是截断成直方图,然后双向KL散度。 论文:目前好像只有ieee能搜到 代码:只…...
ThinkPHP5 的 SQL 注入漏洞
ThinkPHP5 的 SQL 注入漏洞(常被安全社区称为 ThinkPHP5 SQL5 注入漏洞)是 ThinkPHP5 框架中一系列因设计缺陷导致的安全问题,主要影响早期版本。 一、漏洞背景 ThinkPHP5 的 SQL 注入漏洞主要源于框架对用户输入数据的处理不当,尤…...
Linux学习笔记|入门指令
man 指令 用法:man [指令名称] ,用于查看指定指令的帮助手册,获取指令的详细语法、选项及使用示例等信息 。示例:想了解 ls 指令的用法,执行 man ls ,会进入 man 手册页面展示 ls 相关信息。按 q 键可退出。…...
前端实现数据导出成excel
前端(react/vue)实现数据导出成excel 1. 下载依赖 npm install js-export-excel -D 2. 新建exportExcel.js import ExportJsonExcel from js-export-excel; /*** Event: 获取导出数据* description:* param config.tableParams<Object>: 表格接口请求参数* param …...
海事局发布《船舶智能监控系统技术指南(1.0)》,解读智驱力产品为何成为最佳选择!
为深入推进人工智能、边缘计算等新技术在水上交通安全领域应用,强化船舶安全风险实时感知、智能预警,推动水上交通安全治理模式向事前预防转型,中华人民共和国海事局于2025年3月11日正式制定了《船舶智能监控系统技术指南(1.0&…...
uCOS3实时操作系统(系统初始化和任务启动)
文章目录 ucos初始化任务创建任务启动 ucos初始化 系统运行的过程如下:OSInit -> OSTaskCreate -> OSStartucos初始化主要在 OSInit 中进行,下面列举了该初始化过程中比较重要的几个步骤:OSInit()OSInitHook();OS_CPU_ExceptStkBase /…...
线上地图导航小程序源码介绍
基于ThinkPHP、FastAdmin和UniApp三大前沿技术推出的一款线上地图导航小程序源码,ThinkPHP作为后端框架,以其轻量、高效和灵活的特点,确保了小程序的稳定性和可扩展性。FastAdmin则是基于ThinkPHP构建的管理后台,操作简便、功能全…...
Mininet--nodelib.py源码解析
整体构架概述 1. What is it? 本代码是 Mininet 网络仿真框架的扩展模块,包含 LinuxBridge 和 NAT 两类节点。LinuxBridge 提供基于 Linux 网桥的交换机功能,支持生成树协议(STP),用于构建冗余网络拓扑并防…...
[C++]多重继承:构造函数调用顺序解析
在C中,当派生类通过 多重继承 (Multiple Inheritance)继承多个基类(如 A、B、C)时,其构造函数调用顺序遵循以下规则: 1. 基类构造顺序 基类的构造函数调用顺序严格按照派生类定义中的基类声明…...
Math.round(),Math.ceil(),Math.floor(),Math.sqrt(),Math.pow(),Math.abs()等!
希望看到这篇的你今天开心 目录 Math.round():Math.ceil():Math.floor() :Math.sqrt():Math.pow():Math.abs():了解更多: Math.round(): 四舍五入取值 静态方法Math.round()返回四舍五入为最接近的整数的数字值。 对于正数,Math.round() 会将小数部分小…...
Docker 集成KingBase
Docker安装 再linux系统中安装yum命令,通过yum命令可直接安装docker yum命令如下 yum install dockerDocker安装KingBase 安装完成Dockr后,去KingBase官网中下载镜像 下载完成后,通过docker命令将镜像文件导入 docker load -i kdb_x86_64…...
论文速报《Being-0:结合视觉语言模型与模块化技能的人形机器人智能体》
论文链接:https://arxiv.org/pdf/2503.12533 项目主页:https://beingbeyond.github.io/being-0/?utm_sourcecatalyzex.com 0. 简介 人形机器人被认为是实现具身人工智能的理想载体,因其可以像人类一样与现实世界进行物理交互。构建能够在复…...
卷积神经网络--手写数字识别
本文我们通过搭建卷积神经网络模型,实现手写数字识别。 pytorch中提供了手写数字的数据集 ,我们可以直接从pytorch中下载 MNIST中包含70000张手写数字图像:60000张用于训练,10000张用于测试 图像是灰度的,28x28像素 …...
JavaScript-原型、原型链详解
一、构造函数 在 JavaScript 中,构造函数是一种特殊的函数,用于创建和初始化对象,它就像一个 “对象模板”。通过 new 关键字调用构造函数时,会创建一个新对象,并将构造函数中的属性和方法 “绑定” 到这个新对象上。…...
深度学习框架PyTorch——从入门到精通(3.3)YouTube系列——自动求导基础
这部分是 PyTorch介绍——YouTube系列的内容,每一节都对应一个youtube视频。(可能跟之前的有一定的重复) 我们需要Autograd做什么?一个简单示例训练中的自动求导开启和关闭自动求导自动求导与原地操作 自动求导分析器高级主题&…...
永磁同步电机控制算法-VF控制
一、原理介绍 V/F 控制又称为恒压频比控制,给定VF 控制曲线 电压是频率的tt例函数 即控制电压跟随频率变化而变化以保持磁通恒定不变。 二、仿真模型 在MATLAB/simulink里面验证所提算法,搭建仿真。采用和实验中一致的控制周期1e-4,电机部分计算周期为…...
【Docker 运维】Java 应用在 Docker 容器中启动报错:`unable to allocate file descriptor table`
文章目录 一、根本原因二、判断与排查方法三、解决方法1、限制 Docker 容器的文件描述符上限2、在执行脚本中动态设置ulimit的值3、升级至 Java 11 四、总结 容器内执行脚本时报错如下,Java 进程异常退出: library initialization failed - unable to a…...
SpringBoot + Vue 实现云端图片上传与回显(基于OSS等云存储)
前言 在实际生产环境中,我们通常会将图片等静态资源存储在云端对象存储服务(如阿里云OSS、七牛云、腾讯云COS等)上。本文将介绍如何改造之前的本地存储方案,实现基于云端存储的图片上传与回显功能。 一、技术选型 云存储服务&a…...
Session与Cookie的核心机制、用法及区别
Python中Session与Cookie的核心机制、用法及区别 在Web开发中,Session和Cookie是两种常用的用于跟踪用户状态的技术。它们在实现机制、用途和安全性方面都有显著区别。本文将详细介绍它们的核心机制、用法以及它们之间的主要区别。 一、Cookie的核心机制与用法 1…...
离线安装rabbitmq全流程
在麒麟系统(如银河麒麟)上离线安装 RabbitMQ 的具体操作步骤如下: 一、准备工作 确认系统版本:确认麒麟系统的版本,例如银河麒麟高级服务器 V10。确定 RabbitMQ 及依赖版本:根据系统版本确定兼容的 Rabbi…...
llama-webui docker实现界面部署
1. 启动ollama服务 [nlp server]$ ollama serve 2025/04/21 14:18:23 routes.go:1007: INFO server config env"map[OLLAMA_DEBUG:false OLLAMA_FLASH_ATTENTION:false OLLAMA_HOST: OLLAMA_KEEP_ALIVE:24h OLLAMA_LLM_LIBRARY: OLLAMA_MAX_LOADED_MODELS:4 OLLAMA_MAX_…...
第1 篇:你好,时间序列!—— 开启时间数据探索之旅
第 1 篇:你好,时间序列!—— 开启时间数据探索之旅 (图片来源: Stephen Dawson on Unsplash) 你有没有想过: 明天的天气会是怎样?天气预报是怎么做出来的?某支股票未来的价格走势如何预测?购物…...
C++算法(11):vector作为函数参数的三种传递方式详解
在C中,std::vector是最常用的动态数组容器之一。当我们需要将vector传递给函数时,不同的传递方式会对性能和功能产生显著影响。本文将详细介绍三种常见的传递方式及其适用场景,帮助开发者根据需求选择最合适的方法。 1. 按值传递(…...
版本控制利器——SVN简介
版本控制利器——SVN简介 在软件开发和项目管理的领域中,版本控制是一项至关重要的工作。它能帮助团队成员高效协作,确保代码的安全性和可追溯性。今天,我们就来详细介绍一款经典的版本控制系统——SVN(Subversion)。…...
链式栈和线性栈
1. 线性栈(顺序栈) 结构定义: #include <iostream> using namespace std;#define MAX_SIZE 100 // 预定义最大容量// 线性栈结构体 typedef struct {int* data; // 存储数据的数组int top; // 栈顶指针&…...
消息中间件RabbitMQ:简要介绍及其Windows安装流程
一、简要介绍 定义:RabbitMQ 是一个开源消息中间件,用于实现消息队列和异步通信。 场景:适用于分布式系统、异步任务处理、消息解耦、负载均衡等场景。 比喻:RabbitMQ 就像是快递公司,负责在不同系统间安全快速地传递…...
足球 AI 智能体技术解析:从数据采集到比赛预测的全链路架构
一、引言 在足球运动数字化转型的浪潮中,AI 智能体正成为理解比赛、预测赛果的核心技术引擎。本文从工程实现角度,深度解析足球 AI 的技术架构,涵盖数据采集、特征工程、模型构建、实时计算到决策支持的全链路技术方案,揭示其背后…...
VTK知识学习(53)- 交互与Widget(四)
1、测量类Widget 1)概述 与测量相关的主要 Widget如下: vtkDistanceWidget:用于在二维平面上测量两点之间的距离。vtkAngleWidget:用于二维平面的角度测量。vtkBiDimensionalWidget:用于测量二维平面上任意两个正交方向的轴长。 按照前面提到的步骤创…...
基础服务系列-Windows10 安装AnacondaJupyter
下载 https://www.anaconda.com/products/individual 安装 安装Jupyter 完成安装 启动Jupyter 浏览器访问 默认浏览器打开,IE不兼容,可以换个浏览器 修改密码 运行脚本...
使用c++调用deepseek的api(附带源码)
可以给服务器添加deepseek这样就相当于多了一个智能ai助手 deepseek的api申请地址使用格式测试效果源码 deepseek的api申请地址 这边使用硅基流动的api,注册就有免费额度 硅基流动: link 使用格式 api的调用格式,ds的api调用就是用固定协议然后发送到…...
HarmonyOS-ArkUI: animateTo 显式动画
什么是显式动画 啊, 尽管有点糙,但还是解释一下吧, 显式动画里面的“显式”二字, 是程序员在代码调用的时候,就三令五申,明明白白调用动画API而创建的动画。 这个API的名字就是: animateTo。这就是显式动画。说白了您可以大致理解为,显式动画,就是调用animateTo来完成…...
Spring AI MCP
MCP是什么 MCP是模型上下文协议(Model Context Protocol)的简称,是一个开源协议,由Anthropic(Claude开发公司)开发,旨在让大型语言模型(LLM)能够以标准化的方式连接到外…...
Kubernetes 创建 Jenkins 实现 CICD 配置指南
Kubernetes 创建 Jenkins 实现 CICD 配置指南 拉取 Jenkins 镜像并推送到本地仓库 # 从官方仓库拉取镜像(若网络不通畅可使用国内镜像源) docker pull jenkins/jenkins:lts-jdk11# 国内用户可去下面地址寻找镜像源并拉取: https://docker.a…...
01_Flask快速入门教程介绍
一、课程视频 01_Flask快速入门教程介绍 二、课程特点 讲课风格通俗易懂,理论与实战相结合 教程:视频 配套文档 配套的代码 最新本版,Python版本是3.12,Flask版本是3.10 即使是从没接触过Flsk的小白也看得懂学得会 三、适用人…...
SSH反向代理
SSH反向代理 一、过程 1、 确保树莓派和阿里云服务器的 SSH 服务正常运行 检查树莓派的ssh服务 sudo systemctl status ssh如果未启用,请启动并设置开机自启: sudo systemctl enable ssh sudo systemctl start ssh检查阿里云服务器的SSH服务 sudo …...
第 5 篇:初试牛刀 - 简单的预测方法
第 5 篇:初试牛刀 - 简单的预测方法 经过前面四篇的学习,我们已经具备了处理时间序列数据的基本功:加载、可视化、分解以及处理平稳性。现在,激动人心的时刻到来了——我们要开始尝试预测 (Forecasting) 未来! 预测是…...
深度学习中的归一化技术:从原理到实战全解析
摘要:本文系统解析深度学习中的归一化技术,涵盖批量归一化(BN)、层归一化(LN)、实例归一化(IN)、组归一化(GN)等核心方法。通过数学原理、适用场景、优缺点对…...
流量抓取工具(wireshark)
协议 TCP/IP协议簇 网络接口层(没有特定的协议)PPPOE 物理层数据链路层 网络层: IP(v4/v6) ARP(地址解析协议) RARP ICMP(Internet控制报文协议) IGMP传输层:TCP(传输控制协议)UDP(用户数据报协议)应用层…...
【原创】Ubuntu20.04 安装 Isaac Gym 仿真器
Isaac Gym 是 NVIDIA 开发的一个基于GPU的机器人仿真平台。其高效的 GPU 加速能力和大规模并行仿真性能,成为强化学习训练和机器人控制研究的重要选择。 本文将介绍 Isaac Gym 的安装过程【简易】。 1.配置环境 Ubuntu20.04 安装 NVIDIA 显卡驱动 Ubuntu20.04 安…...
AI 速读 SpecReason:让思考又快又准!
在大模型推理的世界里,速度与精度往往难以兼得。但今天要介绍的这篇论文带来了名为SpecReason的创新系统,它打破常规,能让大模型推理既快速又准确,大幅提升性能。想知道它是如何做到的吗?快来一探究竟! 论…...
从“堆料竞赛”到“体验深耕”,X200 Ultra和X200s打响手机价值升维战
出品 | 何玺 排版 | 叶媛 vivo双旗舰来袭! 4月21日,vivo X系列春季新品发布会盛大开启,带来了一场科技与创新的盛宴。会上,消费者期待已久的X200 Ultra及X200s两款旗舰新品正式发布。 vivo两款旗舰新品发布后,其打破…...
Macbook IntelliJ IDEA终端无法运行mvn命令
一、背景 idea工具里执行Maven命令mvn package,报错提示 zsh: command not found: mvn。 macOS,默认使用的是zsh,环境变量通常配置在 ~/.zshrc 文件中。 而我之前一直是配置在~/.bash_profile文件中。 二、环境变量 vi ~/.zshrc设置MAVE…...
CentOS 7进入救援模式——VirtualBox虚拟机
目录 1. 在`VirtualBox`环境下,开机按F12,进入`VirtualBox temporary boot device selection `界面,按`c`键,选中`CD-ROM `回车。2. 选中`Troubleshooting`(故障排除),进入`Troubleshooting`界面3. 接下来会显示救援模式菜单,通常选择`"1) Continue"`(除非您…...
AI软件栈:LLVM分析(六)
LLVM后端代码生成的关键步骤 文章目录 指令选择指令调度寄存器分配 指令选择 完成从基于LLVM IR的DAG转换为基于特定目标平台的DAG(注意,此时描述格式依然是DAG形态)基于TabGen完成指令重映射(典型的处理包括:指令拆散…...