请求响应(Request-Response)和事件响应(Event-Driven)
请求响应(Request-Response)和事件响应(Event-Driven)是两种常见的软件和系统设计框架,它们在目的、设计和实现方式上存在明显的区别。我们在项目里常用的什么redis啊,包括我之前说的那些响应式流,netty等,都有着事件驱动属性
一 、基本概念:
1. 请求响应框架
目的和概念
请求响应框架是一种同步通信模式,常见于客户端-服务器架构中。在这种框架下,客户端发送请求到服务器,服务器经过处理后返回一个响应。通信双方在一个请求处理完毕之前通常不会进行下一个交互。
特点
- 同步性:客户端在发送请求后通常需要等待服务器的响应,期间可能不做其他处理。
- 直接性:请求的发起者明确知道哪个服务器(或服务)会处理该请求,并期待从该服务器接收响应。
- 简单直观:对于许多基本的客户端-服务器应用,请求响应模型提供了一个易理解和实现的交互方式。
应用场景
- Web HTTP服务交互
- 远程过程调用(RPC)
- 数据库查询
2. 事件响应框架
目的和概念
事件响应框架是一种基于事件驱动的异步通信模式,强调在发生某些事件时系统的响应。事件可以由外部源触发,也可以是系统内部的状态变化。响应者不需要知道事件的具体发起者。
特点
- 异步性:系统组件可以在不阻塞主线程的情况下响应事件,从而可以处理多个事件或进行其他作业。
- 松耦合:事件生产者和消费者之间不需要直接的接触或知识,只需对事件进行订阅即可。
- 可扩展性:通过增加新的事件处理者或改进事件处理逻辑,系统可以轻松扩展以处理更多类型的事件或提高处理效率。
应用场景
- 消息队列系统
- 实时数据处理
- 微服务架构中的服务间通信
- 图形用户界面(GUI)编程
主要差异
-
通信模式:
- 请求响应是同步的,需要请求方等待响应。
- 事件响应是异步的,响应方在适当的时候处理事件,不阻塞请求方继续操作。
-
耦合性:
- 请求响应模式中,请求方需要知道响应方的具体信息。
- 事件响应模式通常是松耦合的,参与方只需关注与之相关的事件。
-
适用性:
- 请求响应适用于需直接返回结果的操作。
- 事件响应适合于需要高度扩展性和灵活性的复杂系统,如实时消息通讯或多服务交互。
二、一些可用于事件驱动的中间件
这些工具和中间件中,有些是事件驱动的,有些则主要用于消息传递或数据处理。让我们分别看看它们是否支持事件驱动模式:
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 支持发布-订阅模式,让应用以事件为驱动进行消息处理。
9. Apache Flink
- 部分事件驱动: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.Publish和nc.Subscribe用于发送和接收消息。
9. Apache Flink
实现原理:
- 批处理与流处理统一: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 创建数据流管道,设定每个节点的处理逻辑。
这些中间件和工具各有其独特的设计原则和实现方式,适合不同的应用场景,如实时数据处理、异步消息传递和事件驱动的自动伸缩等。通过代码示例和原理解析,你可以更深入地了解如何将这些工具应用于实际项目中。

浙公网安备 33010602011771号