架构师必知必会系列:事件驱动架构与消息队列

发布于:2023-10-25 ⋅ 阅读:(152) ⋅ 点赞:(0)

作者:禅与计算机程序设计艺术

1.背景介绍

概述

随着互联网应用的日益普及,网站访问量、数据量呈指数级增长,单体应用的性能瓶颈已无法满足业务需求。为了提高系统的处理能力,微服务架构模式应运而生。微服务架构下的系统需要面临很多新的挑战。其中一个重要的问题就是如何在微服务之间通信?分布式系统中的消息队列提供了一种简便有效的方式来实现系统之间的通信。本文将从以下两个方面展开讨论:

  • 事件驱动架构(Event Driven Architecture)
  • 基于消息队列的异步通信机制

    事件驱动架构(EDA)

    事件驱动架构是一种异步的编程模型,它通过定义一套简单的规则来响应事件或信号。事件驱动架构由发布-订阅者模式和命令-查询责任分离模式组成。发布-订阅者模式允许多个组件同时监听同一个主题,当某个事件发生时,订阅该主题的所有组件都会得到通知。命令-查询责任分离模式则将事件驱动的数据流分为命令和查询两类。命令用于触发状态改变的动作,例如创建一个订单;查询则用于获取当前系统的状态信息,例如查看库存量。基于事件驱动架构开发的应用程序可以更加灵活地应对系统的变化,并根据情况调整其行为。

    基于消息队列的异步通信机制

    消息队列(Message Queue)是一个消息传递中间件,用来存储和转发消息。在分布式系统中,消息队列可用于实现异步通信。分布式系统的各个子系统或模块通过生产者与消费者的角色相互配合,完成各自的功能。生产者产生消息,并将其发送给消息队列,然后由消费者接收并处理消息。这样,生产者无需等待消费者的处理结果,就可以继续生产下一条消息。这种方式可以有效减少请求的延迟时间,改善系统的吞吐量。消息队列还能提供不同的消息投递策略,比如可以按照优先级顺序进行处理等。因此,消息队列是分布式系统中非常重要的组件之一,也是很多微服务架构中的通信手段。

本文将围绕事件驱动架构与基于消息队列的异步通信机制展开讨论。

2.核心概念与联系

基本概念

  • 事件:事件是指系统状态的变化或者触发某种条件的事情。
  • 事件源(Event Source):事件源是一个系统或者实体,它向事件总线发布事件。
  • 事件处理器(Event Handler):事件处理器是一个组件,它从事件总线上接收事件,并且根据事件执行一些操作。
  • 事件总线(Event Bus):事件总线是一个中心枢纽,负责路由事件到相应的事件处理器。

    EDA与MQ

    EDA vs MQ

    EDA优点
  • 分离关注点:事件驱动架构使得关注点分离,事件源不再关注消息的实际传输,只需要关注事件的产生即可。
  • 可扩展性:事件驱动架构可以轻松扩展,添加新的事件处理器只需要配置相关的事件。
  • 弹性处理:事件驱动架构的弹性处理可以利用事件处理器的失败重试机制来保证消息的可靠传递。
  • 流程可控性:事件驱动架构可以实施流程控制,让系统可以暂停、恢复、跳过某些步骤,也可以通过异步回调函数来获得结果。
    MQ优点
  • 把控消息堆积:消息队列可以最大限度地降低消息丢失率和积压影响。
  • 解耦服务依赖:由于消息队列的存在,可以解耦服务依赖关系,实现服务间的解耦。
  • 异步通信:消息队列采用异步通信方式,可以避免同步阻塞,提升整体系统的处理效率。
  • 支持广播通信:消息队列支持多种广播通信方式,可以同时向多个事件处理器发送相同的消息。

    基本原理

如图所示,生产者发送消息到消息队列,消息队列将消息持久化保存。消费者从消息队列读取消息,消费者可以选择自己喜欢的方式来处理消息,例如打印日志、执行SQL语句、调用远程接口等。消费者可以订阅感兴趣的主题,从而只接收感兴趣的消息。消息的发布者和消费者之间通过消息总线连接起来,消息总线负责路由消息,确保生产者的消息被正确路由到对应的消费者。

3.核心算法原理和具体操作步骤以及数学模型公式详细讲解

发布订阅模型

发布-订阅(Publish-Subscribe)是一种消息通信模式。订阅者向发布者注册,当发布者发生事件的时候,订阅者就会收到通知,然后对事件进行处理。在分布式系统中,发布-订阅模型一般用于实现不同模块间的解耦。

工作过程

  1. 订阅者向事件总线订阅主题。订阅者首先向事件总线订阅感兴趣的主题,指定事件处理器。
  2. 当事件发生时,发布者向事件总线发布消息。发布者向事件总线发布消息时,它也会附带有主题信息。
  3. 事件总线把消息发送给所有已注册的订阅者。
  4. 订阅者接收到消息后,进行消息处理。

    演示示例

  5. 注册事件总线,订阅主题。订阅者创建自己的事件处理器,并向事件总线订阅主题。订阅者调用订阅方法订阅主题,传入自己的事件处理器。
  6. 模拟事件发生,发布消息。发布者生成事件,向事件总线发布消息。
  7. 事件总线接收到消息,向订阅者推送消息。事件总线把消息推送给所有已注册的订阅者。
  8. 消费者接收到消息,进行消息处理。消费者处理消息,输出日志、执行SQL语句、调用远程接口等。

    实现原理

    数据结构
  • TopicManager:主题管理器,用于存储所有已订阅的主题及其对应的处理器。
  • EventBus:事件总线,用于路由事件,存储订阅关系,将消息发送给相应的订阅者。
    接口设计
  • register(String topicName, EventHandler eventHandler):注册事件处理器。
  • unregister(EventHandler eventHandler):取消注册事件处理器。
  • publish(Object message):发布消息。
  • subscribe(String topicName, EventHandler eventHandler):订阅主题。
  • unsubscribe(EventHandler eventHandler):取消订阅主题。
    实现细节
    发布者只需向事件总线发布消息即可,消息总线查找该消息的主题,并将消息推送给所有的订阅者。订阅者接收到消息后,根据自己的要求对消息进行处理。事件总线在运行过程中,需要定时扫描订阅关系,删除失效的订阅关系,并维护主题信息。

    命令查询模型

    命令-查询(Command Query Separation CQS)是一种软件设计原则,用于指导对象设计,即如何划分类、方法和对象属性,以提高软件的内聚性和可读性。命令-查询分离要求对象只能做一件事情,不能同时做两种以上事情。命令模型只处理指令,不关心数据的查询;查询模型只读取数据,不关心修改数据的命令。CQS将复杂的操作分解为多个简单操作,每个简单操作都是原子的。CQS的意义在于将复杂的操作拆解为多个简单操作,简化对象的实现。

    操作方法

  • Command(命令):改变系统的状态的操作。
  • Query(查询):获取系统状态的操作。

    演示示例

    假设有一个订单系统,用户可以在订单系统中提交订单,查询订单详情,支付订单,退款订单等。订单系统可以用命令-查询模型设计如下:
  1. 创建订单(Command):用户提交订单时,订单系统接受订单数据,生成订单号,写入数据库。
  2. 查询订单详情(Query):用户可以通过订单号查询订单详情。
  3. 支付订单(Command):用户支付订单时,订单系统更新订单状态。
  4. 退款订单(Command):用户退款时,订单系统扣除余额,写入数据库。

命令-查询模型分离了命令和查询操作,并且区分了它们的职责范围。命令操作改变系统的状态,查询操作只返回系统的状态。

命令-查询模型的好处在于封装,隔离复杂性。命令操作仅实现单一的操作逻辑,不会受到外部因素的干扰;查询操作返回系统的当前状态,不会涉及修改数据。因此,可以提高系统的可测试性、可理解性和健壮性。

EDA与MQ

EDA vs MQ

EDA采用发布-订阅模式实现事件驱动,可以很容易实现一个事件处理器。但是,如果有很多发布者,并且需要同时处理多个消息,则需要使用消息队列。MQ是一种先进的消息队列系统,可以很好的解决事件处理器竞争、消息积压和异步通信等问题。但是,如果只是需要实现简单的任务队列功能,那么EDA就足够了。下面来看看EDA和MQ的一些区别:

  • 使用场景:

    • 如果只需要实现一个消息队列,可以选择EDA。
    • 如果需要实现多个消息队列,可以使用EDA+MQ,即将不同类型的消息路由到不同的消息队列。
    • 如果需要同时处理EDA和MQ,可以使用EDA作为消息代理,将MQ的功能集成到EDA中。
  • 实现复杂度:

    • EDA的实现复杂度较低,只需要知道事件处理器的位置即可。
    • MQ的实现复杂度较高,需要考虑多个消息队列的并发处理、持久化存储、路由算法、消息过滤等。
  • 性能:

    • EDA的性能优秀,可以实现高并发和低延迟。

    • MQ的性能较差,但对于大规模集群环境来说,还是可以胜任的。

      4.具体代码实例和详细解释说明

      // 生产者(Publisher)
      public class Order {
      private String orderNo;
      
      public void createOrder() {
          // 生成订单编号
          this.orderNo = generateOrderId();
      
          // 将订单信息写入数据库
          System.out.println("订单" + orderNo + "创建成功!");
      
          // 发布订单创建事件
          EventBus.getInstance().publish(new CreateOrderEvent(this));
      }
      

}

// 消息总线(EventBus) import java.util.*;

public class EventBus { private static final Map<Class<?>, List > eventHandlersMap = new HashMap<>();

/**
 * 注册事件处理器
 */
public static synchronized boolean register(Class<?> eventType, EventHandler eventHandler) {
    if (eventHandlersMap.containsKey(eventType)) {
        return false;
    } else {
        List<EventHandler> handlers = eventHandlersMap.getOrDefault(eventType, new ArrayList<>());
        handlers.add(eventHandler);
        eventHandlersMap.put(eventType, handlers);
        return true;
    }
}

/**
 * 注销事件处理器
 */
public static synchronized boolean unregister(EventHandler eventHandler) {
    for (List<EventHandler> handlerList : eventHandlersMap.values()) {
        Iterator<EventHandler> it = handlerList.iterator();
        while (it.hasNext()) {
            if (Objects.equals(it.next(), eventHandler)) {
                it.remove();
                return true;
            }
        }
    }
    return false;
}

/**
 * 发布消息
 */
public static void publish(Object message) {
    Class<? extends Object> clazz = message.getClass();
    List<EventHandler> handlers = eventHandlersMap.getOrDefault(clazz, Collections.emptyList());

    for (EventHandler handler : handlers) {
        try {
            handler.handle(message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

/**
 * 获取全部事件处理器
 */
public static Collection<EventHandler> getAllEventHandler() {
    Set<EventHandler> allEventHandler = new HashSet<>();
    for (List<EventHandler> handlerList : eventHandlersMap.values()) {
        allEventHandler.addAll(handlerList);
    }
    return allEventHandler;
}

private EventBus() {}

private static class SingletonHolder {
    private static final EventBus INSTANCE = new EventBus();
}

public static EventBus getInstance() {
    return SingletonHolder.INSTANCE;
}

}

// 事件处理器(EventHandler) public interface EventHandler { void handle(T t); }

class CreateOrderEventHandler implements EventHandler { @Override public void handle(CreateOrderEvent event) { // 处理订单创建事件 System.out.println("订单:" + event.getOrder().orderNo + "创建成功!"); } }

// 事件(Event) public abstract class Event {

}

class CreateOrderEvent extends Event { private Order order;

public CreateOrderEvent(Order order) {
    super();
    this.order = order;
}

public Order getOrder() {
    return order;
}

}

// 消费者(Consumer) public class OrderService { private static final Logger LOGGER = LoggerFactory.getLogger(OrderService.class);

public void queryOrderInfo(String orderId) throws Exception {
    // 从数据库中查询订单详情
    LOGGER.info("查询订单:" + orderId + "的信息...");
}

public void payOrder(String orderId) throws Exception {
    // 更新订单状态
    LOGGER.info("订单:" + orderId + "支付成功!");
}

}

// 客户端 public class Demo { public static void main(String[] args) throws InterruptedException { // 初始化订单系统 Order order = new Order(); order.createOrder();

    // 注册订单事件处理器
    EventBus.register(CreateOrderEvent.class, new CreateOrderEventHandler());

    Thread.sleep(Long.MAX_VALUE);
}

} ```

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到