rabbitmq分布式事务 - 指南

news/2025/9/29 16:29:14/文章来源:https://www.cnblogs.com/slgkaifa/p/19119012

rabbitmq分布式事务 - 指南

1. 总体架构图(一眼看懂)

┌------------------┐        1.本地事务             ┌------------------┐
│   订单服务        │  ---►DB+event表(同一事务)---►  │  定时补偿任务      │
│  (producer)     │                              └------------------┘
│                  │        2.发送消息              ▲
│                  ├-------------------------------►│┌------------------┐
│                  │  3.ConfirmCallback ack/nack   │ │  rabbit mq       │
│                  │◄-------------------------------┤└------------------┘
└------------------┘                             ││                                        ││ 5.补偿/回滚  4.消费失败/业务校验失败        │▼                                        ▼
┌------------------┐        6.对账任务            ┌------------------┐
│  补偿/对账服务     │------------------------►.   │   库存/账户服务    │
└------------------┘                             └------------------┘

2. 角色与职责清单

组件职责关键技术点
订单服务1. 本地事务落库+写事件表
2. 发送消息并监听 confirm
@Transactional+Publisher Confirm
事件表仅 5 列即可:id,biz_id,event_type,payload,status,create_time状态枚举:UNSENT/SENT/DONE
补偿任务UNSENT 重投;扫 DLX 报警;触发补偿消息@Scheduled(fixedDelay=5s)
库存服务1. 幂等消费
2. 业务校验失败 立即发补偿消息
3. 技术异常抛异常触发重投
唯一索引/SETNX+手动 basicAck/Nack
对账任务每日离线 FULL JOIN 业务表 vs 事件表;输出差异SQL/Spark 均可
补偿服务监听补偿队列,做“冲正”:关闭订单、退款、加回库存普通消费者,逻辑与业务反向

3. 生产端:可靠投递(代码级)

spring:
rabbitmq:
publisher-confirm-type: correlated   # 开启 confirm
@Transactional
public void createOrder(OrderDTO dto){
// 1. 落单
Order order = orderDao.insert(dto);
// 2. 同一事务写事件
eventDao.insert(Event.builder()
.bizId(order.getId())
.eventType("ORDER_CREATED")
.payload(JSON.toJSONString(dto))
.status("UNSENT")
.build());
}
// 3. 事务提交后异步发消息
@EventListener(TransactionPhase.AFTER_COMMIT)
public void sendAfterTx(OrderCreatedEvent event){
Event evt = eventDao.findByBizId(event.getOrderId());
CorrelationData cd = new CorrelationData(evt.getId());
rabbitTemplate.convertAndSend(
"order.event.exchange",
"stock.reduce",
evt.getPayload(),
cd);
}
// 4. ConfirmCallback 更新状态
rabbitTemplate.setConfirmCallback((cd, ack, cause) -> {
if (ack) {
eventDao.updateStatus(cd.getId(), "SENT");
} else {
log.warn("消息未送达 broker, 等定时任务补偿");
}
});

4. 消费端:幂等 + 业务失败补偿

@RabbitListener(queues = "stock.reduce.queue")
public void handle(Message msg, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
String orderId = JSON.parseObject(msg.getBody()).getString("orderId");
int quantity   = JSON.parseObject(msg.getBody()).getIntValue("quantity");
try {
// 1. 幂等判断(唯一索引)
if (stockDao.alreadyDeduct(orderId)) {
channel.basicAck(tag, false);
return;
}
// 2. 真正扣减
boolean ok = stockDao.deduct(orderId, quantity);
if (!ok) {                // 库存不足——业务失败
sendCompensate(orderId, "STOCK_LACK");
}
channel.basicAck(tag, false);
} catch (Exception e) {       // 技术异常
channel.basicNack(tag, false, true); // 重新投递
}
}
private void sendCompensate(String orderId, String reason){
CompensateCmd cmd = new CompensateCmd(orderId, reason);
rabbitTemplate.convertAndSend("compensate.exchange", "order.cancel", cmd);
}

5. 补偿服务(所谓“回滚”)

@RabbitListener(queues = "order.cancel.queue")
public void handleCancel(CompensateCmd cmd) {
Order order = orderDao.find(cmd.getOrderId());
if (order == null || order.getStatus() == "CLOSED") return;
// 1. 关闭订单
orderDao.updateStatus(cmd.getOrderId(), "CLOSED");
// 2. 退款
accountService.refund(order.getUserId(), order.getAmount());
// 3. 释放库存(幂等)
stockDao.addBack(cmd.getOrderId());
// 4. 事件表记 DONE,防止再对账
eventDao.updateStatus(cmd.getOrderId(), "DONE");
}

6. 对账任务(离线)

-- 每日凌晨运行
INSERT INTO diff_${biz_date}
SELECT
COALESCE(b.order_id, m.biz_id) AS order_id,
CASE WHEN b.order_id IS NULL THEN 'ONLY_MSG'
WHEN m.biz_id  IS NULL THEN 'ONLY_BIZ'
ELSE 'STATUS_MISMATCH' END AS type
FROM biz_order b
FULL JOIN event_msg m ON b.order_id = m.biz_id
WHERE b.order_date = ${biz_date}
AND (b.order_id IS NULL OR m.biz_id IS NULL OR b.status != m.status);
  • ONLY_BIZ → 补发消息
  • ONLY_MSG → 发补偿消息关闭订单
  • 差异 >0 条即钉钉 @oncall

7. 异常场景全覆盖

场景表现兜底手段
本地事务失败订单都没落库无消息,业务方自己回滚
confirm 丢失事件表仍 UNSENT定时任务 5s 重扫重投
消费端技术异常代码 NPE重新投递(requeue=true)
消费端业务失败库存不足立即发补偿消息 + ack
消息一直重试失败超过最大重试DLX,人工处理
对账不一致漏消息/脏消息离线补平或冲正

8. 性能 & 运维要点


9. 一句话总结

RabbitMQ 没有“回滚”按钮,只有“补发 + 补偿 + 对账”三板斧;
把本地消息表、confirm、幂等、补偿、对账 5 步全部落地,即可在纯 RabbitMQ 环境下实现生产级分布式事务

下面给出一份可直接复制到 IDE 跑通的「RabbitMQ 最终一致性」完整 Demo,所有配置、SQL、Java 代码、MQ 声明全部贴全。
Spring Boot 3.2 + RabbitMQ 3.12


  1. 工程骨架(Maven 依赖)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
</parent>
<properties>
<java.version>17</java.version>
</properties>
<dependencies><!-- web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- rabbit --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><!-- mysql --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><!-- mybatis-plus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.5</version></dependency><!-- lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies>

  1. application.yml(全部配置)
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/shop?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtual-host: /
publisher-confirm-type: correlated   # 开启 publisher confirm
publisher-returns: true              # 消息不可达会触发 return
template:
mandatory: true                    # 配合 returns 使用
listener:
type: simple
simple:
acknowledge-mode: manual         # 手工 ack
prefetch: 5                      # 限流
default-requeue-rejected: false  # 业务异常不重新入队,直接 DLX
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath*:/mapper/**/*.xml

  1. MySQL 表 SQL(一次性执行)
CREATE DATABASE IF NOT EXISTS shop DEFAULT CHARACTER SET utf8mb4;
USE shop;
-- 订单表
CREATE TABLE t_order (
id            BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id       BIGINT      NOT NULL,
sku_id        BIGINT      NOT NULL,
quantity      INT         NOT NULL,
amount        DECIMAL(10,2) NOT NULL,
status        VARCHAR(32) NOT NULL DEFAULT 'INIT',
create_time   DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 本地事件表
CREATE TABLE t_local_event (
id            BIGINT PRIMARY KEY AUTO_INCREMENT,
biz_id        VARCHAR(64) NOT NULL,
event_type    VARCHAR(64) NOT NULL,
payload       TEXT        NOT NULL,
status        VARCHAR(32) NOT NULL DEFAULT 'UNSENT',
create_time   DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time   DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_bizid_type (biz_id, event_type),
INDEX idx_status (status)
) ENGINE=InnoDB;
-- 库存表(为了演示简单)
CREATE TABLE t_stock (
id            BIGINT PRIMARY KEY AUTO_INCREMENT,
sku_id        BIGINT NOT NULL UNIQUE,
available     INT    NOT NULL
);
-- 扣库存流水(幂等)
CREATE TABLE t_stock_flow (
id            BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id      VARCHAR(64) NOT NULL UNIQUE,
sku_id        BIGINT NOT NULL,
quantity      INT    NOT NULL,
status        VARCHAR(32) NOT NULL,
create_time   DATETIME DEFAULT CURRENT_TIMESTAMP
);

  1. RabbitMQ 配置类(声明队列、交换机、DLX)
@Configuration
public class RabbitConfig {
/* ---------------- 业务队列 ---------------- */
public static final String STOCK_REDUCE_QUEUE = "stock.reduce.queue";
public static final String STOCK_EXCHANGE     = "stock.topic";
public static final String STOCK_RK           = "stock.reduce";
/* ---------------- 补偿队列 ---------------- */
public static final String COMPENSATE_QUEUE   = "order.compensate.queue";
public static final String COMPENSATE_EX      = "compensate.topic";
public static final String COMPENSATE_RK      = "order.cancel";
/* ---------------- 死信参数 ---------------- */
private static final String DLX_NAME  = "dlx.topic";
private static final String DLQ_NAME  = "stock.reduce.dlq";
@Bean
TopicTopicExchange stockExchange() {
return ExchangeBuilder.topicExchange(STOCK_EXCHANGE).durable(true).build();
}
@Bean
TopicTopicExchange dlxExchange() {
return ExchangeBuilder.topicExchange(DLX_NAME).durable(true).build();
}
@Bean
public Queue stockReduceQueue() {
return QueueBuilder.durable(STOCK_REDUCE_QUEUE)
.withArgument("x-dead-letter-exchange", DLX_NAME)
.withArgument("x-dead-letter-routing-key", DLQ_NAME)
.build();
}
@Bean
public Binding stockBinding() {
return BindingBuilder
.bind(stockReduceQueue())
.to(stockExchange())
.with(STOCK_RK);
}
@Bean
public Queue dlq() {
return QueueBuilder.durable(DLQ_NAME).build();
}
@Bean
public Binding dlqBinding() {
return BindingBuilder.bind(dlq()).to(dlxExchange()).with(DLQ_NAME);
}
/* 补偿交换机队列 */
@Bean
public TopicExchange compensateEx() {
return ExchangeBuilder.topicExchange(COMPENSATE_EX).durable(true).build();
}
@Bean
public Queue compensateQueue() {
return QueueBuilder.durable(COMPENSATE_QUEUE).build();
}
@Bean
public Binding compensateBinding() {
return BindingBuilder.bind(compensateQueue()).to(compensateEx()).with(COMPENSATE_RK);
}
}

  1. 实体 & Mapper(MyBatis-Plus)
@Data
@TableName("t_order")
public class Order {
private Long id;
private Long userId;
private Long skuId;
private Integer quantity;
private BigDecimal amount;
private String status;
private LocalDateTime createTime;
}
@Data
@TableName("t_local_event")
public class LocalEvent {
private Long id;
private String bizId;
private String eventType;
private String payload;
private String status;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
@Mapper
public interface OrderMapper extends BaseMapper<Order> {}@Mapperpublic interface LocalEventMapper extends BaseMapper<LocalEvent> {}

  1. 事务消息发送工具(confirm + 本地事件)
@Component
@RequiredArgsConstructor
public class EventPublisher {
private final RabbitTemplate rabbitTemplate;
private final LocalEventMapper eventMapper;
@PostConstruct
public void init() {
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
eventMapper.updateStatus(correlationData.getId(), "SENT");
} else {
// 不处理,等定时任务
}
});
}
/**
* 事务内仅落库;AFTER_COMMIT 再调本方法
*/
public void publish(String bizId, String eventType, Object payload) {
LocalEvent event = new LocalEvent();
event.setBizId(bizId);
event.setEventType(eventType);
event.setPayload(JSON.toJSONString(payload));
event.setStatus("UNSENT");
eventMapper.insert(event);
CorrelationData cd = new CorrelationData(event.getId().toString());
rabbitTemplate.convertAndSend(
RabbitConfig.STOCK_EXCHANGE,
RabbitConfig.STOCK_RK,
event.getPayload(),
cd);
}
}

  1. 订单服务(本地事务 + 事件)
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderMapper orderMapper;
private final EventPublisher publisher;
private final ApplicationEventPublisher appEventPublisher;
@Transactional
public String createOrder(Long userId, Long skuId, Integer quantity) {
// 1. 落单
Order order = new Order();
order.setUserId(userId);
order.setSkuId(skuId);
order.setQuantity(quantity);
order.setAmount(BigDecimal.valueOf(quantity * 100)); // 单价 100
order.setStatus("INIT");
orderMapper.insert(order);
// 2. 写事件表(同一事务)
publisher.publish(order.getId().toString(), "ORDER_CREATED",
Map.of("orderId", order.getId(),
"skuId", skuId,
"quantity", quantity));
return order.getId().toString();
}
}

  1. 库存服务(幂等 + 业务失败补偿)
@Component
@RequiredArgsConstructor
public class StockConsumer {
private final StockFlowMapper flowMapper;
private final RabbitTemplate rabbitTemplate;
@RabbitListener(queues = RabbitConfig.STOCK_REDUCE_QUEUE)
public void reduce(Message msg, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
try {
String body = new String(msg.getBody());
JSONObject json = JSON.parseObject(body);
String orderId = json.getString("orderId");
Long skuId = json.getLong("skuId");
Integer quantity = json.getInteger("quantity");
// 1. 幂等
if (flowMapper.exists(orderId)) {
channel.basicAck(tag, false);
return;
}
// 2. 业务校验
Integer available = flowMapper.availableOf(skuId);
if (available < quantity) {
// 库存不足 -> 补偿
sendCompensate(orderId, "STOCK_NOT_ENOUGH");
channel.basicAck(tag, false);  // 必须 ack
return;
}
// 3. 扣减
flowMapper.deduct(skuId, quantity);
flowMapper.insert(orderId, skuId, quantity, "SUCCESS");
channel.basicAck(tag, false);
} catch (Exception e) {
log.error("扣库存异常", e);
channel.basicNack(tag, false, true); // 重试
}
}
private void sendCompensate(String orderId, String reason) {
Map<String, String> cmd = Map.of("orderId", orderId, "reason", reason);rabbitTemplate.convertAndSend(RabbitConfig.COMPENSATE_EX,RabbitConfig.COMPENSATE_RK,JSON.toJSONString(cmd));}}

  1. 补偿消费者(关闭订单 + 退款)
@Component
@RequiredArgsConstructor
public class CompensateConsumer {
private final OrderMapper orderMapper;
@RabbitListener(queues = RabbitConfig.COMPENSATE_QUEUE)
public void compensate(String json) {
JSONObject cmd = JSON.parseObject(json);
String orderId = cmd.getString("orderId");
Order order = orderMapper.selectById(orderId);
if (order == null || "CLOSED".equals(order.getStatus())) return;
// 1. 关单
order.setStatus("CLOSED");
orderMapper.updateById(order);
// 2. 退款(demo 直接日志)
log.warn(">>>> 退款操作 orderId={}, amount={}", orderId, order.getAmount());
// 3. 释放库存由 stock 服务监听同一条 cmd 自行加回
}
}

  1. 定时补偿任务(扫 UNSENT)
@Component
@RequiredArgsConstructor
public class ResendScheduler {
private final LocalEventMapper eventMapper;
private final RabbitTemplate rabbitTemplate;
// 每 5 秒扫一次
@Scheduled(fixedDelay = 5000)
public void resend() {
List<LocalEvent> list = eventMapper.selectList(new QueryWrapper<LocalEvent>().eq("status", "UNSENT").le("create_time", LocalDateTime.now().minusSeconds(10))); // 兜底延迟for (LocalEvent e : list) {CorrelationData cd = new CorrelationData(e.getId().toString());rabbitTemplate.convertAndSend(RabbitConfig.STOCK_EXCHANGE,RabbitConfig.STOCK_RK,e.getPayload(),cd);}}}

  1. 启动类
@SpringBootApplication
@EnableScheduling
@MapperScan("com.example.mapper")
public class TxApplication {
public static void main(String[] args) {
SpringApplication.run(TxApplication.class, args);
}
}

  1. 快速自测

  2. 启动 RabbitMQ、MySQL,执行上方 SQL

  3. curl -X POST http://localhost:8080/order?userId=1&skuId=10&quantity=2

  4. 观察控制台:

  • 订单落库 → 事件 UNSENT → 消息发送 → 收到 confirm → 事件变 SENT → 库存扣减成功
  • 把库存改成 1 再下单,日志出现 >>>> 退款操作,订单状态变为 CLOSED,即补偿成功。

  1. 小结

这份代码直接把前面所有本地消息表、confirm、幂等、补偿、对账落地:

  • 零消息丢失:confirm + 定时重扫 UNSENT
  • 幂等:t_stock_flow.order_id 唯一索引
  • 业务失败:立即发补偿消息,关单+退款
  • 技术异常:basicNack 重投 → 进 DLX → 人工

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/921988.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网站seo的内容是什么好看的html页面模板

Git是一种分布式版本控制系统&#xff0c;用于敏捷高效地处理任何或小或大的项目。以下是Git的详细介绍&#xff1a; 一、Git的基本概念 分布式&#xff1a;Git的最大特点之一是它的分布式设计。这意味着每个开发者都拥有完整的代码仓库&#xff0c;包含项目的全部历史记录。…

Vue 3.6 引入 Vapor Mode,虚拟DOM已死?

Vue 3.6 引入的 Vapor Mode 看似是“退回去”直接操作 DOM,但这并非简单的倒退,而是一次基于新技术条件的“范式跃迁”​。它通过更智能的“编译时优化”解决了传统直接操作 DOM 的痛点,同时继承了虚拟 DOM 的开发体…

part 10

T1 读题十分恶心,大概是有一个 \(n \cdot m\) 的网格图,统计最多的从 \((1,1)\) 到 \((n,n)\) 的路径数每一步往右或往下走,还有若干个障碍,路径要满足,按包含障碍的集合大小升序排序之后,使得各个路径拥有的障碍…

Nordic发布用于nRF54L系列的nRF Connect SDK裸机选项

全球低功耗无线连接解决方案领导者Nordic Semiconductor推出nRF Connect SDK 裸机选项,这是面向新一代nRF54L系列超低功耗无线SoC的新型软件解决方案。该方案独立于Zephyr实时操作系统(RTOS),可用于开发简单的低功耗…

写入方式、COW 与写放大

写入方式、COW 与写放大明白了,你希望这个文档 在博客园或者 Markdown 环境下显示效果更美观,排版整齐、表格和代码块清晰、层级逻辑一目了然。我帮你彻底优化一下格式,兼顾可读性和视觉效果:Linux 写入方式、COW …

微软SSO集成中的顺序用户ID身份验证绕过漏洞剖析

本文详细分析了微软单点登录集成中因使用可预测的顺序用户ID导致的严重身份验证绕过漏洞,揭示了后端未验证令牌声明、信任客户端输入等关键设计缺陷,为开发者和安全测试人员提供了重要警示。身份验证绕过:微软SSO集…

content和text方法的区别

使用requests进行网页请求时,对页面内容进行解码和编码的时候,需要用到.content和.text。Requests对象的get和post方法都会返回一个Response对象,这个对象里面存的是服务器返回的所有信息,包括响应头,响应状态码等…

完整教程:从零开始学神经网络——前馈神经网络

完整教程:从零开始学神经网络——前馈神经网络pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", &…

shell脚本动态域名解析阿里云

下面是一个利用Shell脚本调用阿里云DNS API实现动态域名解析(DDNS)的示例。该方案适用于家庭宽带等动态公网IP环境,主要依赖阿里云的域名解析API。 🖥️ 阿里云DDNS Shell脚本示例 #!/bin/bash# 阿里云DDNS动态域…

聪明的wyk

王俞宽是个人物

Windows下进程和账户权限

一、Windows 进程创建的底层流程(六阶段深度解析) 进程创建是用户态 API 触发、内核态资源分配、子系统协同的复杂过程,核心依赖CreateProcess系列函数(实际为宏定义,映射到CreateProcessA/W),具体分为六个阶段…

论状压记忆化搜索

其实非常简单,甚至比递推写法简单 比如P2704,递推做这个比较麻烦,但状压记搜强大 #include<bits/stdc++.h> #include<bits/extc++.h> using namespace std; using namespace __gnu_cxx; using namespac…

做微视频的网站组工网站建设方案

二、学习回归 1. y y y与 f θ ( x ) f_\theta(x) fθ​(x) y y y 是实际数据x对应的值 f θ ( x ) f_\theta(x) fθ​(x)是我们构造出来的函数&#xff0c;例如 f θ ( x ) θ 0 θ 1 x f_\theta(x) \theta_0 \theta_1 x fθ​(x)θ0​θ1​x 所以我们希望这两个越接近&…

Spring Gateway动态路由实现方案 - 详解

Spring Gateway动态路由实现方案 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Mo…

postman使用总结 - 详解

postman使用总结 - 详解2025-09-29 16:09 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; fon…

Nordic 高性能无线SoC nRF54LM20A,专为低功耗蓝牙与Matter设计

Nordic 宣布推出新一代nRF54L系列超低功耗无线系统级芯片 (SoC)的最新成员nRF54LM20A。nRF54L系列基于Nordic创新的22nm技术平台,不仅简化设计挑战,同时实现了可靠通信、更长的电池寿命和紧凑的产品设计。 Nordic 短…

调用setState 之后发生了什么?

触发状态更新 当你在组件里调用: this.setState({ count: this.state.count + 1 });React 并不会马上修改 this.state,而是 把更新请求放到一个队列中(即所谓的异步/批量更新机制)。 合并新旧状态React 会将你传入…

网站建设考虑哪些因素沈阳妇科医院排名前十名

在线预览|GB/T 41510-2022http://c.gb688.cn/bzgk/gb/showGb?typeonline&hcno696806EC48F4105CEF7479EB32C80C9E 知识点&#xff1a; 安全等级定义&#xff0c;设计寿命&#xff0c;剩余寿命&#xff0c;使用寿命。 标准附录有应力的具体解算演示。

湖北专业网站建设市面价wordpress格子主题

A. Submission Bait&#xff08;博弈&#xff09; 题意&#xff1a;爱丽丝和鲍勃在大小为n的数组a中进行游戏&#xff0c;他们轮流进行运算&#xff0c;爱丽丝先开始&#xff0c;不能运算的一方输&#xff0c;一开始mx0&#xff0c;每次操作&#xff0c;玩家可以选择一个牵引i…