Skip to content

Speing-Could 学习笔记 ​

分布式/微服务方向的起步学习(熟练度 1/5)。当前进度:先学好 MyBatis-Plus(它是微服务里数据访问层的事实标准),再进入 Nacos、Gateway、Sentinel 等微服务组件。

1. 学习Mybatis-Plus-基础 ​

【重点要点】

  • 定位:MyBatis 的增强工具,只增强不改变——保留 MyBatis 的 XML/注解写法,额外提供单表 CRUD、条件构造器等开箱能力,免写大量样板 SQL。
  • BaseMapper<T>:Mapper 接口继承它即自带 insert / deleteById / updateById / selectById / selectList 等单表方法。
  • 通用 Service:接口继承 IService<T>、实现类继承 ServiceImpl<M, T>,得到 save / getById / list / page 等批量方法。
  • 条件构造器:QueryWrapper(字段名写字符串)与 LambdaQueryWrapper(方法引用,编译期查错、更推荐);配合 eq / like / orderByDesc / in / between / last("limit 1") 组合查询。
  • 分页插件:注册 MybatisPlusInterceptor + PaginationInnerInterceptor 后,Page 对象直接分页(物理分页)。
  • 代码生成器:FastAutoGenerator 一键生成 Entity/Mapper/Service/Controller。
  • 易错点:selectPage 必须配置分页插件否则全表查;Lambda 条件类字段写错会按错列查;实体驼峰字段与库下划线列默认自动映射。
java
// 1. Mapper:继承 BaseMapper,自带单表 CRUD
@Mapper
public interface UserMapper extends BaseMapper<User> { }

// 2. Service:IService + ServiceImpl
public interface UserService extends IService<User> { }

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService { }
java
// 3. LambdaQueryWrapper:类型安全的条件查询
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getStatus, 1)
       .like(User::getName, "张")
       .orderByDesc(User::getCreateTime)
       .last("limit 10");
List<User> users = userService.list(wrapper);

// 分页
Page<User> page = userService.page(new Page<>(1, 10), wrapper);
page.getRecords(); page.getTotal(); page.getPages();
java
// 4. 分页插件配置
@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}
java
// 5. 代码生成器(3.5.1+)
FastAutoGenerator.create("jdbc:mysql://localhost:3306/blog", "root", "123456")
    .globalConfig(b -> b.author("me").outputDir("src/main/java"))
    .packageConfig(b -> b.parent("com.example"))
    .execute();

2. 学习Mybatis-Plus-扩展功能 ​

【重点要点】

  • 逻辑删除:实体字段加 @TableLogic,删除变 update 软删;查询自动加 deleted = 0 条件(解决物理删除无法恢复、关联数据断裂问题)。
  • 乐观锁:实体字段加 @Version + 注册 OptimisticLockerInnerInterceptor,更新时自动带版本号比对(防止并发覆盖)。
  • 自动填充:实现 MetaObjectHandler,配合 @TableField(fill = FieldFill.INSERT / INSERT_UPDATE) 自动写入创建时间/更新时间/操作人等字段。
  • 防全表更新:注册 BlockAttackInnerInterceptor,拦截不带 WHERE 的 update/delete,防止误操作清库。
  • 多租户(SaaS):TenantLineInnerInterceptor + 配置租户字段,SQL 自动追加租户条件,一套代码多租户隔离。
  • 性能分析:注册 SqlExplainInterceptor(分析 SQL 执行计划)或开启 SQL 日志(configuration.log-impl: StdOutImpl 调试用)。
  • 易错点:逻辑删除与唯一索引冲突(软删后再插同值主键会撞唯一索引);乐观锁更新失败(影响行数 0)要提示用户重试;自动填充只在 MP 内置方法生效,自定义 SQL 需手动赋值;多租户插件要放在拦截器链最内层。
java
// 逻辑删除 + 乐观锁 + 自动填充字段
@Data
public class User {
    private Long id;
    private String name;

    @TableLogic
    private Integer deleted;          // 0 正常 / 1 已删

    @Version
    private Integer version;          // 乐观锁版本号

    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}
java
// 自动填充实现
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
        this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
    }
    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
    }
}
java
// 完整拦截器链:分页 + 乐观锁 + 防全表更新
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));   // 分页
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());         // 乐观锁
    interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());              // 防全表更新
    return interceptor;
}

3. 学习业务的拆分 ​

【重点要点】

  • 为什么要拆:单体应用代码膨胀后,团队协作冲突多、扩展只能整体扩(扛不住单模块高并发)、故障互相拖累;微服务按"业务边界"把大系统拆成一个个独立部署的小服务。
  • 拆分原则:按业务域拆(如 用户服务/订单服务/商品服务/支付服务),不是按技术层拆(controller/service/dao 拆开没意义);每个服务自治:自己的数据库、自己的接口、独立部署。
  • 拆分维度:业务划分 + 数据库划分(各服务独立库,从"一个库里多张表"变成"各服务各自库")。
  • 拆分后的新问题:服务间如何通信(HTTP/OpenFeign)、服务如何被发现(注册中心)、如何统一入口(网关)、数据一致性(分布式事务,后续再学)、链路追踪。
  • 易错点:拆分不是越细越好(服务多了运维/联调成本高,小项目单体反而合适);严禁服务间共享数据库表(会退化成"分布式单体");接口设计要稳定,避免频繁破坏性变更。
text
单体架构                         微服务架构
┌──────────────┐        ┌──────────┐ ┌──────────┐
│ 用户/订单/商品 │   →    │ 用户服务  │ │ 订单服务  │
│ 同一进程同一库 │        │ 独立库    │ │ 独立库    │
└──────────────┘        └────┬─────┘ └────┬─────┘
                             │  HTTP/OpenFeign 通信  │
                             └───────┴───────────────┘
                                   注册中心(Nacos)发现问题

4. 学习了解nacos注册中心 ​

【重点要点】

  • 注册中心作用:服务提供者启动时注册自己的地址(IP:端口)到注册中心;服务消费者从注册中心发现可用实例列表,再调用——解决"服务地址写死/实例变化"问题。
  • Nacos 是阿里开源的一体化平台:注册中心 + 配置中心二合一;启动默认端口 8848,管理台 /nacos。
  • 服务注册:服务端加 spring-cloud-starter-alibaba-nacos-discovery,配置 spring.cloud.nacos.discovery.server-addr(注意:旧版用 spring.cloud.nacos.server-addr,新版用 discovery/config 前缀)。
  • 服务发现:OpenFeign/RestTemplate 按服务名调用,由负载均衡器从注册中心取实例;心跳/健康检查剔除不健康实例。
  • 配置中心:把配置挪到 Nacos 上,@RefreshScope + bootstrap.yml(旧版)/ spring.config.import=nacos:(新版)实现改配置不重启。
  • 易错点:服务名要一致(消费者按 name 找);nacos 与 Spring Boot 版本要匹配(Alibaba 版本号对应表);默认命名空间 public,多环境用命名空间/分组隔离。
yaml
# 服务提供者 & 消费者都要有
spring:
  application:
    name: user-service          # 注册进 Nacos 的服务名
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
java
// 消费者用 @LoadBalanced RestTemplate 按服务名调用(注册中心自动发现)
@Configuration
public class RestConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

// 调用:http://user-service/user/1  而不是写死 IP
restTemplate.getForObject("http://user-service/user/1", User.class);

5. 学习gateway网关,springcould提供 ​

【重点要点】

  • 网关职责:所有请求的统一入口——路由转发(按路径分发到对应服务)、鉴权、限流、跨域、日志,前置一层"门户"。
  • Spring Cloud Gateway 基于 WebFlux(响应式,非阻塞),核心三要素:Route(路由:id + uri + 断言集合)、Predicate(断言:匹配规则,如按 Path/Header/时间)、Filter(过滤器:请求/响应加工)。
  • 配置路由:spring.cloud.gateway.routes:id 路由名、uri 目标(lb://服务名 走负载均衡发现)、predicates 断言(Path=/user/**)、filters(如 StripPrefix=1 去前缀)。
  • 常用断言:Path、Method、Header、Query、Cookie、After/Before/Between(时间窗)。
  • 常用过滤器:StripPrefix(剥掉路径段数)、AddRequestHeader、自定义 GlobalFilter(做 JWT 鉴权通常写一个全局过滤器)。
  • 易错点:lb:// 需要配合注册中心/负载均衡;gateway 不在 spring-web 下,别把它当普通 web 依赖(排除 starter-web);路由不匹配返回 404 先查 predicates;跨域在网关统一配置 AddResponseHeader 或 CorsGlobalFilter。
yaml
spring:
  cloud:
    gateway:
      routes:
        - id: user-route
          uri: lb://user-service            # 负载均衡到注册中心的服务
          predicates:
            - Path=/user/**                 # 匹配 /user/xx 的请求
          filters:
            - StripPrefix=1                 # 去掉第一段 /user,再转发给服务
        - id: order-route
          uri: lb://order-service
          predicates:
            - Path=/order/**
java
// 全局过滤器:统一鉴权示例(Gateway + WebFlux)
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (token == null || token.isBlank()) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();   // 401 拦截
        }
        return chain.filter(exchange);      // 放行
    }
    @Override
    public int getOrder() { return -100; }  // 优先级:数字越小越先执行
}

6. 学习openFeign统一管理clent服务 ​

【重点要点】

  • OpenFeign:声明式 HTTP 客户端——把"调别人服务"的 REST 调用写成接口,省去手写 RestTemplate/HTTP 模板代码。
  • 使用三步:接口加 @FeignClient(name="user-service") → 方法上写 SpringMVC 注解(@GetMapping 等,路径与目标服务一致)→ 启动类/配置类加 @EnableFeignClients。
  • 负载均衡:Spring Cloud LoadBalancer 自动集成(Feign 按服务名走注册中心选实例,替代已废弃的 Netflix Ribbon)。
  • 参数映射:GET 用 @RequestParam/@PathVariable,POST 对象用 @RequestBody(和 Controller 写法一致);返回类型直接声明为目标 DTO。
  • 超时与容错:spring.cloud.openfeign.client.config.default.connect-timeout/read-timeout 配超时;远程异常默认抛 FeignException,可配 fallback(CircuitBreaker,进阶)。
  • 易错点:@FeignClient 的 name 必须与目标服务在注册中心的 name 一致;Feign 接口的方法签名要和对方 Controller 完全对应(路径/参数类型/返回类型),否则运行时 404/反序列化失败;默认同步阻塞,别在接口里写复杂业务。
java
// 1. 声明 Feign 客户端:订单服务调用用户服务
@FeignClient(name = "user-service")
public interface UserClient {

    @GetMapping("/user/{id}")
    User getUserById(@PathVariable("id") Long id);
}
java
// 2. 启用 Feign
@SpringBootApplication
@EnableFeignClients        // 扫描 @FeignClient 接口
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}
java
// 3. 业务中使用:像调本地方法一样调远程
@Service
public class OrderServiceImpl {
    @Autowired
    private UserClient userClient;

    public OrderDetail getById(Long id) {
        User user = userClient.getUserById(order.getUserId());   // 远程调用自动完成
        return new OrderDetail(order, user);
    }
}
yaml
# 超时配置(读/连接超时,默认较保守)
spring:
  cloud:
    openfeign:
      client:
        config:
          default:
            connect-timeout: 3000
            read-timeout: 5000

7. 学习MQ消息队列 ​

【重点要点】

  • 为什么用 MQ:服务间异步解耦(下单后发消息通知库存/积分,不用同步等)、流量削峰(秒杀高并发先入队)、日志/广播场景。
  • 常见产品:RabbitMQ(AMQP,路由灵活,课程用这个)、Kafka(高吞吐日志流)、RocketMQ(阿里,事务消息)。
  • RabbitMQ 核心模型:交换机(Exchange)→ 队列(Queue)→ 消费者;生产者发到交换机,交换机按**路由键(routing key)和绑定(binding)**把消息路由到队列。
  • 四种交换机:direct(路由键精确匹配)、fanout(广播到所有绑定队列)、topic(通配符匹配 #/*)、headers(按头匹配,少用)。
  • SpringAMQP:RabbitTemplate.convertAndSend(exchange, routingKey, msg) 发送;@RabbitListener(queues="queue") 消费;队列/交换机用 @Bean 声明(或 @RabbitListener 自动声明)。
  • 可靠性:发送方 confirm 回调确认;消费方手动 ack + 重试(spring.rabbitmq.listener.simple.acknowledge-mode: manual);死信队列处理失败消息。
  • 易错点:队列/交换机名字或路由键不匹配 → 消息"神秘消失"(发到了没人绑定的交换机);topic 的 # 匹配零个或多个词、* 匹配一个词;消费端要幂等(消息可能重发);默认自动 ack 异常会丢消息。
yaml
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
java
// 发送消息
@Autowired
private RabbitTemplate rabbitTemplate;

public void orderCreated(Order order) {
    // 发到 exchange.order 交换机,路由键 order.created
    rabbitTemplate.convertAndSend("exchange.order", "order.created", order);
}
java
// 消费消息
@Component
public class OrderListener {

    @RabbitListener(queues = "queue.order")
    public void onOrder(Order order) {
        // 业务:扣库存、发短信、更新统计
        System.out.println("收到订单:" + order.getId());
    }
}
java
// 声明交换机与队列(topic 模式示例)
@Configuration
public class MqConfig {
    @Bean
    public TopicExchange orderExchange() {
        return new TopicExchange("exchange.order");
    }
    @Bean
    public Queue orderQueue() {
        return new Queue("queue.order");
    }
    @Bean
    public Binding orderBinding() {
        return BindingBuilder.bind(orderQueue())
            .to(orderExchange())
            .with("order.#");          // 匹配 order.created / order.paid 等
    }
}

8. 学习ES搜索引擎 ​

【重点要点】

  • ES(Elasticsearch)是什么:基于 Lucene 的分布式搜索引擎,核心是倒排索引——把文档拆成词条,记录"词条 → 文档"映射,搜索时按词条查文档(比 LIKE 快得多)。
  • 核心概念对比 MySQL:索引 Index ↔ 数据库;类型(已废弃)↔ 表;文档 Document ↔ 行;字段 Field ↔ 列。
  • 分词:中文用 IK 分词器(ik_max_word 最细粒度 / ik_smart 粗粒度),自定义词典可加业务词。
  • 索引映射(Mapping):建索引时定义字段类型(text 可分词 / keyword 精确 / 数值 / 日期)、analyzer 分词器。
  • DSL 查询:match(分词匹配)、term(精确)、range(范围)、bool(组合 must/should/filter)、match_all。
  • Java 客户端:RestHighLevelClient(旧)/ co.elastic.clients(新);或 Spring Data Elasticsearch。
  • 数据同步:MySQL 数据 → ES 常用三种方案:同步双写(业务里同时写)、异步 MQ(发消息由监听器写 ES,课程用这种)、Canal 监听 binlog。
  • 易错点:text 字段默认被分词,做精确匹配/聚合要用 keyword;查询 DSL 的 bool 里 filter 不参与打分更快;ES 字段一旦建好不能随便改类型(要重建索引);高亮/分页/聚合易出细节问题。
json
// 索引映射:定义字段与分词器
PUT /item
{
  "mappings": {
    "properties": {
      "id":      { "type": "keyword" },
      "name":    { "type": "text", "analyzer": "ik_max_word" },
      "price":   { "type": "double" },
      "brand":   { "type": "keyword" }
    }
  }
}
json
// DSL:多条件查询(品牌精确 + 名称分词匹配 + 价格范围)
GET /item/_search
{
  "query": {
    "bool": {
      "must":   [{ "match": { "name": "手机" } }],
      "filter": [
        { "term":   { "brand": "华为" } },
        { "range":  { "price": { "gte": 1000 } } }
      ]
    }
  }
}

9. 学习Redis高级-缓存与分布式锁 ​

【重点要点】

  • 缓存三大问题及解法(面试高频):
    • 缓存穿透:查不存在的数据 → 每次打数据库。解决:缓存空值(null 也缓存短时间)/ 布隆过滤器。
    • 缓存击穿:热点 key 过期瞬间大量请求打库。解决:互斥锁(重建缓存加锁)/ 逻辑过期(不设物理过期,缓存值里带过期时间,过期后异步重建,课程秒杀场景用这个)。
    • 缓存雪崩:大量 key 同时过期或 Redis 宕机。解决:过期时间加随机值 / 集群高可用。
  • 缓存一致性:更新数据库后删缓存(延迟双删:先删缓存→更新库→延迟再删);或用 Canal 监听 binlog 删缓存;先更库再删缓存,避免旧缓存覆盖新值。
  • 分布式锁:多个服务实例抢同一把锁,不能用本地锁。Redis 实现:SETNX key value + 过期时间原子设置(防死锁)+ 释放时校验(防误删别人锁);更专业用 Redisson(RLock,看门狗自动续期,防锁过期问题)。
  • 秒杀/扣库存场景:lua 脚本保证"查库存 + 扣减"原子性;或 Redis 预扣 + 异步同步数据库。
  • 易错点:锁的 key 要区分业务(如 lock:order:用户id);设置过期时间要原子(SET key v EX n NX);删除锁要用 lua 比对 value 再删;Redisson 锁用完必须 finally unlock。
bash
# 原子设置锁:NX 不存在才设置,EX 过期时间
SET lock:order:1001 1 NX EX 10

# 释放锁要校验是自己的 value 再删(lua)
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end
java
// Redisson 分布式锁
@Autowired
private RedissonClient redissonClient;

public void deductStock(Long id) {
    RLock lock = redissonClient.getLock("lock:item:" + id);
    try {
        lock.lock(10, TimeUnit.SECONDS);      // 看门狗默认 30s 自动续期
        // 查库存、扣减、写库(业务)
    } finally {
        lock.unlock();
    }
}
java
// 缓存空值防穿透:查不到也缓存
public Item queryById(Long id) {
    String json = redis.opsForValue().get("item:" + id);
    if (json != null) {
        if ("".equals(json)) return null;          // 缓存空值
        return JSON.parseObject(json, Item.class);
    }
    Item item = itemMapper.selectById(id);
    if (item == null) {
        redis.opsForValue().set("item:" + id, "", 5, TimeUnit.MINUTES);   // 空值短缓存
        return null;
    }
    redis.opsForValue().set("item:" + id, JSON.toJSONString(item));
    return item;
}

10. 学习Sentinel服务保护 ​

【重点要点】

  • 为什么需要:微服务链路长,一个服务慢/挂 → 上游线程被拖死 → 雪崩(如订单调用户,用户挂了订单线程池占满)。
  • Sentinel 是阿里的流量控制/熔断降级组件(替代 Hystrix),控制台可视化配置规则。
  • 三个核心能力:
    • 流量控制(Flow):按 QPS/并发线程数限流,防止突发流量打爆;可配直接/关联/链路模式与排队等待。
    • 熔断降级(Degrade):慢调用比例、异常比例、异常数达到阈值 → 熔断(快速失败/降级兜底),保护下游。
    • 热点参数限流:对某个参数(如商品 id)单独限流。
  • 集成:spring-cloud-starter-alibaba-sentinel + 控制台(默认 8080 端口,-Dcsp.sentinel.dashboard.server);@SentinelResource(value, blockHandler, fallback) 标注资源并指定降级方法。
  • 线程隔离(舱壁模式):给每个下游分配独立线程池/信号量,一个服务慢不拖垮其他(Sentinel 用信号量隔离;课程里和线程池隔离对比)。
  • 易错点:懒加载——Sentinel 默认第一次访问才注册资源,冷启动规则看不到(可加 -Dcsp.sentinel.heartbeat.client.ip / 预热);blockHandler 与 fallback 的方法签名要求;规则默认存内存,重启丢失(可配 Nacos 持久化)。
yaml
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080      # Sentinel 控制台地址
      eager: true                      # 关闭懒加载
java
// 标记资源 + 降级兜底
@SentinelResource(
    value = "item.queryById",
    blockHandler = "queryByIdBlock",   // 被限流/熔断时调用
    fallback = "queryByIdFallback"     // 业务异常时调用
)
public Item queryById(Long id) {
    return itemService.queryById(id);
}

// 方法签名:原方法参数 + BlockException
public Item queryByIdBlock(Long id, BlockException e) {
    return Item.fallback();            // 返回兜底数据
}
public Item queryByIdFallback(Long id, Throwable e) {
    log.error("查询失败", e);
    return null;
}

11. 学习Seata分布式事务 ​

【重点要点】

  • 问题背景:微服务各自独立库,跨服务写操作无法用单库事务保证(下单:订单库扣减 + 库存库扣减,一个成功一个失败就数据不一致)。
  • Seata 分布式事务模型:TC(事务协调器,独立服务) + TM(事务管理器,发起方) + RM(资源管理器,参与方)。
  • AT 模式(最常用,自动补偿):
    • 一阶段:各参与方本地提交 + 记录回滚日志(undo_log)(before/after 镜像);
    • 二阶段:全部成功则删除 undo_log;有失败则由 TC 通知回滚,RM 按 undo_log 反向补偿。
    • 优点:无侵入(普通 SQL 即可),适合大部分业务。
  • 其他模式:XA(强一致,数据库支持,性能低)、TCC(Try-Confirm-Cancel 手动补偿,适合需要控制的场景)、SAGA(长事务)。
  • 使用:业务入口方法加 @GlobalTransactional;部署 Seata Server(默认端口 8091)+ 参与方配 seata.tx-service-group 与 TC 地址;数据库加 undo_log 表。
  • 易错点:AT 模式要求数据库支持事务(InnoDB);undo_log 表每库都要建;本地事务必须能被回滚(别 try-catch 吞异常);全局锁冲突时性能下降明显,长事务慎用;与 Spring Cloud 版本/注册中心配合要配置正确。
sql
-- 每个参与事务的数据库都要建 undo_log 表
CREATE TABLE IF NOT EXISTS `undo_log` (
  `id`            BIGINT(20)   NOT NULL AUTO_INCREMENT,
  `branch_id`     BIGINT(20)   NOT NULL,
  `xid`           VARCHAR(100) NOT NULL,
  `context`       VARCHAR(128) NOT NULL,
  `rollback_info` LONGBLOB     NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
yaml
# 参与方(订单/库存服务)配置
seata:
  enabled: true
  tx-service-group: hmall_tx_group      # 事务分组(与 server 配置一致)
  registry:
    type: nacos                         # 注册中心类型
    nacos:
      server-addr: 127.0.0.1:8848
  service:
    vgroup-mapping:
      hmall_tx_group: default           # 分组映射到集群
java
// 发起全局事务:订单服务入口方法
@GlobalTransactional                     // 开启分布式事务
public Long createOrder(Order order) {
    orderMapper.insert(order);           // 订单库
    itemClient.deductStock(order.getItemId(), order.getNum());  // 远程扣库存(参与方)
    return order.getId();                // 任一步失败,整体回滚
}

以「技术栈」为核心的个人知识库