请求响应(Request-Response)和事件响应(Event-Driven)

请求响应(Request-Response)和事件响应(Event-Driven)是两种常见的软件和系统设计框架,它们在目的、设计和实现方式上存在明显的区别。我们在项目里常用的什么redis啊,包括我之前说的那些响应式流,netty等,都有着事件驱动属性

一 、基本概念:

1. 请求响应框架

目的和概念

请求响应框架是一种同步通信模式,常见于客户端-服务器架构中。在这种框架下,客户端发送请求到服务器,服务器经过处理后返回一个响应。通信双方在一个请求处理完毕之前通常不会进行下一个交互。

特点

  • 同步性:客户端在发送请求后通常需要等待服务器的响应,期间可能不做其他处理。
  • 直接性:请求的发起者明确知道哪个服务器(或服务)会处理该请求,并期待从该服务器接收响应。
  • 简单直观:对于许多基本的客户端-服务器应用,请求响应模型提供了一个易理解和实现的交互方式。

应用场景

  • Web HTTP服务交互
  • 远程过程调用(RPC)
  • 数据库查询

2. 事件响应框架

目的和概念

事件响应框架是一种基于事件驱动的异步通信模式,强调在发生某些事件时系统的响应。事件可以由外部源触发,也可以是系统内部的状态变化。响应者不需要知道事件的具体发起者。

特点

  • 异步性:系统组件可以在不阻塞主线程的情况下响应事件,从而可以处理多个事件或进行其他作业。
  • 松耦合:事件生产者和消费者之间不需要直接的接触或知识,只需对事件进行订阅即可。
  • 可扩展性:通过增加新的事件处理者或改进事件处理逻辑,系统可以轻松扩展以处理更多类型的事件或提高处理效率。

应用场景

  • 消息队列系统
  • 实时数据处理
  • 微服务架构中的服务间通信
  • 图形用户界面(GUI)编程

主要差异

  1. 通信模式

    • 请求响应是同步的,需要请求方等待响应。
    • 事件响应是异步的,响应方在适当的时候处理事件,不阻塞请求方继续操作。
  2. 耦合性

    • 请求响应模式中,请求方需要知道响应方的具体信息。
    • 事件响应模式通常是松耦合的,参与方只需关注与之相关的事件。
  3. 适用性

    • 请求响应适用于需直接返回结果的操作。
    • 事件响应适合于需要高度扩展性和灵活性的复杂系统,如实时消息通讯或多服务交互。

二、一些可用于事件驱动的中间件

这些工具和中间件中,有些是事件驱动的,有些则主要用于消息传递或数据处理。让我们分别看看它们是否支持事件驱动模式:

1. Apache Kafka

  • 事件驱动:Kafka 可以用于事件驱动架构,消费者可以实时地响应事件(消息)的到来。生产者生成事件,消费者被动订阅。

2. RabbitMQ

  • 事件驱动:作为消息队列系统,RabbitMQ 支持事件驱动的设计理念,消费者可以在有消息抵达队列时做出响应。

3. AWS Lambda

  • 事件驱动:Lambda 是典型的事件驱动计算服务,被 AWS 事件(如 S3 上传、DynamoDB 更新)或自定义事件触发执行,不需要手动干预。

4. Redis Streams

  • 事件驱动:Redis Streams 提供类似事件流功能,消费者可以在消息抵达流时进行处理,支持事件驱动的设计。

5. Apache Pulsar

  • 事件驱动:与 Kafka 类似,Pulsar 允许消费者监听主题并在有事件产生时作出响应。

6. KEDA (Kubernetes Event-driven Autoscaling)

  • 事件驱动:KEDA 本身是为事件驱动的自动伸缩而设计,根据事件的数量(如队列长度)自动调整 Kubernetes Pod 的副本数。

7. Google Cloud Pub/Sub

  • 事件驱动:Pub/Sub 允许发布者发布事件,并立即让订阅者作出响应,支持事件驱动架构。

8. NATS

  • 事件驱动:NATS 支持发布-订阅模式,让应用以事件为驱动进行消息处理。
  • 部分事件驱动:Flink 主要用于数据处理,虽然可以处理流数据(实时数据流可以看作事件流),但它更侧重于持续的数据流处理,而不是单个事件。

10. StreamSets

  • 事件驱动:StreamSets 通过数据流管道处理实时数据,能够实时响应数据变化,虽然不是传统意义上的事件驱动,但具备类似特性。

综上所述,除了 Apache Flink 和 StreamSets,其他工具和中间件都具备事件驱动的特性,即可以根据事件的产生和传递,触发行为。Flink 和 StreamSets 虽然能够处理流数据,但更多用于批处理和持续流处理,而不是单个事件的响应。

三、基础代码

1. Apache Kafka

实现原理:

  • 日志划分:Kafka 把消息流分为多个分区,每个分区被视为一个有序、不可变的消息记录序列。日志内是append-only方式追加的(消息被追加到一个持久的、有序的、不可变的日志中)
  • 消费者组:每个消费者组都能同时消费一个 Kafka 主题,组内的消费者分工合作消费分区内的消息。
  • 复制和容错:通过分区的复制实现数据的高可用。

代码案例解析:

生产者代码分析:

Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092"); // Kafka 代理地址
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

Producer<String, String> producer = new KafkaProducer<>(props);
producer.send(new ProducerRecord<>("my-topic", "key", "value")); // 发送消息
producer.close();

消费者代码分析:

Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "my-group"); // 消费者组
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("my-topic")); // 订阅主题

while (true) {
    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100)); // 拉取消息
    for (ConsumerRecord<String, String> record : records) {
        System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value()); // 处理消息
    }
}

2. RabbitMQ

实现原理:

  • 生产者发送消息至交换机,由交换机根据规则将消息路由到队列。
  • 消费者从队列中取消息。队列提供持久性和可靠性的存储。
  • 支持多种交换机类型,如 direct、topic、fanout 和 headers。

代码案例解析:

import pika

# 连接RabbitMQ
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello')  # 声明队列

# 发送消息
channel.basic_publish(exchange='', routing_key='hello', body='Hello World!')
print(" [x] Sent 'Hello World!'")
connection.close()

# 消费消息
def callback(ch, method, properties, body):
    print(f" [x] Received {body}")

channel.basic_consume(queue='hello', on_message_callback=callback, auto_ack=True)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()

3. AWS Lambda

实现原理:

  • 事件驱动:由AWS事件(如S3文件上传)或第三方服务触发。
  • 自动扩展:根据请求数量动态扩展,按需分配资源。
  • 无服务器:无需用户管理服务器,封装容器镜像运行函数。

代码案例解析:

def lambda_handler(event, context):
    message = 'Hello, World!'
    print(message)
    return message
  • event 是事件数据(如 HTTP 请求的 body)。
  • context 提供函数运行时信息(如时间限制/内存上限)。

4. Redis Streams

实现原理:

  • 添加条目:使用XADD命令,将消息添加到流末尾。
  • 消费组XREADGROUP命令,消费者组协作读取消息。
  • 流的游标:以游标形式跟踪已处理的消息。

代码案例解析:

import redis

r = redis.Redis()
# 将消息添加到 Stream
r.xadd('mystream', {'key1': 'value1', 'key2': 'value2'})

# 读取
messages = r.xread({'mystream': '0-0'}, count=5, block=0)
for message in messages:
    print(message)
  • xadd 用于将条目添加到流。
  • xread 用于异步地读取消息。

5. Apache Pulsar

实现原理:

  • 主题分层:Pulsar 采用多租户架构,组织为租户、命名空间和主题。
  • 独立和共有订阅:支持共享、排他、多播订阅,灵活处理消费者。
  • 持久性和瞬态存储:消息可以落盘(BookKeeper)、保留策略配置。

代码案例解析:

import org.apache.pulsar.client.api.*;

PulsarClient client = PulsarClient.builder()
        .serviceUrl("pulsar://localhost:6650")
        .build();

// 生产者
Producer<byte[]> producer = client.newProducer()
        .topic("my-topic")
        .create();

producer.send("msg".getBytes()); // 发送消息

// 消费者
Consumer<byte[]> consumer = client.newConsumer()
        .topic("my-topic")
        .subscriptionName("my-subscription")
        .subscribe();

Message<byte[]> msg = consumer.receive();
System.out.printf("Message received: %s", new String(msg.getData())); // 处理消息

consumer.acknowledge(msg);
client.close();

6. KEDA (Kubernetes Event-driven Autoscaling)

实现原理:

  • KEDA 使用 Kubernetes 的 Horizontal Pod Autoscaler (HPA)来自动扩展容器。
  • 支持基于事件的触发器(如消息队列的长度)。

配置案例解析:

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: my-scale-object
spec:
  scaleTargetRef:
    name: my-deployment
  triggers:
  - type: kafka
    metadata:
      bootstrapServers: my-kafka-broker:9092
      topic: my-topic
      consumerGroup: my-group
      lagThreshold: "100"
  • scaleTargetRef 指定要伸缩的 Kubernetes 对象。
  • triggers 定义了扩展的条件(例如 Kafka 消息积压长度)。

7. Google Cloud Pub/Sub

实现原理:

  • 发布-订阅模式:主题和订阅者解耦,允许异步通信。
  • 自动调度和负载平衡:服务自动分配接收到的消息。
  • 持久性:消息在被成功处理前保留。

代码案例解析:

from google.cloud import pubsub_v1

project_id = "your-project-id"
topic_id = "your-topic-id"

# 发布消息
publisher = pubsub_v1.PublisherClient()
topic_path = publisher.topic_path(project_id, topic_id)
data = "Hello, World!"
data = data.encode("utf-8")
future = publisher.publish(topic_path, data)

# 订阅消息
subscriber = pubsub_v1.SubscriberClient()
subscription_path = subscriber.subscription_path(project_id, "your-subscription-id")

def callback(message):
    print(f"Received {message}.")
    message.ack()

streaming_pull_future = subscriber.subscribe(subscription_path, callback)

8. NATS

实现原理:

  • 轻量协议:高效简短的协议,低延迟,面向任务发布-订阅模式。
  • 多种传递模式:支持发布-订阅、请求-响应和队列组。

代码案例解析:

import (
    "github.com/nats-io/nats.go"
    "log"
)

func main() {
    nc, _ := nats.Connect(nats.DefaultURL)
    
    // 发布消息
    nc.Publish("foo", []byte("Hello, World!"))
    
    // 订阅消息
    nc.Subscribe("foo", func(m *nats.Msg) {
        log.Printf("Received a message: %s", string(m.Data))
    })

    nc.Flush()
    nc.Close()
}
  • nats.Connect用于连接到 NATS 服务器。
  • nc.Publishnc.Subscribe用于发送和接收消息。

实现原理:

  • 批处理与流处理统一:DataStream 用于流处理,DataSet 用于批处理。
  • 事件时间与窗口:流处理中的事件时间支持,允许在时间窗口上聚合数据。

代码案例解析:

import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.api.java.DataSet;
import org.apache.flink.api.common.functions.FilterFunction;

public class BatchJob {
    public static void main(String[] args) throws Exception {
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
        
        DataSet<String> text = env.readTextFile("data.txt"); // 读取文本文件
        
        DataSet<String> filtered = text.filter(new FilterFunction<String>() {
            @Override
            public boolean filter(String value) {
                return value.startsWith("N"); // 过滤以N开头的字符串
            }
        });

        filtered.writeAsText("output.txt"); // 写入输出文件
        env.execute("Flink Batch Java API Skeleton");
    }
}

10. StreamSets

实现原理:

  • 可视化管道:通过图形界面进行数据流的设计和配置。
  • 多阶段处理:允许在数据收集、清洗、转换和存储期间进行复杂处理。

因为 StreamSets 主要通过图形界面进行操作,此处不展示代码案例。通常,用户通过 UI 创建数据流管道,设定每个节点的处理逻辑。

这些中间件和工具各有其独特的设计原则和实现方式,适合不同的应用场景,如实时数据处理、异步消息传递和事件驱动的自动伸缩等。通过代码示例和原理解析,你可以更深入地了解如何将这些工具应用于实际项目中。

posted @ 2024-12-12 14:36  J九木  阅读(207)  评论(0)    收藏  举报