首页 文章 精选 留言 我的

精选列表

搜索[c 14],共10015篇文章
优秀的个人博客,低调大师

《Spring Mongo实战》第14招 事务不是第一选择

## 01、痛点 凌晨两点,报警响起:库存已经扣了,但库存流水没有写,客服对不上账。 我是老李,刚接手一个从 MySQL 迁到 MongoDB 的商城系统。团队原来的 Service 层满是 @Transactional,迁移后几乎原样保留,每个写方法都套上事务。最典型的是扣库存和写库存流水,两个集合操作,大家认为多文档事务正好保证一致。 结果压测时库存集合频繁锁等待,接口响应从几十毫秒涨到两秒,事务回滚率也上升。问题不是事务不可用,而是我们把关系库的用法带进了文档库。官方明确提醒:多文档事务成本更高,不能替代好的 Schema Design。今天就把这件事讲透。 ## 02、是什么 MongoDB 从 4.0 副本集开始支持多文档事务,4.2 分片集群也支持。Spring Data MongoDB 提供 MongoTransactionManager,可以用 @Transactional 注解管理事务。事务覆盖多个文档、多个集合,提供 ACID 保证。 但有个关键区别:单文档操作天然原子。你更新一个文档里的多个字段,即使没有显式事务,也不会出现写一半的状态。这是 MongoDB 存储引擎 WiredTiger 提供的单文档原子性。多文档事务则是另一套机制,需要副本集、协调者、参与者和多数派写入,成本完全不是一回事。 因此,多文档事务是真实可用的工具,但不应该成为默认选择。 ## 03、为什么 为什么从 MySQL 迁移过来后给所有 Service 都加事务是一种危险习惯? 第一,事务模型不同。MySQL 单节点行锁事务比较成熟,很多业务在一个连接里提交几个 update,开销相对固定。MongoDB 多文档事务涉及协调器和多个参与者,需要 prepare、commit、多数派确认,还有全局逻辑时钟和事务元数据。事务越多,协调成本越高。 第二,锁与冲突被放大。多文档事务持有写锁或快照,写冲突会导致事务中止。给所有 Service 加事务,等于把很多本可以单文档原子完成的写操作升级成跨文档事务,放大锁竞争,吞吐下降,尾延迟升高。 第三,事务有边界限制。事务有默认最长运行时间,超过会失败;大批量写入在事务里容易触发压力。长事务还会拖累快照和缓存,影响其他操作。 第四,也是最关键的,它掩盖 Schema 问题。很多从关系库迁过来的系统,仍然拆成大量小集合,然后用事务模拟关系库的 join 和原子性。这样 MongoDB 被用成更慢的“无模式 MySQL”。正确的做法是重新审视文档边界,把本属于一个聚合的数据嵌入到同一个文档,用单文档原子性保证一致性。 官方说得很直接:多文档事务成本更高,不能替代好的 Schema Design。先用单文档原子性,再在真正跨文档一致性场景使用事务。 ## 04、架构 要理解成本,得看多文档事务的架构。 单文档更新:WiredTiger 对单文档修改天然原子,写一次即可。 多文档事务:客户端开始事务后,读写会到达不同 mongod 节点;协调者维护事务参与者列表;提交时先进入 prepare 阶段,每个参与者写入 prepare 记录;协调者发出 commit 决策;各参与者写入 commit 记录,按多数派写关注返回。这中间每一个环节都有网络往返和磁盘写。事务期间的未提交变更需要保存在内存和临时记录中,写冲突时还要回滚。 这也解释了为什么官方反复提醒:事务是保底工具,不是建模方式。一个真正的跨文档一致性场景,比如订单、库存、积分分属不同聚合,才值得用事务。能嵌入文档的,先考虑嵌入;能单文档原子更新的,不要开事务。 ## 05、实战 假设我们要实现“扣库存 + 写库存流水”,并用事务保证一起成功或一起失败。 环境准备:MongoDB 需要副本集。本地可以初始化单节点副本集,连接串带上 replicaSet 参数。Spring Boot 引入 spring-boot-starter-data-mongodb。 配置事务管理器: ```java @Configuration public class MongoTxConfig { @Bean MongoTransactionManager transactionManager(MongoDatabaseFactory factory) { return new MongoTransactionManager(factory); } } ``` 定义两个文档: ```java @Document('product_stock') public class ProductStock { @Id private String id; private String sku; private int qty; // getters/setters } @Document('inventory_flow') public class InventoryFlow { @Id private String id; private String sku; private int qtyDelta; private String reason; private Instant createdAt; // getters/setters } ``` 定义 Repository: ```java public interface ProductStockRepository extends MongoRepository { Optional findBySku(String sku); } public interface InventoryFlowRepository extends MongoRepository { } ``` 核心 Service: ```java @Service public class InventoryService { private final ProductStockRepository stockRepository; private final InventoryFlowRepository flowRepository; public InventoryService(ProductStockRepository stockRepository, InventoryFlowRepository flowRepository) { this.stockRepository = stockRepository; this.flowRepository = flowRepository; } @Transactional public void deductWithFlow(String sku, int qty) { ProductStock stock = stockRepository.findBySku(sku) .orElseThrow(() -> new IllegalStateException('库存不存在')); if (stock.getQty() < qty) { throw new IllegalStateException('库存不足'); } stock.setQty(stock.getQty() - qty); stockRepository.save(stock); InventoryFlow flow = new InventoryFlow(); flow.setSku(sku); flow.setQtyDelta(-qty); flow.setReason('ORDER_DEDUCT'); flow.setCreatedAt(Instant.now()); flowRepository.save(flow); if (qty == 666) { throw new RuntimeException('故意触发回滚'); } } } ``` 验证回滚:调用 deductWithFlow('SKU-1', 666),异常抛出后,去数据库查询。 ```shell db.product_stock.find({sku:'SKU-1'}) db.inventory_flow.find({sku:'SKU-1'}) ``` 库存数量仍然是原值,inventory_flow 集合里没有新增流水。说明扣库存和写流水都回滚了。 如果去掉 @Transactional,只把两个 save 顺序执行,那么第二个失败时第一个已经落库,就会出现开头那种“库存扣了但流水没写”的不一致。这个实验能直观看到多文档事务的价值,也能看到它只应该用在必要场景。 ## 06、洞见 MongoDB 最好的事务优化,可能不是调事务参数,而是重新设计 Document Boundary。 比如扣库存和写流水,如果库存流水可以内嵌进库存文档,保留最近若干条,那么一次 update 就能完成扣减和流水追加: ```javascript db.product_stock.updateOne( { sku: 'SKU-1', qty: { $gte: 2 } }, { $inc: { qty: -2 }, $push: { recentFlows: { $each: [{ at: new Date(), delta: -2, reason: 'ORDER_DEDUCT' }], $slice: -100 } } } ) ``` 这是单文档原子操作,不需要多文档事务。如果全量流水必须独立保存,还可以用 Change Stream 追踪库存文档的 recentFlows 变化,异步归档到流水集合,把强一致事务降级为最终一致。 所以,事务调优只是在既有边界内缓解问题;真正的突破是重新划分边界。一个项目里多文档事务数量多,往往说明文档边界没有设计好。每次想加事务前,先问一句:如果我把这两个集合合成一个文档,是否更自然? ## 07、行动 不要只在嘴上说,先去项目里找出所有事务。 第一步,盘点。搜索 @Transactional 注解、MongoTransactionManager、显式 startSession 的位置,列成清单。 第二步,分类。每个事务涉及哪些集合?维护什么不变量?是强一致还是最终一致? 第三步,消除。能嵌入的集合合并;能用 $inc、$set、$push、$pull、arrayFilters 等单文档原子更新的,改成单文档更新;能容忍最终一致的,用发件箱、补偿或 Change Stream 替代。 第四步,保留。只有真正跨聚合、必须强一致的场景才保留多文档事务。保留时也要控制事务范围,尽量短,避免外部调用。 第五步,度量。记录事务数量、跨文档事务比例、平均事务时长、中止率、锁等待和 P99 延迟。最好把“新增跨文档事务需要评审”写进开发规范。 这一套动作下来,很多项目会发现 80% 的事务其实可以消失。 ## 08、小结 从凌晨报警说起,我们看了 Spring Data MongoDB 多文档事务的用法和回滚验证,也看了它背后的架构成本。多文档事务不是不能用,但如果从 MySQL 迁过来后给所有 Service 都加事务,就很危险。先利用单文档原子性和嵌入模型,再在真正跨文档一致性场景使用事务,这是 MongoDB 官方反复强调的顺序。 ## 09、链接与概念说明 概念说明: - Document Boundary:文档边界,决定一个文档包含哪些字段和子文档。 - Single-Document Atomicity:单文档原子性,MongoDB 单文档操作天然具备。 - Multi-Document Transaction:多文档事务,跨文档、跨集合 ACID。 - Replica Set:副本集,多文档事务必须在副本集或分片集群上运行。 - Write Concern Majority:多数派写关注,事务提交时通常需要。 - WiredTiger:MongoDB 默认存储引擎。 - oplog:操作日志,事务通过 applyOps 等机制复制到从节点。 - Change Stream:变更流,可用于事件驱动和最终一致同步。 链接: - MongoDB 官方文档:Transactions - MongoDB 官方文档:Data Modeling - Spring Data MongoDB 文档:Mongo Transactions - MongoDB Blog:Multi-Document Transactions

优秀的个人博客,低调大师

《Spring Boot + JPA 实战》第14招 库存并发怎么锁

## 01、痛点 假设一次秒杀活动,商品库存只有10件。活动开始瞬间,100个线程同时涌进来。如果没有并发控制,最终可能卖出12件甚至更多,或者库存变成负数。代码一般是:先查库存,判断大于0,然后减库存,保存。这一串操作在并发下会发生“丢失更新”。两个请求同时读到stock=10,都判断可以买,然后都执行stock=9并更新,结果只少了一件,却卖了两件。更糟糕的是,可能出现死锁:事务A锁住行1等待行2,事务B锁住行2等待行1。大促时数据库连接池耗尽、CPU飙高、用户看到超卖或支付成功却没发货。 我是老李,曾经在团队里处理过一次库存扣减事故。复盘时发现,这个问题本质上是“最后10件商品面对100个并发请求”。这个场景既简单又尖锐,特别适合用来理解JPA与MySQL的并发控制。现在讲它,是因为类似问题不只出现在秒杀,任何涉及余额、积分、优惠券、计数器的系统都会遇到。早一点把锁的边界和选型搞清楚,能避免很多深夜报警。 ## 02、是什么 围绕这个场景,我们需要理解几组概念。 **丢失更新(Lost Update)**:两个事务同时读同一行,基于旧值做修改,后提交的覆盖先提交的,导致前一次更新丢失。在库存场景中,两个请求都读到stock=10,都扣减后写回9,最终只少了1件,但实际卖了两件。 **乐观锁与@Version**:JPA中的乐观锁通常用版本号或时间戳实现。实体上增加`@Version`字段,更新时带上版本条件:`UPDATE inventory SET stock=?, version=version+1 WHERE id=? AND version=?`。如果影响行数为0,说明版本已变,事务抛出`OptimisticLockException`,应用需要重试。乐观锁假设冲突少,先干活再检查。 **悲观锁**:JPA通过`@Lock(LockModeType.PESSIMISTIC_WRITE)`或者原生SQL`SELECT ... FOR UPDATE`实现。事务一开始就锁定行,其他事务必须等待。悲观锁假设冲突多,先锁住再干活。 **SELECT FOR UPDATE**:InnoDB提供的行级排他锁。在事务内执行`SELECT * FROM inventory WHERE id=? FOR UPDATE`,会对该行加X锁,直到事务提交或回滚。其他事务如果也要`FOR UPDATE`同一行,会阻塞。 **原子UPDATE**:直接执行`UPDATE inventory SET stock=stock-1 WHERE id=? AND stock>0`。数据库在更新时会对行加锁,并且`stock>0`条件在同一个语句内完成检查和扣减,避免先查后改的竞态。这个语句可以利用数据库的原子性,不需要应用层检查,但需要判断影响行数。 **死锁(Deadlock)**:两个或多个事务互相等待对方释放锁。例如事务A锁住行1,请求行2;事务B锁住行2,请求行1。InnoDB有死锁检测,会回滚其中一个事务,报`Deadlock found when trying to get lock`。应用需要捕获并重试。 **InnoDB行锁**:InnoDB在索引上实现行锁,如果查询没有走索引,可能会锁更多行甚至表。库存扣减一般按主键或唯一索引定位行,所以是行级锁。 ## 03、为什么 很多人觉得数据库默认隔离级别就能避免并发问题,其实不是。MySQL InnoDB默认REPEATABLE READ,它能解决不可重复读和幻读(依赖间隙锁),但解决不了“丢失更新”。原因是多个事务可以同时读同一行,基于相同的旧值进行修改。比如两个事务都读到stock=10,在各自事务内做扣减,后提交的覆盖先提交的。隔离级别保证了读的一致性,但不会阻止两个事务基于同一快照做写操作。 如果只使用JPA默认的脏检查,没有@Version也没有显式锁,更新时Hibernate通常生成`UPDATE inventory SET stock=? WHERE id=?`,不会检查版本,因此会直接覆盖,产生丢失更新。所以必须显式做并发控制。 还有一层原因:数据库行锁是宝贵的资源。如果悲观锁锁住行后,事务内还有远程调用、复杂计算或长事务,锁持有时间变长,其他事务排队,连接池可能打满。乐观锁虽然不加数据库锁,但冲突多时大量事务回滚,浪费CPU和连接。理解这些机制,是为了在正确场景做出权衡。 ## 04、架构 我们搭建一个简化架构:Spring Boot应用通过JPA/Hibernate访问MySQL。核心表`inventory`: ```sql CREATE TABLE inventory ( id BIGINT PRIMARY KEY, product_name VARCHAR(100), stock INT NOT NULL, version INT NOT NULL DEFAULT 0 ); ``` 应用层有一个`InventoryService`,提供`deductStock(Long id, int quantity)`方法。事务边界在Service方法上使用`@Transactional`。数据库连接池假设20个连接,100个并发请求会排队。 三种方式的实现路径不同: 1. 乐观锁:实体上使用`@Version`,扣减逻辑为“读实体 -> 扣减 -> 保存”。冲突时捕获`OptimisticLockException`并重试。 2. 悲观锁:使用`@Lock(PESSIMISTIC_WRITE)`的查询,事务内“锁行 -> 读当前值 -> 扣减 -> 保存”。其他事务在查询时阻塞。 3. 原子UPDATE:直接执行一条UPDATE语句,通过影响行数判断是否成功,不需要先读实体。事务非常短,只有一条更新。 从架构上看,方案1依赖应用层重试机制;方案2依赖数据库行锁和连接池;方案3依赖SQL的原子性,减少与ORM的交互。 ## 05、实战 我们使用JUnit并发测试,模拟100个线程同时抢最后10件库存。初始stock=10,每个线程尝试扣1件,最终成功扣减的请求数应该等于10,库存为0。记录总请求数、成功数、冲突数、平均耗时。 **方案一:乐观锁@Version** 实体: ```java @Entity public class Inventory { @Id private Long id; private String productName; private Integer stock; @Version private Integer version; // getters and setters } ``` Service扣减方法: ```java @Transactional public boolean deductWithOptimisticLock(Long id) { Inventory inv = repo.findById(id).orElseThrow(); if (inv.getStock() 0) { try { return deductWithOptimisticLock(id); } catch (OptimisticLockException e) { // 等待极小随机时间,继续重试 } } return false; } ``` 压测结果:初始10件,100个并发请求全部尝试扣减。由于乐观锁不加数据库锁,所有请求都能读到最后一次提交的版本,但只有部分请求能成功更新版本。每次更新版本+1,只有10个请求能成功。其余请求捕获异常并重试。最终成功数10,库存0。但重试次数很多,接近90次。CPU消耗在高冲突下的版本检查和回滚。 **方案二:悲观锁** Service方法: ```java @Transactional public boolean deductWithPessimisticLock(Long id) { Inventory inv = repo.findByIdForUpdate(id); if (inv.getStock() 0; } ``` Repository执行SQL: ```sql UPDATE inventory SET stock = stock - 1 WHERE id = ? AND stock > 0; ``` 压测时,数据库执行UPDATE会对行加锁,但每个语句执行极快,事务只有一条更新。100个并发UPDATE进入InnoDB,逐个执行。由于条件`stock>0`,第11个事务时stock=0,影响行数为0,返回false。最终成功10个,库存0。没有应用层重试,也没有显式悲观锁的长时间持有。 实验结果对比: | 方案 | 成功数 | 冲突/失败 | 重试 | 平均耗时 | 库存最终值 | |------|--------|-----------|------|----------|------------| | 乐观锁@Version | 10 | 90异常需重试 | 90+ | 较高(由于重试) | 0 | | 悲观锁FOR UPDATE | 10 | 90排队等待后返回false | 0 | 排队耗时 | 0 | | 原子UPDATE | 10 | 90返回false | 0 | 最低 | 0 | 从结果看,原子UPDATE性能最好,因为临界区最短,没有事务先读后写。乐观锁产生了大量冲突重试,悲观锁阻塞了连接。 ## 06、洞见 很多人以为悲观锁更安全,乐观锁性能更高。但实战结果告诉我们:**悲观锁不等于更安全,乐观锁也不等于性能一定更高。** 悲观锁确实通过行锁强制串行化,保证同一时刻只有一个事务修改。但“安全”不止于数据一致性,还包括可用性。如果锁持有的临界区过长(比如事务内调用了外部RPC、进行了复杂计算),其他事务会长时间阻塞,连接池被占满,系统吞吐量骤降,甚至引发连锁故障。另外,悲观锁容易触发死锁。如果事务内先锁A再锁B,另一个事务先锁B再锁A,就会死锁。InnoDB会自动回滚一个事务,但应用收到死锁异常如果不处理,用户看到的是失败。所以悲观锁不等于系统更安全,它只是把并发冲突转化为等待和可能死锁。 乐观锁通过版本号避免加锁,在低冲突场景下表现出色。但冲突概率高时,大量事务在提交时发现版本不一致而回滚,应用程序不断重试,数据库CPU被无效更新浪费,连接也被重试占满。极端情况下,甚至可能出现“活锁”:某个请求每次都抢不到。因此,乐观锁也不等于性能一定更高。它的性能优势依赖于“冲突少”这个前提。 **锁选择取决于三个变量:冲突概率、临界区长度、失败重试成本。** - 冲突概率高(如秒杀最后几件):建议原子UPDATE或悲观锁。原子UPDATE最短临界区,推荐。 - 临界区长(读后要经过复杂计算、调用外部服务):乐观锁可能更合适,因为乐观锁不长期占用数据库锁,但要做好重试。 - 失败重试成本低(幂等操作,重试不会产生副作用):乐观锁重试可以接受;如果失败涉及用户体验或外部副作用,悲观锁或原子条件更稳。 以最后10件商品100并发场景为例,冲突概率极高,但临界区可以做到极短——一条UPDATE即可。所以原子UPDATE完胜。如果用乐观锁,就会看到大量重试和异常风暴;如果用悲观锁,排队也可能成为瓶颈,但比乐观锁好。没有一种锁是银弹,关键是匹配场景。 ## 07、行动 针对三个典型场景,我们可以给出选型建议。 **库存场景:使用原子UPDATE,必要时加乐观锁兜底。** 库存扣减通常是高频、短事务,要求强一致。首选`UPDATE inventory SET stock=stock-1 WHERE id=? AND stock>0`,判断影响行数。若业务上需要在内存中读取实体做校验(例如获取商品信息给用户展示),可以先读,但扣减动作必须走原子UPDATE,避免先读后写。对于需要审计或版本冲突检测的场景,可以在表上加`version`字段,用乐观锁配合重试作为兜底,但核心扣减仍然依赖条件更新。 **余额场景:使用悲观锁或数据库约束+事务。** 余额涉及资金,不能容忍丢失更新,且通常需要记录流水。可以先用悲观锁锁住账户行(`SELECT ... FOR UPDATE`),在事务内校验余额、扣减、插入流水。因为资金操作频率低于库存,但安全性要求更高,临界区可能包含写流水表,所以适合悲观锁。如果金额扣减逻辑简单,也可以用原子UPDATE配合余额不为负的条件,并记录流水。但要避免长事务。余额表一般行数少,锁竞争可控,悲观锁是稳妥选择。 **用户资料场景:使用乐观锁@Version。** 用户更新昵称、头像、个人简介等,冲突概率极低。不同用户修改自己的资料,几乎不会同时修改同一行。使用JPA的@Version可以防止多人同时编辑时覆盖。如果偶尔发生版本冲突,让用户重新加载或自动重试即可,失败重试成本很低。不要在用户资料表上用悲观锁,否则一个用户提交一个长表单(甚至包含图片处理)时会长期锁行,影响其他操作。 这三个场景的选择展示了“按冲突概率、临界区长度、失败重试成本”决策的方法。库存高频高冲突,原子更新;余额高频但需流水,悲观锁或原子扣减;资料低频低冲突,乐观锁。 ## 08、小结 最后10件商品面对100个并发请求,把一个看似简单的扣库存变成了理解JPA与MySQL并发控制的完美实验。我们从丢失更新讲到死锁,从@Version讲到SELECT FOR UPDATE和原子UPDATE。实战压测告诉我们:没有万能锁,只有合适的锁。悲观锁不保证系统更安全,乐观锁也不保证性能更高,选型必须回到冲突概率、临界区长度和失败重试成本三个维度。库存适合原子条件更新,余额适合悲观锁加流水,用户资料适合乐观锁版本号。搞懂这些,下次再遇到并发抢购,你就能睡个安稳觉。 ## 09、链接与概念说明 - **JPA乐观锁**:通过@Version注解,更新时使用版本条件,冲突抛OptimisticLockException。参考JSR 338规范。 - **JPA悲观锁**:通过LockModeType.PESSIMISTIC_WRITE映射到数据库行锁,如SELECT FOR UPDATE。 - **MySQL SELECT FOR UPDATE**:在事务内对行加排他锁,直到事务结束。参考MySQL 8.0官方文档“Locking Reads”。 - **InnoDB行锁**:通过索引记录加锁,事务使用主键更新时行锁,否则可能间隙锁或表锁。参考“InnoDB Locking”。 - **死锁**:InnoDB自动检测死锁并回滚一个事务,死锁日志可通过`SHOW ENGINE INNODB STATUS`查看。 - **原子UPDATE**:利用单条UPDATE的原子性完成检查和扣减,减少锁持有时间和竞态窗口。 - **丢失更新**:两个事务基于旧值更新,后提交覆盖先提交,解决方法是乐观锁、悲观锁或原子条件更新。 - 扩展阅读:MySQL官方文档“InnoDB Locking and Transaction Model”、Hibernate User Guide“Locking”。

优秀的个人博客,低调大师

4 月 14 日,openKylin 与您相约 FOSSASIA Summit 2023

FOSSASIA Summit是一个致力于开源方案及专案的技术型聚会,自2009年以来已持续举办十多年。2023年FOSSASIA峰会将于4月13日(星期四)至4月15日(星期六)在新加坡Lifelong Learning Institute举行,主题涵盖云计算、人工智能、区块链、开源文化、开源硬件、Linux 内核、网络安全、开放科学等多个领域。 届时,openKylin技术委员会委员李剑峰、王文竹及openKylin社区战略合作伙伴鉴释科技创始人兼CEO梁宇宁将受邀参会并发表《The practice and experience of openKylin on RISC-V Architecture》主题演讲,给来自全球的技术开发者带来openKylin社区在RISC-V软硬件生态建设方面的经验与成果介绍,向大家展示中国开源社区的潜力,并同国际技术社区共研RISC-V未来之势,欢迎大家报名观看直播~此外,本次活动还设置了展览环节,欢迎大家前来一起玩耍。 活动官网: http://summit.fossasia.org 活动日程&预约: https://eventyay.com/e/7cfe0771/schedule openKylin(开放麒麟)社区旨在以“共创”为核心,在开源、自愿、平等、协作的基础上,通过开源、开放的方式与企业构建合作伙伴生态体系,共同打造桌面操作系统顶级社区,推动Linux开源技术及其软硬件生态繁荣发展。 社区首批理事成员单位包括麒麟软件、普华基础软件、中科方德、麒麟信安、凝思软件、一铭软件、中兴新支点、元心科技、中国电科32所、技德系统、北京麟卓、先进操作系统创新中心等13家产业同仁和行业机构。 审核:openKylin

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

用户登录
用户注册