我的需求是springboot集成kafka需要读取证书文件,然后进行消费工厂的创建和监听,当然这里需要用到绝对路径文件的读取,相对路径是不支持的。
开始之前我们使用常用的几种方式获取路径文件的方式:
getClass().getClassLoader().getResource() 方式
java
@Bean
public DefaultKafkaConsumerFactory <String, String> consumerFactory() {
Map <String, Object> consumerConfig = new HashMap <>();
consumerConfig.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaProperties.getBootstrapServers());
consumerConfig.put(ConsumerConfig.GROUP_ID_CONFIG, "owner-data-sync");
consumerConfig.put(ConsumerConfig.CLIENT_ID_CONFIG, "owner-data-sync");
consumerConfig.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
consumerConfig.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
// 设置值的反序列化器为 ErrorHandlingDeserializer2,并配置类型信息
consumerConfig.put(ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS, JsonDeserializer.class);
consumerConfig.put(JsonDeserializer.USE_TYPE_INFO_HEADERS, false); // 启用类型信息头
consumerConfig.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
String pemUrl = "";
String csrUrl = "";
if (c3ConfigProperties.getEnvironment().equals("dev")) {
pemUrl = "file/dev/kafka/client.jks";
csrUrl = "file/dev/kafka/truststore.jks";
} else if (c3ConfigProperties.getEnvironment().equals("beta")) {
pemUrl = "file/beta/kafka/client.jks";
csrUrl = "file/beta/kafka/truststore.jks";
} else if (c3ConfigProperties.getEnvironment().equals("prod")) {
pemUrl = "file/prod/kafka/client.jks";
csrUrl = "file/prod/kafka/truststore.jks";
}
URL resourceUrl = getClass().getClassLoader().getResource(pemUrl);
URL csrResourceUrl = getClass().getClassLoader().getResource(csrUrl);
String keyStorePath = new File(resourceUrl.getFile()).getAbsolutePath();
String trustStorePath = new File(csrResourceUrl.getFile()).getAbsolutePath();
consumerConfig.put("security.protocol", "SSL");
consumerConfig.put("ssl.truststore.password", kafkaProperties.getTrustStorePassword());
consumerConfig.put("ssl.keystore.location", keyStorePath);
consumerConfig.put("ssl.truststore.location", trustStorePath);
consumerConfig.put("ssl.keystore.password", kafkaProperties.getKeyStorePassword());
consumerConfig.put("ssl.key.password", kafkaProperties.getKeyPassword());
return new DefaultKafkaConsumerFactory <>(consumerConfig);
}
这段代码看着没有问题实际上有很大问题,getClass().getClassLoader().getResource()
方法无法直接读取文件系统中的绝对路径,它是基于类路径的。这意味着它只能读取位于类路径下的资源文件,而无法直接访问文件系统中的其他位置的文件,所以在容器pod下的java服务其实这样运行必然报错的。
Resource 方式:
-
更灵活的资源管理 :Spring 的
ResourceLoader
接口提供了更灵活的资源管理功能,可以处理各种类型的资源,包括类路径下的文件、URL、文件系统中的文件等。 -
依赖注入支持 :在 Spring 应用程序中,
ResourceLoader
可以通过依赖注入的方式使用,这使得在 Spring 环境中管理资源文件变得更加方便。 -
资源抽象 :Spring 的
Resource
接口提供了对资源的抽象表示,使得可以统一处理各种类型的资源,提供了更高层次的抽象和功能。 -
丰富的功能 :Spring 的
ResourceLoader
接口提供了丰富的功能,如资源文件的装饰、转换等操作,使得资源文件的读取和处理更加灵活和方便。
缺点:相对于直接使用 getClass().getClassLoader().getResource()
方法,使用 Spring 的 ResourceLoader
接口可能会带来一定的性能开销,因为它涉及到更多的抽象和功能
我们基于代码上改了一下:
java
@Bean
public DefaultKafkaConsumerFactory <String, String> consumerFactory() {
Map <String, Object> consumerConfig = new HashMap <>();
consumerConfig.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaProperties.getBootstrapServers());
consumerConfig.put(ConsumerConfig.GROUP_ID_CONFIG, "owner-data-sync");
consumerConfig.put(ConsumerConfig.CLIENT_ID_CONFIG, "owner-data-sync");
consumerConfig.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
consumerConfig.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
// 设置值的反序列化器为 ErrorHandlingDeserializer2,并配置类型信息
consumerConfig.put(ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS, JsonDeserializer.class);
consumerConfig.put(JsonDeserializer.USE_TYPE_INFO_HEADERS, false); // 启用类型信息头
consumerConfig.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
String pemUrl = "";
String csrUrl = "";
if (c3ConfigProperties.getEnvironment().equals("dev")) {
pemUrl = "file/dev/kafka/client.jks";
csrUrl = "file/dev/kafka/truststore.jks";
} else if (c3ConfigProperties.getEnvironment().equals("beta")) {
pemUrl = "file/beta/kafka/client.jks";
csrUrl = "file/beta/kafka/truststore.jks";
} else if (c3ConfigProperties.getEnvironment().equals("prod")) {
pemUrl = "file/prod/kafka/client.jks";
csrUrl = "file/prod/kafka/truststore.jks";
}
try {
// 获取证书资源
Resource pemResource = resourceLoader.getResource("classpath:"+pemUrl);
Resource csrResource = resourceLoader.getResource("classpath:"+csrUrl);
// 获取证书文件的路径
String keyStorePath = pemResource.getFile().getAbsolutePath();
String trustStorePath = csrResource.getFile().getAbsolutePath();
consumerConfig.put("ssl.keystore.location", keyStorePath);
consumerConfig.put("ssl.truststore.location", trustStorePath);
}catch (Exception e){
}
consumerConfig.put("security.protocol", "SSL");
consumerConfig.put("ssl.truststore.password", kafkaProperties.getTrustStorePassword());
consumerConfig.put("ssl.keystore.password", kafkaProperties.getKeyStorePassword());
consumerConfig.put("ssl.key.password", kafkaProperties.getKeyPassword());
return new DefaultKafkaConsumerFactory <>(consumerConfig);
}
这是方式直接读取resource目录下面的文件也是不行,只能配合挂载的方式进行读取。
Groovy
volumes:
- name: keystore-volume
hostPath:
path: /path/to/keystore.p12
springboot的yaml 方式读取文件:
Groovy
spring:
kafka:
bootstrap-servers: SSL://*-*:443
ssl:
protocol: TLS
trust-store-password: a123456
key-store-password: a123456
key-password: a123456
key-store-location: file:client.jks
trust-store-location: file:truststore.jks
consumer:
value-deserializer: org.apache.kafka.common.serialization.ByteArrayDeserializer
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
properties:
security.protocol: SSL
auto.offset.reset: earliest
client.id: owner-data-sync
max.poll.records: 100 # 每次拉取的最大记录数
group-id: owner-data-sync
listener:
concurrency: 3
producer:
topic: *.event
在容器化环境中,尤其是在 Kubernetes 中,直接使用 file:
方式指定文件路径可能会遇到一些问题,因为容器中的文件系统通常是短暂的,并且不同于本地文件系统。
-
容器化环境的限制 :在容器中直接使用
file:
方式指定文件路径可能会受到容器化环境的限制,因为容器中的文件系统通常是隔离的,并且不支持直接访问宿主机的文件系统。 -
使用 Volume 挂载文件 :在 Kubernetes 中,可以通过 Volume 将宿主机的文件挂载到容器中,然后在应用程序中访问挂载的路径来读取文件。可以在 Kubernetes 的 Pod 配置中通过 Volume 将文件挂载到容器中,并在 Spring Boot 配置文件中使用
file:
方式指定挂载路径。 -
环境变量传递文件路径:另一种方法是通过环境变量将文件路径传递给应用程序,然后在应用程序中读取该环境变量并使用对应的路径来访问文件。
如果需要使用这样的方式一定要把文件上传到指定的目录