Java系統(tǒng)中拆分同步和異步詳解
前言
很多開發(fā)人員說,將應(yīng)用程序切換到異步處理很復(fù)雜。因?yàn)樗麄冇幸粋€(gè)天然需要同步通信的Web應(yīng)用程序。在這篇文章中,我想介紹一種方法來達(dá)到異步通信的目的:使用一些眾所周知的庫和工具來設(shè)計(jì)他們的系統(tǒng)。 下面的例子是用Java編寫的,但我相信它更多的是基本原理,同一個(gè)應(yīng)用程序可以用任何語言來重新寫。
所需的工具和庫:
- Spring Boot
- RabbitMQ
1.Web應(yīng)用程序
一個(gè)用Spring MVC編寫的Web應(yīng)用程序并運(yùn)行在Tomcat上。 它所做的只是將一個(gè)字符串發(fā)送到一個(gè)隊(duì)列中 (異步通信的開始) 并等待另一個(gè)隊(duì)列中的消息作為HTTP響應(yīng)發(fā)送回來。
首先,我們需要定義幾個(gè)依賴項(xiàng),然后等待Spring Boot執(zhí)行所有必要的自動(dòng)配置。
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>com.thedeanda</groupId> <artifactId>lorem</artifactId> </dependency> </dependencies> @SpringBootApplication public class BlockingApplication { public static void main(String[] args) { SpringApplication.run(BlockingApplication.class, args); } @RestController public static class MessageController { private final RabbitTemplate rabbitTemplate; public MessageController(CachingConnectionFactory connectionFactory) { this.rabbitTemplate = new RabbitTemplate(connectionFactory); } @GetMapping("invoke") public String sendMessage() { Message response = rabbitTemplate.sendAndReceive("uppercase", null, request()); return new String(response.getBody()); } private static Message request() { Lorem LOREM = LoremIpsum.getInstance(); String name = LOREM.getFirstName() + " " + LOREM.getLastName(); return new Message(name.getBytes(), new MessageProperties()); } } @Bean public CachingConnectionFactory connectionFactory() { CachingConnectionFactory factory = new CachingConnectionFactory(); factory.setAddresses("localhost:5672"); factory.setUsername("admin"); factory.setPassword("admin"); return factory; } }
2.消費(fèi)端應(yīng)用程序
第二個(gè)應(yīng)用程序僅僅是一個(gè)等待消息的RabbitMQ的消費(fèi)端,將拿到的字符串轉(zhuǎn)換為大寫,然后將此結(jié)果發(fā)送到輸出隊(duì)列中。
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> </dependencies> @SpringBootApplication public class ServiceApplication { public static void main(String[] args) { SpringApplication.run(ServiceApplication.class, args); } public static class MessageListener { public String handleMessage(byte[] message) { Random rand = new Random(); // Obtain a number between [0 - 49] + 50 = [50 - 99] int n = rand.nextInt(50) + 50; String content = new String(message); try { Thread.sleep(n); } catch (InterruptedException e) { e.printStackTrace(); } return content.toUpperCase(); } } @Bean public CachingConnectionFactory connectionFactory() { CachingConnectionFactory factory = new CachingConnectionFactory(); factory.setAddresses("localhost:5672"); factory.setUsername("admin"); factory.setPassword("admin"); return factory; } @Bean public SimpleMessageListenerContainer serviceListenerContainer() { SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); container.setConnectionFactory(connectionFactory()); container.setConcurrentConsumers(20); container.setMaxConcurrentConsumers(40); container.setQueueNames("uppercase_messages"); container.setMessageListener(new MessageListenerAdapter(new MessageListener())); return container; } }
3.底層如何執(zhí)行的?
程序啟動(dòng)并首次調(diào)用sendMessage()方法后,我們可以看到Spring AMQP支持自動(dòng)創(chuàng)建了一個(gè)新的回復(fù)隊(duì)列并等待來自我們的服務(wù)應(yīng)用程序的響應(yīng)。
2019-05-12 17:23:21.451 INFO 4574 --- [nio-8080-exec-1] .l.DirectReplyToMessageListenerContainer : Container initialized for queues: [amq.rabbitmq.reply-to]
2019-05-12 17:23:21.457 INFO 4574 --- [nio-8080-exec-1] .l.DirectReplyToMessageListenerContainer : SimpleConsumer [queue=amq.rabbitmq.reply-to, consumerTag=amq.ctag-VF-iqD9rLEuljIBstbCI1A identity=10e58093] started
如果我們?cè)谙M(fèi)端應(yīng)用程序中查看消息,我們可以看到Spring自動(dòng)傳播有關(guān)回復(fù)隊(duì)列的信息以及**相關(guān)ID,**用于將其傳遞回Web應(yīng)用程序以便能夠?qū)⒄?qǐng)求和響應(yīng)配對(duì)在一起。
這就是發(fā)生魔術(shù)的地方。 當(dāng)然,如果您想使其更復(fù)雜,您可以在協(xié)作中包含更多服務(wù),然后將Web應(yīng)用程序的最終響應(yīng)放入與自動(dòng)生成的隊(duì)列不同的隊(duì)列中, 該隊(duì)列只具有正確的關(guān)聯(lián)ID。 另外,不要忘記設(shè)置合理的超時(shí)。
這個(gè)解決方案還有一個(gè)很大的缺點(diǎn) - 應(yīng)用程序吞吐量。 我故意這樣做,以便我可以跟進(jìn)這篇文章,進(jìn)一步深入調(diào)查AsyncProfiler! 但是目前,我們使用Tomcat作為主HTTP服務(wù)器,默認(rèn)為200個(gè)線程,這意味著我們的應(yīng)用程序無法同時(shí)處理200多條消息,因?yàn)槲覀兊姆?wù)器線程正在等待RabbitMQ 回復(fù)隊(duì)列的響應(yīng),直到有消息進(jìn)入或發(fā)生超時(shí)。
感謝您閱讀本文,敬請(qǐng)關(guān)注后續(xù)內(nèi)容! 如果您想自己嘗試一下,請(qǐng)查看我的GitHub存儲(chǔ)庫。
原文鏈接:dzone.com/articles/ho…
作者:Petr Bouda
譯者:KeepGoingPawn
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Java中向文件寫入數(shù)據(jù)的幾種常見方式分享
在日常開發(fā)中,肯定離不開要和文件打交道,今天就簡(jiǎn)單羅列一下平時(shí)比較常用的創(chuàng)建文件并向文件中寫入數(shù)據(jù)的幾種方式,文中有詳細(xì)的代碼示例供大家參考,具有一定的參考價(jià)值,需要的朋友可以參考下2023-10-10MyBatis-Plus攔截器對(duì)敏感數(shù)據(jù)實(shí)現(xiàn)加密
做課程項(xiàng)目petstore時(shí)遇到需要加密屬性的問題,而MyBatis-Plus為開發(fā)者提供了攔截器的相關(guān)接口,本文主要介紹通過MyBatis-Plus的攔截器接口自定義一個(gè)攔截器類實(shí)現(xiàn)敏感數(shù)據(jù)如用戶密碼的加密功能,感興趣的可以了解一下2021-11-11Spring5新功能@Nullable注解及函數(shù)式注冊(cè)對(duì)象
這篇文章主要為大家介紹了Spring5新功能詳解@Nullable注解及函數(shù)式注冊(cè)對(duì)象,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05Java+mysql本地圖片上傳數(shù)據(jù)庫及下載示例
本篇文章主要介紹了Java+mysql本地圖片上傳數(shù)據(jù)庫及下載示例,具有一定的參加價(jià)值,有興趣的可以了解一下。2017-01-01JAVA實(shí)現(xiàn)長(zhǎng)連接(含心跳檢測(cè)Demo)
這篇文章主要介紹了JAVA實(shí)現(xiàn)長(zhǎng)連接(含心跳檢測(cè)Demo),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10Spring @Value如何通過${}、#{}注入不同類型的值
這篇文章主要介紹了Spring @Value如何通過${}、#{}注入不同類型的值問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05