Spark BulkLoad 写入 HBase 多列场景实战:HFile 生成与避坑指南
1. 项目背景与核心需求拆解1.1 为什么会有这个需求手里有一批数据要进HBase量还不小每天增量在千万级别。最开始用的是HBase原生的Put API单条或者批量提交跑了一段时间发现两个问题一是写入吞吐上不去RegionServer的写压力很大频繁触发flush和compaction二是Spark任务本身跑得慢大量时间花在RPC等待上。后来换成BulkLoad方式直接把HFile生成好再加载进HBase写入性能提升了一个数量级而且对RegionServer的冲击小了很多。但问题来了——业务方的数据不是单列族单列的简单结构一张表有多个列族每个列族下面还有多个列而且不同行的列还不完全一样。Spark生成的RowKey和列值需要按照HBase的存储格式组织成HFile这就涉及到HFileOutputFormat2的使用、KeyFamilyQualifier的构造、以及多列场景下的数据组装逻辑。Spark 2.4.6搭配HBase 1.3.1这个组合虽然不算新但在不少存量项目中仍然在跑网上能找到的完整示例要么是单列场景要么版本对不上踩坑的人不少。这篇文章就把整个链路拆开从数据准备、HFile生成、到最终BulkLoad加载把多列场景下的关键细节讲清楚。适合正在用Spark做HBase批量写入的开发者尤其是那些数据列不固定、需要动态组装多列的场景。1.2 整体方案选型对比在Spark里往HBase写数据常见的有三种路子方案写入方式吞吐量对集群影响适用场景Put API逐条/批量RPC低大小批量、实时性要求高BulkLoad生成HFile后加载高小大批量、离线场景Spark-HBase Connector封装Put中中快速开发、数据量中等BulkLoad的核心优势在于绕过了RegionServer的写路径直接生成HFile文件然后通过LoadIncrementalHFiles工具将文件移动到HBase的存储目录下。这个过程不经过WAL不触发flush对集群的冲击极小。缺点是不够实时适合T1或者小时级别的离线批处理。选Spark 2.4.6是因为项目环境已经固定HBase 1.3.1也是存量集群的版本。这两个版本的API兼容性整体没问题但有几个细节需要注意后面会展开。2. 核心细节解析与实操要点2.1 HFileOutputFormat2的工作机制HFileOutputFormat2是HBase提供的一个OutputFormat专门用于在MapReduce或Spark任务中生成HFile文件。它的核心逻辑是每个Reducer在Spark中是每个Partition输出一个HFile文件文件内部按照RowKey、列族、列限定符、时间戳排序。在Spark中使用时需要将数据转换成(ImmutableBytesWritable, KeyValue)的键值对形式。其中ImmutableBytesWritable是RowKeyKeyValue包含了完整的单元格信息——行键、列族、列限定符、时间戳、值。这里有个关键点KeyValue的构造函数需要传入行键、列族、列限定符、时间戳和值。对于多列场景每一列都需要构造一个独立的KeyValue对象。也就是说如果一行有5列就需要生成5个KeyValue它们的RowKey相同但列族和列限定符不同。val keyValue new KeyValue( rowKeyBytes, // 行键 familyBytes, // 列族 qualifierBytes, // 列限定符 timestamp, // 时间戳 valueBytes // 值 )2.2 多列场景下的数据组装多列场景的难点在于数据源的结构可能是嵌套的比如一个JSON对象里面有多层字段需要扁平化成HBase的列。另外不同行的列可能不一样有些行有某些列有些行没有。HBase本身是稀疏存储的不存在的列不会占用空间所以不需要为缺失的列填充空值。在Spark中通常的做法是先把数据转换成RDD[(ImmutableBytesWritable, KeyValue)]或者RDD[(ImmutableBytesWritable, List[KeyValue])]。如果是后者还需要用flatMap展开成前者。我一般倾向于直接生成扁平的KeyValue列表因为HFileOutputFormat2要求的就是扁平的键值对。val hfileRDD sourceRDD.flatMap { row val rowKey buildRowKey(row) val rowKeyBytes new ImmutableBytesWritable(Bytes.toBytes(rowKey)) val timestamp System.currentTimeMillis() val keyValues new ListBuffer[KeyValue]() // 列族1下的多列 keyValues new KeyValue(rowKeyBytes.get(), Bytes.toBytes(cf1), Bytes.toBytes(col1), timestamp, Bytes.toBytes(row.col1)) keyValues new KeyValue(rowKeyBytes.get(), Bytes.toBytes(cf1), Bytes.toBytes(col2), timestamp, Bytes.toBytes(row.col2)) // 列族2下的多列 keyValues new KeyValue(rowKeyBytes.get(), Bytes.toBytes(cf2), Bytes.toBytes(col3), timestamp, Bytes.toBytes(row.col3)) keyValues.map(kv (rowKeyBytes, kv)) }注意KeyValue的构造函数中行键参数是byte[]不是ImmutableBytesWritable。所以需要调用rowKeyBytes.get()获取底层的字节数组。这个细节很容易搞错编译报错的时候可以检查一下。2.3 列族与列限定符的映射策略多列场景下列族和列限定符的映射关系需要提前规划好。通常有两种方式第一种是硬编码映射在代码里写死哪些字段对应哪个列族和列限定符。这种方式简单直接但扩展性差新增字段需要改代码重新发布。第二种是配置化映射把映射关系放在配置文件或者配置表中代码读取配置动态生成KeyValue。这种方式灵活适合字段经常变化的场景。我一般推荐第二种尤其是在字段不固定的业务场景下。可以用一个简单的Map结构来维护映射关系val columnMapping Map( col1 - (cf1, qualifier1), col2 - (cf1, qualifier2), col3 - (cf2, qualifier3) )然后在遍历数据字段时根据字段名查找对应的列族和列限定符。如果某个字段在映射表中不存在可以选择跳过或者记录日志。2.4 时间戳的处理HBase的每个单元格都有一个时间戳默认是写入时的系统时间。在BulkLoad场景下时间戳是在生成KeyValue时指定的。如果业务上需要保留数据本身的时间属性比如数据产生的时间那么应该用数据中的时间字段作为时间戳而不是用当前系统时间。val timestamp row.eventTime // 使用数据中的时间字段如果不需要保留数据时间直接用System.currentTimeMillis()也可以。但要注意同一行的多个列如果时间戳不一致在HBase中会表现为不同版本的数据。所以同一行的所有列最好使用同一个时间戳避免版本混乱。3. 实操过程与核心环节实现3.1 环境准备与依赖配置先确认Spark和HBase的版本以及它们之间的依赖关系。Spark 2.4.6默认依赖的HBase版本是1.2.6左右但我们需要用1.3.1所以需要在项目中显式引入HBase 1.3.1的依赖并排除Spark自带的HBase依赖。Maven的pom.xml配置如下properties spark.version2.4.6/spark.version hbase.version1.3.1/hbase.version /properties dependencies dependency groupIdorg.apache.spark/groupId artifactIdspark-core_2.11/artifactId version${spark.version}/version exclusions exclusion groupIdorg.apache.hbase/groupId artifactIdhbase-client/artifactId /exclusion exclusion groupIdorg.apache.hbase/groupId artifactIdhbase-server/artifactId /exclusion /exclusions /dependency dependency groupIdorg.apache.hbase/groupId artifactIdhbase-client/artifactId version${hbase.version}/version /dependency dependency groupIdorg.apache.hbase/groupId artifactIdhbase-server/artifactId version${hbase.version}/version /dependency dependency groupIdorg.apache.hbase/groupId artifactIdhbase-mapreduce/artifactId version${hbase.version}/version /dependency /dependencies提示hbase-mapreduce这个包在HBase 1.3.1中是必须的因为HFileOutputFormat2和LoadIncrementalHFiles都在这个包里。有些教程只引了hbase-client和hbase-server结果运行时报ClassNotFoundException就是漏了这个包。3.2 HBase表结构设计在生成HFile之前需要先确认HBase表的结构。假设我们要写入的表叫test_table有两个列族cf1和cf2每个列族下有多个列限定符。建表语句如下create test_table, {NAME cf1, VERSIONS 1, BLOOMFILTER ROW}, {NAME cf2, VERSIONS 1, BLOOMFILTER ROW}注意VERSIONS 1表示只保留一个版本的数据。如果业务上需要保留多个版本可以调大这个值。但在BulkLoad场景下如果时间戳相同多个版本会互相覆盖需要特别注意。3.3 Spark任务的核心代码完整的Spark任务代码分为几个部分读取数据源、构建RowKey、组装KeyValue、生成HFile、加载到HBase。import org.apache.hadoop.fs.Path import org.apache.hadoop.hbase.{HBaseConfiguration, KeyValue, TableName} import org.apache.hadoop.hbase.client.{ConnectionFactory, HTable} import org.apache.hadoop.hbase.io.ImmutableBytesWritable import org.apache.hadoop.hbase.mapreduce.{HFileOutputFormat2, LoadIncrementalHFiles} import org.apache.hadoop.hbase.util.Bytes import org.apache.spark.sql.SparkSession import scala.collection.mutable.ListBuffer object SparkBulkLoadMultiColumn { def main(args: Array[String]): Unit { val spark SparkSession.builder() .appName(SparkBulkLoadMultiColumn) .enableHiveSupport() .getOrCreate() val hbaseConf HBaseConfiguration.create() hbaseConf.set(hbase.zookeeper.quorum, zk1,zk2,zk3) hbaseConf.set(hbase.zookeeper.property.clientPort, 2181) val tableName test_table val hfilePath /tmp/hfile_output // 读取数据源 val sourceDF spark.sql(SELECT * FROM source_table) // 构建RDD[(ImmutableBytesWritable, KeyValue)] val hfileRDD sourceDF.rdd.flatMap { row val rowKey row.getAs[String](id) val rowKeyBytes Bytes.toBytes(rowKey) val immutableRowKey new ImmutableBytesWritable(rowKeyBytes) val timestamp System.currentTimeMillis() val keyValues new ListBuffer[(ImmutableBytesWritable, KeyValue)]() // 列族cf1下的列 val cf1 Bytes.toBytes(cf1) keyValues ((immutableRowKey, new KeyValue(rowKeyBytes, cf1, Bytes.toBytes(name), timestamp, Bytes.toBytes(row.getAs[String](name))))) keyValues ((immutableRowKey, new KeyValue(rowKeyBytes, cf1, Bytes.toBytes(age), timestamp, Bytes.toBytes(row.getAs[Int](age).toString)))) // 列族cf2下的列 val cf2 Bytes.toBytes(cf2) keyValues ((immutableRowKey, new KeyValue(rowKeyBytes, cf2, Bytes.toBytes(address), timestamp, Bytes.toBytes(row.getAs[String](address))))) keyValues ((immutableRowKey, new KeyValue(rowKeyBytes, cf2, Bytes.toBytes(phone), timestamp, Bytes.toBytes(row.getAs[String](phone))))) keyValues.toList } // 配置HFileOutputFormat2 val table ConnectionFactory.createConnection(hbaseConf) .getTable(TableName.valueOf(tableName)) val regionLocator ConnectionFactory.createConnection(hbaseConf) .getRegionLocator(TableName.valueOf(tableName)) HFileOutputFormat2.configureIncrementalLoad( spark.sparkContext.hadoopConfiguration, table, regionLocator ) // 生成HFile hfileRDD.saveAsNewAPIHadoopFile( hfilePath, classOf[ImmutableBytesWritable], classOf[KeyValue], classOf[HFileOutputFormat2], hbaseConf ) // 加载HFile到HBase val loadIncrementalHFiles new LoadIncrementalHFiles(hbaseConf) loadIncrementalHFiles.doBulkLoad( new Path(hfilePath), table.asInstanceOf[HTable] ) spark.stop() } }3.4 关键参数计算与选择RowKey设计RowKey的设计直接影响到HBase的读写性能。常见的策略有加盐、哈希、反转等。如果数据量不大直接用业务主键也可以。但如果数据量大且写入集中建议做散列处理避免热点。// 加盐示例在RowKey前加两位随机数 val salt (Math.abs(rowKey.hashCode) % 100).toString val saltedRowKey salt _ rowKeyHFile大小HFile的大小由hbase.hregion.max.filesize参数控制默认是10GB。在BulkLoad场景下生成的HFile大小取决于每个Partition的数据量。如果单个HFile过大加载时可能会触发Region分裂。可以通过调整Spark的Partition数量来控制每个HFile的大小。// 调整Partition数量 val hfileRDD sourceDF.rdd.repartition(100).flatMap { ... }时间戳精度HBase的时间戳是毫秒级。如果同一行在同一毫秒内写入多次后面的会覆盖前面的。在BulkLoad场景下如果数据源中有重复的RowKey需要提前去重或者使用不同的时间戳。3.5 实操现场记录在实际跑任务的时候有几个环节需要特别关注第一步确认HBase表存在且列族配置正确。如果表不存在configureIncrementalLoad会报错。列族的VERSIONS配置也要注意如果设置为1那么相同RowKey相同列族相同列限定符的数据只会保留最新版本。第二步检查HFile输出目录。saveAsNewAPIHadoopFile会生成一个目录里面包含_SUCCESS文件和多个HFile文件。如果目录已存在任务会报错需要提前删除。hadoop fs -rm -r /tmp/hfile_output第三步执行BulkLoad。doBulkLoad方法会将HFile文件移动到HBase的存储目录下。这个过程是原子的要么全部成功要么全部失败。如果失败需要检查HBase的日志常见原因是HFile的格式不匹配或者Region的边界不对。第四步验证数据。加载完成后用HBase Shell扫描表确认数据是否正确写入。scan test_table, {LIMIT 10}4. 常见问题与排查技巧实录4.1 常见问题速查表问题现象可能原因解决方法ClassNotFoundException: HFileOutputFormat2缺少hbase-mapreduce依赖添加hbase-mapreduce依赖IllegalArgumentException: Cant read hfileHFile格式不匹配确认HBase版本与生成HFile的版本一致RegionTooBusyExceptionRegion写入压力大调整RowKey散列增加Region数量HFile加载后数据为空时间戳或列族配置错误检查KeyValue构造参数任务OOMPartition数据量过大增加Partition数量减少单Partition数据量BulkLoad失败FileNotFoundExceptionHFile输出目录不存在确认saveAsNewAPIHadoopFile执行成功4.2 独家避坑技巧坑一HBase依赖冲突。Spark自带的HBase依赖版本和项目需要的版本不一致会导致各种奇怪的错误。最稳妥的做法是在pom.xml中排除Spark自带的HBase依赖显式引入项目需要的版本。如果用的是SBT也需要做类似的排除。坑二KeyValue的RowKey参数。KeyValue的构造函数第一个参数是byte[]不是ImmutableBytesWritable。很多示例代码里写的是rowKeyBytes.get()但如果rowKeyBytes是ImmutableBytesWritable类型需要先调用get()方法获取字节数组。这个细节在编译时可能不会报错但运行时会出问题。坑三时间戳不一致导致数据覆盖。如果同一行的不同列使用了不同的时间戳在HBase中会表现为不同版本。如果表的VERSIONS设置为1那么只有最新时间戳的列会被保留其他列会丢失。所以同一行的所有列必须使用同一个时间戳。坑四HFile输出目录权限问题。BulkLoad需要将HFile文件从输出目录移动到HBase的存储目录这涉及到HDFS的权限。如果Spark任务使用的用户没有权限访问HBase的存储目录BulkLoad会失败。解决方法是确保Spark任务使用的用户有足够的权限或者使用HBase的管理员用户执行BulkLoad。坑五Region边界不对。如果HBase表的预分区和HFile中的RowKey范围不匹配BulkLoad时会出现RegionSplit异常。解决方法是提前做好预分区确保HFile中的RowKey落在正确的Region范围内。4.3 性能调优建议调整Partition数量Partition数量决定了生成的HFile文件数量。Partition太少单个HFile过大加载时可能触发Region分裂Partition太多生成大量小HFile加载效率低。一般建议每个Partition处理100MB到500MB的数据。使用Snappy压缩HFile支持多种压缩算法Snappy在压缩比和速度之间取得了较好的平衡。可以在建表时指定压缩算法create test_table, {NAME cf1, COMPRESSION SNAPPY}, {NAME cf2, COMPRESSION SNAPPY}预分区在创建表时预先划分好Region避免BulkLoad时自动分裂。预分区的数量根据数据量和RowKey分布来定一般建议每个Region的大小在1GB到5GB之间。create test_table, {NAME cf1, COMPRESSION SNAPPY}, {NAME cf2, COMPRESSION SNAPPY}, SPLITS [1, 2, 3, 4, 5, 6, 7, 8, 9]批量提交如果数据量特别大可以分批次执行BulkLoad避免单次加载过多HFile导致RegionServer压力过大。5. 多列场景下的扩展思考5.1 动态列的處理实际业务中列的数量和名称可能是不固定的。比如用户画像数据每个用户的标签数量不一样标签名称也不一样。这种情况下需要在代码中动态遍历数据字段根据字段名生成对应的列限定符。val dynamicColumns row.getValuesMap(row.schema.fieldNames) dynamicColumns.foreach { case (fieldName, fieldValue) val (family, qualifier) columnMapping.getOrElse(fieldName, (cf_default, fieldName)) keyValues ((immutableRowKey, new KeyValue( rowKeyBytes, Bytes.toBytes(family), Bytes.toBytes(qualifier), timestamp, Bytes.toBytes(fieldValue.toString) ))) }这种方式的好处是扩展性强新增字段不需要改代码。但需要注意字段名到列族和列限定符的映射关系要维护好避免出现列族或列限定符不存在的情况。5.2 多列族写入的注意事项HBase的列族数量不宜过多一般建议不超过3个。列族过多会导致MemStore和StoreFile数量增加影响性能。如果业务上确实需要多个列族建议把访问模式相似的列放在同一个列族下。在BulkLoad场景下多个列族的HFile会分别生成加载时也会分别处理。如果某个列族的数据量特别大可以考虑单独做BulkLoad避免单个任务处理过多数据。5.3 数据一致性保障BulkLoad的一个潜在问题是数据一致性。因为BulkLoad绕过了WAL如果加载过程中出现故障可能会导致部分数据丢失。为了保证数据一致性可以采取以下措施第一在BulkLoad之前先确认HFile文件已经完整生成并且_SUCCESS文件存在。第二BulkLoad完成后用HBase的hbck工具检查表的完整性。第三如果业务上对数据一致性要求极高可以在BulkLoad之后做一次数据校验对比源数据和HBase中的数据。hbase hbck -details test_table5.4 与其他写入方式的对比BulkLoad虽然性能好但并不是所有场景都适用。如果数据量不大或者对实时性要求高用Put API或者Spark-HBase Connector可能更合适。BulkLoad更适合T1的离线批处理场景数据量大、对实时性要求不高。另外BulkLoad生成的HFile文件需要占用HDFS空间如果数据量特别大需要考虑HDFS的存储容量。而Put API是直接写入HBase的不需要额外的HDFS空间。我在实际项目中的体会是BulkLoad和Put API可以结合使用。历史数据用BulkLoad批量导入增量数据用Put API实时写入。这样既能保证历史数据的导入效率又能满足增量数据的实时性要求。最后再分享一个小技巧如果BulkLoad过程中出现RegionTooBusyException可以适当降低BulkLoad的并发度或者调整HBase的hbase.regionserver.handler.count参数增加RegionServer的处理线程数。这个参数默认是30可以根据实际情况调大到50或者100。

相关新闻

数据库设计避坑指南:范式权衡、索引优化与扩展性实战

数据库设计避坑指南:范式权衡、索引优化与扩展性实战

搞数据库设计这么多年,我越来越觉得这活儿像“打地基”。房子盖歪了还能装修找补一下,地基要是没打好,后面住着天天漏风渗水,想返工就得推倒重来。数据库表结构一旦设计失误,等系统上线、数据堆积如山的时候&#xff0…

2026/10/15 5:02:07 阅读更多 →
数据字典、值集、快码表、弹性域、直列表:五个概念一次讲清

数据字典、值集、快码表、弹性域、直列表:五个概念一次讲清

这几个词在企业信息化圈子里几乎天天被挂在嘴边,尤其是做实施、做开发、做接口对接的同事,基本上每轮需求评审都会撞上。但说实话,能把“数据字典、值集、快码表、弹性域、直列表”这五个概念一次性讲明白的人并不多。我自己刚入行那半年也是…

2026/10/15 5:02:07 阅读更多 →
pandas统计分析下半场:groupby、透视表与时间序列实战

pandas统计分析下半场:groupby、透视表与时间序列实战

1. 为什么“下半场”才是pandas统计分析真正的分水岭很多人学pandas,学到read_csv、head()、describe()就觉得自己已经会了。我当初也是这么想的,直到有一次拿到一份三十多万行的销售流水,想按区域、按季度、按品类做交叉汇总,结果…

2026/10/15 5:02:07 阅读更多 →

最新新闻

DSC热分析实操指南:从原理到异常排查的技术要点

DSC热分析实操指南:从原理到异常排查的技术要点

拿到一个从没见过的样品,想知道它耐不耐热、在什么温度发生结晶或者玻璃化转变、某个反应到底放出多少热量,这些问题我习惯先让差示扫描量热仪跑一遍。干材料热分析这些年,差示扫描量热仪(DSC)是实验室里出场率最高的一…

2026/10/15 5:42:36 阅读更多 →
Linux命令行入门:从访问终端到掌握基础命令

Linux命令行入门:从访问终端到掌握基础命令

1. 为什么第一步要和命令行打交道接触Linux这件事,绕不开的第一个坎就是“访问命令行”。哪怕你只是想搭个网站、跑个Python脚本、玩玩树莓派,最终都要坐在一个黑底白字的终端前面敲命令。很多人刚接触Linux时总想先找一个漂亮的图形界面,觉得…

2026/10/15 5:42:36 阅读更多 →
从零构建高可靠数据解析管道:rea型项目设计与避坑指南

从零构建高可靠数据解析管道:rea型项目设计与避坑指南

1. 从“rea”这个标题说起:一个被低估的通用缩写第一次看到“rea”这个标题,我脑子里蹦出来的第一反应是——这大概率又是一个被缩写玩坏的项目名。在技术圈混久了你会发现,越是短到只有三个字母的标题,背后藏的东西往往越杂。它可…

2026/10/15 5:42:36 阅读更多 →
Cherry Studio:本地离线多模型AI客户端,支持LLaMA-3/Phi-4跨平台部署

Cherry Studio:本地离线多模型AI客户端,支持LLaMA-3/Phi-4跨平台部署

简介:本资源是一份关于AI桌面客户端Cherry Studio的深度介绍文档,面向希望提升日常效率的开发者、内容创作者、教育工作者及企业协作团队,解决多模型切换难、本地与云端AI工具割裂、文档与代码协同低效等实际问题。文档为单个16KB的Word&…

2026/10/15 5:42:36 阅读更多 →
校招技术笔试通关指南:从题型拆解到算法与系统设计备考

校招技术笔试通关指南:从题型拆解到算法与系统设计备考

又是一年秋招季,我朋友圈里不少准备冲技术岗的同学最近都在刷各种题库。其中被问得最频繁的,就是某在线教育公司(业内一般叫它Y公司)的校招技术类笔试题。这家的笔试在圈子里名声一直很特别。不单单是考算法,也不像有些…

2026/10/15 5:42:36 阅读更多 →
AI魔改与AIGC短剧合规指南:红线、审核与实操清单

AI魔改与AIGC短剧合规指南:红线、审核与实操清单

我最近频繁收到做内容的朋友私信,问得最多的就是一句话:AI魔改还能不能做?AI生成的短剧发出去会不会被限流?甚至有人直接甩过来一个通知截图,内容是“该作品因涉及AI生成内容未标注,已限制推荐”。说实话&a…

2026/10/15 5:41:36 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →