news 2026/9/13 2:41:04

分库分表实战:容量评估、分片键选型与平滑迁移全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
分库分表实战:容量评估、分片键选型与平滑迁移全解析

1. 什么时候该分库分表:先算一笔“容量账”

我见过不少团队把分库分表当成“大厂标配”,数据量刚过百万就急着拆库拆表,结果引入了一大堆复杂度,收益却微乎其微。说实话,这件事什么时候做、做到什么程度,完全取决于你现在和可预期未来到底撞上了哪堵墙。我自己的经验是:分库分表是水平扩展的关键手段,但它不是一道开胃菜,而是一台手术——术前诊断比手术本身更重要。

1.1 单库单表的真实上限在哪儿

很多文章喜欢拿“单表几百万行就开始慢”说事,这个说法其实不够严谨。单表性能瓶颈不只看行数,更看你的访问模式和数据特征。

拿MySQL InnoDB举例,一张表能否扛住,核心看三个指标:

  • B+树索引深度:主键索引的每个叶子节点默认16KB,假设一行数据1KB,一个叶子节点大约16行,三层B+树可以支撑约两千万行数据。所以“千万级以下不用慌”是有理论依据的,但前提是查询都能走主键索引。
  • 写入吞吐:单库的写入能力受制于磁盘IO、binlog同步、锁竞争。即使数据量不大,只要写入QPS冲到几千,单库也可能先扛不住。
  • 连接数:单实例默认最大连接数151,即使调到2000,也扛不住业务量增长带来的并发连接需求。

我接手过一个订单系统,数据量不到800万行,但每天凌晨的批量任务一跑,整个库的CPU直接飙到90%以上。原因就是一批复杂聚合查询全落在单库上,再加上业务高峰期的实时读写,资源互相挤占。所以判断要不要分库分表,看行数只是最粗的一条线,更关键的是评估:单库是否还能同时满足读写延迟、并发吞吐和数据容量三方面的要求。

1.2 四个判断指标,满足两条就该立项

我一般用下面四个指标给业务做体检,建议你也建立这样一套评估框架:

指标警戒线说明
数据容量单表超过2000万行,或单库超过2TB索引效率下降,备份恢复时间不可接受
写入QPS持续超过2000,或峰值超过5000单机磁盘IO和锁竞争成为瓶颈
连接数应用连接池接近数据库max_connections的60%预留容量不足,突发流量会直接打满
慢查询比例超过总查询数的1%,或p99延迟超过200ms索引优化已无法覆盖全部业务路径

满足一到两条,先做读写分离、缓存、归档冷数据这些“低风险改造”;如果满足三条以上,或者单条数据本身就很大导致单表容量激增,分库分表就提上日程。

1.3 别急着拆:先排除缓存、归档和读写分离

一段真实的对话经常是这样的:

  • 你说:“这张表已经5000万行了,拆吧。”
  • 业务方问:“拆了能解决什么问题?”
  • 你说:“查询更快了……大概。”
  • 业务方沉默。

如果回答不了“具体解决什么问题”,那就说明问题定义不清楚。很多“单表变大变慢”的场景,先用三个手段就能缓解:

  1. 冷热归档:把状态为“已完成”且超过90天的订单迁到历史库。这是成本最低、收益最明显的一步,很多业务做完这一步,数据量直接下降70%。
  2. 缓存兜底:热点读集中在最近订单和未读消息上,用Redis抗住这部分读流量,数据库压力会小很多。
  3. 读写分离:报表查询、后台管理类查询全部走从库,主库只服务线上交易路径。

这套组合拳打完,如果单库仍然长期处于高水位,再考虑分库分表。分库分表解决的是“容量上限”和“写入水平扩展”问题,它不是查询优化的替代品。

提示:做技术选型前先量化现状,没有监控数据支撑的架构升级都是拍脑袋。至少要有数据库CPU、磁盘IO、慢查询、连接数这四类监控曲线,再说拆不拆。

2. 分片键选不好,后面全是坑

分库分表方案里最重要、也最不可逆的决策就是分片键(Sharding Key)。分布式系统里有一个著名的说法:分片键决定了这个系统能做什么、不能做什么。选对了,大部分查询仍然像单库一样简单;选错了,后面每一个需求都是在给架构还债。

2.1 为什么说分片键是生死决策

分片键决定了数据如何被路由到不同的物理库表。如果业务查询条件里带上了分片键,路由层可以精确计算出目标分片,一次查询只打一个库,效率和单库一样高;如果查询条件里没有分片键,路由层只能把请求广播到所有分片,再对结果做聚合,这种“全分片扫描”的代价会随着分片数量线性增长。

我见过一个典型的反例:某团队把订单表按“订单状态”分表——已支付一张表、未支付一张表、已退款一张表。听起来很符合业务直觉,但订单状态是高频变化的字段,每笔订单从创建到完成要跨好几张表,事务没法在一个分片内完成,还得引入分布式事务。更麻烦的是,按状态分表的数据分布极不均匀,未支付订单可能只有几千条,历史已完成订单却有上亿条。这不是水平扩展,这是给自己做了一个无法运维的“垂直切碎”。

2.2 候选分片键的取舍:用户ID、订单ID还是时间

在大多数业务里,真正值得考虑的候选分片键其实只有两三个。以电商订单系统为例:

  • 用户ID分片:非常适合“用户视角”的业务。用户查自己的订单列表,天然带上用户ID,路由精准,体验最好。而且用户维度天然隔离,单个用户的数据量不会超过单分片承载能力。
  • 订单ID分片:适合后台运营、客服查询场景。客服需要按订单号查详情,订单ID作为分片键可以让这类查询精准路由。但用户查询自己的订单列表时,没有订单ID,只能广播。常见的折中方案是:以订单ID分片,同时维护一份“用户ID到订单ID”的映射关系,或者生成订单ID时把用户ID编码进去(如雪花ID的bit位设计)。
  • 时间分片:适合日志、流水、事件类数据,按天/按月分表。优点是归档方便,过期数据直接DROP表;缺点是热点永远落在最新分片上,写入压力没有分散,本质上只是解决了“容量无限增长”问题,没有解决“热点写入”问题。

2.3 我的选择原则:谁最核心,就优先服务谁

没有完美的分片键,只有最适合业务核心路径的权衡。我给自己定过三条原则:

第一,分片键必须能覆盖业务最高频、最核心的查询路径。电商里最高频的是“用户查自己的订单”,那就优先保证用户维度查询是精准路由。

第二,分片键必须具备稳定的“终态”属性。也就是说,这个字段一旦确定基本不会改变。用户ID天然终态;手机号可能换绑,不适合;订单状态会变,绝对不行。

第三,分片键的值域必须足够离散。枚举值很少的字段(如性别、类型、渠道)都会造成数据倾斜,切分后某些分片负载远高于其他分片。

如果核心查询有两个以上维度,我的做法是:选一个主分片键,其他的维度靠“索引表”或“冗余表”来支撑。例如订单表按用户ID分片,同时维护一张“订单号到用户ID”的全局映射表。虽然多了一次查询开销,但保证了主路径的简单高效。

3. 容量规划与分片算法:动手之前先算账

分库分表的老手和新手之间,最明显的差别就是:新手上来就定“分10个库100张表”,老手会先拿一年的业务数据做推演。分片数的确定不是拍脑袋,它直接影响未来扩容的难度和数据分布均匀度。

3.1 分片数怎么算:一个可复用的推演过程

我用一个实际案例来说明推演过程。假设业务是B端零售系统的订单表:

  • 当前每天新增订单量:50万行
  • 订单数据保留策略:永久保留
  • 两年后预估日均新增:200万行(按年增长100%算)
  • 两年后总数据量:200万 × 730 ≈ 14.6亿行

单分片(单库单表)建议上限按2000万行算,那么所需分片总数:

14.6亿 ÷ 2000万 ≈ 73个分片

综合考虑单库承载能力和运维成本,我习惯每个物理库放8~16张分片表。如果每个库放8张表,那么需要约10个物理库。所以最终方案定为:10个库 × 8张表 = 80个分片

注意,这只是容量维度的下限。还要再算一下写入QPS:

  • 峰值写入:按日均200万的5倍峰值系数算,大约每秒11500行写入。
  • 单分片承受峰值写入:11500 ÷ 80 ≈ 144 QPS。

这个量级对单库来说非常轻松,所以瓶颈在容量而非并发。如果峰值QPS超过单库承受力,就需要增加分片数来分摊写入压力。总之,分片数 = max(按容量计算所需分片数, 按并发计算所需分片数),还要留出30%~50%的buffer。

3.2 三种主流分片算法:Hash取模、一致性哈希、Range

算法选型直接决定数据分布和后续扩容方式,三种方案各有适应场景。

Hash取模是最常见的方案:分片号 = hash(分片键) % 分片总数。优点是实现简单、数据分布均匀;缺点是一旦分片总数变化,几乎所有数据的映射关系都会变化,扩容时要做全量数据迁移。

一致性哈希把分片键映射到一个环形哈希空间,每个物理分片负责环上的一段区间。扩容时只影响相邻分片的数据,迁移量大幅减少。缺点是实现复杂度高,而且需要处理节点不均匀问题(一般引入虚拟节点)。

Range分片按分片键的连续区间划分,比如用户ID 1~1000万在分片1,1000万~2000万在分片2。它的优势是范围查询友好,扩容可以按区间分段追加;风险是数据可能倾斜,某些热门区间负载过高。

我的建议很直接:绝大多数业务场景,用最简单的Hash取模就够了,配合“2倍扩容”策略来规避迁移痛点。一致性哈希适合分片节点频繁变动的场景,但大部分业务一年也扩容不了几次,引入一致性哈希的复杂度远大于它节省的迁移成本。Range分片则适合时序类数据或明确能按业务维度切分的场景。

3.3 一个容易被忽略的设计:提前预留分片位数

一个让后面少吃苦的决定是:把分片总数设计为2的N次方,并且取模时用位运算替代取模运算。比如分片数定为64,路由时直接shard = hash(userId) & 63。这不是纯粹为了性能,而是为了扩容时能“只迁移一半数据”而不是“全量迁移”。

具体的做法是:初始只用64个分片里的前32个(或者前64个,但未来扩容到128个)。当需要从64扩容到128时,数据迁移可以按位运算的规律只搬一半:根据hash值的第7位是0还是1,决定数据留在原分片还是迁移到新分片。这个技巧在ShardingSphere等中间件里已经有现成实现,但你自己理解原理后,才能合理评估它的迁移方案是否可靠。

提示:任何分片方案都要写清楚“扩容SOP”,并且在大促前至少演练一次。我见过太多系统在扩容演练时才发现中间件版本不支持在线迁移,只能停机扩容。

4. 路由层选型:中介软件与自研Proxy的取舍

分库分表的落地形态,核心是路由层怎么做。目前行业里有三类主流方案:客户端式中间件(如ShardingSphere-JDBC)、代理式中间件(如MyCAT、ShardingSphere-Proxy、Atlas)、以及自研Proxy。选型要考虑团队维护能力、业务复杂度和响应要求。

4.1 三类方案的定位和取舍

  • 客户端式(ShardingSphere-JDBC):以jar包形式嵌入应用,应用直连数据库。优点是性能损耗极小,功能丰富,支持分布式事务、读写分离、数据加密等。缺点是语言绑定Java,多语言团队适配成本高。
  • 代理式(ShardingSphere-Proxy / MyCAT):独立部署的代理服务,应用通过数据库协议连代理,代理再转发到后端分片。优点是语言无关,业务侵入性小,适合多语言团队。缺点是多了一层网络开销,延迟增加;代理节点本身也需要高可用部署。
  • 自研Proxy:可控性最高,但开发量极大,需要处理协议解析、连接管理、分布式事务、高可用等一整套问题,只建议大厂或有充足中间件人力的团队考虑。

以我们当时的情况为例:后端是统一的Java技术栈,团队对ShardingSphere比较熟,流量还没到需要独立代理层支撑的水平,所以选了ShardingSphere-JDBC。它直接嵌入业务服务,路由和SQL改写都在应用内完成,性能几乎无损。后续如果某个独立业务线想接非Java应用,再单独部署一个ShardingSphere-Proxy接入同一个注册中心,两种模式可以混用。

4.2 ShardingSphere-JDBC核心配置解读

ShardingSphere-JDBC的配置逻辑核心就一句话:告诉它“哪个逻辑表对应哪些物理表,用哪个分片键、哪个算法路由”。下面是一个典型订单表配置示例(YAML格式):

rules: - !SHARDING tables: t_order: actualDataNodes: ds${0..9}.t_order_${0..7} tableStrategy: standard: shardingColumn: user_id shardingAlgorithmName: order_table_hash_mod keyGenerateStrategy: column: order_id keyGeneratorName: snowflake defaultDatabaseStrategy: standard: shardingColumn: user_id shardingAlgorithmName: order_db_hash_mod shardingAlgorithms: order_db_hash_mod: type: HASH_MOD props: sharding-count: 10 order_table_hash_mod: type: HASH_MOD props: sharding-count: 8

这段配置里有几个容易忽略的点:

  • actualDataNodes里库和表的数量必须和分片算法里的sharding-count完全匹配,否则路由会落空。
  • 库策略和表策略用的是同一个分片键user_id,保证同一个用户的所有订单落在同一个库、同一张表里——这样按用户维度的查询连跨分片聚合都省了。
  • 分片键和主键生成策略可以分开:分片用user_id,主键用雪花算法生成order_id,两者互不干扰。

4.3 自研Proxy的风险评估

如果你的团队确实考虑自研Proxy,我有一个很现实的提醒:不要低估“兼容全部SQL”的难度。业务侧的SQL千奇百怪,JOIN、子查询、GROUP BYORDER BY、分页、聚合函数组合起来,SQL改写规则极其复杂。自研Proxy很容易陷入“支持了90%的SQL,剩下10%的SQL要靠开发改代码”的窘境。

我的建议是:除非现有方案完全无法满足业务需求(比如需要跨语言、需要强一致事务、需要定制路由协议),否则优先选择成熟中间件。自研Proxy的时间成本在人力密集的大厂或许可以接受,但对大多数团队来说,投入产出比太低,还会成为长期维护负担。

5. 存量迁移与双写,线上发布的全流程

方案设计再好,最后都要落到“怎么从单库平滑切换到分库分表”。这个过程的难点不在于“写代码”,而在于“切流量”的时机和风险控制。我推荐一套经过多次验证的流程:全量同步 + 增量回放 + 双写校验 + 灰度切换。

5.1 第一步:全量同步和增量Binlog回放

迁移前先把老库的表结构转换成分表后的结构。比如老订单表t_order要变成t_order_0t_order_7共8张表,每张表除了原有字段外,需要保证user_id是分片键并且建立好索引。

全量同步阶段,我用的是自研的迁移工具加Binlog订阅的组合:

  1. 在源库开启Binlog,记录当前位点。
  2. 用DataX或自研导出工具把存量数据按分片规则写入目标分表。
  3. 启动Binlog消费任务,从记录的位点开始回放增量数据到目标分表。
  4. 并行启动数据校验任务,定时对比源库和目标库的行数、关键字段checksum。

这个阶段的核心是:全量先跑,增量持续追,校验不放过。如果源库数据量太大,全量同步会跑很久,增量回放会持续追位点,Binlog消费任务要保证它在全量完成前不追丢。

5.2 第二步:双写切换

增量回放追平后,进入双写阶段。双写的意思是:业务写入时,同时写老库和新库,老库作为生产主库,新库作为影子库。

ShardingSphere下双写一般借助ShardingSphereDataSourcedual数据源功能,或者自己在业务代码里封装一层“写老库+写分片库”的DAO。我偏向前者,因为它对业务代码透明。配置上需要注意:双写不能影响主链路延迟,所以对影子库的写入要走异步队列,或者直接使用带超时控制的附加数据源,避免影子库抖动拖垮主流程。

双写持续期间要反复做数据一致性校验。我一般每天跑一次全量校验,同时抽查最近一小时变更数据的实时一致性。只有校验连续3天通过,才敢进入下一步。

5.3 第三步:灰度切换与回滚预案

灰度切换的关键是“能随时切回来”。顺序建议如下:

  1. 读流量灰度:把5%的读流量切到新库,对比延迟和错误率,同时校验数据准确性。
  2. 写流量灰度:把1%的写流量切到新库(通过开关或按用户ID白名单)。此时新库和旧库的角色对调:新库成为“影子主库”,写入同时异步回放到老库。
  3. 全量切换:确认稳定后,把所有读写流量切到新库,老库转为只读备份。

回滚预案一定要提前准备好。我的做法是:在整个灰度期间,保留从新库到老库的增量回放任务,也就是“反向双写”。一旦新库出现严重问题,立即把读写流量切回老库,不会丢数据。

提示:灰度期间不要只监控接口错误率,还要监控数据延迟、分片数据倾斜、慢查询等指标。很多问题不是“切了就崩”,而是“切了一天后越来越慢”。

6. 跨分片查询、分布式事务与全局主键:四个躲不掉的痛点

分库分表真正让人难受的地方,不是迁移时的体力活,而是上线后每天都在面对的那些“单库时代根本不算事”的问题。

6.1 跨分片Join与聚合:别硬撑,改造查询

分片后,一条SQL如果涉及多个分片,ShardingSphere会把SQL下发到每个分片执行,再把结果汇聚。这个过程对应用透明,但有两个代价:一是性能损失,二是有SQL兼容限制。

实际业务里,跨分片Join查询我见过四种解法,按推荐程度排序:

  • 数据冗余:在业务写入时就把需要Join的字段冗余到主表中,查询时单表搞定。比如订单表冗余了商品名称和商品快照价格,查订单列表就不用再去Join商品表。
  • 全局表:数据量小、更新不频繁的表(如地区表、字典表)在每个分片都放一份全量副本,Join只在分片内发生。
  • 搜索引擎/OLAP引擎:复杂的多条件检索、聚合统计,把数据同步到ES或ClickHouse,查询走搜索引擎,业务库只承担OLTP。
  • 应用层组装:分两次查询,在代码里做内存组装。适合数据量小的关联场景,但要注意避免N+1查询。

6.2 全局主键:雪花ID的生成细节和坑

分库分表后,数据库自增主键不能用了。主键生成必须全局唯一且尽量有序,目前最常用的就是雪花算法(Snowflake)。

雪花ID的本质是一个64位整数:1位符号位 + 41位毫秒时间戳 + 10位机器ID + 12位序列号,一毫秒内单机可以生成4096个ID。几个容易踩的坑:

  • 时钟回拨:服务器NTP时间回拨后,可能生成重复ID。保险做法是保留“上次生成时间戳”,发现当前时间小于上次时间就拒绝生成或等待时钟追上。
  • 机器ID分配:如果机器ID没有全局唯一管理,两台机器可能生成相同ID。建议用配置中心或注册中心统一分配机器ID。
  • 分片友好性:订单ID里可以编码分片信息,例如把用户ID的一部分放进订单ID的末几位,后端的“订单ID查详情”就能从ID里直接解析出分片键,省掉一次映射表查询。

6.3 分布式事务:最终一致性比强一致更现实

分库后,一个业务操作可能跨多个分片更新数据。最典型的是“创建订单 + 扣减库存 + 增加积分”,各写各的分片。

业界对分布式事务有几种方案,别一上来就TCC,成本和收益要算清楚:

  • 事务消息(本地消息表/RocketMQ事务消息):把核心业务操作和“发消息”放在同一个本地事务里,消息消费者再去执行其他分片的更新。缺点是最终一致,存在短暂不一致窗口。适合大多数交易场景。
  • TCC(Try-Confirm-Cancel):每个参与方都要实现Try、Confirm、Cancel三个接口,开发量大,适合对一致性要求极高且并发量可控的场景。
  • Seata AT模式:通过undo_log实现自动回滚,业务侵入比TCC小很多,但性能开销和运维复杂度都需要评估。

我的建议很直接:优先把业务设计成“单分片内完成”,把需要跨分片更新的动作通过消息异步化。与其追求一套漂亮的分布式事务框架,不如从业务划分上减少跨分片事务。

6.4 深度分页:LIMIT的大坑

分库分表后,ORDER BY create_time LIMIT 100000, 20这样的SQL会被改写成每个分片都执行LIMIT 100000, 20,然后内存汇总,再取第100020条。随着页码增大,性能急剧恶化,因为每个分片都要捞出前10万条数据来做排序。

解决深度分页的方案有三个:

  • 游标分页(推荐):把“页码”改成“上次最后一条记录的排序值”,查询条件变成WHERE create_time < ? ORDER BY create_time DESC LIMIT 20。这个方案性能稳定,且不受分片数影响。
  • 禁止深翻页:产品层面限制最多翻到100页。
  • 加辅助列:如果业务必须支持翻页,可以在各分片维护一张“排序值到ID的索引表”,减少排序数据量。

这三种方案里,游标分页是唯一能长期抗住并发压力的。如果产品经理非要“跳转第500页”,把搜索需求丢给ES就对了。

7. 复盘与避坑清单:那些文档里没写的细节

分库分表的方案设计和容量规划,前面几部分已经把主线讲清楚了。这一节我专门整理一份踩坑清单,都是真实项目中卡过壳的细节。

7.1 五个回看时才明白的教训

教训一:路由字段的隐式类型转换会让分片失效。有一次排查线上慢查询,发现某个分片上的数据量明显比其他分片大。最后定位到原因是:分片键在数据库里的类型是varchar,但应用传入的是Long型。ShardingSphere在做路由计算时拿到的hash值不一致,导致一部分数据路由到分片,查询时却算到了另一个分片,结果被迫广播。后来统一了分片键的传入类型,并加了一组监控报警来发现分片不均衡。

教训二:分片键允许更新,看起来很美好,实际上是大坑。有一版方案支持用户更换分片键(比如手机号分片改ID分片),结果每次更新都要处理跨分片数据搬迁,还会在搬迁过程中出现短暂查不到数据的情况。后来的设计方案强制分片键不可更新,如果有修改需求,通过“新增记录 + 废弃旧记录”的方式处理。

教训三:中间件版本升级要单独走压测。从ShardingSphere 4.x升级到5.x,很多配置项和SQL改写逻辑都变了。我们就是因为升级后没有做全量回归,导致某些带GROUP BY的复杂查询结果集计算错误。升级中间件版本,必须准备一套完整的SQL回归用例集。

教训四:分片表的DDL变更要在所有分片上同时执行,否则路由报错。这听起来是常识,但自动化运维平台没覆盖到位时,少执行一个分片的ALTER TABLE,线上查询就可能随机失败。建议把DDL变更纳入统一的自动化平台,遍历所有分片执行并校验结果。

教训五:连接池大小要重新计算,不能沿用单库配置。分片后,一个事务如果涉及多个分片,应用会同时持有多个分片连接。如果连接池最大连接数仍然是原来的值,高并发下很容易把数据库连接打满。需要根据“单事务同时打开的连接数 × 并发事务数”重新推导连接池配置。

7.2 上线前后我建议你准备的一份自检清单

每次做分库分表项目,我都会在方案评审和上线前对照下面这份清单检查一遍:

  • [ ] 分片键是否覆盖了核心查询路径?是否保证最高频查询能精准路由?
  • [ ] 分片算法是否有明确的扩容SOP?是否已经在测试环境演练过?
  • [ ] 主键生成方案是否全局唯一?时钟回拨是否有兜底策略?
  • [ ] 所有跨分片查询是否都有替代方案(冗余、全局表、搜索引掊)?
  • [ ] 分布式事务方案是否符合业务一致性要求?是否有降级预案?
  • [ ] 深度分页是否已经改造为游标分页?
  • [ ] 数据迁移工具是否支持断点续跑?校验任务是否自动告警?
  • [ ] 灰度切换的开关和回滚脚本是否经过演练?
  • [ ] 连接池大小是否按分片数重新核算?
  • [ ] 监控看板是否覆盖分片数据倾斜、各分片延迟和慢查询?

这套清单看着琐碎,但每一条背后都有真实的事故支撑。分库分表这种改动,一旦上线运行,回头的成本非常高昂,宁可多花一周准备,也不要上线后日夜救火。

最后再分享一个个人体会:分库分表做得好的团队,通常不是算法最先进的团队,而是“敬重数据”的团队——每一步都算过账、每一条SQL都验证过、每一次切换都有回滚。这套严谨的工作方式,比分库分表本身更能决定系统的长期稳定性。希望这篇来自实战的拆解,能让你在规划自己的水平扩展方案时少走几步弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 2:39:50

kotaemon 如何配置 PDF_JS 实现浏览器内 PDF 预览?

kotaemon 如何配置 PDF_JS 实现浏览器内 PDF 预览&#xff1f; 【免费下载链接】kotaemon An open-source RAG-based tool for chatting with your documents. 项目地址: https://gitcode.com/GitHub_Trending/kot/kotaemon kotaemon 的 RAG 问答自带引用标注&#xff0…

作者头像 李华
网站建设 2026/9/13 2:37:24

3C工厂实战:C# 上位机基于 Modbus 实现 100 台传感器数据采集(生产级架构+零丢包+全场景踩坑)

3C电子组装产线里&#xff0c;传感器是工位的眼睛。光电、光纤、位移、温湿度、扭力、压力这类现场传感器&#xff0c;90%以上都原生支持 Modbus 协议。一个标准工位段少说几十台&#xff0c;整条线下来上百台是常态。 很多新手做上位机采集&#xff0c;上来就是写个 for 循环逐…

作者头像 李华
网站建设 2026/9/13 2:35:39

校园自习预约系统设计:从高并发到状态机的工程实践

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

作者头像 李华