利用Spring Cloud GCP--发送Pub/Sub信息及其在SpringBoot的实现

515 阅读4分钟

概述

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服务有很多应用,它是一个解耦应用组件的好方法。