1)、RabbitMQ學習
一、簡歷:
RabbitMQ是實現了高級消息隊列協議(AMQP)的開源消息代理軟件(亦稱面向消息的中間件)。RabbitMQ服務器是用Erlang語言編寫的,而羣集和故障轉移是構建在開放電信平臺框架上的。所有主要的編程語言均有與代理接口通訊的客戶端庫。
二、何謂隊列?
隊列是一個存儲、組織數據的數據結構,其最大的特性就是FIFO(先進先出),rabbitmq中queue是Rabbitmq的內部對象,用於存儲消息。
三、何爲消息隊列?(MQ)
- 服務之間最常見的通信方式是直接調用彼此來通信,消息從一端發出後立即就可以到達另一端,稱爲即時消息通訊(同步通信);
- 消息從某一端發出後,首先進入一個容器進行臨時存儲,當達到某種條件後,再由這個容器發送給另一端,稱爲延遲消息通訊(異步通信);
當然,容器的一個具體實現就是MQ;
四、消息隊列的四大好處:
**1、解耦;**每個成員不必受其他成員的影響,可以更獨立自主的做自己的事情,只通過一個簡單的容器來聯繫;
**2、提速;**在對於不使用消息隊列的時候,我們來想象一個場景:比如下訂單,下訂單後的操作有:減庫存、減金額、發送短信等等微服務操作,如果這些都靠server來發送,減庫存100ms、減金額100ms、發送短信100ms,那麼總體花費就是100+100+100=300ms,如果你使用多線程的方式也是接近200ms。如果使用消息隊列,那麼我們就不用管它多久發送達到,只需要插入到隊列中,讓他自己去執行就可以了,主程序只需要50ms即可。
**3、廣播;**有利於一次性給多個微服務發送消息message。
**4、削峯;**比如在秒殺的時候,同時1w個請求來請求服務器,如果全部打在服務器和數據庫上,服務器基本上是馬上GG,這時候我們就可以引入消息隊列,設置消息隊列的大小,比如秒殺的件只有100件,那麼設置消息隊列的大小爲100,其他沒有入隊列的全部返回未秒殺成功,這樣就成功的削峯完畢。
五、何爲AMQP?
1、介紹:
一個提供統一消息服務的應用層標準高級消息隊列協議,是一個通用的應用層協議。消息發送與接收的雙發遵守這個協議就可以實現異步通訊。
2、原理:
- Broker:接收和分發消息的應用,RabbitMQ Server就是Message Broker。
- Virtual host:出於多租戶和安全設計的,把AMQP的基本組件劃分到一個虛擬的分組中,類似於網絡中的namespace概念。當多個不同的用戶使用同一個RabbitMQ Server提供服務的時候,就可以劃分出多個vhost,來對每一個用戶創建自己的exchange/Queue。
- Connection:publisher/consumer和broker之間的TCP連接。斷開連接的操作只會在client端進行,Broker不會斷開連接,除非出現網絡故障或者broker服務出現問題。並且在一個TCP連接中也可以開通多個信道,進行流量均衡輸送。
- Channel: 如果每一次訪問RabbitMQ都建立一個connection,那麼在消息量較大的時候建立TCP connection的開銷將會是巨大的,並且效率也是極低的。Channel是在Connection內部建立的邏輯連接,如果應用程序支持多線程,通常每個thread創建單獨的channel進行通訊,AMQP method 包含了channel id幫助客戶端和message broker識別channel,所以channel之間是完全隔離的。Channel作爲輕量級的Connection極大減少了操作系統建立TCP connection的開銷。
- Queue:消息最終被送到這裏等待consumer取走,一個message可以被同時拷貝到多個queue中。
- Binding:exchange和queue之間的虛擬連接,binding中可以包含routing key。Binding信息被保存到exchange中的查詢表中,用於message的分發依據。類似於計算機網絡中的路由器機制。
3、典型的“生成/消費”消息模型:
-
生產者發送消息到broker server(RabbitMQ)。在Broker內部,用戶創建Exchange和Queue,然後通過binding機制將兩者聯繫起來。Exchange分發消息,根據類型 / binding的不同分發策略選擇正確的queue隊列。
-
消費者Consumer從queue中拿取消息。
4、Exchange類型
Exchange類型有:Direct、Fanout、Topic三種類型。
-
Direct類型:點對點
-
Fanout類型:廣播
-
Topic類型:通配符匹配規則
#:代表0個或多個單詞、字符
*:代表1個或多個單詞、字符
2)、SpringBoot集成RabbitMQ:
1、打開上一次的mybatis-springboot環境:
2、添加pow.xml依賴:
<!--ampq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>
3、配置application.properties:
# 配置rabbitmq
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.addresses=47.97.192.241
4、在Test中進行測試RabbitMQ:
rabbitmq在springboot中也存在自動配置類,自動配置類可以讀取配置文件中的配置進行自動配置。
而且我們可以看到 這個配置類 其實是一個懶加載機制:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
該配置判斷:在容器中是否存在RabbitTemplate模版,如果存在才進行自動配置,如果沒有使用RabbitTemplate就不進行自動配置。
並且我們找到rabbitTemplate這個函數,可以看到配置過程,配置文件是作爲參數傳入的:properties;
@Bean
@ConditionalOnSingleCandidate(ConnectionFactory.class)
@ConditionalOnMissingBean(RabbitOperations.class)
public RabbitTemplate rabbitTemplate(RabbitProperties properties,
ObjectProvider<MessageConverter> messageConverter,
ObjectProvider<RabbitRetryTemplateCustomizer> retryTemplateCustomizers,
ConnectionFactory connectionFactory) {
PropertyMapper map = PropertyMapper.get();
RabbitTemplate template = new RabbitTemplate(connectionFactory);
messageConverter.ifUnique(template::setMessageConverter);
template.setMandatory(determineMandatoryFlag(properties));
RabbitProperties.Template templateProperties = properties.getTemplate();
if (templateProperties.getRetry().isEnabled()) {
template.setRetryTemplate(
new RetryTemplateFactory(retryTemplateCustomizers.orderedStream().collect(Collectors.toList()))
.createRetryTemplate(templateProperties.getRetry(),
RabbitRetryTemplateCustomizer.Target.SENDER));
}
map.from(templateProperties::getReceiveTimeout).whenNonNull().as(Duration::toMillis)
.to(template::setReceiveTimeout);
map.from(templateProperties::getReplyTimeout).whenNonNull().as(Duration::toMillis)
.to(template::setReplyTimeout);
map.from(templateProperties::getExchange).to(template::setExchange);
map.from(templateProperties::getRoutingKey).to(template::setRoutingKey);
map.from(templateProperties::getDefaultReceiveQueue).whenNonNull().to(template::setDefaultReceiveQueue);
return template;
}
我們這就來看一下properties這個配置文件類:
@ConfigurationProperties(prefix = "spring.rabbitmq")
public class RabbitProperties {
/**
* RabbitMQ host.
*/
private String host = "localhost";
/**
* RabbitMQ port.
*/
private int port = 5672;
/**
* Login user to authenticate to the broker.
*/
private String username = "guest";
/**
* Login to authenticate against the broker.
*/
private String password = "guest";
/**
* SSL configuration.
*/
private final Ssl ssl = new Ssl();
/**
* Virtual host to use when connecting to the broker.
*/
private String virtualHost;
/**
* Comma-separated list of addresses to which the client should connect.
*/
private String addresses;
我們從這個配置類可以得到:
如果我們沒有配置host的時候默認爲本機localhost;
如果我們沒有配置username和password都是guest以及我們可以配置的一些其他屬性:虛擬地址virtualHost,服務端地址addresses;
我們並且可以通過調試得到:通過自動配置後,已經生成好了exchange和routingKey爲空串的template對象:
配置完成後,再進入我們的test方法,設置exchange和routingKey還有message進行消息轉發。
@Autowired
RabbitTemplate rabbitTemplate;
@Test
public void test2() {
// 接收數據
Object o=rabbitTemplate.receiveAndConvert("ogj1");
assert o != null;
System.out.println(o.getClass());
System.out.println(o);
}
5、運行成功:
我們來看看在rabbitmq服務端中是否存在這個消息在queue中:
打開ip:15672,登陸:
我們發現,是序列化的形式,原因是rabbitmq自身使用的是jdk自帶的序列化機制,所以結果是一段不明代碼。
6、我們來用springboot 接收一下rabbitmq中的消息:
@Test
public void test2() {
// 接收數據
Object o=rabbitTemplate.receiveAndConvert("ogj1");
assert o != null;
System.out.println(o.getClass());
System.out.println(o);
}
運行結果:
成功接收到該queue中的消息。
問題:我們怎麼來自定義序列化? 當然是自定義config類啦!
我們 來看一下message的轉換類:
public interface MessageConverter {
Message toMessage(Object var1, MessageProperties var2) throws MessageConversionException;
default Message toMessage(Object object, MessageProperties messageProperties, @Nullable Type genericType) throws MessageConversionException {
return this.toMessage(object, messageProperties);
}
Object fromMessage(Message var1) throws MessageConversionException;
}
rabbitmq默認使用的是jdk序列模式,我們可以從MessageConvert中選擇自定義爲JSON。
我們找到這個Json實現類,那麼我們下一步就是來自定義Config:
@Configuration
public class MyAmqConfig {
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
我們就可以看到這個是一個json序列化對象了。
3)、RabittMQ的監聽機制:
使用註解:EnableRabbit+RabbitListener
@EnableRabbit //開啓基於註解的rabbitmq
@SpringBootApplication
public class DemoMybatisApplication {
public static void main(String[] args) {
SpringApplication.run(DemoMybatisApplication.class, args);
}
}
/**
* 監聽rabbitmq隊列中的消息,只要這個隊列中有message進入,那麼就一定會收到這個消息
* @param user
*/
@RabbitListener(queues = "ogj1")
public void receiveMessage(User user){
System.out.println(user.toString());
}
發送消息到queue中:
@Autowired
RabbitTemplate rabbitTemplate;
@Test
public void test1() {
// 點對點的發送:direct
Map<String,Object> map=new HashMap<String, Object> ();
map.put("msg","這是一個rabbitmq message");
map.put("data", Arrays.asList("hello world",123,true));
User user = new User();
user.setUsername("歐光繼");
user.setPasswd("123456");
user.setAddress("重慶市");
user.setId("1");
user.setPhone("123456789");
user.setYoubian("402460");
// 對象被默認的jdk序列化形式發送出去
rabbitTemplate.convertAndSend("exchange.direct","ogj1", user);
System.out.println("發送成功!");
}
測試:
首先運行主程序。然後運行test單元測試程序,進行發送消息到queue。
結果:
並且只要隊列有消息,那麼就會馬上收到該消息並打印。
4)、AmqpAdmin對於RabbitMQ的管理:
我們也可以使用AmqpAdmin來在程序中對rabbitmq進行管理操作。
/**
* 使用amqpAdmin管理rabbitmq
*/
@Autowired
AmqpAdmin amqpAdmin;
@Test
public void createExchange() {
//創建一個Exchange
amqpAdmin.declareExchange(new DirectExchange("amqpadmin.exchange"));
}
@Test
public void createQueue() {
//創建一個隊列
amqpAdmin.declareQueue(new Queue("amqpadmin.quque",true));
}
@Test
public void binding() {
//創建綁定規則
amqpAdmin.declareBinding(new Binding("amqpadmin.queue",
Binding.DestinationType.QUEUE,"amqpadmin.exchange",
"amqp.haha",null));
}