📬 分布式消息队列:三大终极难题!

副标题:顺序性、可靠性、幂等性,一个都不能少!🎯


🎬 开场:消息队列的三大噩梦

场景1:顺序错乱 📝:

markdown 复制代码
用户操作:
1. 创建订单
2. 支付订单
3. 发货

消息到达顺序:
1. 发货 ❌
2. 创建订单
3. 支付订单

结果:商品还没下单就发货了!😱

场景2:消息丢失 💔:

erlang 复制代码
发送了100条消息
MQ只收到98条
2条消息人间蒸发了...

结果:钱扣了,但订单没创建!😭

场景3:重复消费 🔄:

diff 复制代码
同一条消息被消费了3次
结果:
- 同一笔钱扣了3次
- 同一个商品发了3件
- 用户收到3条短信

用户:我只买了一件啊!😤

这就是我们今天要解决的三大问题!


📊 问题概览

问题 后果 解决难度
顺序性 业务逻辑错乱 ⭐⭐⭐⭐
可靠性 消息丢失 ⭐⭐⭐⭐⭐
幂等性 重复处理 ⭐⭐⭐

1️⃣ 顺序性:让消息按序到达

为什么会乱序?

生活比喻 🚗:

复制代码
你去肯德基点餐:
队列1:汉堡 → 薯条 → 可乐
队列2:汉堡 → 薯条 → 可乐
队列3:汉堡 → 薯条 → 可乐

三个窗口并行处理
取餐顺序可能是:
薯条 → 汉堡 → 可乐 ❌ 乱了!

MQ中的乱序原因

复制代码
原因1:多分区
消息发送到不同分区
并行消费导致乱序

原因2:多Consumer
多个消费者并发处理
处理速度不同导致乱序

原因3:重试机制
失败重试可能导致后发先至

解决方案1:单分区顺序

Kafka实现

java 复制代码
/**
 * 确保同一个订单的所有消息发到同一个分区
 */
@Service
public class OrderProducer {
    
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;
    
    public void sendOrderMessage(Long orderId, String message) {
        // 使用orderId作为key,确保相同orderId的消息发到同一个分区
        kafkaTemplate.send(
            "order-topic",
            orderId.toString(),  // key决定分区
            message
        );
    }
}

原理

ini 复制代码
Kafka分区策略:
hash(key) % partition_count = 分区号

示例:
订单123的所有消息:
- 创建订单 → hash(123) % 3 = 0 → 分区0
- 支付订单 → hash(123) % 3 = 0 → 分区0
- 发货     → hash(123) % 3 = 0 → 分区0

所有消息都在分区0,顺序保证!✅

消费端

java 复制代码
@Service
public class OrderConsumer {
    
    /**
     * 单线程消费,保证顺序
     */
    @KafkaListener(
        topics = "order-topic",
        concurrency = "1"  // 重点:单线程消费
    )
    public void consume(ConsumerRecord<String, String> record) {
        String orderId = record.key();
        String message = record.value();
        
        // 顺序处理
        processOrder(orderId, message);
    }
}

问题

erlang 复制代码
优点:
✅ 顺序100%保证

缺点:
❌ 性能低(单线程)
❌ 单点故障(一个分区挂了影响大)

解决方案2:局部有序 + 并发消费

java 复制代码
/**
 * 使用内存队列保证局部有序
 */
@Service
public class OrderConsumerWithQueue {
    
    // 为每个订单维护一个队列
    private ConcurrentHashMap<String, BlockingQueue<OrderMessage>> orderQueues 
        = new ConcurrentHashMap<>();
    
    // 线程池
    private ExecutorService executor = Executors.newFixedThreadPool(10);
    
    @KafkaListener(topics = "order-topic", concurrency = "3")
    public void consume(ConsumerRecord<String, String> record) {
        String orderId = record.key();
        OrderMessage message = parse(record.value());
        
        // 获取订单对应的队列
        BlockingQueue<OrderMessage> queue = orderQueues.computeIfAbsent(
            orderId,
            k -> {
                LinkedBlockingQueue<OrderMessage> q = new LinkedBlockingQueue<>();
                // 启动一个线程处理这个订单的消息
                executor.submit(() -> processQueue(orderId, q));
                return q;
            }
        );
        
        // 将消息放入队列
        queue.offer(message);
    }
    
    private void processQueue(String orderId, BlockingQueue<OrderMessage> queue) {
        while (true) {
            try {
                // 从队列取消息,顺序处理
                OrderMessage message = queue.take();
                processOrderMessage(orderId, message);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
}

原理图

复制代码
Kafka分区0  → 订单123队列 → 线程1处理
Kafka分区1  → 订单456队列 → 线程2处理
Kafka分区2  → 订单789队列 → 线程3处理

同一订单的消息在同一个队列
不同订单的消息并行处理

既保证了顺序,又提高了并发!✅

解决方案3:RocketMQ顺序消息

java 复制代码
/**
 * RocketMQ天然支持顺序消息
 */
@Service
public class RocketMQOrderProducer {
    
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    
    public void sendOrderMessage(Long orderId, String message) {
        // 使用orderId作为shardingKey
        rocketMQTemplate.syncSendOrderly(
            "order-topic",
            message,
            orderId.toString()  // shardingKey
        );
    }
}

@Service
@RocketMQMessageListener(
    topic = "order-topic",
    consumerGroup = "order-consumer-group",
    consumeMode = ConsumeMode.ORDERLY  // 顺序消费
)
public class RocketMQOrderConsumer implements RocketMQListener<String> {
    
    @Override
    public void onMessage(String message) {
        // RocketMQ保证同一个shardingKey的消息顺序消费
        processOrder(message);
    }
}

全局顺序 vs 局部顺序

类型 实现方式 性能 适用场景
全局顺序 单分区+单消费者 极低 日志收集
局部顺序 多分区+Key路由 订单处理 ⭐

2️⃣ 可靠性:消息一条都不能丢!

消息在哪里可能丢失?

复制代码
发送端 → MQ服务器 → 消费端
   ↓        ↓         ↓
 丢失点1   丢失点2   丢失点3

丢失点1:生产者发送失败

java 复制代码
// ❌ 错误做法:不管发送结果
kafkaTemplate.send("topic", "message");
// 如果网络抖动,消息可能丢失!

// ✅ 正确做法:同步发送 + 重试
@Service
public class ReliableProducer {
    
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;
    
    public void sendMessage(String topic, String message) {
        // 方式1:同步发送
        try {
            SendResult<String, String> result = kafkaTemplate.send(
                topic,
                message
            ).get(3, TimeUnit.SECONDS);  // 等待确认
            
            log.info("发送成功: offset={}", result.getRecordMetadata().offset());
        } catch (Exception e) {
            log.error("发送失败,进行重试", e);
            
            // 重试3次
            for (int i = 0; i < 3; i++) {
                try {
                    kafkaTemplate.send(topic, message).get(3, TimeUnit.SECONDS);
                    log.info("重试成功");
                    return;
                } catch (Exception retryEx) {
                    log.error("第{}次重试失败", i + 1);
                }
            }
            
            // 重试失败,记录到数据库
            saveFailedMessage(topic, message);
        }
    }
    
    /**
     * 方式2:异步发送 + 回调
     */
    public void sendMessageAsync(String topic, String message) {
        kafkaTemplate.send(topic, message).addCallback(
            result -> {
                log.info("发送成功: offset={}", 
                    result.getRecordMetadata().offset());
            },
            ex -> {
                log.error("发送失败", ex);
                // 重试或记录失败消息
                retryOrSave(topic, message);
            }
        );
    }
}

配置Kafka生产者确认机制

yaml 复制代码
spring:
  kafka:
    producer:
      acks: all  # 等待所有副本确认
      retries: 3  # 自动重试3次
      batch-size: 16384  # 批量大小
      buffer-memory: 33554432  # 缓冲区大小

acks参数说明

ini 复制代码
acks=0:  不等待确认(可能丢失)❌
acks=1:  等待Leader确认(Leader挂了可能丢失)⚠️
acks=all: 等待所有副本确认(最可靠)✅

丢失点2:MQ服务器宕机

持久化配置

java 复制代码
// Kafka配置
@Configuration
public class KafkaConfig {
    
    @Bean
    public KafkaAdmin admin() {
        Map<String, Object> configs = new HashMap<>();
        configs.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        return new KafkaAdmin(configs);
    }
    
    @Bean
    public NewTopic orderTopic() {
        return TopicBuilder.name("order-topic")
            .partitions(3)
            .replicas(3)  // 3个副本
            .config(TopicConfig.MIN_IN_SYNC_REPLICAS_CONFIG, "2")  // 最少2个副本确认
            .build();
    }
}

配置说明

ini 复制代码
replicas=3:  数据有3份副本
min.insync.replicas=2:  至少2份副本确认才算成功

即使1台服务器挂了,数据也不会丢!✅

RocketMQ刷盘策略

java 复制代码
// 同步刷盘(安全)
flushDiskType=SYNC_FLUSH

// 异步刷盘(性能)
flushDiskType=ASYNC_FLUSH

生产环境建议:
- 重要消息:SYNC_FLUSH ✅
- 普通消息:ASYNC_FLUSH,配合主从同步

丢失点3:消费者处理失败

java 复制代码
@Service
public class ReliableConsumer {
    
    @Autowired
    private OrderService orderService;
    
    /**
     * ❌ 错误做法:先提交offset,再处理
     */
    @KafkaListener(topics = "order-topic")
    public void consumeWrong(ConsumerRecord<String, String> record) {
        // Kafka自动提交了offset
        
        // 处理消息
        orderService.process(record.value());
        // 如果这里抛异常,消息丢失了!
    }
    
    /**
     * ✅ 正确做法:先处理,再手动提交
     */
    @KafkaListener(
        topics = "order-topic",
        containerFactory = "manualKafkaListenerContainerFactory"
    )
    public void consumeRight(ConsumerRecord<String, String> record,
                             Acknowledgment ack) {
        try {
            // 先处理消息
            orderService.process(record.value());
            
            // 处理成功,手动提交offset
            ack.acknowledge();
            
        } catch (Exception e) {
            log.error("处理失败,消息将重新消费", e);
            // 不提交offset,消息会重新消费
        }
    }
}

/**
 * 配置手动提交
 */
@Configuration
public class KafkaConsumerConfig {
    
    @Bean
    public ConcurrentKafkaListenerContainerFactory<String, String> 
            manualKafkaListenerContainerFactory() {
        
        ConcurrentKafkaListenerContainerFactory<String, String> factory 
            = new ConcurrentKafkaListenerContainerFactory<>();
        
        factory.setConsumerFactory(consumerFactory());
        factory.getContainerProperties()
            .setAckMode(AckMode.MANUAL);  // 手动提交
        
        return factory;
    }
}

端到端可靠性保证

ini 复制代码
┌──────────────────────────────────────────────┐
│           端到端可靠性保证                    │
├──────────────────────────────────────────────┤
│                                              │
│  ① 生产者                                    │
│     └─ acks=all + 重试 + 失败记录            │
│                                              │
│  ② MQ服务器                                  │
│     └─ 多副本 + 持久化 + 主从同步            │
│                                              │
│  ③ 消费者                                    │
│     └─ 先处理后提交 + 失败重试               │
│                                              │
└──────────────────────────────────────────────┘

三个环节都做好,消息才真正可靠!

3️⃣ 幂等性:处理一次就够了!

为什么会重复消费?

sql 复制代码
原因1:网络抖动
消费者处理完了,但ack确认丢失了
MQ以为没处理,再次投递

原因2:消费者宕机重启
offset还没提交,重启后从上次的offset继续消费

原因3:Rebalance
消费者组重新分配分区,可能重复消费

解决方案1:唯一ID去重

java 复制代码
@Service
public class IdempotentConsumer {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    @KafkaListener(topics = "order-topic")
    public void consume(ConsumerRecord<String, String> record) {
        // 1. 提取消息唯一ID
        String messageId = extractMessageId(record.value());
        String redisKey = "msg:processed:" + messageId;
        
        // 2. 检查是否已处理
        Boolean isProcessed = redisTemplate.opsForValue()
            .setIfAbsent(redisKey, "1", 7, TimeUnit.DAYS);
        
        if (isProcessed != null && isProcessed) {
            // 首次处理
            try {
                processMessage(record.value());
                log.info("消息处理成功: {}", messageId);
            } catch (Exception e) {
                // 处理失败,删除标记,允许重试
                redisTemplate.delete(redisKey);
                throw e;
            }
        } else {
            // 重复消息,跳过
            log.warn("重复消息,跳过: {}", messageId);
        }
    }
}

解决方案2:数据库唯一索引

sql 复制代码
-- 创建消息处理记录表
CREATE TABLE message_record (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    message_id VARCHAR(64) UNIQUE NOT NULL,  -- 消息唯一ID
    topic VARCHAR(100),
    content TEXT,
    processed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_message_id (message_id)
);
java 复制代码
@Service
public class DatabaseIdempotentConsumer {
    
    @Autowired
    private MessageRecordMapper messageRecordMapper;
    
    @Autowired
    private OrderService orderService;
    
    @Transactional
    @KafkaListener(topics = "order-topic")
    public void consume(String message) {
        String messageId = extractMessageId(message);
        
        try {
            // 1. 插入消息记录(利用唯一索引)
            MessageRecord record = new MessageRecord();
            record.setMessageId(messageId);
            record.setTopic("order-topic");
            record.setContent(message);
            messageRecordMapper.insert(record);
            
            // 2. 处理业务
            orderService.process(message);
            
            log.info("消息处理成功: {}", messageId);
            
        } catch (DuplicateKeyException e) {
            // 唯一索引冲突,说明消息已处理
            log.warn("重复消息,跳过: {}", messageId);
        }
    }
}

解决方案3:业务幂等性设计

java 复制代码
/**
 * 订单创建的幂等性设计
 */
@Service
public class OrderServiceImpl implements OrderService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    @Transactional
    public Order createOrder(CreateOrderRequest request) {
        // 1. 检查订单是否已存在(用外部流水号作为唯一标识)
        Order existingOrder = orderMapper.selectByOutTradeNo(
            request.getOutTradeNo()
        );
        
        if (existingOrder != null) {
            log.warn("订单已存在: {}", request.getOutTradeNo());
            return existingOrder;  // 返回已有订单
        }
        
        // 2. 创建订单
        Order order = new Order();
        order.setOutTradeNo(request.getOutTradeNo());  // 外部流水号(唯一)
        order.setUserId(request.getUserId());
        order.setAmount(request.getAmount());
        order.setStatus(OrderStatus.CREATED);
        
        orderMapper.insert(order);
        
        return order;
    }
}

数据库设计

sql 复制代码
CREATE TABLE `order` (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    out_trade_no VARCHAR(64) UNIQUE NOT NULL,  -- 外部流水号,唯一索引
    user_id BIGINT NOT NULL,
    amount DECIMAL(10, 2),
    status VARCHAR(20),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_out_trade_no (out_trade_no)  -- 唯一索引保证幂等
);

解决方案4:状态机保证幂等

java 复制代码
/**
 * 使用状态机保证操作幂等
 */
@Service
public class OrderStateMachine {
    
    @Autowired
    private OrderMapper orderMapper;
    
    /**
     * 支付订单(幂等)
     */
    @Transactional
    public boolean payOrder(Long orderId) {
        // 使用乐观锁更新状态
        int updated = orderMapper.updateStatus(
            orderId,
            OrderStatus.PAID,           // 新状态
            OrderStatus.CREATED          // 当前状态必须是CREATED
        );
        
        if (updated > 0) {
            log.info("订单支付成功: {}", orderId);
            return true;
        } else {
            // 更新失败,可能已经支付过了
            Order order = orderMapper.selectById(orderId);
            if (order.getStatus() == OrderStatus.PAID) {
                log.warn("订单已支付: {}", orderId);
                return true;  // 已支付,返回成功
            } else {
                log.error("订单状态异常: {}", order.getStatus());
                return false;
            }
        }
    }
}

SQL

sql 复制代码
-- 状态机更新
UPDATE `order`
SET status = #{newStatus},
    updated_at = NOW()
WHERE id = #{orderId}
  AND status = #{currentStatus}  -- 只有当前状态符合才更新

幂等性方案对比

方案 优点 缺点 适用场景
Redis去重 性能高 依赖Redis 高并发场景 ⭐
数据库唯一索引 可靠 性能稍低 金融交易 ⭐⭐
业务幂等设计 自然 需要设计 所有场景 ⭐⭐⭐
状态机 严谨 复杂 流程类业务 ⭐⭐

🎯 综合案例:订单系统

完整的可靠消息处理

java 复制代码
/**
 * 订单消息生产者(保证可靠性)
 */
@Service
public class OrderMessageProducer {
    
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;
    
    @Autowired
    private MessageLogMapper messageLogMapper;
    
    @Transactional
    public void sendOrderMessage(Order order) {
        // 1. 先记录消息日志(与业务在同一事务)
        MessageLog log = new MessageLog();
        log.setMessageId(UUID.randomUUID().toString());
        log.setTopic("order-topic");
        log.setContent(JSON.toJSONString(order));
        log.setStatus(MessageStatus.PENDING);
        messageLogMapper.insert(log);
        
        // 2. 异步发送消息
        CompletableFuture.runAsync(() -> {
            try {
                kafkaTemplate.send(
                    "order-topic",
                    order.getId().toString(),  // key
                    log.getContent()
                ).get(3, TimeUnit.SECONDS);
                
                // 3. 发送成功,更新状态
                log.setStatus(MessageStatus.SENT);
                messageLogMapper.updateById(log);
                
            } catch (Exception e) {
                log.setStatus(MessageStatus.FAILED);
                log.setErrorMsg(e.getMessage());
                messageLogMapper.updateById(log);
            }
        });
    }
    
    /**
     * 定时任务:重试失败的消息
     */
    @Scheduled(fixedDelay = 60000)  // 每分钟执行
    public void retryFailedMessages() {
        List<MessageLog> failedMessages = messageLogMapper.selectFailed();
        
        for (MessageLog log : failedMessages) {
            try {
                kafkaTemplate.send(log.getTopic(), log.getContent())
                    .get(3, TimeUnit.SECONDS);
                
                log.setStatus(MessageStatus.SENT);
                messageLogMapper.updateById(log);
                
            } catch (Exception e) {
                log.setRetryCount(log.getRetryCount() + 1);
                
                if (log.getRetryCount() >= 3) {
                    // 重试3次失败,标记为死信
                    log.setStatus(MessageStatus.DEAD);
                }
                
                messageLogMapper.updateById(log);
            }
        }
    }
}

/**
 * 订单消息消费者(保证顺序性和幂等性)
 */
@Service
public class OrderMessageConsumer {
    
    @Autowired
    private OrderService orderService;
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    // 为每个订单维护一个队列(保证顺序)
    private ConcurrentHashMap<String, BlockingQueue<OrderMessage>> orderQueues 
        = new ConcurrentHashMap<>();
    
    private ExecutorService executor = Executors.newFixedThreadPool(10);
    
    @KafkaListener(
        topics = "order-topic",
        concurrency = "3"
    )
    public void consume(ConsumerRecord<String, String> record,
                       Acknowledgment ack) {
        
        String orderId = record.key();
        String content = record.value();
        OrderMessage message = JSON.parseObject(content, OrderMessage.class);
        
        // 获取订单对应的队列
        BlockingQueue<OrderMessage> queue = orderQueues.computeIfAbsent(
            orderId,
            k -> {
                LinkedBlockingQueue<OrderMessage> q = new LinkedBlockingQueue<>();
                executor.submit(() -> processQueue(orderId, q));
                return q;
            }
        );
        
        // 放入队列
        queue.offer(message);
        
        // 手动提交offset
        ack.acknowledge();
    }
    
    private void processQueue(String orderId, BlockingQueue<OrderMessage> queue) {
        while (true) {
            try {
                OrderMessage message = queue.take();
                
                // 幂等性检查
                String messageId = message.getMessageId();
                String redisKey = "msg:processed:" + messageId;
                
                Boolean isFirst = redisTemplate.opsForValue()
                    .setIfAbsent(redisKey, "1", 7, TimeUnit.DAYS);
                
                if (isFirst != null && isFirst) {
                    // 首次处理
                    try {
                        orderService.processOrderMessage(message);
                        log.info("订单消息处理成功: orderId={}, messageId={}", 
                            orderId, messageId);
                    } catch (Exception e) {
                        // 处理失败,删除标记
                        redisTemplate.delete(redisKey);
                        log.error("订单消息处理失败: {}", messageId, e);
                    }
                } else {
                    log.warn("重复消息,跳过: {}", messageId);
                }
                
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
}

💡 最佳实践总结

1. 顺序性保证

DO

  • 使用Key路由,保证同一业务的消息到同一分区
  • 消费端使用内存队列+单线程处理
  • 区分全局顺序和局部顺序需求

DON'T

  • 不要在需要顺序的场景使用多线程消费
  • 不要在不需要顺序的场景牺牲性能

2. 可靠性保证

DO

  • 生产端:acks=all + 重试 + 失败记录
  • MQ端:多副本 + 持久化
  • 消费端:先处理后提交 + 失败重试

DON'T

  • 不要使用fire-and-forget发送
  • 不要自动提交offset
  • 不要忽略失败的消息

3. 幂等性保证

DO

  • 使用全局唯一ID
  • 利用数据库唯一索引
  • 业务设计天然幂等
  • 使用状态机

DON'T

  • 不要假设消息只会消费一次
  • 不要在消费逻辑中产生副作用

🎯 面试高频问题

Q1:如何保证消息不丢失?

A:三个环节都要保证:

  1. 生产者
java 复制代码
// acks=all + 同步发送
SendResult result = producer.send(message).get();
  1. Broker
ini 复制代码
replicas=3
min.insync.replicas=2
  1. 消费者
java 复制代码
// 先处理后提交
process(message);
ack.acknowledge();

Q2:如何保证消息顺序?

A

Kafka

markdown 复制代码
1. 相同key发到同一分区
2. 单线程消费

RocketMQ

markdown 复制代码
1. MessageQueue Selector
2. 顺序消费模式

Q3:如何保证幂等性?

A:四种方案:

  1. 唯一ID + Redis:性能最好
  2. 数据库唯一索引:最可靠
  3. 业务幂等设计:最优雅
  4. 状态机:最严谨

🎉 总结

核心要点 ✨

  1. 顺序性

    • Key路由 + 单分区
    • 内存队列 + 单线程处理
  2. 可靠性

    • 生产端重试
    • MQ多副本
    • 消费端先处理后提交
  3. 幂等性

    • 唯一ID去重
    • 数据库唯一索引
    • 业务幂等设计

记忆口诀 📝

vbnet 复制代码
消息队列三大难,
顺序可靠加幂等。

顺序保证用分区,
相同Key同一区。
单线程来消费它,
队列管理顺序佳。

可靠保证三环节,
发送确认加重试。
多副本来持久化,
先处理后提交它。

幂等设计有四法,
Redis去重数据库。
业务设计天然好,
状态机来最严谨!

📚 参考资料

  1. Kafka官方文档 - Reliability
  2. RocketMQ官方文档 - 顺序消息
  3. 分布式消息队列的设计与实现

最后送你一句话

"消息队列不是万能的,但没有可靠的消息队列是万万不能的。"

愿你的消息系统顺序井然、可靠无虞、幂等自如! 📬✨


表情包时间 🎭

erlang 复制代码
消息乱序时:
😱 先发货再下单?这什么鬼!

消息丢失时:
💔 钱扣了,订单没了...

消息重复时:
😤 为什么扣了我三次钱!

都保证了:
😊 秩序井然,稳如老狗!
相关推荐
调试人生的显微镜5 小时前
Wireshark抓包教程:JSON和HTTPS抓取
后端
回家路上绕了弯5 小时前
亿级别黑名单与短链接:该选什么数据结构?从需求到落地的技术选型指南
后端
间彧6 小时前
Java CompletableFuture详解与应用实战
后端
seanmeng20226 小时前
在EKS上部署ray serve框架
后端
Java水解6 小时前
Go基础:Go语言中 Goroutine 和 Channel 的声明与使用
java·后端·面试
用户41429296072396 小时前
一文读懂 API:连接数字世界的 “隐形桥梁”
后端
PFinal社区_南丞6 小时前
别再盲接 OTel:Go 可观察性接入的 8 个大坑
后端