Java 消息代理:企业集成的 5 项基本技术

大家好,这里是架构资源栈 !点击上方关注,添加"星标",一起学习大厂前沿架构!

Java 消息代理通过实现分布式系统之间的可靠通信路径,改变了企业集成。我广泛使用了这些技术,发现它们对于构建可有效扩展的弹性架构至关重要。

Java 企业集成中的消息代理

消息代理充当处理应用程序组件之间的消息验证、路由和传递的中介。它们构成了异步通信的基础,使系统可以在没有直接依赖的情况下进行交互。

核心优势包括服务解耦、提高容错能力以及在流量高峰期间更好地处理负载。根据我的经验,这种架构方法一直在提高系统可靠性。

Apache ActiveMQ

ActiveMQ 是 Java 中最成熟的消息代理之一,提供全面的 JMS 支持以及现代协议。它的成熟度为企业环境带来了稳定性。

我已经在多个生产系统中实现了 ActiveMQ,它的简单配置使得新接触面向消息的中间件的团队也可以使用它。

ini 复制代码
// Producer example with ActiveMQ
ConnectionFactory factory = new ActiveMQConnectionFactory("tcp://localhost:61616");
Connection connection = factory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("order.processing");
MessageProducer producer = session.createProducer(destination);
producer.setDeliveryMode(DeliveryMode.PERSISTENT);

TextMessage message = session.createTextMessage("New order: #1001");
message.setStringProperty("OrderType", "Standard");
producer.send(message);

// Clean up resources
producer.close();
session.close();
connection.close();

Enter fullscreen mode Exit fullscreen mode

对于消费者来说,实现消息接收需要类似的连接设置:

ini 复制代码
// Consumer example with ActiveMQ
ConnectionFactory factory = new ActiveMQConnectionFactory("tcp://localhost:61616");
Connection connection = factory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("order.processing");
MessageConsumer consumer = session.createConsumer(destination);

consumer.setMessageListener(message -> {
    if (message instanceof TextMessage) {
        try {
            TextMessage textMessage = (TextMessage) message;
            System.out.println("Received: " + textMessage.getText());
            System.out.println("Order type: " + textMessage.getStringProperty("OrderType"));
        } catch (JMSException e) {
            e.printStackTrace();
        }
    }
});

// Keep the consumer running (in a real application, you'd have a cleaner approach)
// When ready to shut down:
// consumer.close();
// session.close();
// connection.close();

Enter fullscreen mode Exit fullscreen mode

ActiveMQ 还支持集群以实现高可用性,我已经在生产中对其进行了配置,以确保即使在代理发生故障时也能传递消息。

RabbitMQ

RabbitMQ 在需要复杂路由模式的场景中表现出色。其高级消息队列协议 (AMQP) 的实现通过交换器和队列提供了复杂的消息路由。

我在微服务架构中部署了 RabbitMQ,它的路由功能对于将消息定向到适当的服务非常有用。

ini 复制代码
// RabbitMQ producer with exchange-based routing
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (Connection connection = factory.newConnection();
     Channel channel = connection.createChannel()) {

    // Declare exchange and queue
    String exchangeName = "orders.exchange";
    String queueName = "orders.processing";
    String routingKey = "order.created";

    channel.exchangeDeclare(exchangeName, "topic", true);
    channel.queueDeclare(queueName, true, false, false, null);
    channel.queueBind(queueName, exchangeName, routingKey);

    // Create and send message
    String message = "{"orderId":"12345","customer":"John Doe","amount":99.95}";
    channel.basicPublish(exchangeName, routingKey,
                         new AMQP.BasicProperties.Builder()
                             .contentType("application/json")
                             .deliveryMode(2) // persistent
                             .build(),
                         message.getBytes(StandardCharsets.UTF_8));
}

Enter fullscreen mode Exit fullscreen mode

RabbitMQ 消费者可以实现确认模式来确保可靠的处理:

java 复制代码
// RabbitMQ consumer with manual acknowledgment
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();

String queueName = "orders.processing";
channel.queueDeclare(queueName, true, false, false, null);
// Limit to processing one message at a time
channel.basicQos(1);

DeliverCallback deliverCallback = (consumerTag, delivery) -> {
    String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
    try {
        System.out.println("Processing message: " + message);
        // Process the message...

        // Acknowledge successful processing
        channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
    } catch (Exception e) {
        // Nack and requeue on failure
        channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, true);
    }
};

channel.basicConsume(queueName, false, deliverCallback, consumerTag -> {});

Enter fullscreen mode Exit fullscreen mode

RabbitMQ 的可靠性延伸至其管理界面,它提供有关队列性能和系统健康状况的宝贵见解。

阿帕奇·卡夫卡

Kafka 代表了消息传递系统的范式转变,专注于高吞吐量、持久、分布式事件流。它特别适合日志聚合、事件源和实时分析。

我在每天处理数百万事件的系统中实现了 Kafka,它的线性可扩展性至关重要。

ini 复制代码
// Kafka producer example
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("acks", "all");

Producer<String, String> producer = new KafkaProducer<>(props);

// Send messages with keys for consistent partitioning
for (int i = 1; i <= 5; i++) {
    String key = "user-" + i;
    String value = "{"userId":"" + key + "","action":"login","timestamp":" + System.currentTimeMillis() + "}";
    ProducerRecord<String, String> record = new ProducerRecord<>("user-events", key, value);

    producer.send(record, (metadata, exception) -> {
        if (exception == null) {
            System.out.printf("Message sent to partition %d with offset %d%n", 
                              metadata.partition(), metadata.offset());
        } else {
            System.err.println("Failed to send message: " + exception.getMessage());
        }
    });
}

producer.flush();
producer.close();

Enter fullscreen mode Exit fullscreen mode

Kafka的消费者模型采用消费者组进行并行处理:

csharp 复制代码
// Kafka consumer group example
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "user-analytics-group");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("auto.offset.reset", "earliest");
props.put("enable.auto.commit", "false");

KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Collections.singletonList("user-events"));

try {
    while (true) {
        ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
        for (ConsumerRecord<String, String> record : records) {
            System.out.printf("Received message: key = %s, value = %s, partition = %d, offset = %d%n",
                             record.key(), record.value(), record.partition(), record.offset());

            // Process the message...

            // Manually commit offsets
            Map<TopicPartition, OffsetAndMetadata> offsetMap = new HashMap<>();
            offsetMap.put(
                new TopicPartition(record.topic(), record.partition()),
                new OffsetAndMetadata(record.offset() + 1)
            );
            consumer.commitSync(offsetMap);
        }
    }
} finally {
    consumer.close();
}

Enter fullscreen mode Exit fullscreen mode

Kafka 的优势在于其保留功能以及通过 Kafka Streams 和 KSQL 实现的流处理集成。

Apache ActiveMQ Artemis

Artemis 代表了下一代 ActiveMQ,它建立在高性能核心上,具有更高的吞吐量和集群能力。

我的团队已将旧的 ActiveMQ 部署迁移到 Artemis,并在保持协议兼容性的同时体验到了显著的性能提升。

java 复制代码
// Artemis producer using JMS 2.0 API
try (ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("tcp://localhost:61616");
     JMSContext context = cf.createContext(JMSContext.AUTO_ACKNOWLEDGE)) {

    Queue queue = context.createQueue("orders.priority");
    JMSProducer producer = context.createProducer();

    // Set message properties
    producer.setProperty("priority", "high")
           .setProperty("region", "EMEA")
           .setPriority(8);

    // Send the message
    producer.send(queue, "Urgent order requiring immediate processing");
}

Enter fullscreen mode Exit fullscreen mode

Artemis 支持 JMS 2.0 简化消费者 API:

ini 复制代码
// Artemis consumer using JMS 2.0 API with message selector
try (ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("tcp://localhost:61616");
     JMSContext context = cf.createContext(JMSContext.AUTO_ACKNOWLEDGE)) {

    Queue queue = context.createQueue("orders.priority");
    String selector = "priority = 'high' AND region = 'EMEA'";

    // Create consumer with selector to filter messages
    JMSConsumer consumer = context.createConsumer(queue, selector);

    // Synchronous receiving with timeout
    String message = consumer.receiveBody(String.class, 5000);
    if (message != null) {
        System.out.println("Received priority message: " + message);
        // Process message...
    }

    // Or asynchronous with lambda (uncomment to use)
    /*
    consumer.setMessageListener(message -> {
        try {
            String body = message.getBody(String.class);
            System.out.println("Received priority message: " + body);
            // Process message...
        } catch (JMSException e) {
            e.printStackTrace();
        }
    });
    */
}

Enter fullscreen mode Exit fullscreen mode

Artemis 在集群和高可用性方面提供了显著的改进,我利用这一点来构建有弹性的消息传递系统。

Redis 发布/订阅

Redis Pub/Sub 提供了一种轻量级的消息传递解决方案,在需要超低延迟且消息持久性是可选的场景中表现出色。

我使用 Redis Pub/Sub 进行实时通知和系统仪表板,它的简单性和速度非常合适。

ini 复制代码
// Redis publisher using Jedis
Jedis jedis = new Jedis("localhost");
String channel = "user-notifications";
String message = "{"userId":"user123","message":"New friend request","timestamp":1623412800}";

// Publish message to channel
jedis.publish(channel, message);
jedis.close();

Enter fullscreen mode Exit fullscreen mode

订阅 Redis 频道需要专用连接:

typescript 复制代码
// Redis subscriber using Jedis
Jedis jedisSubscriber = new Jedis("localhost");
JedisPubSub pubSub = new JedisPubSub() {
    @Override
    public void onMessage(String channel, String message) {
        System.out.println("Channel: " + channel + " Message: " + message);
        // Process the message...

        // To stop listening (from another thread):
        // this.unsubscribe();
    }

    @Override
    public void onSubscribe(String channel, int subscribedChannels) {
        System.out.println("Subscribed to: " + channel);
    }
};

// Subscribe to channel(s) - this is a blocking call
jedisSubscriber.subscribe(pubSub, "user-notifications");

Enter fullscreen mode Exit fullscreen mode

虽然 Redis Pub/Sub 不提供消息持久性,但它可以与 Redis Streams 结合使用,以获得更持久的方法:

javascript 复制代码
// Redis Streams example (for persistence)
Jedis jedis = new Jedis("localhost");
String streamKey = "user-activity";
Map<String, String> fields = new HashMap<>();
fields.put("userId", "user123");
fields.put("action", "login");
fields.put("timestamp", String.valueOf(System.currentTimeMillis()));

// Add entry to stream
String entryId = jedis.xadd(streamKey, StreamEntryID.NEW_ENTRY, fields);
System.out.println("Added entry with ID: " + entryId);

// Read from stream
List<StreamEntry> entries = jedis.xread(
    XReadParams.xReadParams().count(10).block(2000),
    Map.of(streamKey, new StreamEntryID("0-0"))
);

for (StreamEntry entry : entries) {
    System.out.println("Entry ID: " + entry.getID());
    for (Map.Entry<String, String> field : entry.getFields().entrySet()) {
        System.out.println(field.getKey() + ": " + field.getValue());
    }
}

jedis.close();

Enter fullscreen mode Exit fullscreen mode

Redis 的简单性使其成为轻量级消息传递需求的绝佳选择,或作为更强大的消息代理的补充系统。

集成模式和最佳实践

在所有这些消息代理中,我发现某些集成模式始终很有价值:

消息幂等性确保了消息重新处理的安全,我通过添加唯一的消息 ID 和跟踪已处理的消息来实现这一点:

typescript 复制代码
// Simplified idempotent consumer example
public class IdempotentMessageProcessor {
    private final Set<String> processedMessageIds = ConcurrentHashMap.newKeySet();

    public void processMessage(String messageId, String content) {
        if (processedMessageIds.contains(messageId)) {
            System.out.println("Message " + messageId + " already processed, skipping");
            return;
        }

        try {
            // Process the message
            System.out.println("Processing message: " + content);

            // Mark as processed
            processedMessageIds.add(messageId);
        } catch (Exception e) {
            System.err.println("Failed to process message: " + e.getMessage());
            // Don't mark as processed to allow retry
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

死信队列(DLQ)可以妥善处理消息处理失败:

ini 复制代码
// Example of configuring a dead letter queue in ActiveMQ
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

// Create the original destination
Queue originalQueue = session.createQueue("orders.processing");

// Create the DLQ
Queue dlq = session.createQueue("ActiveMQ.DLQ.orders.processing");

// Configure a producer to use the original queue
MessageProducer producer = session.createProducer(originalQueue);

// Configure the message to use the DLQ if it can't be delivered
TextMessage message = session.createTextMessage("Order data that might fail processing");
message.setIntProperty("JMSXDeliveryCount", 0);
message.setStringProperty("JMS_AMQP_FirstAcquirer", "true");

// Send the message
producer.send(message);

Enter fullscreen mode Exit fullscreen mode

当下游服务出现故障时,断路器可防止系统过载:

ini 复制代码
// Simple circuit breaker pattern for messaging
public class MessagingCircuitBreaker {
    private enum State { CLOSED, OPEN, HALF_OPEN }

    private State state = State.CLOSED;
    private int failureCount = 0;
    private final int failureThreshold;
    private long openTimestamp;
    private final long resetTimeoutMs;

    public MessagingCircuitBreaker(int failureThreshold, long resetTimeoutMs) {
        this.failureThreshold = failureThreshold;
        this.resetTimeoutMs = resetTimeoutMs;
    }

    public synchronized boolean allowRequest() {
        if (state == State.CLOSED) {
            return true;
        } else if (state == State.OPEN) {
            // Check if timeout has elapsed to transition to half-open
            if (System.currentTimeMillis() - openTimestamp > resetTimeoutMs) {
                state = State.HALF_OPEN;
                return true;
            }
            return false;
        } else { // HALF_OPEN
            return true;
        }
    }

    public synchronized void recordSuccess() {
        if (state == State.HALF_OPEN) {
            state = State.CLOSED;
            failureCount = 0;
        }
    }

    public synchronized void recordFailure() {
        failureCount++;
        if (state == State.CLOSED && failureCount >= failureThreshold) {
            state = State.OPEN;
            openTimestamp = System.currentTimeMillis();
        } else if (state == State.HALF_OPEN) {
            state = State.OPEN;
            openTimestamp = System.currentTimeMillis();
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

结论

选择正确的消息代理取决于特定要求:

  • ActiveMQ 提供稳定性和丰富的功能集,适合传统企业集成。
  • RabbitMQ 擅长微服务架构所需的复杂消息路由模式。
  • Kafka 为数据密集型应用程序提供高吞吐量流处理能力。
  • Artemis 为 JMS 应用程序提供了下一代性能。
  • Redis Pub/Sub 提供轻量级消息传递,用于实时更新。

根据我在各个行业实施这些系统的经验,最成功的集成将适当的技术选择与可靠的架构模式相结合。消息代理不仅可以连接系统,还可以实现可随时间推移而发展的弹性、可扩展的架构。

消息代理的采用不断提高我所研究的分布式系统的可靠性和可维护性,与传统的单片方法相比,可以实现更细粒度的扩展和更好的故障隔离。


原文地址:mp.weixin.qq.com/s/9sQ0kQVPB... 本文由博客一文多发平台 OpenWrite 发布!

相关推荐
sugar__salt7 分钟前
多线程(1)——认识线程
java·开发语言
妙极矣27 分钟前
JAVAEE初阶01
java·学习·java-ee
碎叶城李白42 分钟前
NIO简单群聊
java·nio
xxjiaz1 小时前
水果成篮--LeetCode
java·算法·leetcode·职场和发展
CodeFox1 小时前
动态线程池 v1.2.1 版本发布,告警规则重构,bytebuddy 替换 cglib,新增 jmh 基准测试等!
java·后端
ℳ₯㎕ddzོꦿ࿐1 小时前
Java集成Zxing和OpenCV实现二维码生成与识别工具类
java·opencv
雪落山庄2 小时前
LeetCode100题
java·开发语言·数据结构
码熔burning2 小时前
【MQ篇】RabbitMQ之发布订阅模式!
java·分布式·rabbitmq·mq
XiaoLeisj2 小时前
【设计模式】深入解析代理模式(委托模式):代理模式思想、静态模式和动态模式定义与区别、静态代理模式代码实现
java·spring boot·后端·spring·设计模式·代理模式·委托模式
李少兄2 小时前
解决Spring Boot版本冲突导致的`NoSuchFieldError`
java·spring boot·后端