面试题答案
一键面试1. 消息发送
- 消息生产者:
- 在用户下单成功后,由下单服务作为消息生产者,构建包含订单详细信息(如订单ID、商品ID、购买数量等)的消息。
- 使用消息队列客户端(如Kafka的Producer、RabbitMQ的Publisher等)将消息发送到指定的消息队列主题或队列中。例如,在Kafka中:
ProducerRecord<String, String> record = new ProducerRecord<>("order - processing - topic", orderInfo); producer.send(record);
- 消息格式:采用JSON格式,这样既易于解析,又具有较好的跨语言兼容性。例如:
{ "orderId": "123456", "productId": "789", "quantity": 2, "userId": "user123" }
2. 消息处理逻辑
- 消息消费者:
- 创建多个消费者组分别处理不同的业务逻辑,如库存扣减组和订单状态更新组。
- 以库存扣减消费者为例,使用消息队列客户端(如Kafka的Consumer、RabbitMQ的Consumer等)从队列中拉取消息。例如,在Kafka中:
Consumer<String, String> consumer = KafkaConsumerFactory.createConsumer(); consumer.subscribe(Arrays.asList("order - processing - topic")); while (true) { ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100)); for (ConsumerRecord<String, String> record : records) { // 解析消息 OrderInfo orderInfo = parseOrderInfo(record.value()); // 调用库存服务扣减库存 inventoryService.deductInventory(orderInfo.getProductId(), orderInfo.getQuantity()); } }
- 事务性处理:
- 对于库存扣减和订单状态更新等操作,要保证原子性。可以使用数据库的事务机制。例如,在库存扣减和订单状态更新的业务逻辑中:
@Transactional public void processOrder(OrderInfo orderInfo) { inventoryService.deductInventory(orderInfo.getProductId(), orderInfo.getQuantity()); orderService.updateOrderStatus(orderInfo.getOrderId(), OrderStatus.PROCESSED); }
3. Redis缓存更新策略
- 写后更新:
- 在订单处理完成后(库存扣减和订单状态更新成功),更新Redis缓存。例如,更新商品库存信息:
Jedis jedis = new Jedis("localhost"); Product product = productService.getProductById(orderInfo.getProductId()); jedis.set("product:" + orderInfo.getProductId(), JSON.toJSONString(product));
- 缓存过期策略:
- 对于商品信息等缓存,设置合理的过期时间。例如,热门商品设置较短的过期时间(如1小时),冷门商品设置较长的过期时间(如1天)。
jedis.setex("product:" + orderInfo.getProductId(), 3600, JSON.toJSONString(product));
- 缓存预热:
- 在系统启动时,将热门商品信息、常用订单概要等数据预先加载到Redis缓存中,减少首次查询的响应时间。
4. 异常处理
- 消息丢失:
- 消息确认机制:使用消息队列的确认机制。例如,在RabbitMQ中,生产者发送消息时设置mandatory标志,并且监听ReturnCallback,如果消息无法路由到队列,会回调通知生产者。消费者端在处理完消息后,手动确认消息已消费,如在RabbitMQ中:
channel.basicConsume("order - processing - queue", false, "myConsumerTag", (consumerTag, delivery) -> { try { // 处理消息 processMessage(delivery.getBody()); channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); } catch (Exception e) { channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, true); } }, consumerTag -> {});
- 持久化:将消息队列和消息设置为持久化。在Kafka中,通过设置相应的主题配置参数,保证消息在服务器故障后不丢失。
- 缓存雪崩:
- 分散过期时间:避免大量缓存同时过期。在设置缓存过期时间时,在基础过期时间上加上一个随机值。例如:
int baseExpireTime = 3600; int randomOffset = new Random().nextInt(1800); jedis.setex("product:" + orderInfo.getProductId(), baseExpireTime + randomOffset, JSON.toJSONString(product));
- 缓存降级:当Redis出现大规模故障时,启用本地缓存(如Guava Cache)或者直接查询数据库,同时记录日志,以便后续分析和恢复。例如:
LoadingCache<String, Product> localCache = CacheBuilder.newBuilder() .maximumSize(1000) .expireAfterWrite(10, TimeUnit.MINUTES) .build(new CacheLoader<String, Product>() { @Override public Product load(String key) throws Exception { return productService.getProductById(key); } }); Product product = localCache.get(orderInfo.getProductId());