如何用Apache Kafka在微服务之间进行交流

285 阅读9分钟

微服务之间进行通信的传统方法之一是通过它们的REST APIs。然而,随着系统的发展和微服务数量的增加,通信变得更加复杂,架构可能开始类似于我们的老朋友意大利面条的反模式,服务之间相互依赖或紧密耦合,拖累了开发团队。这种模式可以表现出低延迟,但只有在服务高度可用的情况下才能发挥作用。

为了克服这个设计上的缺点,新的架构旨在将发送者和接收者解耦,采用异步消息传递。在以Kafka为中心的架构中,低延迟被保留了下来,还有一些额外的优势,比如在可用的消费者之间进行消息平衡和集中管理。

在处理棕地平台(遗留)时,建议采用实现异步消息传递的方式来解耦单体,并为迁移到微服务做准备。

在本教程中,你将学习如何:

  • 用JHipster创建一个微服务架构
  • 启用Kafka集成以实现微服务的通信
  • 将Okta设置为认证提供者

什么是Kafka?

Apache Kafka是一个分布式流媒体平台。它最初被认为是一个消息队列,由LinkedIn在2011年开源。它的社区对Kafka进行了进化,以提供关键的功能:

  • 发布和订阅记录流,就像一个消息队列。
  • 存储系统,因此消息可以被异步地消费。Kafka将数据写入一个可扩展的磁盘结构中,并复制它以实现容错。生产者可以等待写确认。
  • 用Kafka Streams API进行流处理,可以将复杂的输入流聚合或连接到处理后的数据输出流上。

传统的消息传递模式是队列和发布-订阅。在队列中,每条记录都会被送到一个消费者手中。在发布-订阅中,记录会被所有的消费者收到。

Kafka中的消费者组是一个抽象,它结合了两种模式。记录处理可以在消费者组的成员之间进行负载平衡,Kafka允许你向多个消费者组广播消息。这是同样的发布-订阅语义,其中订阅者是一个消费者集群而不是一个单一的进程。

Kafka的流行使用案例包括:

  • 传统的消息传递,以更好的延迟和可扩展性将数据生产者与处理器解耦。
  • 用实时发布-订阅馈送来跟踪网站活动。
  • 作为基于文件的日志聚合的替代品,事件数据成为消息流。
  • 数据管道,其中从主题中消耗的数据被转换并馈送到新的主题。
  • 作为分布式系统的外部提交日志。
  • 作为事件源应用程序的后端日志存储,其中每个状态变化都按时间顺序记录。

与Kafka的微服务通信

让我们建立一个有JHipster和Kafka支持的微服务架构。在本教程中,你将创建storealert 微服务。商店微服务将创建和更新商店记录。alert 微服务将接收来自store 的更新事件并发送电子邮件提醒。

先决条件

安装JHipster

npm install -g generator-jhipster@7.9.3

--version 命令应该输出这样的内容

$ jhipster --version
INFO! Using bundled JHipster
7.9.3

为该项目创建一个目录:

mkdir jhipster-kafka
cd jhipster-kafka

创建一个apps.jdl 文件,在JHipster领域语言(JDL)中定义存储、警报和网关应用程序。通过在商店和警报应用程序的定义中添加messageBroker kafka ,启用Kafka集成。

application {
  config {
    baseName gateway,
    packageName com.okta.developer.gateway,
    applicationType gateway,
    authenticationType oauth2,
    prodDatabaseType postgresql,
    serviceDiscoveryType eureka,
    testFrameworks [cypress]
  }
  entities Store, StoreAlert
}

application {
  config {
    baseName store,
    packageName com.okta.developer.store,
    applicationType microservice,
    authenticationType oauth2,
    databaseType mongodb,
    devDatabaseType mongodb,
    prodDatabaseType mongodb,
    enableHibernateCache false,
    serverPort 8082,
    serviceDiscoveryType eureka
    messageBroker kafka
  }
  entities Store
}

application {
  config {
    baseName alert,
    packageName com.okta.developer.alert,
    applicationType microservice,
    authenticationType oauth2,
    serverPort 8082,
    serviceDiscoveryType eureka
    messageBroker kafka
  }
  entities StoreAlert
}

enum StoreStatus {
  OPEN,
  CLOSED
}

entity Store {
  name String required,
  address String required,
  status StoreStatus,
  createTimestamp Instant required,
  updateTimestamp Instant
}

entity StoreAlert {
  storeName String required,
  storeStatus String required,
  timestamp Instant required
}

microservice Store with store
microservice StoreAlert with alert

现在,在你的jhipster-kafka 文件夹中,用以下命令导入这个文件:

jhipster jdl apps.jdl

用Docker Compose配置微服务部署

在项目文件夹中,为Docker Compose创建一个子文件夹并运行JHipster的docker-compose 子生成器。

mkdir docker-compose
cd docker-compose
jhipster docker-compose

生成器将要求你定义以下内容:

  1. 应用程序的类型:微服务应用
  2. 网关的类型:基于Spring云网关的JHipster网关
  3. 将服务的根目录保留为默认的:./
  4. 要包括哪些应用:网关存储警报
  5. 哪些应用程序要与集群数据库一起使用:(无)
  6. 如果应该启用监控:没有
  7. JHipster注册表的密码。<default>

几乎当生成器完成时,输出中显示一个警告:

WARNING! Docker Compose configuration generated, but no Jib cache found
If you forgot to generate the Docker image for this application, please run:
To generate the missing Docker image(s), please run:
 ./mvnw -ntp -Pprod verify jib:dockerBuild in /home/indiepopart/jhipster-kafka/alert
 ./mvnw -ntp -Pprod verify jib:dockerBuild in /home/indiepopart/jhipster-kafka/gateway
 ./mvnw -ntp -Pprod verify jib:dockerBuild in /home/indiepopart/jhipster-kafka/store

你稍后将生成图像,但首先,让我们为你的微服务添加一些安全和Kafka集成。

添加OpenID Connect(OIDC)认证

这个微服务架构被设置为针对Keycloak进行认证。让我们更新设置,使用Okta作为认证提供者。

在你开始之前,你需要一个免费的Okta开发者账户。安装Okta CLI并运行okta register ,以注册一个新账户。如果你已经有一个账户,运行okta login 。然后,运行okta apps create jhipster 。选择默认的应用程序名称,或根据你的需要进行更改。 然后,将重定向URI改为:

http://localhost:8081/login/oauth2/code/oidc,http://localhost:8761/login/oauth2/code/oidc

使用http://localhost:8081,http://localhost:8761 为注销重定向URI。

Okta CLI是做什么的?

Okta CLI简化了JHipster应用程序的配置,并为您做了几件事:

  1. 创建一个具有正确(见上文,下面是默认值)重定向URI的OIDC应用。
    • 登录:http://localhost:8080/login/oauth2/code/oidchttp://localhost:8761/login/oauth2/code/oidc
    • 注销:http://localhost:8080http://localhost:8761
  2. 创建JHipster期望的ROLE_ADMINROLE_USER
  3. 将你的当前用户添加到ROLE_ADMINROLE_USER 组中。
  4. 在你的默认授权服务器中创建一个groups ,并将用户的组添加到其中。

注意http://localhost:8761* 重定向URI是为JHipster注册处准备的,在用JHipster创建微服务时经常使用。Okta CLI默认会添加这些。

完成后,你会看到如下的输出:

Okta application configuration has been written to: /path/to/app/.okta.env

运行cat .okta.env (或Windows上的type .okta.env ),查看你的应用程序的发行者和凭证。它将看起来像这样(除了占位符的值将被填充)。

export SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER_URI="/oauth2/default"
export SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_ID="{clientId}"
export SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_SECRET="{clientSecret}"

注意:您也可以使用Okta管理控制台来创建您的应用程序。参见在Okta上创建一个JHipster应用程序以了解更多信息。

在该项目中,创建一个docker-compose/.env 文件并添加以下变量。对于数值,使用你创建的Okta网络应用程序的设置:

OIDC_ISSUER_URI={yourIssuer}
OIDC_CLIENT_ID={yourClientId}
OIDC_CLIENT_SECRET={yourClientSecret}

编辑docker-compose/docker-compose.yml ,并为服务store-appalert-appgateway-appjhipster-registry 更新SPRING_SECURITY_* 的设置:

- SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER_URI=${OIDC_ISSUER_URI}
- SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_ID=${OIDC_CLIENT_ID}
- SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_SECRET=${OIDC_CLIENT_SECRET}

使用Spring Cloud Config来覆盖OIDC设置

除了在docker-compose.yml 中为每个应用程序设置环境变量外,还有一种方法是使用Spring Cloud Config。JHipster注册表包括Spring Cloud Config,所以这很容易做到。

打开docker-compose/central-server-config/application.yml ,在那里添加你的Okta设置:

spring:
  security:
    oauth2:
      client:
        provider:
          oidc:
            issuer-uri: /oauth2/default
        registration:
          oidc:
            client-id: {yourClientId}
            client-secret: {yourClientSecret}

注册中心、网关、商店和警报应用程序都被配置为在启动时读取该配置。

在商店和警报微服务之间进行通信

JHipster生成器为声明messageBroker kafka (在JDL中)的应用程序添加了一个spring-cloud-starter-stream-kafka 依赖关系,使Spring Cloud Stream编程模型Apache Kafka绑定器一起使用Kafka作为消息传递中间件。

Spring Cloud Stream最近又被添加到JHipster中。现在,我们可以不使用Kafka核心API,而是使用绑定器的抽象,在代码中声明输入/输出参数,并让具体的绑定器实现来处理到经纪人目的地的映射。

重要提示:目前,JHipster包括Spring Cloud Stream 3.2.4,它已经废弃了基于注解的编程模型、@EnableBinding@StreamListener 注解,而采用了功能编程模型。请继续关注JHipster未来的更新。

在这个例子中,更新store 微服务,以便在商店实体被更新时,通过Kafka向alert 微服务发送消息。

首先,为一个新的主题store-alerts ,创建一个出站绑定。添加接口KafkaStoreAlertProducer:

package com.okta.developer.store.config;

import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;

public interface KafkaStoreAlertProducer {
    String CHANNELNAME = "binding-out-store-alert";

    @Output(CHANNELNAME)
    MessageChannel output();
}

WebConfigurer 中包含出站绑定:

package com.okta.developer.store.config;

@EnableBinding({ KafkaSseConsumer.class, KafkaSseProducer.class, KafkaStoreAlertProducer.class })
@Configuration
public class WebConfigurer implements ServletContextInitializer {
...

将绑定配置添加到application.yml:

spring:
  cloud:
    stream:
      ...
      bindings:
        ...
        binding-out-store-alert:
          destination: store-alerts-topic
          content-type: application/json
          group: store-alerts

store 项目中,创建一个AlertService ,用于发送事件的详细信息:

package com.okta.developer.store.service;

import com.okta.developer.store.config.KafkaStoreAlertProducer;
import com.okta.developer.store.domain.Store;
import com.okta.developer.store.service.dto.StoreAlertDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.stereotype.Service;
import org.springframework.util.MimeTypeUtils;

import java.util.HashMap;
import java.util.Map;

@Service
public class AlertService {

    private final Logger log = LoggerFactory.getLogger(AlertService.class);

    private final MessageChannel output;

    public AlertService(@Qualifier(KafkaStoreAlertProducer.CHANNELNAME) MessageChannel output) {
        this.output = output;
    }

    public void alertStoreStatus(Store store) {
        try {
            StoreAlertDTO storeAlertDTO = new StoreAlertDTO(store);
            log.debug("Request the message : {} to send to store-alert topic ", storeAlertDTO);

            Map<String, Object> map = new HashMap<>();
            map.put(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON);
            MessageHeaders headers = new MessageHeaders(map);
            output.send(new GenericMessage<>(storeAlertDTO, headers));
        } catch (Exception e){
            log.error("Could not send store alert", e);
            throw new AlertServiceException(e);
        }
    }
}

创建引用的AlertServiceException 类:

package com.okta.developer.store.service;

public class AlertServiceException extends RuntimeException {

    public AlertServiceException(Throwable e) {
        super(e);
    }
}

并在...service.dto 包中添加一个StoreAlertDTO 类:

package com.okta.developer.store.service.dto;

import com.okta.developer.store.domain.Store;

public class StoreAlertDTO {

    private String storeName;
    private String storeStatus;

    public StoreAlertDTO(Store store){
        this.storeName = store.getName();
        this.storeStatus = store.getStatus().name();
    }

    public String getStoreName() {
        return storeName;
    }

    public void setStoreName(String storeName) {
        this.storeName = storeName;
    }

    public String getStoreStatus() {
        return storeStatus;
    }

    public void setStoreStatus(String storeStatus) {
        this.storeStatus = storeStatus;
    }

}

AlertService 注入到StoreResource API的实现中,修改其构造函数。同时修改updateStore 的调用,为alert 服务发布一个StoreAlertDTO:

@RestController
@RequestMapping("/api")
public class StoreResource {

    ...
    private final StoreRepository storeRepository;
    private final AlertService alertService;

    public StoreResource(StoreRepository storeRepository, AlertService alertService) {
        this.storeRepository = storeRepository;
        this.alertService = alertService;
    }

    ...

    @PutMapping("/stores/{id}")
    public ResponseEntity<Store> updateStore(
        @PathVariable(value = "id", required = false) final String id,
        @Valid @RequestBody Store store
    ) throws URISyntaxException {
        ...

        Store result = storeRepository.save(store);

        log.debug("SEND store alert for Store: {}", store);
        alertService.alertStoreStatus(result);

        ...
    }

   ...
}

在生产中启用调试日志

既然你要部署prod 配置文件,让我们在生产中启用日志。修改store/src/main/java/com/okta/.../config/LoggingAspectConfiguration.java 类:

@Configuration
@EnableAspectJAutoProxy
public class LoggingAspectConfiguration {

    @Bean
    @Profile({JHipsterConstants.SPRING_PROFILE_DEVELOPMENT, JHipsterConstants.SPRING_PROFILE_PRODUCTION})
    public LoggingAspect loggingAspect(Environment env) {
        return new LoggingAspect(env);
    }
}

编辑store/src/main/resources/config/application-prod.yml ,将商店应用程序的日志级别改为DEBUG

logging:
  level:
    ROOT: INFO
    tech.jhipster: INFO
    com.okta.developer.store: DEBUG

在警报微服务中添加一个EmailService

现在让我们定制alert 微服务。首先,在配置中添加消费者声明KafkaStoreAlertConsumer:

package com.okta.developer.alert.config;

import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.MessageChannel;

public interface KafkaStoreAlertConsumer {
    String CHANNELNAME = "binding-in-store-alert";

    @Input(CHANNELNAME)
    MessageChannel input();
}

WebConfigurer 中包括绑定:

package com.okta.developer.alert.config;

@EnableBinding({ KafkaSseConsumer.class, KafkaSseProducer.class, KafkaStoreAlertConsumer.class })
@Configuration
public class WebConfigurer implements ServletContextInitializer {
...

将入站绑定配置添加到application.yml:

spring:
  cloud:
    stream:
      bindings:
        ...
        binding-in-store-alert:
          destination: store-alerts-topic
          content-type: application/json
          group: store-alerts

创建一个EmailService 来发送商店更新通知,使用Spring框架的JavaMailSender:

package com.okta.developer.alert.service;

import com.okta.developer.alert.service.dto.StoreAlertDTO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;

@Service
public class EmailService {

    private JavaMailSender emailSender;

    @Value("${alert.distribution-list}")
    private String distributionList;

    public EmailService(JavaMailSender emailSender){
        this.emailSender = emailSender;
    }

    public void sendSimpleMessage(StoreAlertDTO alertDTO){
        try {
            SimpleMailMessage message = new SimpleMailMessage();
            message.setTo(distributionList);
            message.setSubject("Store Alert: " + alertDTO.getStoreName());
            message.setText(alertDTO.getStoreStatus());
            message.setFrom("StoreAlert");
            emailSender.send(message);
        } catch (Exception exception) {
            throw new EmailServiceException(exception);
        }
    }
}

创建引用的EmailServiceException:

package com.okta.developer.alert.service;

public class EmailServiceException extends RuntimeException {

    public EmailServiceException(Exception exception) {
        super(exception);
    }
}

...service.dto 包中添加一个StoreAlertDTO 类:

package com.okta.developer.alert.service.dto;

public class StoreAlertDTO {

    private String storeName;
    private String storeStatus;

    public String getStoreName() {
        return storeName;
    }

    public void setStoreName(String storeName) {
        this.storeName = storeName;
    }

    public String getStoreStatus() {
        return storeStatus;
    }

    public void setStoreStatus(String storeStatus) {
        this.storeStatus = storeStatus;
    }

}

alert/src/main/resources/config/application.yml alert/src/test/resources/config/application.yml 中添加一个新的属性,用于商店警报的目标邮件。

alert:
  distribution-list: {distributionListAddress}

注意:你需要在src/test/.../application.yml 中为电子邮件设置一个值(例如:list@email.com 就可以了),以便测试通过。对于Docker,你将用下面的环境变量覆盖{distributionListAddress}{username} +{password} 占位符的值。

更新application-prod.yml 中的spring.mail.* 属性,将Gmail设置为电子邮件服务:

spring:
  ...
  mail:
    host: smtp.gmail.com
    port: 587
    username: {username}
    protocol: smtp
    tls: true
    properties.mail.smtp:
      auth: true
      starttls.enable: true

添加一个Kafka消费者来坚持警报和发送电子邮件

创建一个AlertConsumer 服务来保存一个StoreAlert ,并在通过Kafka收到警报信息时发送电子邮件通知。添加KafkaProperties,StoreAlertRepository, 和EmailService 作为构造函数参数。然后添加一个start() 方法来初始化消费者并进入处理循环:

package com.okta.developer.alert.service;

import com.okta.developer.alert.config.KafkaStoreAlertConsumer;
import com.okta.developer.alert.domain.StoreAlert;
import com.okta.developer.alert.repository.StoreAlertRepository;
import com.okta.developer.alert.service.dto.StoreAlertDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Service;

import java.time.Instant;
@Service
public class AlertConsumer {

    private final Logger log = LoggerFactory.getLogger(AlertConsumer.class);

    private StoreAlertRepository storeAlertRepository;

    private EmailService emailService;

    public AlertConsumer(StoreAlertRepository storeAlertRepository, EmailService emailService) {
        this.storeAlertRepository = storeAlertRepository;
        this.emailService = emailService;
    }

    @StreamListener(value = KafkaStoreAlertConsumer.CHANNELNAME, copyHeaders = "false")
    public void consume(Message<StoreAlertDTO> message) {
        StoreAlertDTO dto = message.getPayload();
        log.info("Got message from kafka stream: {} {}", dto.getStoreName(), dto.getStoreStatus());
        try {
            StoreAlert storeAlert = new StoreAlert();
            storeAlert.setStoreName(dto.getStoreName());
            storeAlert.setStoreStatus(dto.getStoreStatus());
            storeAlert.setTimestamp(Instant.now());

            storeAlertRepository.save(storeAlert);
            emailService.sendSimpleMessage(dto);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }
}

注意:在消息处理过程中任何未处理的异常都会使服务离开消费者组。这就是为什么上面有代码来捕捉Exception

作为最后一个定制步骤,按照你为store 微服务所做的同样方式更新日志配置。

微服务+Kafka容器部署

修改docker-compose/docker-compose.yml ,为alert 应用程序添加以下环境变量:

- SPRING_MAIL_USERNAME=${MAIL_USERNAME}
- SPRING_MAIL_PASSWORD=${MAIL_PASSWORD}
- ALERT_DISTRIBUTION_LIST=${DISTRIBUTION_LIST}

编辑docker-compose/.env ,为新的环境变量添加值:

MAIL_USERNAME={yourGmailAccount}
MAIL_PASSWORD={yourPassword}
DISTRIBUTION_LIST={anotherEmailAccount}

确保Docker Desktop正在运行,然后为store 微服务生成Docker镜像。从store 目录中运行以下命令:

./mvnw -ntp -Pprod verify jib:dockerBuild
# `npm run java:docker` is a shortcut for the above command

注意:如果你使用Apple Silicon,你将需要使用npm run java:docker:arm64

alertgateway 应用程序重复上述步骤:

在你运行你的微服务架构之前,确保你有足够的内存分配。Docker Desktop的默认值是2GB,我推荐8GB。这个设置在Docker > 资源 > 高级下。

然后,使用Docker Compose运行一切:

cd docker-compose
docker compose up

在每个服务启动时,你会看到大量的日志记录。等待一两分钟,然后打开http://localhost:8761 ,用你的Okta账户登录。这是JHipster注册表,你可以用它来监控你的应用程序的状态。等到所有的服务都启动。

打开一个新的终端窗口,跟踪alert 微服务的日志,以验证它正在处理StoreAlert 记录:

docker logs -f docker-compose-alert-1 | grep Consumer

你应该看到日志条目表明alert 微服务在启动时加入的消费者组:

2022-09-05 15:20:44.146  INFO 1 --- [           main] org.apache.kafka.clients.Metadata        : [Consumer clientId=consumer-store-alerts-4, groupId=store-alerts] Cluster ID: pyoOBVa3T3Gr1VP3rJBOlQ
2022-09-05 15:20:44.151  INFO 1 --- [           main] o.a.k.c.c.internals.ConsumerCoordinator  : [Consumer clientId=consumer-store-alerts-4, groupId=store-alerts] Resetting generation due to: consumer pro-actively leaving the group
2022-09-05 15:20:44.151  INFO 1 --- [           main] o.a.k.c.c.internals.ConsumerCoordinator  : [Consumer clientId=consumer-store-alerts-4, groupId=store-alerts] Request joining group due to: consumer pro-actively leaving the group
2022-09-05 15:20:44.162  INFO 1 --- [           main] o.a.k.clients.consumer.ConsumerConfig    : ConsumerConfig values:
2022-09-05 15:20:44.190  INFO 1 --- [           main] o.a.k.clients.consumer.KafkaConsumer     : [Consumer clientId=consumer-store-alerts-5, groupId=store-alerts] Subscribed to topic(s): store-alerts-topic
2022-09-05 15:20:44.225  INFO 1 --- [container-0-C-1] org.apache.kafka.clients.Metadata        : [Consumer clientId=consumer-store-alerts-5, groupId=store-alerts] Resetting the last seen epoch of partition store-alerts-topic-0 to 0 since the associated topicId changed from null to 0G-IFWw-S9C3fEGLXDCOrw
2022-09-05 15:20:44.226  INFO 1 --- [container-0-C-1] org.apache.kafka.clients.Metadata        : [Consumer clientId=consumer-store-alerts-5, groupId=store-alerts] Cluster ID: pyoOBVa3T3Gr1VP3rJBOlQ
2022-09-05 15:20:44.227  INFO 1 --- [container-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator  : [Consumer clientId=consumer-store-alerts-5, groupId=store-alerts] Discovered group coordinator kafka:9092 (id: 2147483645 rack: null)
2022-09-05 15:20:44.229  INFO 1 --- [container-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator  : [Consumer clientId=consumer-store-alerts-5, groupId=store-alerts] (Re-)joining group
2022-09-05 15:20:44.238  INFO 1 --- [container-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator  : [Consumer clientId=consumer-store-alerts-5, groupId=store-alerts] Request joining group due to: need to re-join with the given member-id
2022-09-05 15:20:44.239  INFO 1 --- [container-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator  : [Consumer clientId=consumer-store-alerts-5, groupId=store-alerts] (Re-)joining group

一旦一切就绪,去http://localhost:8081 的网关并登录。创建一个商店实体,然后更新它。alert 微服务在处理从store 服务收到的消息时应该记录条目:

2022-09-05 18:08:31.546  INFO 1 --- [container-0-C-1] c.o.d.alert.service.AlertConsumer        : Got message from kafka stream: Candle Shop CLOSED

如果你在试图发送通知时,在alert 微服务的日志中看到MailAuthenticationException ,这可能是你的Gmail安全配置:

alert-app_1           | org.springframework.mail.MailAuthenticationException: Authentication failed; nested exception is javax.mail.AuthenticationFailedException: 535-5.7.8 Username and Password not accepted. Learn more at
alert-app_1           | 535 5.7.8  https://support.google.com/mail/?p=BadCredentials *** - gsmtp
alert-app_1           |
alert-app_1           | 	at org.springframework.mail.javamail.JavaMailSenderImpl.doSend(JavaMailSenderImpl.java:440)

要启用从alert 应用程序的登录,请进入myaccount.google.com,然后选择安全标签。为你的账户打开2步验证。在登录谷歌一节中,选择应用密码,并创建一个新的应用密码。在 "选择应用"下拉菜单中,设置"其他"(自定义名称),并输入该密码的名称。点击 "**生成 "**并复制该密码。更新docker-compose/.env ,并为Gmail认证设置应用密码:

MAIL_PASSWORD={yourAppPassword}

重要的是:一旦测试完成,不要忘记删除应用程序的密码。

用CTRL+C停止所有的容器,然后用docker compose up ,再次重启。再次更新一个商店,这次你应该会收到一封包含商店状态的邮件。