ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

分布式服务设计,代码评审该盯住哪些细节

分布式服务设计,代码评审该盯住哪些细节 分布式服务设计代码评审该盯住哪些细节服务拆分后的代码评审应把注意力放在数据边界、调用超时、幂等性和可观测性上。本文给出可执行的检查方向而非用一次演练代替生产判断。订单库Order DB里多出了 340 条无关联主表的孤立扣款记录而库存库Stock DB对应的商品库存已经被强行扣减成了负数。拉出报错日志一看源头是一次标准的微服务拆分提交。开发者把原本在一个单体事务里的OrderService与StockService拆成了两个独立部署的 HTTP RPC 微服务。在拆分后的代码里开发者依然保留了旧的控制习惯// ❌ 典型的分布式违规代码在本地 Transactional 里面直接发 RPC 远程调用 Transactional public void submitOrder(OrderDTO dto) { orderRepository.save(buildOrder(dto)); // 危险HTTP 网络超时如 2000ms会导致本地 DB 锁持有时间急剧拉长且远程扣库存成功后本地可能回滚 stockRpcClient.deductStock(dto.getSkuId(), dto.getCount()); }把单体应用拆分成分布式微服务最大的痛点绝不是怎么画架构图而是代码层面如何处理跨网络边界的事务失效、幂等重复调用以及循环依赖。在代码评审Code Review中盯紧这几项细节是防止分布式故障倾泻到生产环境的最后防线。# 扫描日志中由于在事务内嵌套 RPC 调用导致的数据库连接池等待超时 grep -E HikariPool-1 - Connection is not available|TransactionTimedOut /var/log/order-service/*.log | head -n 15 # 查看数据库中的分布式事务 undo_log 残留情况 (以 Seata / 本地消息表为例) mysql -h db-primary.internal -u order_writer -pSecret123 -e USE order_db; SELECT id, xid, rollback_info, log_status FROM undo_log ORDER BY id DESC LIMIT 10;微服务拆分后事务防线演进流图从单体应用拆分到分布式系统数据一致性保障机制必须从“数据库强一致”平滑过渡到“最终一致性Eventual Consistency”。在拆分后的设计中绝对禁止在本地数据库事务Transactional的代码块内部嵌入任何 RPC 远程调用如 Feign、Dubbo 或 HTTP。因为网络是不可靠的一旦 RPC 端响应迟钝本地事务锁如 Row Lock就会一直挂起瞬间拉爆数据库连接池。生产级 Transactional Outbox 防线代码实现为了确保订单创建与消息发送的原子性在代码评审时应当强制要求采用Transactional Outbox 模式本地消息表结合 Spring 的TransactionSynchronizationManager实现事务提交后再推送 MQ。package com.company.distributed.order.service; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; import java.util.UUID; Service public class DistributedOrderService { private static final Logger log LoggerFactory.getLogger(DistributedOrderService.class); private final JdbcTemplate jdbcTemplate; private final ObjectMapper objectMapper; private final MqMessagePublisher mqPublisher; public DistributedOrderService(JdbcTemplate jdbcTemplate, ObjectMapper objectMapper, MqMessagePublisher mqPublisher) { this.jdbcTemplate jdbcTemplate; this.objectMapper objectMapper; this.mqPublisher mqPublisher; } Transactional public String createOrderSafe(String userId, String skuId, int count) { String orderId ORD_ System.currentTimeMillis() _ UUID.randomUUID().toString().substring(0, 6); // 1. 本地 DB 操作写订单主表 jdbcTemplate.update(INSERT INTO t_order (order_id, user_id, sku_id, count, status) VALUES (?, ?, ?, ?, ?), orderId, userId, skuId, count, CREATED); // 2. 本地 DB 操作写 Outbox 本地消息表 (保证原子性) String eventId EVT_ UUID.randomUUID(); OrderCreatedEvent event new OrderCreatedEvent(eventId, orderId, userId, skuId, count); try { String payloadJson objectMapper.writeValueAsString(event); jdbcTemplate.update(INSERT INTO t_outbox_message (event_id, payload, status) VALUES (?, ?, PENDING), eventId, payloadJson); } catch (Exception e) { throw new RuntimeException(序列化 Outbox 消息失败, e); } // 3. 拦截点绝不在此处发 MQ注册 Spring 事务 Hook必须在 DB 真正 Commit 成功后再触发 MQ 异步发送 if (TransactionSynchronizationManager.isSynchronizationActive()) { TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { Override public void afterCommit() { log.info(本地 DB 事务提交成功开始异步投递 MQ 消息, eventId{}, eventId); mqPublisher.asyncSend(order-created-topic, eventId, event); } }); } return orderId; } public record OrderCreatedEvent(String eventId, String orderId, String userId, String skuId, int count) {} public interface MqMessagePublisher { void asyncSend(String topic, String key, Object message); } }评审分布式代码时必须盯住的 4 级 Checklist在代码 Review 时可以用这 4 条铁律作为硬性拦截门禁Checklist 1RPC 调用是否剥离出了Transactional审查方法检查是否有带有Transactional注解的方法体中调用了feignClient.xxx()或restTemplate.exchange()。整改标准RPC 调用必须移到事务方法之外或者采用 Outbox 异步化解耦。Checklist 2消费端接口是否具备物理幂等键Idempotency Key审查方法看 MQ 消费端或 HTTP 接收端是否直接拿到 Payload 就进行INSERT或UPDATE。整改标准消费端必须从 Header 或 Body 中提取唯一eventId或bizOrderNo先查 Redis / DB 幂等去重表。若已消费则直接 ACK 返回成功杜绝网络重试带来的二次扣减。Checklist 3分布式超时时间Timeout与重试策略的物理匹配审查方法看 Feign / HTTP Client 的 Read Timeout 配置。整改标准下游 API Read Timeout 设为 3 秒时上游 Gateway 全局超时必须设为 3.5 秒。如果上游超时比下游短会导致上游取消了连接而下游依然在后台偷偷执行完毕造成数据不一致。Checklist 4服务拆分后是否引发了跨服务循环依赖审查方法看OrderService调用UserService同时UserService又通过 RPC 或 MQ 反向同步调用OrderService。整改标准凡是出现双向循环依赖的微服务必须通过重构将公共依赖下沉为独立的领域服务或者通过单向 Event 驱动彻底解耦。把分布式系统的边界在代码评审环节画清楚才能避免微服务拆分后沦为“分布式单体Distributed Monolith”的泥潭。
返回列表