教程

Kafka 中的 InstanceAlreadyExistsException 异常

1、简介 Apache Kafka 是一个功能强大的分布式流平台,被广泛用于构建实时数据管道和流应用。然而,Kafka 在运行过程中可能会遇到各种异常和错误。其中一个常见的异常就是 InstanceAlreadyExistsException。 本文将带你了解 Kafka 出现 InstanceAlreadyExistsException 异常的原因和解决办法。 2、InstanceAlreadyExistsException 异常是什么? InstanceAlreadyExistsException 是 java.lang.RuntimeException 的子类。在 Kafka 的上下文中,这个异常通常在尝试创建具有与现有生产者或消费者相同的 Client ID 的 Kafka 生产者或消费者时出现。 每个 Kafka 客户端实例都有一个唯一的 Client ID,它对 Kafka 集群内的元数据跟踪和客户端连接管理至关重要。如果试图创建一个新的客户端实例,而 Client ID 已被现有客户端使用,Kafka 会抛出 InstanceAlreadyExistsException(实例已存在异常)。 3、内部机制 虽然我们提到 Kafka 会抛出这个异常,但值得注意的是,Kafka 通常会在其内部机制中优雅地处理这个异常。通过在内部处理异常,Kafka 可以将问题隔离和限制在其自身的子系统中。这可以防止异常影响主线程,并潜在地导致更广泛的系统不稳定或停机。 在 Kafka 的内部实现中,registerAppInfo() 方法通常在初始化 Kafka 客户端(生产者或消费者)时被调用。假设现有的客户端有相同的 client.id,该方法会捕获 InstanceAlreadyExistsException。由于异常是在内部处理的,它不会被抛到主线程上,而人们可能希望在主线程上捕获异常。 4、InstanceAlreadyExistsException 的原因 在本节中,我们将通过代码示例来研究导致 InstanceAlreadyExistsException 的各种情况。 4.1、消费者组中重复的 Client ID Kafka 规定同一消费者组内的消费者有不同的 Client ID。当一个组内的多个消费者共享相同的 Client ID 时,Kafka 的消息传递语义可能会变得不可预测。这会干扰 Kafka 管理偏移量和维护消息顺序的能力,可能导致消息重复或丢失。因此,当多个消费者共享同一个 Client ID 时,就会触发该异常。

使用 MongoDB 和 Spring AI 构建 RAG 应用

1、概览 AI(人工智能)技术的使用正成为现代开发中的一项关键技能。在本文中,我们将构建一个 RAG Wiki 应用,它可以根据存储的文档回答问题。 我们会通过 Spring AI 将应用与 MongoDB Vector 数据库 和 LLM 集成。 2、RAG 应用 当自然语言生成需要依赖上下文数据时,我们就会使用 RAG(Retrieval-Augmented Generation)应用。RAG 应用的一个关键组成部分是向量数据库(Vector Database),它在有效管理和检索这些数据方面起着至关重要的作用: 我们使用 Embedding Model 来处理源文件。Embedding Model 将文档中的文本转换为高维向量。这些向量捕捉了内容的语义,使我们能够根据上下文而不仅仅是关键词匹配来比较和检索类似的内容。然后,我们将文档存储到向量数据库。 保存文档后,我们可以通过以下方式发送提示信息: 首先,我们使用 Embedding Model 来处理问题,将其转换为捕捉其语义的向量。 接下来,进行相似性搜索,将问题的向量与存储在向量库中的文档向量进行比较。 从最相关的文档中,为问题建立一个上下文。 最后,将问题及其上下文发送给 LLM,LLM 会根据所提供的上下文构建与查询相关的回复。 3、MongoDB Atlas Search 在本教程中,我们将使用 MongoDB Atlas Search 作为我们的向量数据库。它提供的向量搜索功能可以满足我们在本项目中的需求。为了测试,我们使用 mongodb-atlas-local Docker 容器来设置 MongoDB Atlas Search 的本地实例。 创建一个 docker-compose.yml 文件: version: '3.1' services: my-mongodb: image: mongodb/mongodb-atlas-local:7.0.9 container_name: my-mongodb environment: - MONGODB_INITDB_ROOT_USERNAME=wikiuser - MONGODB_INITDB_ROOT_PASSWORD=password ports: - 27017:27017 4、依赖和配置 首先,添加必要的依赖项。由于我们的应用要提供 HTTP API,因此加入 spring-boot-starter-web 依赖:

使用 OpenFeign 和 CompletableFuture 并行处理多个 HTTP 请求

1、简介 在处理分布式系统时,调用外部服务并保持低延迟是一项至关重要的任务。 本文将带你了解如何使用 OpenFeign 和 CompletableFuture 来并行处理多个 HTTP 请求,处理错误,并设置网络和线程超时。 2、示例项目 为了说明并行请求的用法,我们要实现一个功能,允许客户在网站上购买物品。首先,该服务发出一个请求,根据客户所在国家获取可用的付款方式。其次,它发送一个请求给客户生成有关购买的报告。购买报告不包括有关付款方式的信息。 先添加 spring-cloud-starter-openfeign 依赖: <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> 3、创建调用外部依赖的客户端 使用 @FeignClient 注解创建两个指向 localhost:8083 的客户端: @FeignClient(name = "paymentMethodClient", url = "http://localhost:8083") public interface PaymentMethodClient { @RequestMapping(method = RequestMethod.GET, value = "/payment_methods") String getAvailablePaymentMethods(@RequestParam(name = "site_id") String siteId); } 第一个客户端名为 paymentMethodClient。它调用 GET /payment_methods,使用代表客户所在国家/地区的 site_id 请求参数获取可用的付款方式。 第二个客户端如下: @FeignClient(name = "reportClient", url = "http://localhost:8083") public interface ReportClient { @RequestMapping(method = RequestMethod.POST, value = "/reports") void sendReport(@RequestBody String reportRequest); } 我们将其命名为 reportClient,它调用 POST /reports 生成购买报告。

使用 Stream API 处理 JDBC ResultSet

1、概览 通常,我们使用遍历从 JDBC ResultSet 中迭代检索到的数据,不过在某些情况下,我更喜欢用 record Stream。 本文将带你了解使用 Stream API 处理 JDBC 结果集的几种方法。 2、使用 Spliterators 首先是纯 JDK 方法,使用 Spliterators 创建流。 首先,为实体定义一个 Model: public record CityRecord(String city, String country) { } 在 CityRecord 中,我们存储了有关 city(城市)及其 country(国家)的信息。 接下来,创建一个能与数据库交互并返回 Stream<CityRecord> 的 Repository: public class JDBCStreamAPIRepository { private static final String QUERY = "SELECT name, country FROM cities"; private final Logger logger = LoggerFactory.getLogger(JDBCStreamAPIRepository.class); public Stream<CityRecord> getCitiesStreamUsingSpliterator(Connection connection) throws SQLException { PreparedStatement preparedStatement = connection.

Spring Security 整合 Firebase Authentication

1、概览 在现代 Web 应用中,用户身份认证和授权是至关重要的组成部分。从零开始构建身份认证层是一项具有挑战性的复杂任务。不过,随着基于云的身份认证服务的兴起,这一过程变得简单多了。 Firebase Authentication 就是这样一个例子,它是 Firebase 和谷歌 提供的一种完全托管的身份认证服务。 本文将带你了解如何将 Firebase Authentication 与 Spring Security 整合,以创建和认证我们的用户。我们要进行必要的配置,实现用户注册和登录功能,并创建一个自定义 Authentication Filter 来验证私有 API 端点的用户 Token。 2、项目设置 在实现之前,需要加入 SDK 依赖并正确配置应用。 2.1、依赖 首先,在项目的 pom.xml 文件中添加 Firebase admin 依赖: <dependency> <groupId>com.google.firebase</groupId> <artifactId>firebase-admin</artifactId> <version>9.3.0</version> </dependency> 该依赖提供了必要的类,用于在应用中与 Firebase Authentication 服务交互。 2.2、定义 Firebase 配置 Bean 现在,为了与 Firebase Authentication 交互,我们需要配置私钥(Private Key)来验证 API 请求。 在本例中,我们在 src/main/resources 目录下创建 private-key.json 文件。不过,在生产中,私钥应从环境变量中加载,或从 secret 管理系统中获取,以提高安全性。 使用 @Value 注解加载私钥,并用它来定义 Bean: @Value("classpath:/private-key.json") private Resource privateKey; @Bean public FirebaseApp firebaseApp() { InputStream credentials = new ByteArrayInputStream(privateKey.

Java JMS 读写 IBM MQ 队列

1、简介 本文将会带你了解如何使用 Java JMS(Java Message Service)从 IBM MQ 队列读写消息。 2、设置环境 我们可以在 Docker 容器中运行 IBM MQ,以避免手动安装和配置的复杂性。 使用以下命令以基本配置运行容器: docker run -d --name my-mq -e LICENSE=accept -e MQ_QMGR_NAME=QM1 MQ_QUEUE_NAME=QUEUE1 -p 1414:1414 -p 9443:9443 ibmcom/mq 接下来,需要在 pom.xml 文件中添加 IBM MQ 客户端: <dependency> <groupId>com.ibm.mq</groupId> <artifactId>com.ibm.mq.allclient</artifactId> <version>9.4.0.0</version> </dependency> 3、配置 JMS Connection 首先,我们需要用 QueueConnectionFactory 建立 JMS Connection(连接),用于创建与队列管理器(Queue Manager)的连接: public class JMSSetup { public QueueConnectionFactory createConnectionFactory() throws JMSException { MQQueueConnectionFactory factory = new MQQueueConnectionFactory(); factory.setHostName("localhost"); factory.setPort(1414); factory.setQueueManager("QM1"); factory.setChannel("SYSTEM.DEF.SVRCONN"); return factory; } } 首先创建一个 MQQueueConnectionFactory 实例,用于配置和创建与 IBM MQ 服务器的连接。我们将主机名设置为 localhost,因为 MQ 服务器是在本地 Docker 容器内运行的。暴露的映射端口为 1414。

在 Hibernate 中更新和插入前更改字段值

1、概览 在使用 Hibernate 时,经常会遇到这样的情况:在将实体持久化到数据库之前,需要更改字段的值。这种情况可能是因为需要执行必要的字段转换。 本文将通过一个示例:即在执行更新和插入操作前将字段值转换为大写字母,来了解实现这一目的的不同方法。 2、实体生命周期回调 首先,定义一个简单的实体类 Student: @Entity @Table(name = "student") public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column private String name; // Getter / Setter 方法省略 } 第一种方法是 JPA 实体生命周期回调。JPA 提供了一组注解,允许我们在不同的 JPA 生命周期事件中执行一个方法,例如: @PrePresist:在插入事件之前执行。 @PreUpdate:在更新事件之前执行。 我们在 Student 实体类中添加 changeNameToUpperCase() 方法。该方法将 name 字段改为大写。该方法通过 @PrePersist 和 @PreUpdate 进行注解,以便 JPA 在持久化和更新之前调用该方法: @Entity @Table(name = "student") public class Student { @PrePersist @PreUpdate private void changeNameToUpperCase() { name = StringUtils.

在 Spring Boot 3 中迁移 HttpStatus 到 HttpStatusCode

1、概览 本文将带你了解如何在 Spring Boot 应用中使用 HttpStatusCode,重点是 3.3.3 版中引入的最新增强功能。通过这些增强功能,HttpStatusCode 已被纳入 HttpStatus 实现,从而简化了我们处理 HTTP 状态码的方式。 这些改进的主要目的是提供一种更灵活、更可靠的方法来处理标准和自定义 HTTP 状态码,使我们在处理 HTTP 响应时具有更高的灵活性和可扩展性,同时保持向后兼容性。 2、HttpStatus 枚举 在 Spring 3.3.3 之前,HTTP 状态码在 HttpStatus 中表示为枚举。这限制了自定义或非标准 HTTP 状态码的使用,因为枚举是一组固定的预定义值。 尽管 HttpStatus 类尚未被弃用,但一些返回原始 Integer 状态码的枚举和方法(如 getRawStatusCode() 和 rawStatusCode())现已被弃用。 使用 @ResponseStatus 注解来提高代码的可读性仍然是我们推荐的方法。 我们可以将 HttpStatus 与 HttpStatusCode 结合使用,以实现更灵活的 HTTP 响应管理: @GetMapping("/exception") public ResponseEntity<String> resourceNotFound() { HttpStatus statusCode = HttpStatus.NOT_FOUND; if (statusCode.is4xxClientError()) { return new ResponseEntity<>("Resource not found", HttpStatusCode.valueOf(404)); } return new ResponseEntity<>("Resource found", HttpStatusCode.

NetBeans Profiler 的编程式用法

1、概览 对应用程序进行分析可以深入了解其运行时的行为。Java 生态系统中有多种流行的分析器(Profiler),如用于通用分析的 NetBeans Profiler、JProfiler 和 VisualVM。 本文将带你了解如何以编程方式使用 NetBeans profiler API。 2、NetBeans Profiler NetBeans IDE 提供免费的分析器来分析 Java 应用。它通过 IDE 中直观的嵌入式用户界面,提供了评估 CPU 性能和内存使用情况的功能。 然而,NetBeans Profiler 还提供了可用于编程式的分析 API。这可用于 Heap Dump 的自动化分析,而不需要依赖于 UI 界面。 Heap Dump(堆转储)是一段时间内应用的内存快照。它是深入了解内存使用情况的良好指标,因为它包括内存中的实时对象、对象的类和字段以及对象之间的引用。 3、示例项目 要使用 NetBeans Profiler API,首先在 pom.xml 中添加 依赖: <dependency> <groupId>org.netbeans.modules</groupId> <artifactId>org-netbeans-lib-profiler</artifactId> <version>RELEASE220</version> </dependency> 该依赖提供了 JavaClasses 和 Instances 等各种工具类,以帮助我们分析类、创建的实例数量和使用的内存。 接着,创建一个简单的项目并分析它的 Heap Dump: class SolarSystem { private static final Logger LOGGER = Logger.getLogger(SolarSystem.class.getName()); private int id; private String name; private List<String> planet = new ArrayList<>(); // 构造函数 public void logSolarSystem() { LOGGER.

使用 JdbcTemplate 调用存储过程

1、概览 本文将带你了解如何使用 Spring JDBC 框架的 JdbcTemplate 来调用存储过程。数据库存储过程类似于函数。函数支持输入参数并有返回类型,而存储过程同时支持输入和输出参数。 2、先决条件 来看看 PostgreSQL 数据库中一个简单的存储过程: CREATE OR REPLACE PROCEDURE sum_two_numbers( IN num1 INTEGER, IN num2 INTEGER, OUT result INTEGER ) LANGUAGE plpgsql AS ' BEGIN sum_result := num1 + num2; END; '; 存储过程 sum_twoo_numbers 接收两个输入数字,并在输出参数 sum_result 中返回它们的和。一般来说,存储过程可以支持多个输入和输出参数。但在本例中,我们只考虑了一个输出参数。 3、使用 JdbcTemplate#call() 方法 来看看如何使用 JdbcTemplate#call() 方法调用数据库存储过程: void givenStoredProc_whenCallableStatement_thenExecProcUsingJdbcTemplateCallMethod() { List<SqlParameter> procedureParams = List.of(new SqlParameter("num1", Types.INTEGER), new SqlParameter("num2", Types.NUMERIC), new SqlOutParameter("result", Types.NUMERIC) ); Map<String, Object> resultMap = jdbcTemplate.call(new CallableStatementCreator() { @Override public CallableStatement createCallableStatement(Connection con) throws SQLException { CallableStatement callableStatement = con.