轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 架构设计与模式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 架构设计与模式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 架构设计与模式

    • 高可用-分布式基础之CAP理论
    • 高可用-服务容错与降级策略
    • 高可用-故障检测与自动恢复
    • 高可用-混沌工程实践
    • 高可用-分布式事务实战
    • 高可用-多活与容灾架构设计
    • 高性能-缓存架构设计
    • 高性能-性能优化方法论
    • 高性能-异步处理与消息队列
      • 一、引言:从同步到异步的性能演进
      • 二、为什么需要异步处理
        • 2.1 同步调用的性能瓶颈
        • 2.2 异步化改造的效果
        • 2.3 异步处理的核心价值
      • 三、异步处理的基本模式
        • 3.1 线程池异步
        • 3.1.1 基于 Spring @Async
        • 3.1.2 线程池模式的优缺点
        • 3.2 本地消息表
        • 3.2.1 实现思路
        • 3.2.2 本地消息表的优缺点
        • 3.3 消息队列(Message Queue)
      • 四、消息队列核心概念
        • 4.1 消息队列的基本组成
        • 4.2 常见的消息模型
        • 4.2.1 点对点模型(Queue)
        • 4.2.2 发布/订阅模型(Topic)
        • 4.3 主流消息队列选型
      • 五、消息队列的核心机制
        • 5.1 消息持久化
        • 5.1.1 持久化策略
        • 5.1.2 RabbitMQ 持久化配置
        • 5.2 消息确认机制(ACK)
        • 5.2.1 自动确认 vs 手动确认
        • 5.3 消息重试与死信队列
        • 5.3.1 消息重试机制
        • 5.3.2 重试策略
        • 5.4 消息幂等性
        • 5.4.1 幂等性保证方案
        • 5.5 消息顺序性
        • 5.5.1 保证顺序的方案
      • 六、异步处理的典型应用场景
        • 6.1 削峰填谷
        • 6.2 系统解耦
        • 6.3 异步通知
        • 6.4 最终一致性
      • 七、消息队列的性能优化
        • 7.1 生产者优化
        • 7.1.1 批量发送
        • 7.1.2 异步发送
        • 7.2 消费者优化
        • 7.2.1 并发消费
        • 7.2.2 批量消费
        • 7.3 消息压缩
      • 八、消息队列的可靠性保障
        • 8.1 消息不丢失的三板斧
        • 8.1.1 生产者确认
        • 8.1.2 持久化
        • 8.1.3 消费者确认
        • 8.2 高可用架构
        • 8.2.1 集群部署
        • 8.2.2 主从复制
        • 8.2.3 故障转移
      • 九、异步处理的常见陷阱与最佳实践
        • 9.1 消息积压
        • 9.2 消息乱序
        • 9.3 重复消费
        • 9.4 消息丢失
        • 9.5 消息延迟
      • 十、总结:构建高性能异步系统的心智模型
    • 高性能-数据库性能优化
  • 代码质量管理

  • 基础

  • 操作系统

  • 计算机网络

  • AI

  • 编程范式

  • 安全

  • 中间件

  • 心得

  • 架构
  • 架构设计与模式
轩辕李
2025-04-06
目录

高性能-异步处理与消息队列

# 一、引言:从同步到异步的性能演进

在大多数业务系统的早期,处理流程通常是同步的、串行的:

用户请求 → 业务逻辑 → 数据库写入 → 发送通知 → 记录日志 → 返回响应

这种同步模式简单直观,但当系统规模扩大、流量增长时,问题逐渐暴露:

  • 响应时间长:用户必须等待所有步骤完成才能得到反馈
  • 资源利用率低:大量时间浪费在等待 IO 操作上
  • 扩展性差:单点瓶颈会限制整体吞吐量
  • 抗压能力弱:流量高峰时容易出现请求积压甚至系统崩溃

异步处理和消息队列的引入,可以从根本上改变这一局面:

  • 把"立即执行"改为"先接受、后处理"
  • 把"串行等待"改为"并行执行"
  • 把"强耦合"改为"松耦合"

本文将系统性地讨论异步处理与消息队列的核心原理、设计思路、常见模式以及实战经验。


# 二、为什么需要异步处理

# 2.1 同步调用的性能瓶颈

假设一个订单处理流程包含以下步骤:

步骤 耗时 是否核心
订单入库 20ms 是
扣减库存 30ms 是
调用支付 50ms 是
发送邮件 100ms 否
记录审计日志 30ms 否
推送营销消息 80ms 否

如果全部同步执行,总耗时为 310ms,而真正的核心业务只需要 100ms。

用户体验:用户等待 310ms 才能看到"下单成功",其中 210ms 都在等待非核心操作。

# 2.2 异步化改造的效果

通过异步处理,可以做到:

同步部分:订单入库 + 扣减库存 + 调用支付 = 100ms
异步部分:发送邮件、记录日志、推送消息在后台执行

用户响应时间从 310ms 降低到 100ms,性能提升 3 倍。

# 2.3 异步处理的核心价值

  1. 提升响应速度:立即返回,不必等待所有步骤完成
  2. 削峰填谷:流量高峰时先把任务放入队列,平滑处理
  3. 解耦服务:上下游不必直接依赖,降低系统耦合度
  4. 提高可用性:即使下游服务暂时不可用,上游仍可正常工作
  5. 弹性伸缩:可以根据消息堆积情况动态调整消费者数量

# 三、异步处理的基本模式

# 3.1 线程池异步

适用场景:单体应用内的轻量级异步任务。

# 3.1.1 基于 Spring @Async

@Configuration
@EnableAsync
public class AsyncConfig {
    
    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(200);
        executor.setThreadNamePrefix("async-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

@Service
public class OrderService {
    
    @Autowired
    private NotificationService notificationService;
    
    public OrderResult createOrder(Order order) {
        // 同步:核心业务逻辑
        orderRepository.save(order);
        inventoryService.deduct(order.getProductId(), order.getQuantity());
        
        // 异步:发送通知
        notificationService.sendEmailAsync(order);
        
        return OrderResult.success(order.getId());
    }
}

@Service
public class NotificationService {
    
    @Async("taskExecutor")
    public void sendEmailAsync(Order order) {
        // 发送邮件的耗时操作
        emailClient.send(order.getUserEmail(), "订单确认", order.toString());
    }
}

# 3.1.2 线程池模式的优缺点

优点:

  • 实现简单,无需引入额外组件
  • 延迟低,适合对实时性要求高的场景
  • 调试方便,问题排查相对容易

缺点:

  • 单机能力有限,无法跨机器分布式处理
  • 不具备消息持久化能力,进程重启后任务丢失
  • 缺少消息重试、死信队列等高级特性

# 3.2 本地消息表

适用场景:确保本地事务与异步消息的最终一致性。

# 3.2.1 实现思路

@Service
@Transactional
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private MessageTableRepository messageRepository;
    
    public void createOrder(Order order) {
        // 1. 保存订单
        orderRepository.save(order);
        
        // 2. 保存消息到本地消息表(与订单在同一事务)
        Message message = new Message();
        message.setTopic("order-created");
        message.setPayload(JSON.toJSONString(order));
        message.setStatus(MessageStatus.PENDING);
        messageRepository.save(message);
    }
}

@Component
public class MessageScheduler {
    
    @Autowired
    private MessageTableRepository messageRepository;
    
    @Autowired
    private MessageQueueProducer producer;
    
    @Scheduled(fixedDelay = 1000)
    public void scanPendingMessages() {
        List<Message> messages = messageRepository.findByStatus(MessageStatus.PENDING);
        
        for (Message msg : messages) {
            try {
                // 发送到消息队列
                producer.send(msg.getTopic(), msg.getPayload());
                
                // 标记为已发送
                msg.setStatus(MessageStatus.SENT);
                messageRepository.save(msg);
            } catch (Exception e) {
                // 记录日志,后续重试
                log.error("Failed to send message: {}", msg.getId(), e);
            }
        }
    }
}

# 3.2.2 本地消息表的优缺点

优点:

  • 保证本地事务与消息发送的最终一致性
  • 即使消息队列暂时不可用,也不影响业务操作
  • 消息可以持久化,不会因为进程重启而丢失
  • 实现相对简单,对现有代码侵入性较小

缺点:

  • 需要定时任务轮询消息表,存在一定延迟
  • 消息表需要定期清理,否则会无限增长
  • 增加了数据库的存储和查询压力
  • 不适合高并发、实时性要求高的场景
  • 跨库事务场景下实现复杂度较高

# 3.3 消息队列(Message Queue)

适用场景:分布式系统中的标准异步通信方式,是大规模系统的首选方案。

相比前两种方式,消息队列具有更强的能力:

相比线程池异步的优势:

  • 支持分布式部署,可跨机器处理任务
  • 消息持久化,不会因进程重启而丢失
  • 提供完善的消息确认、重试、死信队列等机制
  • 天然支持削峰填谷,应对流量突刺

相比本地消息表的优势:

  • 实时性更高,无需定时轮询
  • 吞吐量更大,适合高并发场景
  • 功能更完善,支持消息路由、优先级、延迟消息等
  • 运维成熟,有丰富的监控和管理工具

适用场景:

  • 需要跨系统、跨服务的异步通信
  • 对吞吐量、实时性要求较高
  • 需要可靠的消息传递保障
  • 系统规模较大,流量波动明显

接下来我们将深入探讨消息队列的核心概念与实践。


# 四、消息队列核心概念

# 4.1 消息队列的基本组成

生产者(Producer) → 消息队列(Queue/Topic) → 消费者(Consumer)
  • 生产者:负责发送消息
  • 消息队列:存储消息,保证消息不丢失
  • 消费者:订阅并处理消息

# 4.2 常见的消息模型

# 4.2.1 点对点模型(Queue)

Producer → [Queue] → Consumer1
                   → Consumer2

特点:

  • 每条消息只能被一个消费者消费
  • 消费者之间竞争消息
  • 适用于任务分发、负载均衡场景

# 4.2.2 发布/订阅模型(Topic)

Producer → [Topic] → Consumer1
                   → Consumer2
                   → Consumer3

特点:

  • 每条消息可以被多个消费者消费
  • 消费者之间相互独立
  • 适用于事件广播、数据同步场景

# 4.3 主流消息队列选型

产品 特点 适用场景
RabbitMQ 功能丰富,支持多种协议,易用性强 中小规模,对可靠性要求高
RocketMQ 高吞吐,分布式事务,延迟消息 电商、金融等大规模场景
Kafka 超高吞吐,持久化强,生态丰富 大数据、日志收集、流处理
Pulsar 多租户,存储计算分离,云原生 云原生环境,多租户场景

# 五、消息队列的核心机制

# 5.1 消息持久化

目的:防止消息丢失。

# 5.1.1 持久化策略

  1. 内存队列:高性能,但重启后消息丢失
  2. 磁盘队列:消息写入磁盘,重启后可恢复
  3. 混合模式:内存 + 异步刷盘,兼顾性能与可靠性

# 5.1.2 RabbitMQ 持久化配置

@Bean
public Queue orderQueue() {
    return QueueBuilder.durable("order-queue")
            .withArgument("x-message-ttl", 60000)
            .build();
}

@Bean
public DirectExchange orderExchange() {
    return new DirectExchange("order-exchange", true, false);
}

# 5.2 消息确认机制(ACK)

目的:确保消息被成功消费。

# 5.2.1 自动确认 vs 手动确认

自动确认:消息一经投递就被标记为已消费,存在消息丢失风险。

手动确认:消费者显式确认消息已处理完成,更可靠。

@RabbitListener(queues = "order-queue")
public void handleOrder(Order order, Channel channel, 
                        @Header(AmqpHeaders.DELIVERY_TAG) long tag) {
    try {
        // 处理订单
        processOrder(order);
        
        // 手动确认
        channel.basicAck(tag, false);
    } catch (Exception e) {
        // 拒绝消息,重新入队
        channel.basicNack(tag, false, true);
    }
}

# 5.3 消息重试与死信队列

# 5.3.1 消息重试机制

@Bean
public Queue orderQueue() {
    return QueueBuilder.durable("order-queue")
            .withArgument("x-dead-letter-exchange", "dlx-exchange")
            .withArgument("x-dead-letter-routing-key", "dlx-routing-key")
            .build();
}

@Bean
public Queue deadLetterQueue() {
    return new Queue("order-dlq", true);
}

# 5.3.2 重试策略

  1. 固定延迟重试:每次重试间隔固定(如 1s、2s、3s)
  2. 指数退避:重试间隔逐步增长(如 1s、2s、4s、8s)
  3. 最大重试次数:超过次数后进入死信队列,人工介入

# 5.4 消息幂等性

问题:由于网络抖动、重试机制等原因,消息可能被重复消费。

# 5.4.1 幂等性保证方案

  1. 唯一消息 ID:每条消息携带全局唯一 ID,消费前先检查是否已处理
@Service
public class OrderConsumer {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    public void consume(Message message) {
        String messageId = message.getId();
        
        // 检查是否已处理
        Boolean isProcessed = redisTemplate.opsForValue()
                .setIfAbsent("msg:" + messageId, "1", 1, TimeUnit.HOURS);
        
        if (Boolean.FALSE.equals(isProcessed)) {
            log.info("Message already processed: {}", messageId);
            return;
        }
        
        // 处理业务逻辑
        handleOrder(message.getPayload());
    }
}
  1. 业务唯一键:利用数据库唯一索引,重复插入会失败
CREATE TABLE orders (
    order_id VARCHAR(64) PRIMARY KEY,
    user_id BIGINT,
    amount DECIMAL(10,2),
    created_at TIMESTAMP
);
  1. 状态机:基于订单状态流转,只处理合法的状态变更

# 5.5 消息顺序性

问题:在分布式环境下,消息顺序可能被打乱。

# 5.5.1 保证顺序的方案

  1. 单队列单消费者:简单但牺牲并发能力
  2. 分区顺序消息:同一个 Key 的消息路由到同一分区,分区内保证顺序
// RocketMQ 顺序消息
producer.send(message, new MessageQueueSelector() {
    @Override
    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
        Long userId = (Long) arg;
        int index = (int) (userId % mqs.size());
        return mqs.get(index);
    }
}, order.getUserId());

# 六、异步处理的典型应用场景

# 6.1 削峰填谷

场景:电商大促、秒杀活动时,流量瞬间暴增。

问题:数据库、下游服务无法承受突发流量,导致系统崩溃。

解决方案:

用户请求 → 快速写入消息队列 → 返回"处理中" → 后台消费者平滑处理

效果:

  • 用户立即得到反馈,不必等待
  • 后端系统按自身能力处理,不会被打垮
  • 通过增加消费者数量弹性扩展处理能力

# 6.2 系统解耦

场景:订单创建后,需要通知多个下游系统(库存、支付、物流、营销)。

同步模式的问题:

  • 订单服务需要直接调用所有下游服务
  • 任何一个下游服务故障都会影响订单创建
  • 新增下游系统需要修改订单服务代码

异步解耦方案:

@Service
public class OrderService {
    
    @Autowired
    private MessageQueueProducer producer;
    
    public void createOrder(Order order) {
        // 1. 保存订单
        orderRepository.save(order);
        
        // 2. 发布订单创建事件
        OrderEvent event = new OrderEvent("ORDER_CREATED", order);
        producer.send("order-events", event);
    }
}

// 各下游系统独立消费
@Service
public class InventoryService {
    @RabbitListener(queues = "inventory-queue")
    public void handleOrderCreated(OrderEvent event) {
        inventoryRepository.deduct(event.getOrder().getProductId());
    }
}

@Service
public class LogisticsService {
    @RabbitListener(queues = "logistics-queue")
    public void handleOrderCreated(OrderEvent event) {
        logisticsService.createShipment(event.getOrder());
    }
}

优势:

  • 订单服务只需发布事件,不关心有多少订阅者
  • 下游系统独立演进,互不影响
  • 新增下游系统无需修改上游代码

# 6.3 异步通知

场景:发送邮件、短信、推送消息等非核心操作。

特点:

  • 耗时较长(100ms - 几秒)
  • 允许一定延迟
  • 不影响核心业务流程

实现:

@Service
public class NotificationProducer {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    public void sendEmail(String email, String subject, String content) {
        EmailNotification notification = new EmailNotification(email, subject, content);
        rabbitTemplate.convertAndSend("notification-exchange", "email", notification);
    }
    
    public void sendSms(String phone, String message) {
        SmsNotification notification = new SmsNotification(phone, message);
        rabbitTemplate.convertAndSend("notification-exchange", "sms", notification);
    }
}

@Service
public class EmailConsumer {
    @RabbitListener(queues = "email-queue", concurrency = "5-10")
    public void handleEmail(EmailNotification notification) {
        emailClient.send(notification.getEmail(), 
                        notification.getSubject(), 
                        notification.getContent());
    }
}

# 6.4 最终一致性

场景:跨系统的数据同步,如用户信息变更需要同步到多个业务系统。

方案:基于事件的最终一致性。

// 用户服务
@Service
public class UserService {
    
    @Transactional
    public void updateUser(User user) {
        // 1. 更新用户信息
        userRepository.save(user);
        
        // 2. 发布用户更新事件
        UserEvent event = new UserEvent("USER_UPDATED", user);
        eventPublisher.publish("user-events", event);
    }
}

// 订单服务消费
@Service
public class OrderUserSyncConsumer {
    @RabbitListener(queues = "order-user-sync-queue")
    public void syncUser(UserEvent event) {
        User user = event.getUser();
        orderUserCache.update(user.getId(), user);
    }
}

// 会员服务消费
@Service
public class MemberUserSyncConsumer {
    @RabbitListener(queues = "member-user-sync-queue")
    public void syncUser(UserEvent event) {
        User user = event.getUser();
        memberRepository.updateUser(user);
    }
}

# 七、消息队列的性能优化

# 7.1 生产者优化

# 7.1.1 批量发送

public class BatchProducer {
    
    private List<Message> batch = new ArrayList<>();
    private static final int BATCH_SIZE = 100;
    
    public synchronized void send(Message message) {
        batch.add(message);
        
        if (batch.size() >= BATCH_SIZE) {
            flush();
        }
    }
    
    private void flush() {
        if (!batch.isEmpty()) {
            producer.sendBatch(batch);
            batch.clear();
        }
    }
}

# 7.1.2 异步发送

// RocketMQ 异步发送
producer.send(message, new SendCallback() {
    @Override
    public void onSuccess(SendResult result) {
        log.info("Message sent successfully: {}", result.getMsgId());
    }
    
    @Override
    public void onException(Throwable e) {
        log.error("Failed to send message", e);
    }
});

# 7.2 消费者优化

# 7.2.1 并发消费

@RabbitListener(queues = "order-queue", concurrency = "10-20")
public void consume(Order order) {
    processOrder(order);
}

# 7.2.2 批量消费

@RabbitListener(queues = "order-queue")
public void consumeBatch(List<Order> orders) {
    orderRepository.saveAll(orders);
}

# 7.3 消息压缩

对于大消息,可以使用 Gzip、Snappy 等压缩算法:

public class CompressedProducer {
    
    public void send(String topic, Object payload) {
        String json = JSON.toJSONString(payload);
        byte[] compressed = GzipUtils.compress(json.getBytes());
        
        Message message = new Message(topic, compressed);
        producer.send(message);
    }
}

# 八、消息队列的可靠性保障

# 8.1 消息不丢失的三板斧

# 8.1.1 生产者确认

rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
    if (ack) {
        log.info("Message sent successfully");
    } else {
        log.error("Message send failed: {}", cause);
    }
});

# 8.1.2 持久化

  • 队列持久化:durable = true
  • 消息持久化:deliveryMode = 2
  • 交换机持久化:durable = true

# 8.1.3 消费者确认

手动确认 ACK,确保消息处理成功后再删除。

# 8.2 高可用架构

# 8.2.1 集群部署

Producer → [Load Balancer] → Broker1
                            → Broker2
                            → Broker3

# 8.2.2 主从复制

  • 同步复制:主从同时写入成功才返回,强一致性但性能较低
  • 异步复制:主写入成功即返回,性能高但可能丢失少量数据

# 8.2.3 故障转移

  • 自动故障检测
  • 主节点故障后自动切换到从节点
  • 客户端自动重连

# 九、异步处理的常见陷阱与最佳实践

# 9.1 消息积压

原因:消费速度跟不上生产速度。

解决方案:

  1. 增加消费者数量
  2. 优化消费逻辑,提升处理速度
  3. 对消息进行优先级分级,优先处理重要消息
  4. 限流保护,防止生产者无限制发送

# 9.2 消息乱序

问题:分布式环境下消息顺序难以保证。

方案:

  • 如果必须保证顺序:使用分区顺序消息
  • 如果业务允许:设计成无序也能处理的逻辑

# 9.3 重复消费

问题:网络抖动、重试机制导致消息重复。

方案:

  • 消费逻辑必须实现幂等性
  • 使用唯一消息 ID 去重
  • 利用数据库唯一约束

# 9.4 消息丢失

问题:消息在传输、存储、消费过程中丢失。

方案:

  • 生产者确认
  • 消息持久化
  • 消费者手动确认
  • 定期数据对账,发现并补偿丢失的消息

# 9.5 消息延迟

问题:消息堆积导致处理延迟增大。

监控指标:

  • 队列深度
  • 消费延迟
  • 消息年龄

应对策略:

  • 设置告警阈值,及时发现异常
  • 弹性扩展消费者
  • 临时降级非核心消息

# 十、总结:构建高性能异步系统的心智模型

异步处理和消息队列是构建高性能、高可用分布式系统的重要基石。

核心要点回顾:

  1. 异步优先于同步:对于非核心、耗时的操作,优先考虑异步处理
  2. 解耦优于耦合:通过消息队列解耦上下游,提升系统灵活性
  3. 可靠性优先:消息不丢失、幂等消费、故障重试是基本要求
  4. 性能与可靠性平衡:根据业务特点选择合适的策略,不必过度设计
  5. 监控与运维:消息积压、延迟、错误率等指标必须实时监控

实践建议:

  • 从简单场景入手:优先使用线程池异步,再逐步引入消息队列
  • 选型要务实:根据团队技术栈、业务规模、可靠性要求综合考虑
  • 幂等性设计:从一开始就考虑消息重复消费的问题
  • 监控先行:没有监控的异步系统是"黑盒",出问题难以排查
  • 持续优化:通过压测发现瓶颈,逐步优化生产和消费性能

当你和你的团队长期坚持这样做,你们会发现:

  • 系统响应速度显著提升,用户体验更好
  • 流量高峰时系统更加稳定,不再频繁崩溃
  • 业务模块更加独立,演进速度更快
  • 系统整体的抗压能力和扩展能力大幅增强

祝你变得更强!

编辑 (opens new window)
#性能优化#高性能#异步处理#消息队列#架构设计
上次更新: 2025/12/17
高性能-性能优化方法论
高性能-数据库性能优化

← 高性能-性能优化方法论 高性能-数据库性能优化→

最近更新
01
AI编程时代的一些心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code 最佳实践(个人版)
08-01
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式