微服务调用dubbo
服务提供者:通过 @DubboService 把 AccountApi 暴露成 Dubbo 服务,注册到 Nacos。AccountApi 接口是公共模块的接口,不是当前模块的
package com.example.account; import com.example.api.AccountApi; import com.example.account.mapper.AccountMapper; import lombok.RequiredArgsConstructor; import org.apache.dubbo.config.annotation.DubboService; import java.math.BigDecimal; /** * 账户服务 —— Dubbo provider。 * <p> * 通过 @DubboService 把 AccountApi 暴露成 Dubbo 服务,注册到 Nacos。 */ @DubboService @RequiredArgsConstructor public class AccountApiImpl implements AccountApi { private final AccountMapper accountMapper; @Override public void deduct(Long userId, BigDecimal money) { int rows = accountMapper.deduct(userId, money); if (rows == 0) { // 余额不足,抛异常 → 分支事务标记失败 → 全局事务回滚 throw new RuntimeException("余额不足,userId=" + userId); } } }
消费者:通过 @DubboReference 从注册中心拿到提供者,这里拿到的也是共同模块的AccountApi
package com.example.order.service; import com.example.api.AccountApi; import com.example.api.StorageApi; import com.example.order.entity.Order; import com.example.order.feign.NotifyFeignClient; import com.example.order.mapper.OrderMapper; import io.seata.spring.annotation.GlobalTransactional; import lombok.RequiredArgsConstructor; import org.apache.dubbo.config.annotation.DubboReference; import org.springframework.stereotype.Service; /** * <ul> * <li>内部服务(storage/account):走 Dubbo RPC(TCP 二进制,靠 Nacos 发现)</li> * <li>外部服务(notify):走 Feign HTTP(REST,固定 URL 直连第三方)</li> * </ul> */ @Service @RequiredArgsConstructor public class OrderService { private final OrderMapper orderMapper; private final NotifyFeignClient notifyFeignClient; // Dubbo 引用:内部服务,靠 Nacos 发现 provider,XID 通过 RpcContext 附件透传 @DubboReference private StorageApi storageApi; @DubboReference private AccountApi accountApi; /** * 下单:创建订单 + 扣库存 + 扣余额,三步必须一起成功,否则一起回滚。 * 注意:不需要 @Transactional,@GlobalTransactional 已接管整个链路。 */ @GlobalTransactional(name = "create-order-tx", rollbackFor = Exception.class) public void createOrder(Order order) { // ① 本地分支事务:创建订单(本服务 = RM,操作 seata_order 库) orderMapper.insert(order); // ② 远程分支事务:扣库存(Dubbo RPC → storage-service,RM) storageApi.deduct(order.getProductId(), order.getCount()); // ③ 远程分支事务:扣余额(Dubbo RPC → account-service,RM) accountApi.deduct(order.getUserId(), order.getMoney()); // ④ 外部通知:Feign HTTP 调用,【不参与】分布式事务 // 通知服务是第三方,没有 undo_log、无法回滚,所以这里尽力而为。 notifyFeignClient.send("用户" + order.getUserId() + "下单成功,金额 " + order.getMoney()); // 四步全部成功 → TM 通知 TC 全局提交 // ①②③ 任一步抛异常(库存不足/余额不足)→ TM 通知 TC 全局回滚 // ④ 抛异常不会回滚①②③(Feign 不在事务链路内,这正体现了“内外边界”) } }
公共模块接口
/** * 账户服务的 Dubbo 接口(内部 RPC 契约)。 */ public interface AccountApi { /** * 扣减余额。 * @param userId 用户 ID * @param money 扣减金额 */ void deduct(Long userId, BigDecimal money); }
public interface StorageApi {
/**
* 扣减库存。
* @param productId 商品 ID
* @param count 扣减数量
*/
void deduct(Long productId, Integer count);
}
这样内部账户/库存服务就不需要controller层,因为不需要对外提供http接口,提供者和消费者都依赖同一个公共模块接口.
浙公网安备 33010602011771号