從頭開始搭建一個Spring boot+RabbitMQ環境


消息隊列在目前分布式系統下具備非常重要的地位,如下的場景是比較適合消息隊列的:

  • 跨系統的調用,異步性質的調用最佳。
  • 高並發問題,利用隊列串行特點。
  • 訂閱模式,數據被未知數量的消費者訂閱,比如某種數據的變更會影響多個系統的數據,訂單數據就是比較好理解的。

之前有一個場景是商品數據在修改后需要推送到elasticsearch中,由於修改產品的並發量以及數據量均不大,所以對於消息未做持久化,而且為了快速上線簡化系統,生產者與消費者更是部署在一個應用中,自生產自消費。這篇將從頭搭建RabbitMQ環境,並且將之集成在Spring boot中。

搭建RabbitMQ環境

erlang

由於RabbitMQ是基於erlang開發的,所以要安裝RabbitMQ先必須安裝erlang。

更換軟件源

使用apt-get時默認的軟件源是us.archive.ubuntu.com,這會經常發生安裝問題,比如速度特別慢或者由於下載不了造成不能安裝。

可以更換成國內的數據源cn.archive.ubuntu.com,速度那是不用說的了(這里感謝我的同事的提醒)。找到下面這個文件然后進行替換。

/etc/apt/sources.list

:%s/us.archive/cn.archive/g 

在沒有更新軟件源時,我采取的是源碼編譯安裝方法,參考這篇文章。我安裝的是最新19.2版本,安裝過程中還遇到各種問題就不一一記錄了。

http://erlang.org/doc/installation_guide/INSTALL.html

測試erlang安裝是否正確,輸入erl,如果看到如下圖所示就說明安裝成功了。

安裝RabbitMQ

在未更換軟件源之前我也是選擇了源碼編譯安裝方法,安裝的最新的3.6.6,但手動啟動時總是不成功,錯誤信息如下:

版本問題

RabbitMQ 3.6.6+ erlang 19.2 啟動失敗的問題暫時未解決,有誰知道的可以告訴我。

由於啟動不成功,最后在更新成國內軟件源之后,再次通過 apt-get 安裝RabbitMQ,默認的版本是3.5.7,好像也可以選版本,以后再嘗試。可喜的是通過apt-get安裝的RabbitMQ成功的啟動起來了。可以通過如下命令查看RabbitMQ狀態。

./rabbitmqctl stauts
RabbitMQ管理工具

這是自帶的一個web插件,可以用來管理消息隊列,啟動它的方法比較簡單:

rabbitmq-plugins enable rabbitmq_management

然后重啟RabbitMQ即可生效。默認生成了guest用戶,但這個guest用戶只能在RabbitMQ所在主機才能訪問,所以要想遠程訪問就需要重新分配一個用戶,有兩種辦法:

  • 通過網頁,以guest登錄然后在頁面上完成操作。
  • 通過命令,創建用戶,授權也可以。

創建用戶,指定用戶名以及密碼

./rabbitmqctl add_user root root //用戶名密碼都是root

分配角色,administrator是可以操作和guest本地用戶一樣的功能,當登錄上rabbitmq_management之后,里面的所有功能都可以使用。

rabbitmqctl set_user_tags root administrator

授權,隊列的操作管理權限。如果不配置,那么客戶端在連接消息隊列時會出問題。

rabbitmqctl set_permissions -p / root ".*" ".*" ".*"

上面語句我沒有執行成功,后續再研究下是不是寫法問題

Spring boot集成RabbitMQ

我們在rabbitmq_management上面可以正常訪問操作后,就可以放心的寫demo了,這里采用spring boot。先看簡單看下RabbitMQ的簡易架構圖,容易理解下面提到的一些組件。

    • 生產者,消息,消費者

 

    • 消息內部:Exchange,Binding,Queues

引用amqp的starter

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

增加配置信息

這里沒有采用自動配置

mq.rabbit.host=192.168.21.128
mq.rabbit.port=5672
mq.rabbit.virtualHost=/
mq.rabbit.username=root
mq.rabbit.password=root

創建RabbitMQConfig

  • ConnectionFactory,類似於數據庫連接等。
@Bean
public ConnectionFactory connectionFactory() {
    CachingConnectionFactory connectionFactory = new CachingConnectionFactory(this.mqRabbitHost,this.mqRabbitPort);

    connectionFactory.setUsername(this.mqRabbitUserName);
    connectionFactory.setPassword(this.mqRabbitPassword);
    connectionFactory.setVirtualHost(this.mqRabbitVirtualHost);
    connectionFactory.setPublisherConfirms(true);

    return connectionFactory;
}
  • RabbitTemplate,用來發送消息。
@Bean
public RabbitTemplate rabbitTemplate() {
    RabbitTemplate template = new RabbitTemplate(connectionFactory());
    return template;
}
  • DirectExchange
@Bean
public DirectExchange defaultExchange() {
    return new DirectExchange(EXCHANGE_NAME);
}
  • Queue,構建隊列,名稱,是否持久化之類
@Bean
public Queue queue() {
    return new Queue(QUEUE_NAME, true);
}
  • Binding,將DirectExchange與Queue進行綁定
@Bean
public Binding binding() {
    return BindingBuilder.bind(queue()).to(defaultExchange()).with(ROUTING_KEY);
}
  • SimpleMessageListenerContainer,消費者

需要將ACK修改為手動確認,避免消息在處理過程中發生異常造成被誤認為已經成功消費的假象。

@Bean
public SimpleMessageListenerContainer messageContainer() {
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory());
    container.setQueues(queue());
    container.setExposeListenerChannel(true);
    container.setMaxConcurrentConsumers(1);
    container.setConcurrentConsumers(1);
    container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
    container.setMessageListener(new ChannelAwareMessageListener() {

        public void onMessage(Message message, com.rabbitmq.client.Channel channel) throws Exception {
            byte[] body = message.getBody();
            logger.info("消費端接收到消息 : " + new String(body));
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }
    });
    return container;
}

服務端,業務邏輯,調用消息隊列。

為了讓客戶端知道消息是否已經成功,消息隊列提供了回調機制(需要實現ConfirmCallback),當消息服務器接收到消息之后會給客戶端一個通知,此時客戶端根據消息應答來決定后續的流程。

@Service
public class ProductServiceImpl extends BaseService implements ProductService, RabbitTemplate.ConfirmCallback {

    @Autowired
    private ProductMapper productMapper;

    private RabbitTemplate rabbitTemplate;

    public ProductServiceImpl(RabbitTemplate rabbitTemplate){
        this.rabbitTemplate=rabbitTemplate;
        this.rabbitTemplate.setConfirmCallback(this);
    }

    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        this.logger.info(" 消息id:" + correlationData);
        if (ack) {
            this.logger.info("消息發送確認成功");
        } else {
            this.logger.info("消息發送確認失敗:" + cause);

        }
    }

    @Override
    public void save(Product product) {

        //執行保存
        String uuid = UUID.randomUUID().toString();
        CorrelationData correlationId = new CorrelationData(uuid);
        rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, RabbitMQConfig.ROUTING_KEY, product.getName(),correlationId);
    }
}

執行結果

可以清晰的看到RabbitMQ發給生產者的信息收到的確認信息,也能看到消息被消費端消費后的信息。

RabbitMQ的其它方面

高可用方案

與常見的數據庫類似,都是主從模式來保證高可用,可以利用HAProxy來實現主從備份方案。

水平擴展方案

主要是為了解決垂直優化的瓶頸問題,主要有這三種:

  • clustering,這是默認內置的一種集群模式,與下面兩種不同的是clustering一般應用於同一局域網。
  • federation,有待后續學習
  • shovel,有待后續學習

不丟消息特性

這個不是RabbitMQ的專利,將消息持久化可以確保RabbitMQ重啟或者死機過程中不至於丟掉沒有消費的消息。

消息不被重復消費

這點要靠消費端來完成,盡管消費端可以通過ACK來通知消息隊列消息已經被消費,但如果消費端消費了消息,此時ACK過程中的通知出現異常,消息隊列會認為消息未被消費會繼續發給消費端。

總結

初次安裝可能會出現一堆問題,特別是需要安裝所依賴的眾多包。RabbitMQ與Erlang可能存在版本依賴問題待后續確認。spring boot下集成RabbitMQ異常簡單,可以根據需求部署集群來實現可擴展高可用的消息系統。

引用


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM