微服务架构下的分布式事务解决方案技术预研:Saga、TCC、本地消息表模式对比分析

Tara402
Tara402 2026-01-15T07:18:15+08:00
0 0 0

引言

随着微服务架构的广泛应用,分布式事务问题成为了系统设计中的核心挑战之一。在传统单体应用中,事务管理相对简单,但在微服务架构下,由于业务逻辑被拆分到不同的服务中,跨服务的事务处理变得异常复杂。本文将深入研究微服务架构中分布式事务的主流解决方案,对比分析Saga模式、TCC模式、本地消息表等技术方案的优缺点,并结合实际业务场景提供选型建议和技术实现细节。

微服务架构下的分布式事务挑战

什么是分布式事务

在微服务架构中,分布式事务指的是跨越多个服务或数据库的操作,这些操作需要作为一个整体来保证数据的一致性。传统的ACID事务无法满足这种跨服务的事务需求,因为每个服务都有自己的数据库实例,无法直接通过本地事务机制实现全局一致性。

分布式事务的核心问题

  1. 数据一致性:确保多个服务间的数据保持一致状态
  2. 可用性:在部分服务不可用时仍能保证系统整体可用性
  3. 性能影响:事务协调机制可能带来额外的延迟和开销
  4. 复杂性管理:分布式环境下的错误处理和恢复机制

Saga模式详解

Saga模式概述

Saga是一种长事务解决方案,它将一个大的分布式事务拆分成多个小的本地事务,每个本地事务都有对应的补偿操作。当某个步骤失败时,可以通过执行前面已成功步骤的补偿操作来回滚整个流程。

Saga模式的工作原理

步骤1: ServiceA -> 步骤2: ServiceB -> 步骤3: ServiceC
         |               |               |
         v               v               v
    [成功]          [成功]          [失败]
         |               |               |
         +---------------+---------------+
                     |
                执行补偿操作

Saga模式的两种实现方式

1. 协议式Saga(Choreography)

在协议式Saga中,每个服务都负责自己的业务逻辑和补偿逻辑,并通过事件驱动的方式协调整个流程。

// ServiceA - 订单服务
@Service
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private EventBus eventBus;
    
    public void createOrder(OrderRequest request) {
        // 创建订单
        Order order = new Order();
        order.setId(UUID.randomUUID().toString());
        order.setStatus("CREATED");
        order.setAmount(request.getAmount());
        
        orderRepository.save(order);
        
        // 发布订单创建事件
        eventBus.publish(new OrderCreatedEvent(order.getId(), order.getAmount()));
    }
    
    // 订单补偿操作
    public void compensateOrder(String orderId) {
        Order order = orderRepository.findById(orderId);
        if (order != null && "CREATED".equals(order.getStatus())) {
            order.setStatus("CANCELLED");
            orderRepository.save(order);
        }
    }
}

// ServiceB - 库存服务
@Service
public class InventoryService {
    
    @Autowired
    private InventoryRepository inventoryRepository;
    
    @Autowired
    private EventBus eventBus;
    
    // 处理订单创建事件
    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        try {
            // 扣减库存
            Inventory inventory = inventoryRepository.findByProductId(event.getProductId());
            if (inventory.getAvailable() >= event.getAmount()) {
                inventory.setAvailable(inventory.getAvailable() - event.getAmount());
                inventoryRepository.save(inventory);
                
                // 发布库存扣减成功事件
                eventBus.publish(new InventoryReservedEvent(event.getOrderId(), event.getAmount()));
            } else {
                throw new InsufficientInventoryException("库存不足");
            }
        } catch (Exception e) {
            // 发布库存扣减失败事件,触发补偿
            eventBus.publish(new InventoryReservationFailedEvent(event.getOrderId(), e.getMessage()));
            throw e;
        }
    }
    
    // 库存补偿操作
    public void compensateInventory(String orderId, int amount) {
        Inventory inventory = inventoryRepository.findByOrderId(orderId);
        if (inventory != null) {
            inventory.setAvailable(inventory.getAvailable() + amount);
            inventoryRepository.save(inventory);
        }
    }
}

2. 协调式Saga(Orchestration)

协调式Saga通过一个协调服务来管理整个Saga流程,各个服务只需要实现业务逻辑和补偿逻辑。

// Saga协调器
@Component
public class OrderSagaCoordinator {
    
    private final List<SagaStep> steps = new ArrayList<>();
    private final Map<String, Object> context = new ConcurrentHashMap<>();
    
    public void executeSaga(OrderRequest request) {
        try {
            // 初始化步骤
            initSteps();
            
            // 执行每个步骤
            for (int i = 0; i < steps.size(); i++) {
                SagaStep step = steps.get(i);
                try {
                    step.execute(context);
                    context.put("current_step", i);
                } catch (Exception e) {
                    // 回滚前面的所有步骤
                    rollbackSteps(i - 1);
                    throw new SagaExecutionException("Saga执行失败", e);
                }
            }
        } catch (Exception e) {
            logger.error("Saga执行失败", e);
            throw e;
        }
    }
    
    private void rollbackSteps(int fromIndex) {
        for (int i = fromIndex; i >= 0; i--) {
            try {
                steps.get(i).rollback(context);
            } catch (Exception e) {
                logger.error("补偿操作执行失败", e);
            }
        }
    }
    
    private void initSteps() {
        steps.clear();
        steps.add(new OrderCreationStep());
        steps.add(new InventoryReservationStep());
        steps.add(new PaymentProcessingStep());
    }
}

// Saga步骤接口
public interface SagaStep {
    void execute(Map<String, Object> context) throws Exception;
    void rollback(Map<String, Object> context) throws Exception;
}

// 订单创建步骤
@Component
public class OrderCreationStep implements SagaStep {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Override
    public void execute(Map<String, Object> context) throws Exception {
        String orderId = UUID.randomUUID().toString();
        Order order = new Order();
        order.setId(orderId);
        order.setStatus("CREATED");
        
        orderRepository.save(order);
        context.put("order_id", orderId);
    }
    
    @Override
    public void rollback(Map<String, Object> context) throws Exception {
        String orderId = (String) context.get("order_id");
        Order order = orderRepository.findById(orderId);
        if (order != null) {
            order.setStatus("CANCELLED");
            orderRepository.save(order);
        }
    }
}

Saga模式的优缺点分析

优点:

  1. 高可用性:每个服务独立运行,单点故障不影响整体系统
  2. 可扩展性强:可以灵活添加新的服务和业务逻辑
  3. 性能较好:避免了长事务的锁竞争问题
  4. 容错能力强:通过补偿机制处理异常情况

缺点:

  1. 复杂度高:需要设计复杂的补偿逻辑
  2. 数据一致性保证弱:最终一致性,可能在短时间内存在数据不一致
  3. 调试困难:流程复杂,问题定位困难
  4. 事务状态管理复杂:需要维护Saga的执行状态

TCC模式详解

TCC模式概述

TCC(Try-Confirm-Cancel)是一种两阶段提交的分布式事务解决方案。它将业务逻辑分为三个阶段:

  1. Try阶段:尝试执行业务操作,预留资源
  2. Confirm阶段:确认执行业务操作,正式提交
  3. Cancel阶段:取消执行业务操作,释放资源

TCC模式的工作流程

Try阶段 -> 业务服务A -> 业务服务B -> 业务服务C
   |          |         |         |
   +----------+---------+---------+
               |
      Confirm/Cancel阶段
               |
    所有服务确认或取消

TCC模式的实现示例

// TCC服务接口
public interface AccountService {
    /**
     * Try阶段:预留账户资金
     */
    void tryDeduct(String accountId, BigDecimal amount);
    
    /**
     * Confirm阶段:确认扣款
     */
    void confirmDeduct(String accountId, BigDecimal amount);
    
    /**
     * Cancel阶段:取消扣款,释放资金
     */
    void cancelDeduct(String accountId, BigDecimal amount);
}

// 账户服务实现
@Service
public class AccountServiceImpl implements AccountService {
    
    @Autowired
    private AccountRepository accountRepository;
    
    @Autowired
    private TccTransactionManager tccTransactionManager;
    
    @Override
    public void tryDeduct(String accountId, BigDecimal amount) {
        // 1. 检查账户余额
        Account account = accountRepository.findById(accountId);
        if (account.getBalance().compareTo(amount) < 0) {
            throw new InsufficientBalanceException("账户余额不足");
        }
        
        // 2. 预留资金(冻结部分资金)
        BigDecimal reservedAmount = account.getReservedAmount().add(amount);
        account.setReservedAmount(reservedAmount);
        accountRepository.save(account);
        
        // 3. 记录TCC事务状态
        tccTransactionManager.recordTry(accountId, amount, "DEDUCT");
    }
    
    @Override
    public void confirmDeduct(String accountId, BigDecimal amount) {
        Account account = accountRepository.findById(accountId);
        if (account != null) {
            // 1. 确认扣款
            account.setBalance(account.getBalance().subtract(amount));
            account.setReservedAmount(account.getReservedAmount().subtract(amount));
            accountRepository.save(account);
            
            // 2. 清除TCC事务状态
            tccTransactionManager.clearTransaction(accountId, amount, "DEDUCT");
        }
    }
    
    @Override
    public void cancelDeduct(String accountId, BigDecimal amount) {
        Account account = accountRepository.findById(accountId);
        if (account != null) {
            // 1. 取消扣款,释放预留资金
            account.setReservedAmount(account.getReservedAmount().subtract(amount));
            accountRepository.save(account);
            
            // 2. 清除TCC事务状态
            tccTransactionManager.clearTransaction(accountId, amount, "DEDUCT");
        }
    }
}

// TCC事务管理器
@Component
public class TccTransactionManager {
    
    private final Map<String, TccTransaction> transactionMap = new ConcurrentHashMap<>();
    
    public void recordTry(String accountId, BigDecimal amount, String operation) {
        TccTransaction transaction = new TccTransaction();
        transaction.setId(UUID.randomUUID().toString());
        transaction.setAccountId(accountId);
        transaction.setAmount(amount);
        transaction.setOperation(operation);
        transaction.setStatus("TRY");
        transaction.setCreateTime(new Date());
        
        transactionMap.put(accountId, transaction);
    }
    
    public void clearTransaction(String accountId, BigDecimal amount, String operation) {
        TccTransaction transaction = transactionMap.remove(accountId);
        if (transaction != null) {
            transaction.setStatus("COMPLETED");
            transaction.setCompleteTime(new Date());
            // 持久化事务状态
            saveTransaction(transaction);
        }
    }
    
    public void rollbackAll() {
        transactionMap.values().forEach(transaction -> {
            if ("TRY".equals(transaction.getStatus())) {
                // 执行回滚操作
                rollbackTransaction(transaction);
            }
        });
    }
    
    private void rollbackTransaction(TccTransaction transaction) {
        // 根据业务类型执行相应的回滚操作
        switch (transaction.getOperation()) {
            case "DEDUCT":
                accountService.cancelDeduct(transaction.getAccountId(), transaction.getAmount());
                break;
            // 其他操作的回滚逻辑
        }
    }
}

// TCC事务实体类
public class TccTransaction {
    private String id;
    private String accountId;
    private BigDecimal amount;
    private String operation;
    private String status;
    private Date createTime;
    private Date completeTime;
    
    // getter和setter方法
}

TCC模式的注解式实现

// TCC注解定义
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TccAction {
    String name() default "";
    String confirmMethod() default "";
    String cancelMethod() default "";
}

// 服务层实现
@Service
public class OrderService {
    
    @Autowired
    private AccountService accountService;
    
    @Autowired
    private InventoryService inventoryService;
    
    @TccAction(name = "orderProcess", 
              confirmMethod = "confirmOrder", 
              cancelMethod = "cancelOrder")
    public void processOrder(OrderRequest request) {
        // 1. 预留账户资金
        accountService.tryDeduct(request.getAccountId(), request.getAmount());
        
        // 2. 预留库存
        inventoryService.tryReserve(request.getProductId(), request.getQuantity());
        
        // 3. 创建订单
        createOrder(request);
    }
    
    @TccAction(name = "confirmOrder", confirmMethod = "", cancelMethod = "")
    public void confirmOrder(OrderRequest request) {
        // 确认账户扣款
        accountService.confirmDeduct(request.getAccountId(), request.getAmount());
        
        // 确认库存扣减
        inventoryService.confirmReserve(request.getProductId(), request.getQuantity());
        
        // 更新订单状态为已完成
        updateOrderStatus(request.getOrderId(), "COMPLETED");
    }
    
    @TccAction(name = "cancelOrder", confirmMethod = "", cancelMethod = "")
    public void cancelOrder(OrderRequest request) {
        // 取消账户扣款
        accountService.cancelDeduct(request.getAccountId(), request.getAmount());
        
        // 取消库存预留
        inventoryService.cancelReserve(request.getProductId(), request.getQuantity());
        
        // 更新订单状态为取消
        updateOrderStatus(request.getOrderId(), "CANCELLED");
    }
}

TCC模式的优缺点分析

优点:

  1. 强一致性:通过两阶段提交保证数据强一致性
  2. 业务侵入性低:只需要在业务方法上添加注解或实现接口
  3. 可扩展性好:可以灵活添加新的TCC服务
  4. 性能相对较好:避免了长时间的锁等待

缺点:

  1. 业务逻辑复杂化:需要为每个业务操作编写Try、Confirm、Cancel三个方法
  2. 开发成本高:需要大量重复代码和补偿逻辑
  3. 事务协调复杂:需要处理复杂的事务状态管理和超时机制
  4. 容错能力有限:一旦某个阶段失败,可能需要人工干预

本地消息表模式详解

本地消息表模式概述

本地消息表是一种基于数据库的分布式事务解决方案。它通过在本地数据库中维护消息表来保证事务的一致性。基本思路是:在执行业务操作的同时,将要发送的消息写入本地消息表,然后通过定时任务或异步机制将消息发送到消息队列。

本地消息表的工作原理

业务操作 -> 写入本地消息表 -> 消息投递 -> 确认消息状态
   |                |              |            |
   v                v              v            v
业务数据         消息记录      消息发送     状态更新

本地消息表实现示例

// 消息实体类
@Entity
@Table(name = "local_message")
public class LocalMessage {
    @Id
    private String messageId;
    
    private String businessType;
    private String businessId;
    private String content;
    private String status; // PENDING, SENDING, SUCCESS, FAILED
    private Integer retryCount;
    private Date createTime;
    private Date updateTime;
    
    // getter和setter方法
}

// 消息服务接口
public interface MessageService {
    void sendMessage(String businessType, String businessId, String content);
    void processMessages();
    void updateMessageStatus(String messageId, String status);
}

// 消息服务实现
@Service
@Transactional
public class MessageServiceImpl implements MessageService {
    
    @Autowired
    private LocalMessageRepository messageRepository;
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @Override
    public void sendMessage(String businessType, String businessId, String content) {
        // 1. 执行业务操作
        executeBusinessOperation(businessType, businessId, content);
        
        // 2. 写入本地消息表
        LocalMessage message = new LocalMessage();
        message.setMessageId(UUID.randomUUID().toString());
        message.setBusinessType(bbusinessType);
        message.setBusinessId(businessId);
        message.setContent(content);
        message.setStatus("PENDING");
        message.setRetryCount(0);
        message.setCreateTime(new Date());
        message.setUpdateTime(new Date());
        
        messageRepository.save(message);
    }
    
    @Override
    public void processMessages() {
        // 查询待处理的消息
        List<LocalMessage> messages = messageRepository.findByStatus("PENDING");
        
        for (LocalMessage message : messages) {
            try {
                // 3. 发送消息到消息队列
                rabbitTemplate.convertAndSend("message.exchange", 
                                            "message.routing.key", 
                                            message.getContent());
                
                // 4. 更新消息状态为发送中
                updateMessageStatus(message.getMessageId(), "SENDING");
                
                // 5. 确认消息发送成功后更新状态为成功
                updateMessageStatus(message.getMessageId(), "SUCCESS");
                
            } catch (Exception e) {
                logger.error("消息发送失败,messageId: {}", message.getMessageId(), e);
                // 6. 更新重试次数
                int retryCount = message.getRetryCount() + 1;
                if (retryCount <= 3) {
                    message.setRetryCount(retryCount);
                    message.setUpdateTime(new Date());
                    messageRepository.save(message);
                } else {
                    // 7. 超过重试次数,标记为失败
                    updateMessageStatus(message.getMessageId(), "FAILED");
                }
            }
        }
    }
    
    @Override
    public void updateMessageStatus(String messageId, String status) {
        LocalMessage message = messageRepository.findById(messageId);
        if (message != null) {
            message.setStatus(status);
            message.setUpdateTime(new Date());
            messageRepository.save(message);
        }
    }
    
    private void executeBusinessOperation(String businessType, String businessId, String content) {
        // 根据业务类型执行相应的业务操作
        switch (businessType) {
            case "ORDER_CREATED":
                orderService.createOrder(businessId, content);
                break;
            case "INVENTORY_RESERVED":
                inventoryService.reserveInventory(businessId, content);
                break;
            default:
                throw new IllegalArgumentException("不支持的业务类型: " + businessType);
        }
    }
}

// 消息队列消费者
@Component
public class MessageConsumer {
    
    @RabbitListener(queues = "message.queue")
    public void handleMessage(String messageContent) {
        try {
            // 1. 解析消息内容
            MessagePayload payload = parseMessage(messageContent);
            
            // 2. 执行相应的业务操作
            executeBusinessOperation(payload);
            
            // 3. 记录处理成功日志
            logger.info("消息处理成功: {}", messageContent);
            
        } catch (Exception e) {
            logger.error("消息处理失败", e);
            // 可以将失败的消息放入死信队列或进行人工处理
            throw new RuntimeException("消息处理失败", e);
        }
    }
    
    private MessagePayload parseMessage(String content) {
        // 解析JSON消息内容
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            return objectMapper.readValue(content, MessagePayload.class);
        } catch (Exception e) {
            throw new RuntimeException("消息解析失败", e);
        }
    }
}

本地消息表的定时任务实现

// 消息处理定时任务
@Component
public class MessageProcessTask {
    
    @Autowired
    private MessageService messageService;
    
    @Scheduled(fixedDelay = 5000) // 每5秒执行一次
    public void processPendingMessages() {
        try {
            logger.info("开始处理待处理消息");
            messageService.processMessages();
            logger.info("消息处理完成");
        } catch (Exception e) {
            logger.error("消息处理任务执行失败", e);
        }
    }
    
    @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
    public void cleanupFailedMessages() {
        try {
            logger.info("开始清理失败消息");
            messageService.cleanupFailedMessages();
            logger.info("失败消息清理完成");
        } catch (Exception e) {
            logger.error("失败消息清理任务执行失败", e);
        }
    }
}

// 消息清理服务
@Service
public class MessageCleanupService {
    
    @Autowired
    private LocalMessageRepository messageRepository;
    
    public void cleanupFailedMessages() {
        // 清理超过一定时间的失败消息
        Date cutoffDate = new Date(System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1000); // 7天前
        
        List<LocalMessage> failedMessages = messageRepository.findByStatusAndCreateTimeBefore(
            "FAILED", cutoffDate);
        
        for (LocalMessage message : failedMessages) {
            // 可以将这些消息放入专门的失败队列进行人工处理
            logger.info("清理失败消息: {}", message.getMessageId());
        }
        
        messageRepository.deleteAll(failedMessages);
    }
}

本地消息表的优缺点分析

优点:

  1. 实现简单:基于现有的数据库和消息队列技术,实现相对简单
  2. 可靠性高:通过本地事务保证消息的可靠性
  3. 容错性好:失败的消息可以重试或人工处理
  4. 成本较低:不需要额外的协调服务

缺点:

  1. 性能影响:需要频繁的数据库读写操作
  2. 数据一致性:仍然存在最终一致性的问题
  3. 复杂度增加:需要维护消息状态和重试机制
  4. 扩展性限制:可能成为系统瓶颈

三种模式的对比分析

功能特性对比

特性 Saga模式 TCC模式 本地消息表
一致性保证 最终一致性 强一致性 最终一致性
实现复杂度 中等
性能影响 中等 中等
容错能力 较强
开发成本 中等
扩展性 一般

适用场景对比

Saga模式适用场景:

  1. 业务流程复杂:涉及多个服务的长事务操作
  2. 对强一致性要求不高:可以接受短暂的数据不一致
  3. 高可用性要求:需要系统具备良好的容错能力
  4. 业务变更频繁:需要灵活调整业务流程

TCC模式适用场景:

  1. 强一致性要求:必须保证数据的强一致性
  2. 资源预留操作:需要在业务开始前预留资源
  3. 金融交易:银行转账、支付等对一致性要求极高的场景
  4. 业务逻辑相对稳定:不经常变更的业务流程

本地消息表适用场景:

  1. 异步通知需求:需要通过消息队列进行异步通知
  2. 系统解耦:需要降低服务间的耦合度
  3. 数据同步:需要在不同系统间同步数据
  4. 成本敏感:对实现复杂度和成本有要求的场景

性能对比分析

// 性能测试代码示例
public class DistributedTransactionPerformanceTest {
    
    @Test
    public void testSagaPerformance() {
        long startTime = System.currentTimeMillis();
        
        // 执行Saga模式的事务
        sagaService.executeOrderProcess(orderRequest);
        
        long endTime = System.currentTimeMillis();
        long duration = endTime - startTime;
        
        System.out.println("Saga模式执行时间: " + duration + "ms");
    }
    
    @Test
    public void testTccPerformance() {
        long startTime = System.currentTimeMillis();
        
        // 执行TCC模式的事务
        tccService.executeOrderProcess(orderRequest);
        
        long endTime = System.currentTimeMillis();
        long duration = endTime - startTime;
        
        System.out.println("TCC模式执行时间: " + duration + "ms");
    }
    
    @Test
    public void testLocalMessagePerformance() {
        long startTime = System.currentTimeMillis();
        
        // 执行本地消息表模式的事务
        localMessageService.executeOrderProcess(orderRequest);
        
        long endTime = System.currentTimeMillis();
        long duration = endTime - startTime;
        
        System.out.println("本地消息表模式执行时间: " + duration + "ms");
    }
}

实际业务场景选型建议

电商订单处理场景

// 电商订单处理服务
@Service
public class ECommerceOrderService {
    
    @Autowired
    private OrderService orderService;
    
    @Autowired
    private InventoryService inventoryService;
    
    @Autowired
    private PaymentService paymentService;
    
    @Autowired
    private NotificationService notificationService;
    
    // 选择合适的分布式事务模式
    public String processOrder(OrderRequest request) {
        // 基于业务特点选择模式
        if (request.isRealTimeRequired()) {
            // 对实时性要求高的场景,使用TCC模式
            return processWithTcc(request);
        } else {
            // 对实时性要求不高的场景,使用Saga模式或本地消息表
            return processWithSaga(request);
        }
    }
    
    private String processWithTcc(OrderRequest request) {
        try {
            // TCC模式处理订单
            tccOrderService.processOrder(request);
            return "SUCCESS";
        } catch (Exception e) {
            logger.error("TCC模式处理订单失败", e);
            return "FAILED";
        }
    }
    
    private String processWithSaga(OrderRequest request) {
        try {
            // Saga模式处理订单
            sagaOrderService.processOrder(request);
            return "SUCCESS";
        } catch (Exception e) {
            logger.error("Saga模式处理订单失败", e);
            return "FAILED";
        }
    }
}

金融支付场景

// 金融支付服务
@Service
public class FinancialPaymentService {
    
    @Autowired
    private AccountService accountService;
    
    @Autowired
    private TransactionService transactionService;
    
    @Autowired
    private AuditService auditService;
    
    // 金融场景强烈推荐使用TCC模式
    public boolean processPayment(PaymentRequest request) {
        try {
            // 使用TCC模式确保支付的强一致性
            tccPaymentService.processPayment(request);
            
            // 记录审计日志
            auditService.recordAuditLog(request);
            
            return true;
        } catch (Exception e) {
            logger.error("支付处理失败", e);
            return false;
        }
    }
}

最佳实践与注意事项

1. 事务状态管理

// 事务状态管理器
@Component
public class TransactionStateManager {
    
    private final Map<String, TransactionStatus> statusMap = new ConcurrentHashMap<>();
    
    public void updateTransactionStatus(String transactionId, String status) {
        TransactionStatus statusInfo = statusMap.computeIfAbsent
相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000