近來打算自己封裝一個比較方便讀寫的Office Excel 工具類,前面已經寫了一些,比較粗糙本就計劃重構一下,剛好公司的電商APP後臺原有的導出Excel實現出現了可怕的性能問題,600行的數據生成Excel工作簿居然需要50秒以上,客戶端連接都被熔斷了還沒導出來,挺巧,那就一起解決吧。
對於消息中間件RabbitMQ,想必各位小夥伴沒有用過、也該有聽過,它是一款目前市面上應用相當廣泛的消息中間件,可以實現消息異步通信、業務服務模塊解耦、接口限流、消息分發等功能,在微服務、分佈式系統架構中可以說是充當着一名了不起的角色!(詳細的介紹,Debug在這裏就不贅述了,各位小夥伴可以上官網看看其更多的介紹及其典型的應用場景)!
在本篇博文中,我們將使用RabbitMQ充當消息發送的組件,將它與後面篇章介紹的“郵件服務”結合實現“用戶秒殺成功後異步發送郵件通知消息,告知用戶秒殺已經成功!”,下面我們一起進入代碼實戰吧。
在上一個版本里呢,我認爲比較巧妙的地方在於用函數式編程的方式代替反射,很早以前瞭解了反射的一些底層後我就知道反射的性能很差,但一直沒實際測試過各種調用場景的性能差距。
至於底層字節碼、CPU指令這些我就不深究了,我還沒到那個級別,那這次就來個簡單的測試吧。
目標:創建Man對象。
方式:
① 直接引用 new Man();
② 使用反射
③ 使用內部類
④ 使用Lombda表達式
⑤ 使用Method Reference
在學習Java8新特性的時候,我所瞭解到的是Lombda表達式是內部類的一種簡化書寫方式,也就是語法糖,但兩者間在運行時居然有比較明顯的性能差距,讓我不得不懷疑它底層到底是啥東西,時間精力有限先記着,有必要的時候再去啃openJDK吧。
還有就是Lombda和Method Reference從表現來看,底層應該是同一個東西,但IDEA既然分開兩種內部類的寫法推薦,那就分開對待吧。
測試時每種方式循環調用 1 億次,每種方式連續計算兩次時間,然後對比第二次運行的結果,直接run沒有采用debug運行。
貼代碼:
緊接着,我們藉助SpringBoot天然具有的一些特性,自動注入RabbitMQ一些組件的配置,包括其“單一實例消費者”配置、“多實例消費者”配置以及用於發送消息的操作組件實例“RabbitTemplate”的配置:
//通用化 Rabbitmq 配置
@Configuration
public class RabbitmqConfig {
private final static Logger log = LoggerFactory.getLogger(RabbitmqConfig.class);
@Autowired
private Environment env;
@Autowired
private CachingConnectionFactory connectionFactory;
@Autowired
private SimpleRabbitListenerContainerFactoryConfigurer factoryConfigurer;
//單一消費者
@Bean(name = "singleListenerContainer")
public SimpleRabbitListenerContainerFactory listenerContainer(){
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(new Jackson2JsonMessageConverter());
factory.setConcurrentConsumers(1);
factory.setMaxConcurrentConsumers(1);
factory.setPrefetchCount(1);
factory.setTxSize(1);
return factory;
}
//多個消費者
@Bean(name = "multiListenerContainer")
public SimpleRabbitListenerContainerFactory multiListenerContainer(){
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factoryConfigurer.configure(factory,connectionFactory);
factory.setMessageConverter(new Jackson2JsonMessageConverter());
//確認消費模式-NONE
factory.setAcknowledgeMode(AcknowledgeMode.NONE);
factory.setConcurrentConsumers(env.getProperty("spring.rabbitmq.listener.simple.concurrency",int.class));
factory.setMaxConcurrentConsumers(env.getProperty("spring.rabbitmq.listener.simple.max-concurrency",int.class));
factory.setPrefetchCount(env.getProperty("spring.rabbitmq.listener.simple.prefetch",int.class));
return factory;
}
@Bean
public RabbitTemplate rabbitTemplate(){
connectionFactory.setPublisherConfirms(true);
connectionFactory.setPublisherReturns(true);
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMandatory(true);
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
log.info("消息發送成功:correlationData({}),ack({}),cause({})",correlationData,ack,cause);
}
});
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.warn("消息丟失:exchange({}),route({}),replyCode({}),replyText({}),message:{}",exchange,routingKey,replyCode,replyText,message);
}
});
return rabbitTemplate;
}
}
直接查看實現,會發現 onSubscribe()
中做了一些判斷,比如 82
104
等幾行都是做了一些 同步
異步
等的判斷,然後初始化 Disposable
, onSubscribe()
是上游 Observable
完成了整條訂閱鏈之後調用的,所以這些操作是在開始訂閱之後才初始化操作,然後 106
行可以看出把一個包裝處理過的 Disposable
傳遞給下游。
查看一下這個scheduler的worker,會發現worker的基類schedule()方法是相同的互相調用的,所以可以直接看多個參數的schedule(),可以看到 73
行創建了一個 ScheduledRunnable
對象,並且把 主線程的handler
以及外面的 Observer
傳遞過去,接着 82
行用主線程的handler發送消息, 119
行 ScheduledRunnable
裏的 run
被調用,接着 Observer也就是runnable
也調用 run
方法