海山数据库(He3DB)源码详解:海山MySQL redo日志-日志文件组

第一天(点亮led灯+led灯闪烁)——Arduino uno R3 学习之旅 发光二极管(Light Emitting Diode,简称LED)是一种能够将电能转化为光能的固态的半导体器件。长脚为阳极短脚为阴极电流必须从长脚流入从短脚流出方可点亮LED##杜邦线杜邦线:是导线中的一种,杜邦线端部有两种接头形式:带针的一种称为公头,带孔的一种称为母头,如下图所示:说明:与电源相连接时,一般采用红色杜邦线,与地连接时采用黑色杜邦线。 阅读详情

# 1、日志文件组
MySQL的数据目录下默认有两个名为ib_logfile0和ib_logfile1的文件,log buffer中的日志默认情况下就是刷新到这两个磁盘文件中。
如果对默认的redo日志文件不满意,可以通过下边几个启动参数来调节:

  • innodb_log_group_home_dir:该参数指定了redo日志文件所在的目录,默认值就是当前的数据目录。
  • innodb_log_file_size:该参数指定了每个redo日志文件的大小,默认值为48MB
  • innodb_log_files_in_group:该参数指定redo日志文件的个数,默认值为2,最大值为100。

从上边的描述中可以看到,磁盘上的redo日志文件不只一个,而是以一个日志文件组的形式出现的。这些文件以ib_logfile[数字](数字可以是0、1、2…)的形式进行命名。在将redo日志写入日志文件组时,是从ib_logfile0开始写,如果ib_logfile0写满了,就接着ib_logfile1写,同理,ib_logfile1写满了就去写ib_logfile2,依此类推。如果写到最后一个文件该咋办?那就重新转到ib_logfile0继续写,所以整个过程如下图所示:

在这里插入图片描述

总共的redo日志文件大小其实就是:innodb_log_file_size × innodb_log_files_in_group

2、redo日志文件格式

前文说过log buffer本质上是一片连续的内存空间,被划分成了若干个512字节大小的block。将log buffer中的redo日志刷新到磁盘的本质就是把block的镜像写入日志文件中,所以redo日志文件其实也是由若干个512字节大小的block组成。redo日志文件组中的每个文件大小都一样,格式也一样,都是由两部分组成:

  • 前2048个字节,也就是前4个block是用来存储一些管理信息的
  • 从第2048字节往后是用来存储log buffer中的block镜像的
    所以上文所说的循环使用redo日志文件,其实是从每个日志文件的第2048个字节开始算,如图所示:

在这里插入图片描述

这里需要介绍一下每个redo日志文件前2048个字节,也就是前4个特殊block的格式是怎样的,如图所示。

在这里插入图片描述

其中,第一个block为log file header,包含以下属性:

  • LOG_HEADER_FORMAT : 4B,redo日志的版本,该值永远为1
  • LOG_HEADER_PAD1 : 4B,字节填充
  • LOG_HEADER_START_LSN : 8B,标记本redo日志文件开始的LSN值,也就是文件偏移量为2048字节初对应的LSN值
  • LOG_HEADER_CREATOR : 32B,一个字符串,标记本redo日志文件的创建者是谁。正常运行时该值为MySQL的版本号,比如:"MySQL使用mysqlbackup命令创建的redo日志文件的该值为"ibbackup"和创建时间。
  • LOG_BLOCK_CHECKSUM : 4B,本block的校验值

在这里插入图片描述

第二个block为checkpoint1,该块中记录了关于checkpoint一些属性:

  • LOG_CHECKPOINT_NO : 8B,服务器做的checkpoint编号,每做一次checkpoint,该值就加1。
  • LOG_CHECKPOINT_LSN : 8B,服务器做checkpoint结束时对应的LSN值,系统奔溃恢复时将从该值开始。
  • LOG_CHECKPOINT_OFFSET : 8B,上个属性中的LSN值在redo日志文件组中的偏移量
  • LOG_CHECKPOINT_LOG_BUF_SIZE : 8B,服务器在做checkpoint操作时对应的logbuffer的大小
  • LOG_BLOCK_CHECKSUM : 4B,本block的校验值

在这里插入图片描述

第四个blockcheckpoint2与checkpoint1中的结构属性相同,因此不做赘述。

3、源码解析

3.1 日志文件组结构体

struct log_group_t{
	/** log group identifier (always 0) */
	ulint				id;
	/** number of files in the group */
	ulint				n_files;
	/** format of the redo log: e.g., LOG_HEADER_FORMAT_CURRENT */
	ulint				format;
	/** individual log file size in bytes, including the header */
	lsn_t				file_size
	/** file space which implements the log group */;
	ulint				space_id;
	/** corruption status */
	log_group_state_t		state;
	/** lsn used to fix coordinates within the log group */
	lsn_t				lsn;
	/** the byte offset of the above lsn */
	lsn_t				lsn_offset;
	/** unaligned buffers */
	byte**				file_header_bufs_ptr;
	/** buffers for each file header in the group */
	byte**				file_header_bufs;

	/** used only in recovery: recovery scan succeeded up to this
	lsn in this log group */
	lsn_t				scanned_lsn;
	/** unaligned checkpoint header */
	byte*				checkpoint_buf_ptr;
	/** buffer for writing a checkpoint header */
	byte*				checkpoint_buf;
	/** list of log groups */
	UT_LIST_NODE_T(log_group_t)	log_groups;
};

日志文件组结构体中包含了文件组id、文件数量、所包含redo日志文件的类型等基本信息。另外,结构体中还有几个较为重要的字段:

  • lsn : 日志组中用于确定位置的lsn
  • lsn_offset : lsn的字节偏移量
  • checkpoint_buf : 用于写入检查点头部的缓冲区
  • UT_LIST_NODE_T(log_group_t) log_groups : 将日志组链接起来的链表

3.2 刷盘时机

理想状态下,事务一提交就会进行刷盘操作,但实际上,刷盘的时机是根据策略来进行的。
InnoDB存储引擎为redo log的刷盘策略提供了innodb_flush_log_at_trx_commit参数,它支持三种策略:

  • 0 :设置为 0 的时候,表示每次事务提交时不进行刷盘操作
  • 1 :设置为 1 的时候,表示每次事务提交时都将进行刷盘操作(默认值)
  • 2 :设置为 2 的时候,表示每次事务提交时都只把redo log buffer内容写入page cache
  • innodb_flush_log_at_trx_commit 参数默认为 1 ,也就是说当事务提交时会调用 fsync 对 redo log 进行刷盘
  • 另外,InnoDB 存储引擎有一个后台线程,每隔1 秒,就会把 redo log buffer 中的内容写到文件系统缓存(page cache),然后调用 fsync 刷盘。

3.2.1 提交刷盘

提交刷盘主要涉及到两个关键函数:log_buffer_flush_to_disk()和log_write_up_to()

void
log_buffer_flush_to_disk(
	bool sync)
{
	ut_ad(!srv_read_only_mode);
	log_write_up_to(log_get_lsn(), sync);
}
  • 这段代码的功能是调用log_write_up_to()函数将日志缓冲区中的内容写入到磁盘上的日志文件中,直到最新的日志记录,并根据参数决定是否同时将日志数据刷新到磁盘。
  • 是否进行刷盘操作主要通过sync参数控制:sync为true,则进行刷盘操作;否则只写入操作系统的缓存。默认情况下,bool sync = true。
void
log_write_up_to(
	lsn_t	lsn,
	bool	flush_to_disk)
{
	byte*           write_buf;
	lsn_t           write_lsn;

	ut_ad(!srv_read_only_mode);

	if (recv_no_ibuf_operations) {

		return;
	}

loop:
	ut_ad(++loop_count < 128);

	log_write_mutex_enter();
	ut_ad(!recv_no_log_write);

	lsn_t	limit_lsn = flush_to_disk
		? log_sys->flushed_to_disk_lsn
		: log_sys->write_lsn;

	if (limit_lsn >= lsn) {
		log_write_mutex_exit();
		return;
	}

#ifdef _WIN32
# ifndef UNIV_HOTBACKUP
	/* write requests during fil_flush() might not be good for Windows */
	if (log_sys->n_pending_flushes > 0
	    || !os_event_is_set(log_sys->flush_event)) {
		log_write_mutex_exit();
		os_event_wait(log_sys->flush_event);
		goto loop;
	}
# else
	if (log_sys->n_pending_flushes > 0) {
		goto loop;
	}
# endif  /* !UNIV_HOTBACKUP */
#endif /* _WIN32 */

	if (flush_to_disk
	    && (log_sys->n_pending_flushes > 0
		|| !os_event_is_set(log_sys->flush_event))) {

		/* Figure out if the current flush will do the job
		for us. */
		bool work_done = log_sys->current_flush_lsn >= lsn;

		log_write_mutex_exit();

		os_event_wait(log_sys->flush_event);

		if (work_done) {
			return;
		} else {
			goto loop;
		}
	}

	log_mutex_enter();
	if (!flush_to_disk
	    && log_sys->buf_free == log_sys->buf_next_to_write) {
		/* Nothing to write and no flush to disk requested */
		log_mutex_exit_all();
		return;
	}

	log_group_t*	group;
	ulint		start_offset;
	ulint		end_offset;
	ulint		area_start;
	ulint		area_end;
	ulong		write_ahead_size = srv_log_write_ahead_size;
	ulint		pad_size;

	DBUG_PRINT("ib_log", ("write " LSN_PF " to " LSN_PF,
			      log_sys->write_lsn,
			      log_sys->lsn));

	if (flush_to_disk) {
		log_sys->n_pending_flushes++;
		log_sys->current_flush_lsn = log_sys->lsn;
		MONITOR_INC(MONITOR_PENDING_LOG_FLUSH);
		os_event_reset(log_sys->flush_event);

		if (log_sys->buf_free == log_sys->buf_next_to_write) {
			/* Nothing to write, flush only */
			log_mutex_exit_all();
			log_write_flush_to_disk_low();
			return;
		}
	}

	start_offset = log_sys->buf_next_to_write;
	end_offset = log_sys->buf_free;

	area_start = ut_calc_align_down(start_offset, OS_FILE_LOG_BLOCK_SIZE);
	area_end = ut_calc_align(end_offset, OS_FILE_LOG_BLOCK_SIZE);

	ut_ad(area_end - area_start > 0);

	log_block_set_flush_bit(log_sys->buf + area_start, TRUE);
	log_block_set_checkpoint_no(
		log_sys->buf + area_end - OS_FILE_LOG_BLOCK_SIZE,
		log_sys->next_checkpoint_no);

	write_lsn = log_sys->lsn;
	write_buf = log_sys->buf;

	log_buffer_switch();

	group = UT_LIST_GET_FIRST(log_sys->log_groups);

	log_group_set_fields(group, log_sys->write_lsn);

	log_mutex_exit();

	/* Calculate pad_size if needed. */
	pad_size = 0;
	if (write_ahead_size > OS_FILE_LOG_BLOCK_SIZE) {
		lsn_t	end_offset;
		ulint	end_offset_in_unit;

		end_offset = log_group_calc_lsn_offset(
			ut_uint64_align_up(write_lsn,
					   OS_FILE_LOG_BLOCK_SIZE),
			group);
		end_offset_in_unit = (ulint) (end_offset % write_ahead_size);

		if (end_offset_in_unit > 0
		    && (area_end - area_start) > end_offset_in_unit) {
			/* The first block in the unit was initialized
			after the last writing.
			Needs to be written padded data once. */
			pad_size = write_ahead_size - end_offset_in_unit;

			if (area_end + pad_size > log_sys->buf_size) {
				pad_size = log_sys->buf_size - area_end;
			}

			::memset(write_buf + area_end, 0, pad_size);
		}
	}

	/* Do the write to the log files */
	log_group_write_buf(
		group, write_buf + area_start,
		area_end - area_start + pad_size,
		ut_uint64_align_down(log_sys->write_lsn,
				     OS_FILE_LOG_BLOCK_SIZE),
		start_offset - area_start);

	srv_stats.log_padded.add(pad_size);

	log_sys->write_lsn = write_lsn;

#ifndef _WIN32
	if (srv_unix_file_flush_method == SRV_UNIX_O_DSYNC) {
		/* O_SYNC means the OS did not buffer the log file at all:
		so we have also flushed to disk what we have written */
		log_sys->flushed_to_disk_lsn = log_sys->write_lsn;
	}
#endif /* !_WIN32 */

	log_write_mutex_exit();

	if (flush_to_disk) {
		log_write_flush_to_disk_low();
	}
}
  • 该函数负责将日志数据从内存缓冲区写入到磁盘上的日志文件,并根据需要决定是否将这些数据同步(刷新)到磁盘。这个函数是数据库日志管理系统的核心部分。

1、前期准备

byte*           write_buf;
lsn_t           write_lsn;

ut_ad(!srv_read_only_mode);

if (recv_no_ibuf_operations) {

	return;
}

loop:
	ut_ad(++loop_count < 128);
  • 初始化变量:write_buf和write_lsn,分别用于记录写入日志偏移量和写入日志lsn
  • 断言检查:确保服务器不在只读模式下运行
  • 如果recv_no_ibuf_operations为真,表示正在运行恢复操作,此时不允许对日志文件进行操作,函数直接返回
  • 断言检查循环次数,防止无限循环

2、条件判断

	log_write_mutex_enter();
	ut_ad(!recv_no_log_write);

	lsn_t	limit_lsn = flush_to_disk
		? log_sys->flushed_to_disk_lsn
		: log_sys->write_lsn;

	if (limit_lsn >= lsn) {
		log_write_mutex_exit();
		return;
	}
  • 如果limit_lsn大于或等于目标lsn,则直接退出互斥锁并返回,因为所需的日志已经写入或刷新到指定位置。
if (log_sys->n_pending_flushes > 0
	|| !os_event_is_set(log_sys->flush_event)) {
	log_write_mutex_exit();
	os_event_wait(log_sys->flush_event);
	goto loop;
}
  • 检查是否有挂起的刷新操作或flush_event事件未设置。是的话,退出锁,等待flush_event,并进入循环。
if (flush_to_disk
	&& (log_sys->n_pending_flushes > 0
	|| !os_event_is_set(log_sys->flush_event))) {

	/* Figure out if the current flush will do the job
	for us. */
	bool work_done = log_sys->current_flush_lsn >= lsn;

	log_write_mutex_exit();

	os_event_wait(log_sys->flush_event);

	if (work_done) {
		return;
	} else {
		goto loop;
	}
}

log_mutex_enter();
if (!flush_to_disk
	&& log_sys->buf_free == log_sys->buf_next_to_write) {
	/* Nothing to write and no flush to disk requested */
	log_mutex_exit_all();
	return;
}
  • 如果需要刷新到磁盘且有挂起的刷新操作或flush_event未触发,则执行以下操作
  • 定义了一个布尔变量work_done,用于检查当前的刷新操作是否已经包含了目标日志序列号lsn。
  • 如果current_flush_lsn大于或等于lsn,则work_done为真,那么函数直接返回。如果work_done不为真,则跳转到loop处循环
  • 接下来,代码进入另一个互斥锁,检查是否不需要刷新到磁盘且日志缓冲区没有待写入的内容。如果这两个条件都满足,则退出所有相关的互斥锁并返回。

3、日志写入准备

log_group_t*	group;  // 指向日志组的指针
ulint		start_offset;  // 日志缓冲区中待写入数据的起始偏移量
ulint		end_offset;    // 日志缓冲区中待写入数据的结束偏移量
ulint		area_start;    // 对齐后的待写入区域的起始偏移量
ulint		area_end;      // 对齐后的待写入区域的结束偏移量
ulong		write_ahead_size = srv_log_write_ahead_size;   // 写前日志大小
ulint		pad_size;   // 填充大小

DBUG_PRINT("ib_log", ("write " LSN_PF " to " LSN_PF,
				log_sys->write_lsn,
				log_sys->lsn));

if (flush_to_disk) {
	log_sys->n_pending_flushes++;   // 增加挂起的刷新操作计数
	log_sys->current_flush_lsn = log_sys->lsn;  // 记录当前正在刷新的日志序列号
	MONITOR_INC(MONITOR_PENDING_LOG_FLUSH);
	os_event_reset(log_sys->flush_event);

	if (log_sys->buf_free == log_sys->buf_next_to_write) {
		/* Nothing to write, flush only */
		log_mutex_exit_all();
		log_write_flush_to_disk_low();
		return;
	}
}

/* 计算待写入数据的起始和结束偏移量,以及对齐偏移量 */
start_offset = log_sys->buf_next_to_write;
end_offset = log_sys->buf_free;

area_start = ut_calc_align_down(start_offset, OS_FILE_LOG_BLOCK_SIZE);
area_end = ut_calc_align(end_offset, OS_FILE_LOG_BLOCK_SIZE);

ut_ad(area_end - area_start > 0);

log_block_set_flush_bit(log_sys->buf + area_start, TRUE);
log_block_set_checkpoint_no(
	log_sys->buf + area_end - OS_FILE_LOG_BLOCK_SIZE,
	log_sys->next_checkpoint_no);

write_lsn = log_sys->lsn;
write_buf = log_sys->buf;

log_buffer_switch();   // 切换缓冲区

group = UT_LIST_GET_FIRST(log_sys->log_groups);  // 从日志系统的日志组列表中获取第一个日志组

log_group_set_fields(group, log_sys->write_lsn);  // 设置日志组的字段,如当前的写入日志序列号

log_mutex_exit();

/* Calculate pad_size if needed. */
pad_size = 0;
/* 如果写前日志大小大于操作系统日志块大小,则需要计算填充大小 */
if (write_ahead_size > OS_FILE_LOG_BLOCK_SIZE) {
	lsn_t	end_offset;
	ulint	end_offset_in_unit;

	end_offset = log_group_calc_lsn_offset(
		ut_uint64_align_up(write_lsn,
					OS_FILE_LOG_BLOCK_SIZE),
		group);
	end_offset_in_unit = (ulint) (end_offset % write_ahead_size);

	if (end_offset_in_unit > 0
		&& (area_end - area_start) > end_offset_in_unit) {
		/* The first block in the unit was initialized
		after the last writing.
		Needs to be written padded data once. */
		pad_size = write_ahead_size - end_offset_in_unit;

		if (area_end + pad_size > log_sys->buf_size) {
			pad_size = log_sys->buf_size - area_end;
		}

		::memset(write_buf + area_end, 0, pad_size);
	}
}
  • 这段代码的目的是在确保日志数据正确写入和刷新到磁盘的同时,处理对齐和填充等细节问题。通过使用互斥锁和条件判断,实现了在多线程环境下对日志系统的安全访问和同步。

4、写入日志文件

/* 这行代码将缓冲区中从area_start到area_end + pad_size的数据写入到指定的日志组中。
 * 写入的起始日志序列号是对log_sys->write_lsn向下对齐到OS_FILE_LOG_BLOCK_SIZE的结果,而偏移量则是start_offset - area_start,表示数据在缓冲区中的相对位置。 */
log_group_write_buf(
	group, write_buf + area_start,
	area_end - area_start + pad_size,
	ut_uint64_align_down(log_sys->write_lsn,
					OS_FILE_LOG_BLOCK_SIZE),
	start_offset - area_start);

srv_stats.log_padded.add(pad_size);  // 将填充大小pad_size添加到日志系统的填充统计中,有助于监控和调试日志写入过程中的填充情况。

log_sys->write_lsn = write_lsn;  // 更新日志系统的写入序列号write_lsn

/* 在非windowa平台上,将已刷新到磁盘的序列号flushed_to_disk_lsn更新为当前的写入序列号write_lsn */
#ifndef _WIN32
	if (srv_unix_file_flush_method == SRV_UNIX_O_DSYNC) {
		log_sys->flushed_to_disk_lsn = log_sys->write_lsn;
	}
#endif /* !_WIN32 */

log_write_mutex_exit();

if (flush_to_disk) {
	log_write_flush_to_disk_low();  // 如果flush_to_disk为真,则调用log_write_flush_to_disk_low函数将日志数据真正刷新到磁盘上。这是确保数据持久化的关键步骤。
}
  • 这段代码是日志写入流程中的一部分,负责将数据写入日志组、更新统计信息和序列号、根据平台特性可能更新已刷新到磁盘的序列号,并最终释放互斥锁和(如果需要)将数据刷新到磁盘。
【智能车Code review】—曲率计算、最小二乘法拟合 【1】曲率计算函数 选三个点计算曲率 float process_curvity(byte x1, byte y1, byte x2, byte y2, byte x3, byte y3) { float K; int S_of_ABC = ((x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1)) / 2; //面积的符号表示方向 byte q1 = (byte)((x2 - x1) * (x2 - x1) + (y2 - y1) * 阅读详情

相关推荐

VS2019手动添加bits/stdc++.h全指南(附解决‘无法打开源文件‘错误)

本文详细介绍了在Visual Studio 2019中手动配置C++万能头文件bits/stdc++.h的完整方法,解决常见的'无法打开源文件'错误。通过分析GCC与MSVC编译器生态差异,提供了纯手动创建和MinGW移植两种可靠方案,并给出详细的步骤、常见错误排查及最佳实践建议,帮助开发者高效配置竞赛开发环境。

weixin_29011395的博客 549

海山数据库(He3DB)源码详解:海山Mysql 崩溃恢复(1)-准备及结束

选取最近发生的那次checkpoint的信息。衡量checkpoint发生时间早晚的信息就是所谓的,只要把和这两个block中的值读出来比一下大小,哪个的值更大,说明哪个block存储的就是最近的一次checkpoint信息。这样就能拿到最近发生的checkpoint对应的值以及它在redo日志文件组中的偏移量。为什么要把checkpoint信息存储到2个block中?万一就在某次写checkpoint信息的过程中MySQL崩溃了,有可能导致正在写入的这个block中的checkpoint信息不正确。

于巍,花名漠雪,移动云数据库技术委员会主任,海山数据库研发负责人,PG中文社区常委,PG中文社区海外联络人。15年数据库内核研发经验,4年美国、以色列等海外研发经历,华为GaussDB、阿里云PolarDB、移动云He3DB等核心架构 1121

PCB作为智能小车底盘的设计方法与工程实践

PCB底盘是一种将机械结构与电子设计深度融合的嵌入式系统集成方案,其本质是利用印制电路板的高精度制造能力(±0.05mm孔位公差)和FR-4材料的结构强度(弯曲强度345MPa),替代传统亚克力或铝合金底盘。该方案通过封装库驱动的正向设计、机械层语义化定义及机电协同布局,实现安装基准统一、共地平面优化与RF天线集成等技术价值。广泛应用于教育机器人、AGV小车、模块化开发平台等对空间紧凑性、量产一致性与电磁兼容性有严苛要求的移动嵌入式场景,尤其适合具备EDA工具使用能力的电子工程师主导的跨学科开发流程。

weixin_36064575的博客 129

Spring Boot 数据源与 MyBatis(-Plus) 自定义配置详解

本文详解 Spring Boot 中单数据源环境下 HikariCP 与 MyBatis-Plus 的自定义配置。通过手动声明 SqlSessionFactory,实现下划线转驼峰、日志输出、一级缓存策略等精细化控制。结合 HikariCP 核心参数调优(如最大连接数、连接生命周期),确保高性能与稳定性。适用于仅需访问单一数据库的场景,避免多数据源复杂性,兼顾性能与可维护性。

BADAO_LIUMANG_QIZHI的博客 204

MCP详解:手把手教你理解和如何使用各种开源MCP服务

MCP 作为一种开放协议,为 AI 模型与外部世界的连接提供了标准化的解决方案。它不仅降低了开发门槛,还极大地扩展了 AI 应用的边界。无论是开发者还是普通用户,都可以通过 MCP 更高效地利用 AI 技术,实现更多智能化的功能。

王哥儿聊AI的博客 6056

智慧矿山无人机空地一体化解决方案

未来,随着技术的不断进步和应用场景的不断拓展,智慧矿山无人机空地一体化解决方案将在矿业领域发挥更加重要的作用,为矿山的可持续发展提供更加有力的保障。该系统能够实现矿山的全方位、全天候监测与管理,提升矿山生产效率,降低安全风险,并为矿山的可持续发展提供有力支持。智慧矿山无人机空地一体化解决方案是当前矿业领域技术创新的重要方向,旨在通过集成无人机技术、物联网、大数据、人工智能等先进技术,实现矿山的智能化、高效化和安全化运营。在矿山爆破、挖掘等危险作业中,无人机可进行远程监控和指挥,降低人员安全风险。

专注多旋翼、复合翼无人机技术研究,从事低空经济行业。 1110

Java EE 进阶:8.MyBatis 操作数据库(入门)(第一弹)

Java EE 进阶:8.MyBatis 操作数据库(入门)(第一弹)

要努力去发光,而不是被照亮~ 136

通信模组性能调优

本文介绍了高通平台通信模组性能调优的方法,主要包括硬件加速技术和CPU负载优化。针对CPU负载优化,提出了内核RPS/RFS功能启用、网卡队列调整、中断均衡、CPU性能模式设置等措施。同时建议关闭热管理并加强散热,调整TCP接收/发送缓存大小以解决TCP异常报文问题。这些优化手段可显著提高整机吞吐率测试中的网络性能表现。

四儿家的小祖宗的博客 1359

Docker Compose:搭建本地多服务开发环境

从一个 Java API、PostgreSQL 和 Redis 开始,配置服务名、端口、环境变量、健康检查、数据卷和依赖关系。理解 Compose 文件如何被解析、项目如何隔离,以及哪些数据不能依赖“重建容器”保留,能够把个人开发环境整理成团队可复用的启动入口。

m0_62221377的博客 461

YOLO26改进策略【YOLO和Mamba】| 2024 VM-UNet,高效的特征提取模块VSS block 二次创新提高精度

VM-UNet模型设计的出发点是解决现有CNN-based和模型在医学图像分割中的局限性,利用(SSMs)的优势,提出一种更有效的医学图像分割模型。具体来说,CNN-based模型在捕捉长距离信息方面存在不足,而模型由于自注意力机制的二次复杂度导致计算负担较重。SSMs模型如Mamba不仅在建模长距离依赖方面表现出色,还具有线性计算复杂度,这为VM-UNet的设计提供了理论基础。

Limiiiing的博客 217

01 - 什么是SVM

摘要:SVM(共享虚拟内存)技术使CPU和GPU能共享同一虚拟地址空间,简化异构计算编程。传统模型需手动管理CPU/GPU内存和拷贝数据,而SVM允许直接传递指针,自动处理页面迁移和一致性维护。其核心包括统一地址空间、按需迁移、页面故障处理和一致性管理,显著降低编程复杂度并提升性能。通过HMM框架、MMU通知和GPU页面异常等机制实现高效内存共享,支持复杂数据结构,为异构计算带来便利。

图形/计算GPU的驱动与虚拟化技术分享园地 3132

AI全栈实战 | 3.9-01 爬虫入门:requests 加 BeautifulSoup 三分钟跑通,但 90% 的人栽在这 4 个“礼貌“问题上

⚠️:本文内容仅用于。请遵守目标站点的服务条款与相关法律法规,不得对目标服务造成负担,不得采集、传播个人隐私数据。技术是中性的,边界在你手里。爬虫的入门技术(requests 发请求 + BeautifulSoup 解析)三分钟就能学会。真正区分"会用爬虫"和"会做爬虫的人"的,是这篇我们跑通"请求→解析→存储"的最小闭环,然后重点把"合规与礼貌"这根弦给你绷上。

源码获取 | 运行部署 | 定制二开修改,如果回复不及时请耐心等待,请见谅。 415

使用Google Earth Engine下载JRC全球水体数据

通过Google Earth Engine平台和提供的源代码,研究人员和水资源管理者可以方便地获取和利用这一重要的水资源监测数据集,从而开展相关的研究和决策支持工作。要使用Google Earth Engine下载JRC全球水体数据,首先需要在Google Earth Engine平台上进行注册和登录。JRC全球水体数据是由欧洲联合研究中心开发的高分辨率全球水体分类数据集,覆盖了整个地球的陆地和水域。下载完成后,你可以使用各种遥感数据处理软件来打开和分析这些数据,以满足不同的研究和应用需求。

LrlDebug的博客 1158

数据库主键选型指南:BIGINT vs VARCHAR

主键选型关乎数据库性能与架构演进。BIGINT(如雪花算法)在顺序写入、二级索引开销和分布式生成上优势显著,适合高并发核心链路;而传统 VARCHAR UUIDv4 因随机性导致频繁页分裂、存储膨胀,性能堪忧。UUIDv7 通过时间戳有序性实现高效追加写入,结合 BINARY(16) 存储可兼顾性能与安全,成为现代微服务新选择。关键在于:主键应稳定、无业务含义,优先选用趋势递增的紧凑类型,并根据场景合理采用双主键模式或 ULID 等替代方案。

小李同学的博客 302

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

上一篇: 海山数据库(He3DB)源码详解:海山MySQL redo日志-写入过程
下一篇: 海山数据库(He3DB)源码详解:CHECK过程
He3DB_ht
博客等级 码龄3年 1000粉丝 97原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值