作者:禅与计算机程序设计艺术
本文是“理解基于事件驱动架构的Web应用”系列第9篇,主要内容是通过阐述事件驱动架构(EDA)的概念、模型及其应用场景、关键技术、实现方法和优点等方面来全面详细地理解并掌握EDA在Web应用中的作用及运用。EDA利用异步消息机制将分布式系统中复杂的业务逻辑模块进行分离,使得各个业务逻辑模块之间耦合性降低,提升系统可扩展性、健壮性及可维护性,并可以有效避免因系统模块之间耦合而导致的难以维护、错误的功能扩展,从而达到提升软件质量、降低成本、提高效率和增加竞争力的目标。本篇文章先对事件驱动架构(EDA)进行简单的介绍,然后重点介绍其在Web应用中的角色及作用。读者可以通读全文,掌握EDA的概念、结构和基本应用,提高对EDA的理解和认识,以及更好地掌握并运用EDA在Web应用中的优势。
在计算机编程领域里,事件驱动架构(Event-Driven Architecture,EDA)是一个用于处理分布式系统中复杂业务逻辑的架构模式。它是一种分布式系统设计模式,用来将复杂的业务逻辑分割成多个相互独立的子系统,然后通过异步消息传递的方式交换信息,让每个子系统只关注自己处理的事务,这样就可以实现分布式系统中各个子系统之间的松耦合,有效地提升系统的可靠性、可用性、伸缩性及性能。引入事件驱动架构的主要原因有两个:
一是当今的软件系统越来越复杂,它们的架构也越来越复杂;
二是分布式架构越来越流行,各种分布式微服务架构正在被广泛采用。
早期的软件开发没有考虑到分布式系统的问题,因此一些复杂的系统逻辑可能散落在不同位置,难以协调运行。而随着时间的推移,为了解决复杂度爆炸的问题,软件工程师们转向了面向对象编程,使用封装、继承、多态等面向对象的编程技巧,设计出了一些架构模式来帮助软件系统应对日益增长的复杂性。其中一种架构模式就是事件驱动架构。
命令查询责任隔离(Command Query Responsibility Segregation,CQRS)是一种架构设计模式,旨在提升应用程序的可伸缩性和弹性。它最初由Eric Evans在2007年提出,并经过若干变体而成为一个重要的设计范式。EDA是命令查询责任分离(CQS)的一个变体,并且它是一种帮助软件系统应对复杂性爆炸的架构模式。虽然两者有些相似之处,但还是有很大的区别。
CQRS与EDA最大的不同之处就在于它们的目的不同。EDA的目的是提高系统的可扩展性、健壮性及可维护性,通过异步消息传递的方式使各个业务逻辑模块之间的耦合降低,从而实现模块化的业务架构。在这种架构下,各个模块之间的通信方式是异步消息,而不是同步调用或阻塞式调用。因此,EDA可以在一定程度上减少依赖于同步资源的线程间切换,改善系统的响应速度,提升系统的整体效率和稳定性。
CQRS的目的是提升应用程序的性能。它把读取数据和修改数据的操作分开,分别放在不同的端点上,使得系统的读写负载均衡,进一步提升系统的吞吐量和并发能力。在CQRS架构下,每一个业务实体都有一个命令端点(Command Endpoints),负责执行修改数据的操作;另有一个查询端点(Query Endpoints),负责执行查询数据的操作。这种划分允许在读写操作时的数据不一致,从而提升系统的容错性和可用性。
除了上面两点区别外,还有一些其他的差异。比如,EDA通常使用消息队列作为基础设施层,以实现异步通信;CQRS则主要使用数据库作为基础设施层。在很多情况下,它们还会共享同样的持久化存储。不过,CQRS有时候会和ESB(Enterprise Service Bus)结合起来,提供统一的消息路由和集成点。另外,CQRS一般需要配合事件溯源(Event Sourcing)一起使用,以保证数据的完整性和追溯性。
从这个角度看,CQRS可能比EDA要更加适合于处理数据读取密集型的业务。但是,在实际项目实践中,两种架构模式仍然存在一些差异,比如对于复杂的查询操作,EDA可能更适合,因为它的架构更简单,缺乏复杂查询操作所需的中间状态。除此之外,它们的使用场景也千差万别,需要结合具体的业务需求进行取舍。
在分布式系统中,业务逻辑的执行可能会跨越多个子系统或者节点。为了避免这些系统间的同步等待,需要引入异步消息机制。异步消息机制即各个子系统之间通过消息传递的方式进行通信,异步通信并不会造成严重的延迟,且能够保证消息的顺序和可靠传递。由于异步通信,分布式系统在很大程度上可以做到高度解耦,从而提升系统的可扩展性、健壮性及可维护性。
当某个子系统完成了一个业务逻辑的处理之后,它并不立刻产生结果。而是将结果存放到消息队列中,通知其他子系统。待其他子系统完成相应工作之后,再将结果通过消息回传给当前子系统。这样,各个子系统之间通过异步通信的方式进行交流,可以有效减轻系统之间的耦合,提升系统的可靠性、可用性、伸缩性及性能。
下图描绘了异步消息机制的原理。
上图展示了异步消息机制的基本流程。假如A系统已经完成了自己的业务逻辑处理,它将结果通过消息发送到消息队列MQ,并告知消息队列MQ要接收到的消息。消息队列MQ收到该消息后,它会将该消息放入自己的消息缓存中,等待接收方B处理。当接收方B处理完毕后,它会将结果再通过消息队列MQ发送回A系统。A系统接收到结果后,完成自己的业务逻辑处理。
通过异步通信,子系统可以专注于自己处理的事务,无须关心其他子系统的情况,从而提升系统的性能、稳定性、可靠性及可扩展性。
有很多种类型的消息队列可供选择,比如Apache Kafka、RabbitMQ、ActiveMQ、ZeroMQ等。这些消息队列都提供了丰富的特性,例如支持持久化、高吞吐量、可靠投递等。下面对常用的消息队列进行介绍。
Apache Kafka是最流行的开源分布式发布-订阅消息系统,具有快速、高吞吐量、可扩展性和容错性等特点。Kafka的性能非常好,它可以在集群内部和外部实现实时的日志记录和流动数据分析。它使用TCP协议进行通信,支持多种语言的客户端接口,包括Java、Scala、Python、Ruby等。
RabbitMQ是一款开源的AMQP协议的消息代理软件,具有强大的高性能、高可靠性和灵活可靠性的特点。它支持多种消息队列模型,如点对点、发布/订阅、主题等。它同时也支持插件机制,可以根据不同的应用场景添加新的功能。
ActiveMQ是Apache下的一个开放源代码的企业级消息总线。它由Java编写,使用多线程异步的方式处理消息,适合部署在要求低延迟、高性能的分布式系统中。ActiveMQ的性能表现尤为突出,它的消费者每秒钟可以处理几百万条消息。
在分布式系统中,事务指的是一个不可分割的工作单位。事务管理器负责确保事务的ACID属性,确保事务的完整性、一致性和隔离性。事务管理器在事务开始之前会向所有参与者发送通知,请求其提交事务。如果任何一个参与者无法提交事务,事务管理器会自动回滚事务,并向其他参与者发送回滚消息。在第二阶段,事务管理器通知所有的参与者准备提交事务。如果某一个参与者无法提供足够的信息,事务管理器会取消事务,并向其他参与者发送取消消息。最后,事务管理器向所有参与者发送提交消息。在两阶段提交协议中,只有在所有参与者都同意提交事务后,才能正式提交事务。
如果要保证数据的完整性和追溯性,除了用事件驱动架构之外,还可以使用事件溯源(Event Sourcing)。事件溯源是一种架构风格,它指的是按照顺序记录系统产生的所有事件,并通过这些事件重构出系统的状态。这种架构模式可以解决一些数据完整性和数据追溯性相关的问题。
事件采集系统是指用于收集各种来源的数据并转换成适合查询的结构,如数据库、日志文件、API输出等。事件采集系统将来自不同来源的数据转化成同一个格式,并将其存储在数据库中。对于存储在数据库中的数据,可以通过查询语句进行复杂的条件查询,可以得到精准的结果。
对EDA进行描述,首先需要定义一些重要的术语,如消息队列、分布式事务、两阶段提交协议、事件溯源和事件采集系统等。EDA利用异步消息机制将复杂的业务逻辑模块进行分离,使得各个业务逻辑模块之间耦合性降低,提升系统可扩展性、健壮性及可维护性。在具体实施的时候,采用如下步骤:
接下来,介绍事件驱动架构模型。事件驱动架构模型是一个分层的架构,它将整个系统划分为四个层次,从上往下依次为数据层、应用层、消息层和调度层。
数据层包括数据存储、数据处理和数据传输。它包括存储了业务数据、用户上传的文件、系统生成的日志等。应用层则包括应用服务器、后台任务队列、数据分析工具、报表生成工具等。应用层通过接口将数据存储在数据层中,并通过调度层调用后台任务队列来处理数据。消息层负责异步通信,主要包括消息队列和分布式事务。调度层负责调度后台任务队列,确保后台任务按序执行。
事件驱动架构模型具备良好的可扩展性和弹性,它可以支持复杂的业务逻辑,并且可以部署在云平台、容器化和微服务架构之上。在系统发生变化的时候,它可以动态地扩展子系统,无须停机。同时,它还可以根据需要动态增加和删除子系统,从而满足用户的动态需求。
最后,介绍EDA的实施过程,并介绍一些常用的工具。EDA的实施过程如下:
可以使用的工具包括消息队列、分布式事务、两阶段提交协议和事件溯源。消息队列可以选择Apache Kafka、RabbitMQ或ActiveMQ等,用于实现异步通信。分布式事务可以用Oracle GoldenGate或Two-Phase Commit协议实现。两阶段提交协议用于确保事务的ACID属性。事件溯源可以结合数据库和日志文件实现。
作者接下来为大家带来的代码实例是基于Spring Boot框架,它是一个轻量级的、开放源代码的Java开发框架,可以用来创建基于Spring的应用程序。
@RestController
public class EventController {
@Autowired
private DomainService domainService;
/**
* 发起活动
*/
@PostMapping("/activity")
public ResponseEntity<Void> createActivity(@RequestBody Activity activity){
try{
// 开启事务
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
// 执行核心业务
Long activityId = domainService.createActivity(activity);
// 提交事务
transactionManager.commit(status);
return ResponseEntity.created(URI.create("http://localhost:8080/activities/" + activityId)).build();
}catch (Exception e){
// 异常回滚事务
transactionManager.rollback(status);
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "活动创建失败");
}
}
}
这里是一个例子,它演示了如何创建一个活动,同时也是EDA架构的基本步骤。
Spring Boot是一套快速配置的脚手架,基于Spring Framework提供了一种简单的方法来创建一个独立运行的、产品级别的基于Spring的应用。你可以使用Spring Boot的特性来快速搭建项目,比如建立RESTful API、连接关系型数据库、配置WebSocket、整合前端组件等等。在Spring Boot中,我们不需要再像传统的Spring项目那样逐个配置文件,只需要简单地添加必要的依赖项即可,启动类也可以自动扫描classpath下的Bean,这使得Spring Boot极大地简化了项目的配置和开发。
Spring Boot框架提供了方便快捷的集成方式,你可以通过添加相应的依赖项来集成MySQL数据库,通过以下配置来启用MySQL数据库:
spring:
datasource:
url: jdbc:mysql://localhost:3306/>?useSSL=false&serverTimezone=UTC
username: >
password: >
driverClassName: com.mysql.cj.jdbc.Driver
将以上配置加入到application.yml文件中即可。
MyBatis是一款ORM框架,它可以将对象关系映射到SQL数据库。Spring Boot提供了内置的 MyBatis starter,所以你不需要单独下载 MyBatis 来使用它。
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.4version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
package com.example.demo.domain;
import lombok.*;
@Data
@Builder
public class Customer {
private String customerName;
private Integer age;
}
package com.example.demo.mapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CustomerMapper {
void insertCustomer(Customer customer);
}
package com.example.demo.repository;
import com.example.demo.domain.Customer;
import com.example.demo.mapper.CustomerMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class CustomerRepository implements ICustomerRepository {
private final CustomerMapper customerMapper;
@Autowired
public CustomerRepository(CustomerMapper customerMapper) {
this.customerMapper = customerMapper;
}
@Override
public void save(Customer customer) {
customerMapper.insertCustomer(customer);
}
}
package com.example.demo.service;
import com.example.demo.domain.Customer;
import com.example.demo.exception.CustomerNotFoundException;
import com.example.demo.repository.ICustomerRepository;
import org.springframework.stereotype.Service;
@Service
public class CustomerService implements ICustomerService {
private final ICustomerRepository repository;
public CustomerService(ICustomerRepository repository) {
this.repository = repository;
}
@Override
public void save(Customer customer) {
repository.save(customer);
}
@Override
public Customer getById(Long id) throws CustomerNotFoundException {
return repository.findById(id).orElseThrow(() -> new CustomerNotFoundException("Invalid ID:" + id));
}
}
这里是一个简单的例子,展示了如何集成MyBatis到Spring Boot中。
EDA已经成为一种主流的架构模式,它在提升系统的可扩展性、健壮性及可维护性方面发挥了重要作用。随着时间的推移,EDA已逐渐成为云计算、微服务架构、Serverless架构的代名词。在未来,EDA将继续发挥越来越重要的作用。
云计算是指利用云平台部署软件应用,能够按需付费的虚拟化技术提供计算资源的服务。通过云计算,你可以快速和低成本地布署应用,使你的应用在全球范围内得到快速部署和更新。云计算还可以帮你节省不必要的支出,最大限度地提升应用的可用性,帮助企业降低成本。
随着云计算的发展,服务器硬件的价格越来越便宜,开发者们发现,使用云平台部署应用非常容易,从而产生了一股反噬的力量。许多大型公司,如亚马逊、微软、谷歌、Facebook等,都在布局云计算。
Serverless架构是一种新兴的架构模式,它借助云平台的弹性扩容能力和按需计费等特点,使用函数计算(Function Compute)这一计算服务,通过云函数来运行应用。与传统的物理服务器不同,云函数只需要支付运行的时间,而不必担心服务器的管理、维护、升级等繁琐工作。Serverless架构最大的优点在于降低运维成本、提升开发效率、简化应用架构。
Serverless架构适用于特定类型的应用,如后台任务处理、事件驱动型应用、即时通信、图像识别等。在未来,随着云计算的发展,Serverless架构将会成为下一个热门方向。
边缘计算是一种新型的计算模式,它利用移动设备的计算能力来处理数据。边缘计算的应用场景如机器人、IoT设备、汽车导航系统等,其原理是在网络边缘安装计算单元,将云计算中的数据分析任务交由边缘计算单元处理,降低云端服务器的压力,提高处理效率。由于移动设备的计算能力越来越强劲,边缘计算将进一步带来革命性的发展。
随着云计算、Serverless架构、边缘计算等新型计算模式的出现,EDA将越来越多地用于分布式系统架构中。本篇文章对EDA的概览进行了介绍,它是一篇十分重要的技术文章,希望它能帮助读者更好地理解并掌握EDA的概念、结构和基本应用。