概述
Pub/Sub是一个异步通信系统,既可靠又可扩展。该服务是基于谷歌的一个基础设施组件,该组件已经被众多谷歌产品使用了十多年。
这个基础设施被谷歌产品如广告、搜索和G-mail使用,每秒发送超过5亿条信息,总计超过1TB/s的数据。
发布/订阅服务的基本原理
Pub/Sub是一种发布/订阅(Pub/Sub)服务,它是一种消息传递服务,其中消息发送者和接收者被分开。在一个Pub/Sub服务中,有几个基本概念。
1.通过该服务的数据被称为消息。
2.2.代表消息馈送的命名实体被称为一个主题。
3.3.一个订阅是一个命名的实体,它表达了接收特定主题消息的愿望。
4.发布者(也被称为生产者):生成消息,并将其发送到特定主题的消息服务(发布)。
5.5.订阅者(也称为消费者)是基于订阅而接收消息的人。
实施
在你的pom.xml中,包括依赖项。spring-cloud-gcp-starter-pubsub、spring-integration-core和spring-cloud-gcp-dependencies是Pub/Sub的最重要的依赖关系。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.gcp.pubsub</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
<spring-cloud-gcp.version>1.2.5.RELEASE</spring-cloud-gcp.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-pubsub</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gcp-dependencies</artifactId>
<version>${spring-cloud-gcp.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
带有SpringBootApplication注释的主文件
package com.cloudgcp.pubsub.demo;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
System.out.println("Application Started!!");
}
}
消耗Pub/Sub消息
我们已经创建了主题、订阅,并设置了一个maven项目。首先,我们需要添加在application.properties中创建的订阅。
spring.cloud.gcp.project-id=staticweb-test
pubsub.subscription=projects/staticweb-test/subscriptions/s-dummy-bucket
这里有两个抽象方法,一个是订阅,另一个是消费。
package com.cloudgcp.pubsub.demo.consumer;
import com.google.cloud.pubsub.v1.Subscriber;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gcp.pubsub.core.PubSubTemplate;
import org.springframework.cloud.gcp.pubsub.support.BasicAcknowledgeablePubsubMessage;
import java.util.function.Consumer;
public abstract class PubSubConsumer {
@Autowired
private PubSubTemplate pubSubTemplate;
/* Name of the Subscription */
public abstract String subscription();
protected abstract void consume(BasicAcknowledgeablePubsubMessage message);
public Consumer<BasicAcknowledgeablePubsubMessage> consumer() {
return basicAcknowledgeablePubsubMessage -> consume(basicAcknowledgeablePubsubMessage);
}
public Subscriber consumeMessage() {
return this.pubSubTemplate.subscribe(this.subscription(), this.consumer());
}
}
我们从application.properties文件中获取订阅。我们使用EventListener注解,在应用程序准备好时开始监听。
package com.cloudgcp.pubsub.demo.consumer;
import com.google.pubsub.v1.PubsubMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.gcp.pubsub.core.PubSubTemplate;
import org.springframework.cloud.gcp.pubsub.support.BasicAcknowledgeablePubsubMessage;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class DemoConsumer extends PubSubConsumer {
private static final Logger LOG = LoggerFactory.getLogger(DemoConsumer.class);
@Autowired
private PubSubTemplate pubSubTemplate;
@Value("${pubsub.subscription}")
private String subscription;
@Override
public String subscription() {
return this.subscription;
}
@Override
protected void consume(BasicAcknowledgeablePubsubMessage basicAcknowledgeablePubsubMessage) {
PubsubMessage message = basicAcknowledgeablePubsubMessage.getPubsubMessage();
try {
System.out.println(message.getData().toStringUtf8());
System.out.println(message.getAttributesMap());
String objectName = message.getAttributesMap().get("objectId");
String bucketName = message.getAttributesMap().get("bucketId");
String eventType = message.getAttributesMap().get("eventType");
LOG.info("Event Type:::::" + eventType);
LOG.info("File Name::::::" + objectName);
LOG.info("Bucket Name::::" + bucketName);
}catch(Exception ex) {
LOG.error("Error Occured while receiving pubsub message:::::", ex);
}
basicAcknowledgeablePubsubMessage.ack();
}
@EventListener(ApplicationReadyEvent.class)
public void subscribe() {
LOG.info("Subscribing {} to {} ", this.getClass().getSimpleName(), this.subscription());
pubSubTemplate.subscribe(this.subscription(), this.consumer());
}
}
当我们收到消息时,在确认消息之前,我们会打印EventType、FileName和Bucket Name。
我们将input.txt文件上传到相应的Bucket中,我们的Spring boot App就会收到通知,并打印对象名称、文件名和其他信息。
我们通过访问消息对象上的这些方法来获得正文和属性图。
message.getData().toStringUtf8()
message.getAttributesMap()
发布Pub/Sub消息
我们需要使用以下命令创建另一个主题,之后我们需要在GCP控制台进行验证。
gcloud pubsub topics create t-another-topic
我们必须为这个主题创建一个订阅,否则,所有发布的消息都会丢失。
我们需要为发布者创建一个抽象的类。我们正在使用Spring框架的PubSubTemplate来发布消息。
package com.cloudgcp.pubsub.demo.publisher;
import com.google.pubsub.v1.PubsubMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gcp.pubsub.core.PubSubTemplate;
import java.util.concurrent.ExecutionException;
public abstract class PubSubPublisher {
private static final Logger LOG = LoggerFactory.getLogger(PubSubPublisher.class);
@Autowired
private PubSubTemplate pubSubTemplate;
protected abstract String topic();
public void publish(PubsubMessage pubsubMessage) throws ExecutionException, InterruptedException {
LOG.info("Publishing to the topic [{}], message [{}]", topic(), pubsubMessage);
pubSubTemplate.publish(topic(), pubsubMessage).get();
}
}
现在在application.properties中配置该主题。
package com.cloudgcp.pubsub.demo.publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class DemoPublisher extends PubSubPublisher {
private static Logger LOG = LoggerFactory.getLogger(DemoPublisher.class);
@Value("${pubsub.topic}")
private String topic;
@Override
protected String topic() {
return this.topic;
}
}
我们已经看到了消费的部分。现在我们要在收到消息后发布另一个主题。这里是消费者文件,我们在收到消息后立即发布。
package com.cloudgcp.pubsub.demo.consumer;
import com.cloudgcp.pubsub.demo.publisher.DemoPublisher;
import com.google.protobuf.ByteString;
import com.google.pubsub.v1.PubsubMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.gcp.pubsub.core.PubSubTemplate;
import org.springframework.cloud.gcp.pubsub.support.BasicAcknowledgeablePubsubMessage;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
@Component
public class DemoConsumer extends PubSubConsumer {
private static final Logger LOG = LoggerFactory.getLogger(DemoConsumer.class);
@Autowired
private PubSubTemplate pubSubTemplate;
@Autowired
private DemoPublisher demoPublisher;
@Value("${pubsub.subscription}")
private String subscription;
@Override
public String subscription() {
return this.subscription;
}
@Override
protected void consume(BasicAcknowledgeablePubsubMessage basicAcknowledgeablePubsubMessage) {
PubsubMessage message = basicAcknowledgeablePubsubMessage.getPubsubMessage();
try {
System.out.println(message.getData().toStringUtf8());
System.out.println(message.getAttributesMap());
String objectName = message.getAttributesMap().get("objectId");
String bucketName = message.getAttributesMap().get("bucketId");
String eventType = message.getAttributesMap().get("eventType");
LOG.info("Event Type:::::" + eventType);
LOG.info("File Name::::::" + objectName);
LOG.info("Bucket Name::::" + bucketName);
String messageId = "messageId " + UUID.randomUUID();
String pubMessage = "File Name Received " + objectName + "From Bucket " + bucketName + "For the event type::" + eventType;
publishMessage(messageId, message.getAttributesMap(), pubMessage);
}catch(Exception ex) {
LOG.error("Error Occured while receiving pubsub message:::::", ex);
}
basicAcknowledgeablePubsubMessage.ack();
}
public void publishMessage(String messageId, Map<String, String> attributeMap, String message) throws ExecutionException, InterruptedException {
LOG.info("Sending Message to the topic:::");
PubsubMessage pubsubMessage = PubsubMessage.newBuilder()
.putAllAttributes(attributeMap)
.setData(ByteString.copyFromUtf8(message))
.setMessageId(messageId)
.build();
demoPublisher.publish(pubsubMessage);
}
@EventListener(ApplicationReadyEvent.class)
public void subscribe() {
LOG.info("Subscribing {} to {} ", this.getClass().getSimpleName(), this.subscription());
pubSubTemplate.subscribe(this.subscription(), this.consumer());
}
}
我们已经自动连接了demoPublisher并为发布创建了一个单独的方法。首先,我们需要建立PubSubMessage并将这些消息作为参数传递给发布方法。
摘要
- Google Pub/Sub是一个异步消息传递服务,它将产生事件的服务与处理事件的服务解耦。
- Pub/Sub是一个面向消息的中间件或流分析管道的事件摄取和交付,它可以整合GCP中的组件。
- 主题是一种资源,所有的发布者在其中发布他们的消息。所有订阅该主题的订阅者都会收到消息。
- 订阅是一种资源,代表了来自单一主题的消息流。你为一个特定的主题创建一个订阅。
- 消息是被发送到主题的实际消息,订阅者在订阅该主题时得到这个消息。这包含一个实际的消息和属性。
- 消息属性是可以与消息一起发送的键值对,这样它就可以标志着关于消息的一些信息。
- 我们还需要了解云计算Pub/Sub的一点是,通信可以是扇出(一对多)或扇入(多对一)或多对多。
总结
在GCP云计算Pub/Sub中,我们学习了如何订阅主题和发布消息。这个Pub/Sub服务有很多应用,它是一个解耦应用组件的好方法。
