Java for Web学习笔记(九五):消息和集群(10)利用RabbitMQ实现订阅和发布

目的

之前,我们学习了利用websocket进行订阅和发布的方式,连接数是N(N-1)/2,随着节点的增加,连接数迅速增加,只适合于小的集群。我们利用RabbitMQ重写一下事件广播。我们只需重写ClusterEventMulticaster,改为AMQPEventMulticaster。作为小例子演示,我们简单地选择fanout方式,由节点对收到的消息进行过滤。

AMQPEventMulticaster的代码

public class AMQPEventMulticaster extends SimpleApplicationEventMulticaster{
    private static final Logger log = LogManager.getLogger();
    private static final String EXCHANGE_NAME = "AMQPMessagingTest";
    private static final String HEADER = "X-Wei-Cluster-Node";

    private final BasicProperties.Builder builder =  new BasicProperties.Builder();
    private Connection amqpConnection;
    private Channel amqpChannel;
    private String queueName;

    @Override
    public void multicastEvent(ApplicationEvent event, ResolvableType eventType) {
        try{
            super.multicastEvent(event, eventType);
        }finally{
            try{
                if(event instanceof ClusterEvent && !((ClusterEvent)event).isRebroadcasted())
                    this.publishClusteredEvent((ClusterEvent)event);
            }catch(Exception e){
                log.error("Failed to broadcast distributable event to cluster.",e);
            }
        }
    }

    protected void publishClusteredEvent(ClusterEvent event) throws IOException{
        this.amqpChannel.basicPublish(EXCHANGE_NAME, "", this.builder.build(),
                SerializationUtils.serialize(event));
    }

    @PostConstruct
    public void setupRabbitConnection() throws IOException, TimeoutException{
        try{
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("191.8.1.107");
            factory.setUsername("test");            
            factory.setPassword("123456");
            this.amqpConnection = factory.newConnection();
            this.amqpChannel = this.amqpConnection.createChannel();            
            this.amqpChannel.exchangeDeclare(EXCHANGE_NAME, "fanout");

            this.queueName = this.amqpChannel.queueDeclare().getQueue();
            this.amqpChannel.queueBind(this.queueName, EXCHANGE_NAME, "");

            Map<String, Object> headers = new Hashtable<>();
            headers.put(HEADER, this.queueName);
            this.builder.headers(headers);            

            Consumer consumer = new DefaultConsumer(amqpChannel){

                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                        byte[] body) throws IOException {
                    Object header = properties.getHeaders().get(HEADER);
                    if(header == null || !header.toString().equals(queueName)){
                        ClusterEvent event = (ClusterEvent)SerializationUtils.deserialize(body);
                        event.setRebroadcasted();
                        multicastEvent(event,null);
                    }
                }                
            };
            this.amqpChannel.basicConsume(this.queueName, true, consumer);
        }catch(Exception e){
            log.error(e);
        }

    }

    @PreDestroy
    public void shutdownRabbitConnection() throws IOException, TimeoutException {
        this.amqpChannel.close();
        this.amqpConnection.close();
    }  
}

这和之前学习的利用websocket进行事件广播,和RabbitMQ的学习并没有太大的区别。唯一不同的是,注意到这次将临时生成的队列名字放在自定义的Header中,相关代码如下:

Map<String, Object> headers = new Hashtable<>();
headers.put(X-Wei-Cluster-Node, queueName);
BasicProperties.Builder builder =  new BasicProperties.Builder()
builder.headers(headers);
basicPublish(EXCHANGE_NAME, "", this.builder.build(), SerializationUtils.serialize(event));

使用消息队列需要小心

在多个节点之间进行消息的传递或者广播,使用同步还是异步,消息在不同节点之间接收存在时延是否对系统有系统,发送方是否需要知道消息已经正确被处理?这些都需要在设计的时候考虑。在《微服务设计》一书中,作者提到过一个教训。他们设计了一个消息队列,当超时不能完成时,消息会被重新放入队列中,将被重新处理。有一次,他们有某个消息,这个消息的会引发处理者worker崩溃,导致超时未能处理,消息重新放入队列,引发其他的worker崩溃,就这样,单点的故障被蔓延到整个系统。后来他们将这些处理有问题的(包括超时)的消息放入另一个队列中(作为消息医院)。类似我们自己在做项目的时候,会将告警发送到告警服务那里,先进行人工干预,看看有没有必要排入需求。使用消息队列,会涉及多个节点,多个相同的或者不同的模块,一般项目具有一定规模,因此需要小心和谨慎。


相关链接: 我的Professional Java for Web Applications相关文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值