先来看一个日常生活快递寄件场景,从寄件人(寄件)到收件人(收件),全流程如下:
图片
当你准备寄送一个包裹时,通常你可以有两种寄件方式:
方案一、你亲自前往快递服务点,填写寄件单、交付包裹、等待工作人员处理,最后得到一张寄送单据。你必须在服务点等待直到所有步骤都完成。这个过程是同步的。
方案二、你可以选择在线预约快递上门取件服务,填写相关信息后,你的请求就被提交给系统。此时,你可以继续进行其他事情,而不需要等待快递员到达。系统会在后台异步处理你的请求,安排合适的快递员前来取件。这样,你就可以在等待的过程中做其他事情,无需阻塞在快递服务点。
这种寄件方式提高了效率,让用户可以更加灵活地安排自己的时间。在后台系统中,快递公司可以通过合理的任务调度,处理多个异步请求,提高寄件服务的整体吞吐量。这种方式类似于在后端异步处理任务,而用户无需等待任务完成,可以继续进行其他操作,提高了整个寄件过程的并发性和响应性。这个过程就是异步。
我们通过这个例子抽象出同步模型和异步模型:
图片
同步模型:一个任务做完做下一个任务,阻塞
异步模型:做当前任务,只需要开启而不需要关心另一个任务如何执行,非阻塞
有了上边的模型,对于同步和异步的概念就有了初步的认识。事实上,在架构设计中,异步思想是指通过异步处理来提高系统的性能、可伸缩性和响应速度。
以下是SpringColud微服务架构的基本套件:
图片
在架构设计中,异步思想可以应用在多个方面。常见的异步实践包括:
......
接下来,我们针对实际项目中的异步设计逐个探究。可能做不到面面俱到,但是可以为真实的场景中的方案设计打开思路。
Spring Cloud Gateway基于Project Reactor反应式编程和WebFlux框架,通过路由、过滤器、事件等机制实现了灵活的网关服务。它适用于构建微服务架构中的业务网关,具有高性能、可扩展性和丰富的功能。
官网地址:https://spring.io/projects/spring-cloud-gateway/
图片
性能比较
对 Zuul/Spring Cloud Gateway 的一些性能分析可以参考 Spring Cloud Gateway 作者 Spencer Gibb 提供的项目:https://github.com/spencergibb/spring-cloud-gateway-bench。
图片
摘自SpringCloud GateWay作者spencergibb 提供的一个压测报告
总的来说,Gateway在处理IO密集型请求场景下有着更大的优势。原因是: 随着Spring 5 推出的WebFlux,它是完全异步且非阻塞的,底层也是基于Netty实现的。我们分别对Reactor模型和Netty做一个简单介绍。
图片
其中:mainReactor主要负责连接处理(不参与数据处理),而subReactor负责数据的读取(不参与连接). 不再是单线程模型那样,接收请求和处理数据都是在一个Reactor下进行。
核心主要是基于NIO的Netty框架,原理说明如下:
组件关系:
图片
概念说明:
每个服务器中都会有一个 Boss(老板),会有一群做事情的WorkerBoss(员工) 会不停地接收新的连接,将连接分配给一个个 Worker 处理连接
执行过程:
图片
Netty 执行过程:
关于SpringCloud GateWay的使用,请自行查阅官网。这里只介绍如何体现NIO异步非阻塞原理的。
场景分析:
比如:商城首页菜单树。一般这种场景我们允许在一定时间数据不一致性。那么就可以使用定时任务+消息队列。如每隔5分钟同步一次,达到数据最终一致。
图片
注意事项:
这种数据同步方案主要适用于数据实时性要求不高的场景,因为:定时任务处理存在一定时间间隔,会有同步延时。同时在时间窗口期数据可能发生变更。还有就是数据最终一致性的保证,主要取决于MQ的可靠性。
场景分析:
三方平台交互,上游系统(A)的数据和下游系统(B)的数据进行接口规范转化。此处可能涉及到很多业务转到同一个平台或者不同平台。而我们接口转化的功能是一致的。当然你可以使用Feign直接调用。但是流量增加、网络阻塞时可能会出现调用失败,导致未能成功送达下游。因此我们可以这样设计:
注意事项:
这种异步设计一方面为了系统内部服务之间解耦,另一方面起到了削峰填谷的作用。但是引入消息队列和转化服务,增加了系统的复杂性。因为链路较长,出现问题时排查起来比较困难。因此要在数据库中尽可能存留记录明细,方便审查。另外,也可能出现消息积压等问题。当然这是消息队列存在的共性问题。
场景分析:
日常我们会遇到很多这种发短信的情况。比如,
......
那么对于短信场景,我们如何设计呢?
图片
注意事项:这种异步设计一方面为了将发送短信的功能独立出来。
场景分析:
在业务系统中,一般我们会进行日志采集和可视化展示。ELK 是由 Elasticsearch、Logstash 和 Kibana 组成的一套日志管理和分析解决方案。结合 Kafka 使用时,通常用于搭建一个高效的日志处理系统。
图片
ELK 工作流程并结合 Kafka 的工作流程描述:
Logstash 作为 Kafka 消费者,通过 Kafka Input 插件订阅一个或多个 Kafka 主题。
Logstash 接收到 Kafka 中的日志消息后,可以进行多种操作,如解析日志、添加字段、过滤、转换格式等。
Logstash 处理日志并发送到 Elasticsearch:
Logstash 通过 Elasticsearch Output 插件将处理后的日志数据发送到 Elasticsearch 集群。
Logstash 可以将日志数据根据配置的索引模式(Index Pattern)划分到不同的索引中,以便更好地管理和查询。
Elasticsearch 存储和索引日志数据:
Elasticsearch 接收 Logstash 发送过来的日志数据,并将其存储在分布式索引中。
Elasticsearch 提供了强大的全文搜索和分析功能,支持对大量的日志数据进行高效的查询和分析。
Kibana 可视化和查询:
Kibana 作为 Elasticsearch 的前端界面,提供了丰富的可视化工具和查询界面。
用户可以使用 Kibana 创建仪表板、图表,执行复杂的查询,实时监控日志数据等。
整个工作流程如下:
+----------------------+ +----------------------+ +----------------------+| Producer | ----> | Kafka | ----> | Logstash | | (Log Generator) | | (Message Broker) | | | +----------------------+ +----------------------+ +----------+-----------+ | | v +----------------------+ | Elasticsearch | | (Log Storage) | +----------------------+ | | v +----------------------+ | Kibana | | (Visualization Tool)| +----------------------+
注意事项:
整个 ELK + Kafka 的架构可以帮助实现高效的日志收集、处理和可视化,适用于大规模分布式系统中的日志管理。
当使用多线程和 CompletableFuture 来执行批处理任务时,可以通过将任务分成多个子任务,并使用 CompletableFuture 来异步执行这些子任务。主要思想如下:
图片
假设我们有一个批处理任务,需要对一组数据进行处理:
import java.util.ArrayList;import java.util.List;import java.util.concurrent.CompletableFuture;import java.util.concurrent.ExecutionException;import java.util.concurrent.Executors;import java.util.concurrent.ThreadPoolExecutor;public class BatchProcessingExample { public static void main(String[] args) { // 模拟一组数据 List<Integer> data = generateData(10); // 定义线程池 ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(5); // 将数据分成多个子任务进行处理 List<CompletableFuture<Void>> futures = new ArrayList<>(); int batchSize = 3; for (int i = 0; i < data.size(); i += batchSize) { List<Integer> batch = data.subList(i, Math.min(i + batchSize, data.size())); CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { // 在这里执行批处理的具体逻辑 processBatch(batch); }, executor); futures.add(future); } // 等待所有子任务完成 CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); // 在所有子任务完成后关闭线程池 allOf.thenRun(executor::shutdown); try { // 等待所有任务完成 allOf.get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } private static List<Integer> generateData(int size) { List<Integer> data = new ArrayList<>(); for (int i = 1; i <= size; i++) { data.add(i); } return data; } private static void processBatch(List<Integer> batch) { // 模拟批处理逻辑 for (Integer value : batch) { System.out.println(Thread.currentThread().getName() + " - Processing: " + value); } }}
以上我们模拟了一组数据,然后将数据分成多个批次,每个批次使用 CompletableFuture 异步执行。CompletableFuture.allOf 用于等待所有子任务完成。
在这个示例中,主要体现了以下异步的思想和操作:
CompletableFuture.runAsync(() -> { // 执行异步任务的逻辑}, executor);
CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
allOf.thenRun(executor::shutdown);
这些异步操作帮助提高程序的并发性和响应性,特别在处理批量任务时,可以更有效地利用系统资源。异步编程模型能够允许程序在等待某些操作完成的同时继续执行其他操作,从而提高系统的效率。
异步设计在处理并发和提高系统性能方面具有优势,但也带来了一些可能的问题。以上提供的场景和方案仅供参考。使用过程中应当根据业务特征合理选择具体方案。
本文链接:http://www.28at.com/showinfo-26-70427-0.html聊聊项目实战中的异步设计
声明:本网页内容旨在传播知识,不代表本站观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。