看到一个问题,说在用java代码发送kafka消息的时候能指定一个partition参数:
import org.apache.kafka.clients.producer.ProducerRecord;
public class KafkaProducerExample {
public static void main(String[] args) {
String topic = "test";
int partition = 0; // 假设你选择了第一个分区
String key = "key";
String value = "value";
ProducerRecord record = new ProducerRecord<>(topic, partition, key, value);
// 使用 KafkaProducer 发送这个 record
}
}
有人说如果partition指定一个大于现有分区数的值,比如999,但是这个主题只有2个分区,发送就会卡住。但是我觉得应该会有一个超时时间,不然一直卡着,占用资源。kafka不会这么笨。
然后就像验证一下,在windows里面有一个车kafka3.0.0,像启动一下,先启动zookeeper,没问题,再启动kafka就报错了:
[2024-10-22 14:40:58,563] ERROR Disk error while writing recovery offsets checkpoint in directory Z:\tmp\kafka-logs: Error while writing to checkpoint file D:\tmp\kafka-logs\recovery-point-offset-checkpoint (kafka.log.LogManager)
[2024-10-22 14:40:58,567] ERROR Error while writing to checkpoint file D:\tmp\kafka-logs\log-start-offset-checkpoint (kafka.server.LogDirFailureChannel)
java.nio.file.AccessDeniedException: D:\tmp\kafka-logs
at sun.nio.fs.WindowsException.translateToIOException(Unknown Source)
at sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source)
at sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source)
at sun.nio.fs.WindowsFileSystemProvider.newFileChannel(Unknown Source)
at java.nio.channels.FileChannel.open(Unknown Source)
at java.nio.channels.FileChannel.open(Unknown Source)
at org.apache.kafka.common.utils.Utils.flushDir(Utils.java:953)
at org.apache.kafka.common.utils.Utils.atomicMoveWithFallback(Utils.java:941)
at kafka.server.checkpoints.CheckpointFile.liftedTree1$1(CheckpointFile.scala:114)
at kafka.server.checkpoints.CheckpointFile.write(CheckpointFile.scala:92)
at kafka.server.checkpoints.OffsetCheckpointFile.write(OffsetCheckpointFile.scala:67)
at kafka.log.LogManager.$anonfun$checkpointLogStartOffsetsInDir$1(LogManager.scala:698)
at kafka.log.LogManager.$anonfun$checkpointLogStartOffsetsInDir$1$adapted(LogManager.scala:694)
at scala.Option.foreach(Option.scala:437)
at kafka.log.LogManager.checkpointLogStartOffsetsInDir(LogManager.scala:694)
at kafka.log.LogManager.$anonfun$shutdown$9(LogManager.scala:545)
at kafka.log.LogManager.$anonfun$shutdown$9$adapted(LogManager.scala:535)
at kafka.utils.Implicits$MapExtensionMethods$.$anonfun$forKeyValue$1(Implicits.scala:62)
at scala.collection.mutable.HashMap$Node.foreachEntry(HashMap.scala:633)
at scala.collection.mutable.HashMap.foreachEntry(HashMap.scala:499)
at kafka.log.LogManager.shutdown(LogManager.scala:535)
at kafka.server.KafkaServer.$anonfun$shutdown$18(KafkaServer.scala:701)
at kafka.utils.CoreUtils$.swallow(CoreUtils.scala:68)
at kafka.server.KafkaServer.shutdown(KafkaServer.scala:701)
at kafka.server.KafkaServer.startup(KafkaServer.scala:435)
at kafka.Kafka$.main(Kafka.scala:109)
at kafka.Kafka.main(Kafka.scala)
没有权限?在linux里面就chmod 777,windows就属性->安全里面设置,但是没用,然后网上说要清空zookeeper和kafka目录,都清了,也没用,还有说要退回到2.8.0或者在linux里面启动,这些应该可以,但是突然就像折腾一下,让3.0.0在windows里面启动
于是看到这一行报错:
at org.apache.kafka.common.utils.Utils.flushDir(Utils.java:953)
去看这个flushDir方法,在网上找源码:
/**
* Flushes dirty directories to guarantee crash consistency.
*
* Note: We don't fsync directories on Windows OS because otherwise it'll throw AccessDeniedException (KAFKA-13391)
*
* @throws IOException if flushing the directory fails.
*/
public static void flushDir(Path path) throws IOException {
if (path != null && !OperatingSystem.IS_WINDOWS && !OperatingSystem.IS_ZOS) {
try (FileChannel dir = FileChannel.open(path, StandardOpenOption.READ)) {
dir.force(true);
}
}
}
但是这个源码是新版的,旧版的没有下面的判断条件:
!OperatingSystem.IS_WINDOWS && !OperatingSystem.IS_ZOS
当然现在回头看新版的注释已经写的很清楚了,是个bug,编号13391,但是当时急于求成,没注意,心想抛异常就在下面这行:
FileChannel.open(path, StandardOpenOption.READ)
于是自己写个代码测试一下:
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Locale;
public class TestKafka {
public static void main(String[] args) throws IOException, URISyntaxException {
try (FileChannel dir = FileChannel.open(Paths.get(new URI("file:///D:/tmp/kafka-logs")), StandardOpenOption.READ)) {
dir.force(true);
}
// System.out.println(System.getProperty("os.name").toLowerCase(Locale.ROOT));
}
}
报错和kafka启动的一样:
Exception in thread "main" java.nio.file.AccessDeniedException: D:\tmp\kafka-logs
at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:89)
at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103)
at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108)
at java.base/sun.nio.fs.WindowsFileSystemProvider.newFileChannel(WindowsFileSystemProvider.java:116)
at java.base/java.nio.channels.FileChannel.open(FileChannel.java:292)
at java.base/java.nio.channels.FileChannel.open(FileChannel.java:345)
at TestKafka.main(TestKafka.java:11)
那就对了,就是这个报错的。
一开始还在想是不是OperatingSystem.IS_WINDOWS判断错误,当成了linux所以出错,应该是环境问题吧,我要验证一下,(虽然后来发现旧代码没有这个判断),于是用javaassit将class文件修改了,打印一下OperatingSystem.IS_WINDOWS。首先从jar包里面把这个class文件提出来,再用javaassit修改,但是发现不用提前class文件,只要把jar包放到类路径里面就行了:将kafka根目录下的kafka-clients-3.0.0.jar 这个jar包加入到javaassit项目里面,然后:
import java.lang.reflect.Method;
import java.util.Locale;
import javassist.*;
public class BytecodeManipulationDemo {
public static void main(String[] args) {
try {
// 获取ClassPool
ClassPool pool = ClassPool.getDefault();
// 加载目标类
CtClass ctClass = pool.get("org.apache.kafka.common.utils.Utils");
// 获取目标方法
CtMethod ctMethod = ctClass.getDeclaredMethod("flushDir");
// 在方法开始处插入日志代码
ctMethod.insertBefore("{System.out.println(OperatingSystem.IS_WINDOWS); }");
ctClass.writeFile("D:/");
} catch (Exception e) {
e.printStackTrace();
}
}
}
结果报错:
javassist.CannotCompileException: [source error] no such field: OperatingSystem/IS_WINDOWS
at javassist.CtBehavior.insertBefore(CtBehavior.java:806)
at javassist.CtBehavior.insertBefore(CtBehavior.java:766)
at BytecodeManipulationDemo.main(BytecodeManipulationDemo.java:19)
Caused by: compile error: no such field: OperatingSystem/IS_WINDOWS
at javassist.compiler.MemberResolver.lookupFieldByJvmName2(MemberResolver.java:288)
at javassist.compiler.TypeChecker.fieldAccess2(TypeChecker.java:941)
at javassist.compiler.TypeChecker.fieldAccess(TypeChecker.java:898)
at javassist.compiler.TypeChecker.atFieldRead(TypeChecker.java:831)
at javassist.compiler.TypeChecker.atExpr(TypeChecker.java:605)
at javassist.compiler.ast.Expr.accept(Expr.java:71)
at javassist.compiler.JvstTypeChecker.atMethodArgs(JvstTypeChecker.java:235)
at javassist.compiler.TypeChecker.atMethodCallCore(TypeChecker.java:763)
at javassist.compiler.TypeChecker.atCallExpr(TypeChecker.java:723)
at javassist.compiler.JvstTypeChecker.atCallExpr(JvstTypeChecker.java:170)
at javassist.compiler.ast.CallExpr.accept(CallExpr.java:49)
at javassist.compiler.CodeGen.doTypeCheck(CodeGen.java:266)
at javassist.compiler.CodeGen.atStmnt(CodeGen.java:360)
at javassist.compiler.ast.Stmnt.accept(Stmnt.java:53)
at javassist.compiler.CodeGen.atStmnt(CodeGen.java:381)
at javassist.compiler.ast.Stmnt.accept(Stmnt.java:53)
at javassist.compiler.Javac.compileStmnt(Javac.java:578)
at javassist.CtBehavior.insertBefore(CtBehavior.java:786)
... 2 more
现在看来是没有import,应该用全路径的类名,但是后来没有继续修改代码,干脆用path,这个方法参数应该是可以的:
ctMethod.insertBefore("{System.out.println(path); }");
运行成功,得到class文件,放到kafka的kafka-client-3.0.0.jar里面相应的位置,启动kafka,什么也没打印,找不到这个打印的路径。
难道是只输出日志,控制台的打印看不到?改成写文件总可以了吧:
ctMethod.insertBefore("{java.io.FileWriter writer=new java.io.FileWriter(\"z:/log.txt\");writer.write(path.toString());writer.close(); }");
这里用全路径名才行,不然像前面一样找不到字段 no such field,简单测试,就没用什么buffer等等,写class文件成功,但是放到jar包里面再启动kafka还是没用,文件没用写入也没用创建。就算事先创建一个空文件也没用,不写入。
然道jar包没生效?是不是在其他地方还有这个类,比如其他的jar包,比如kafka.jar kafka-server,jar等等,但是发现没有。
我写了个类来在文件夹下jar包里找类:
import java.io.File;
import java.io.IOException;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class FindClassInJars {
public static void main(String[] args) {
String directoryPath = "D:\\8\\kafka_2.13-3.0.0\\kafka_2.13-3.0.0\\libs"; // 替换为你的文件夹路径
String classNameToFind = "org/apache/kafka/common/utils/Utils.class"; // 替换为你要查找的类的路径
File dir = new File(directoryPath);
if (dir.isDirectory()) {
File[] files = dir.listFiles((d, name) -> name.endsWith(".jar"));
if (files != null) {
for (File file : files) {
try (JarFile jarFile = new JarFile(file)) {
JarEntry entry = jarFile.getJarEntry(classNameToFind);
if (entry != null) {
System.out.println("Found in: " + file.getAbsolutePath());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
} else {
System.out.println("The specified path is not a directory.");
}
}
}
我把libs目录全重命名了,果然启动就说类路径为空,根本不能启动。zk和kafka都不能启动。我是把旧的kafka-client-3.0.0.jar重命名成kafka-client-3.0.0.jar--- 但是文件还放在libs里面,那我干脆把新的和旧的jar包都删除,看行不行,报错找不到类,那就是旧的jar在作怪,把它放到libs文件夹外面,启动,打印了!成功。所以kafka加载的libs下面的所有的文件,不管你是不是jar结尾的文件,下一步就是修改代码了
然后我回头有看新版的代码,比较旧的代码,原来新代码是windows就不执行后面的dir.force(true);那我就把这个逻辑加进去:(不想升级kafka,折腾一下)
ctMethod.insertBefore("{if(System.getProperty(\"os.name\").toLowerCase(java.util.Locale.ROOT).startsWith(\"windows\")) return; }");
好了现在如果是windows就不会执行后面的
FileChannel.open(Paths.get(new URI("file:///D:/tmp/kafka-logs")), StandardOpenOption.READ)) {
也就不会报错
ok,现在把修改后的class放到jar包里面,重启kafka,kafka启动成功!
ok,最后还有一件事没忘,就是验证这个Partition参数:
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.errors.UnknownTopicOrPartitionException;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
public class KafkaProducerExample {
public static void main(String[] args) {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.RETRIES_CONFIG, 3); // 重试3次
props.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, 10000); // 最大阻塞时间10秒
props.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, 5000); // 请求超时时间5秒
KafkaProducer producer = new KafkaProducer<>(props);
String topic = "test241022";
int partition = 99; // 不存在的分区
String key = "key";
String value = "value";
ProducerRecord record = new ProducerRecord<>(topic, partition, key, value);
try {
RecordMetadata metadata = producer.send(record).get();
System.out.println("Message sent to partition " + metadata.partition() + " with offset " + metadata.offset());
} catch (ExecutionException e) {
if (e.getCause() instanceof UnknownTopicOrPartitionException) {
System.err.println("Error: Specified partition does not exist.");
} else {
e.printStackTrace();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
producer.close();
}
}
}
运行这个,分区号是不存在的一个99,运行后一直刷日子,10s后报错:
最后看到的报错:10s后超时报错,印证了我的猜想
org.apache.kafka.common.errors.TimeoutException: Topic test241022 not present in metadata after 10000 ms.
java.util.concurrent.ExecutionException: org.apache.kafka.common.errors.TimeoutException: Topic test241022 not present in metadata after 10000 ms.
at org.apache.kafka.clients.producer.KafkaProducer$FutureFailure.(KafkaProducer.java:1320)
at org.apache.kafka.clients.producer.KafkaProducer.doSend(KafkaProducer.java:989)
at org.apache.kafka.clients.producer.KafkaProducer.send(KafkaProducer.java:889)
at org.apache.kafka.clients.producer.KafkaProducer.send(KafkaProducer.java:775)
at KafkaProducerExample.main(KafkaProducerExample.java:31)
Caused by: org.apache.kafka.common.errors.TimeoutException: Topic test241022 not present in metadata after 10000 ms.
==========
==========
在使用 Kafka 进行消息传递时,尤其是在开发和测试环境中,我们经常会选择在本地机器上运行 Kafka 集群。有时候,我们会遇到一些特定的问题,比如在 Windows 系统上启动 Kafka 时遇到权限错误。这篇博客将详细讲解如何解决 Kafka 3.0.0 在 Windows 下不能启动的问题,并提供一个完整的解决方案。
Kafka 是一个分布式流处理平台,广泛用于实时数据流的处理和分析。然而,Kafka 的开发和运行环境主要是 Linux 系统,在 Windows 系统上运行 Kafka 可能会遇到一些特定的问题。例如,Kafka 3.0.0 在 Windows 系统上启动时可能会遇到以下错误:
plaintext
Copy
[2024-10-22 14:40:58,563] ERROR Disk error while writing recovery offsets checkpoint in directory Z:\tmp\kafka-logs: Error while writing to checkpoint file D:\tmp\kafka-logs\recovery-point-offset-checkpoint (kafka.log.LogManager)
[2024-10-22 14:40:58,567] ERROR Error while writing to checkpoint file D:\tmp\kafka-logs\log-start-offset-checkpoint (kafka.server.LogDirFailureChannel)
java.nio.file.AccessDeniedException: Z:\tmp\kafka-logs
at sun.nio.fs.WindowsException.translateToIOException(Unknown Source)
at sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source)
at sun.nio.fs.WindowsFileSystemProvider.newFileChannel(Unknown Source)
at java.nio.channels.FileChannel.open(Unknown Source)
at java.nio.channels.FileChannel.open(Unknown Source)
at org.apache.kafka.common.utils.Utils.flushDir(Utils.java:953)
at org.apache.kafka.common.utils.Utils.atomicMoveWithFallback(Utils.java:941)
at kafka.server.checkpoints.CheckpointFile.liftedTree1$1(CheckpointFile.scala:114)
at kafka.server.checkpoints.CheckpointFile.write(CheckpointFile.scala:92)
at kafka.server.checkpoints.OffsetCheckpointFile.write(OffsetCheckpointFile.scala:67)
at kafka.log.LogManager.$anonfun$checkpointLogStartOffsetsInDir$1(LogManager.scala:698)
at kafka.log.LogManager.$anonfun$checkpointLogStartOffsetsInDir$1$adapted(LogManager.scala:694)
at scala.Option.foreach(Option.scala:437)
at kafka.log.LogManager.checkpointLogStartOffsetsInDir(LogManager.scala:694)
at kafka.log.LogManager.$anonfun$shutdown$9(LogManager.scala:545)
at kafka.log.LogManager.$anonfun$shutdown$9$adapted(LogManager.scala:535)
at kafka.utils.Implicits$MapExtensionMethods$.$anonfun$forKeyValue$1(Implicits.scala:62)
at scala.collection.mutable.HashMap$Node.foreachEntry(HashMap.scala:633)
at scala.collection.mutable.HashMap.foreachEntry(HashMap.scala:499)
at kafka.log.LogManager.shutdown(LogManager.scala:535)
at kafka.server.KafkaServer.$anonfun$shutdown$18(KafkaServer.scala:701)
at kafka.utils.CoreUtils$.swallow(CoreUtils.scala:68)
at kafka.server.KafkaServer.shutdown(KafkaServer.scala:701)
at kafka.server.KafkaServer.startup(KafkaServer.scala:435)
at kafka.Kafka$.main(Kafka.scala:109)
at kafka.Kafka.main(Kafka.scala)
从错误信息中可以看出,问题出在 Kafka 在尝试写入日志目录时遇到了权限问题。具体来说,是在调用FileChannel.open
方法时抛出了AccessDeniedException
异常。经过进一步分析和查找 Kafka 源码,我们发现这是一个已知的 Bug,编号为 KAFKA-13391。
在新的 Kafka 版本中,已经通过在flushDir
方法中添加操作系统判断来避免这个问题:
java
Copy
public static void flushDir(Path path) throws IOException {
if (path != null && !OperatingSystem.IS_WINDOWS && !OperatingSystem.IS_ZOS) {
try (FileChannel dir = FileChannel.open(path, StandardOpenOption.READ)) {
dir.force(true);
}
}
}
然而,Kafka 3.0.0 版本的代码中并没有这个判断条件,导致在 Windows 系统上执行dir.force(true)
时抛出异常。
为了解决这个问题,我们可以使用 Java 字节码操作库(如 Javassist)来修改 Kafka 的源码,添加操作系统的判断条件。具体步骤如下:
kafka
获取 Kafka 源代码:
使用 Javassist 进行字节码修改:
Utils
类中的flushDir
方法,添加对 Windows 操作系统的判断,以避免在 Windows 上执行dir.force(true)
。以下是一个示例代码,展示如何使用 Javassist 修改flushDir
方法:
java
Copy
import javassist.*;
public class ModifyKafka {
public static void main(String[] args) {
try {
// 获取ClassPool
ClassPool pool = ClassPool.getDefault();
// 加载目标类
CtClass ctClass = pool.get("org.apache.kafka.common.utils.Utils");
// 获取目标方法
CtMethod ctMethod = ctClass.getDeclaredMethod("flushDir");
// 在方法开始处插入操作系统判断
ctMethod.insertBefore("{ if(System.getProperty(\"os.name\").toLowerCase().startsWith(\"windows\")) return; }");
// 写入修改后的类文件
ctClass.writeFile("D:/"); // 指定输出路径
} catch (Exception e) {
e.printStackTrace();
}
}
}
将修改后的类文件放入 JAR 包中:
jar
命令或其他工具将修改后的类文件重新打包到kafka-clients-3.0.0.jar
中。确保将其放置在正确的目录结构下。启动 Kafka:
为了验证修改是否成功,可以在 Kafka 的启动日志中查找相关信息,确保没有出现AccessDeniedException
错误。如果一切正常,Kafka 将能够顺利启动并运行。
权限设置:确保 Kafka 的日志目录(如Z:\tmp\kafka-logs
)具有适当的读写权限。可以通过右键点击文件夹,选择 “属性”,在 “安全” 选项卡中进行设置。
Kafka 配置:检查 Kafka 的配置文件(如server.properties
),确保所有路径设置正确。
Kafka 版本:如果在生产环境中使用 Kafka,建议使用最新版本,因为新版本通常会修复已知的 Bug 并提供性能改进。
测试:在修改和重新启动 Kafka 后,进行一些简单的生产者和消费者测试,确保消息能够成功发送和接收。
通过对 Kafka 3.0.0 的flushDir
方法进行字节码修改,我们成功解决了在 Windows 环境下启动 Kafka 时遇到的权限问题。这个过程虽然涉及一些技术细节,但通过适当的工具和方法,解决问题并不复杂。希望这篇博客能帮助到在 Windows 上使用 Kafka 的开发者们,顺利搭建自己的消息传递系统。如果在实施过程中遇到其他问题,欢迎随时讨论和交流。
在解决了 Kafka 3.0.0 在 Windows 下不能启动的问题后,我们可以进一步探讨一些优化和最佳实践,以确保 Kafka 的稳定运行和高效使用。
server.properties
中设置适当的日志级别。可以通过调整log4j.properties
文件来控制日志的详细程度。mirror-maker
工具来实现跨集群的备份。通过对 Kafka 3.0.0 在 Windows 下启动问题的深入分析和解决,我们不仅解决了当前的问题,还探讨了 Kafka 的使用最佳实践和优化策略。Kafka 作为一个强大的分布式消息系统,在正确配置和管理下,可以为你的应用程序提供高效的消息传递能力。
希望这篇博客能够为你在使用 Kafka 的过程中提供帮助和启发。如果你在实施中遇到任何问题,或者有其他相关问题,欢迎随时交流和讨论。
为了更好地使用 Kafka,了解其内部工作原理是非常重要的。以下是一些关键概念和机制:
acks=all
时,确保所有副本都确认收到消息后才返回成功。在使用 Kafka 时,可能会遇到一些常见问题。以下是一些解决方案和建议:
acks=all
配置,确保所有副本都确认消息;定期备份数据。enable.idempotence=true
),以确保每条消息只被写入一次。Kafka 是一个强大且灵活的分布式消息系统,适用于各种实时数据流处理场景。通过深入理解其架构、工作原理和最佳实践,我们可以更好地利用 Kafka 来构建高效、可靠的消息传递系统。
希望这篇博客能够为你在使用 Kafka 的过程中提供深入的见解和实用的建议。如果你有任何问题或经验分享,欢迎在评论区讨论。感谢你的阅读!