MQTT - paho.client.mqttv3.persist.MqttDefaultFilePersistence.open(MqttDefaultFilePersistence.java:86
由于项目的需要,我们希望将现有的MqttAdapter移植到K8s集群环境中,以便于更好的管理和提供服务,
业务代码如下:
... ...
MqttClient client= new MqttClient(mqtt_address, http_header);
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(username);
options.setPassword(secret.toCharArray());
options.setAutomaticReconnect(true);
options.setMaxInflight(maxInflight);
client.setCallback(new MqttCallbackExtended() {
~ ~ ~
}
... ...
Dockerfile配置如下:
FROM java8:1205
# 作者
MAINTAINER daopinz
#设置工作目录,在该指令后的 RUN、CMD、ENTRYPOINT, COPY、ADD 指令都会在该目录执行。如果该目录不存在,则会创建!
WORKDIR /opt/plugin/mqttadapter/
# 将jar包添加到容器中并更名为app.jar
COPY mqttAdaptor-1.0.0-SNAPSHOT.jar app.jar
# 运行jar包
RUN bash -c 'mkdir logs' &&\
bash -c 'chmod 777 logs' &&\
bash -c 'touch mqtt-adapter.log' &&\
bash -c 'chmod 777 mqtt-adapter.log'
ENTRYPOINT ["java","-DfilePath=config","-Xms1024m","-Xmx1024m","-Xmn512m","-Xss256k","-server","-Djava.security.egd=file:/dev/./urandom","-jar","app.jar"]
我们还担心直接放到k8s环境会出问题,特意使用docker本地的环境,先跑一遍,问问题了,然后在编写RC或者Deployment来运行这个容器,但是还没高兴几秒钟,就给报了个启动失败的错误,如下:
MqttException (0)
at org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence.open(MqttDefaultFilePersistence.java:86)
at org.eclipse.paho.client.mqttv3.MqttAsyncClient.<init>(MqttAsyncClient.java:481)
at org.eclipse.paho.client.mqttv3.MqttAsyncClient.<init>(MqttAsyncClient.java:328)
at org.eclipse.paho.client.mqttv3.MqttAsyncClient.<init>(MqttAsyncClient.java:323)
at org.eclipse.paho.client.mqttv3.MqttClient.<init>(MqttClient.java:229)
at org.eclipse.paho.client.mqttv3.MqttClient.<init>(MqttClient.java:140)
at com.xxx.xxx.server.MqttAdaptor.start(MqttAdaptor.java:151)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:366)
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:311)
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:134)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:409)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1620)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:208)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:303)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107)
at com.xxx.xxx.Application.main(Application.java:11)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48)
at org.springframework.boot.loader.Launcher.launch(Launcher.java:87)
at org.springframework.boot.loader.Launcher.launch(Launcher.java:50)
at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)
刚开始,我还以为是代码的问题,改了好久,也找了网上很多的参考,确认代码写得没问题,但是她报错的地方就是这里:
client = new MqttClient(mqtt_address, http_header);
我们只是new了一个MqttClient 而已,什么都没做呢。。。看来是要往源码里找找答案了,可以看到抛出异常的第一句:
at org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence.open(MqttDefaultFilePersistence.java:86)
看起来说的是 MqttDefaultFilePersistence.open方法内的86行,抛出的这个异常,那我们吧拉吧啦这段代码瞅瞅:
/*******************************************************************************
* Copyright (c) 2009, 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Dave Locke - initial API and implementation and/or initial documentation
*/
package org.eclipse.paho.client.mqttv3.persist;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Vector;
import org.eclipse.paho.client.mqttv3.MqttClientPersistence;
import org.eclipse.paho.client.mqttv3.MqttPersistable;
import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
import org.eclipse.paho.client.mqttv3.internal.FileLock;
import org.eclipse.paho.client.mqttv3.internal.MqttPersistentData;
/**
* An implementation of the {@link MqttClientPersistence} interface that provides
* file based persistence.
*
* A directory is specified when the Persistence object is created. When the persistence
* is then opened (see {@link #open(String, String)}), a sub-directory is made beneath the base
* for this client ID and connection key. This allows one persistence base directory
* to be shared by multiple clients.
*
* The sub-directory's name is created from a concatenation of the client ID and connection key
* with any instance of '/', '\\', ':' or ' ' removed.
*/
public class MqttDefaultFilePersistence implements MqttClientPersistence {
private static final String MESSAGE_FILE_EXTENSION = ".msg";
private static final String MESSAGE_BACKUP_FILE_EXTENSION = ".bup";
private static final String LOCK_FILENAME = ".lck";
private File dataDir;
private File clientDir = null;
private FileLock fileLock = null;
//TODO
private static FilenameFilter FILENAME_FILTER;
private static FilenameFilter getFilenameFilter(){
if(FILENAME_FILTER == null){
FILENAME_FILTER = new PersistanceFileNameFilter(MESSAGE_FILE_EXTENSION);
}
return FILENAME_FILTER;
}
public MqttDefaultFilePersistence() { //throws MqttPersistenceException {
this(System.getProperty("user.dir")); 在这里会去运行环境中找运行的目录属性
}
/**
* Create an file-based persistent data store within the specified directory.
* @param directory the directory to use.
*/
public MqttDefaultFilePersistence(String directory) { //throws MqttPersistenceException {
dataDir = new File(directory);
}
public void open(String clientId, String theConnection) throws MqttPersistenceException {
if (dataDir.exists() && !dataDir.isDirectory()) {
throw new MqttPersistenceException();
} else if (!dataDir.exists() ) {
if (!dataDir.mkdirs()) {
throw new MqttPersistenceException();
}
}
if (!dataDir.canWrite()) {
throw new MqttPersistenceException(); 其实就是这里在启动的时候直接抛异常,导致启动失败了
}
StringBuffer keyBuffer = new StringBuffer();
for (int i=0;i<clientId.length();i++) {
char c = clientId.charAt(i);
if (isSafeChar(c)) {
keyBuffer.append(c);
}
}
keyBuffer.append("-");
for (int i=0;i<theConnection.length();i++) {
char c = theConnection.charAt(i);
if (isSafeChar(c)) {
keyBuffer.append(c);
}
}
synchronized (this) {
if (clientDir == null) {
String key = keyBuffer.toString();
clientDir = new File(dataDir, key);
if (!clientDir.exists()) {
clientDir.mkdir();
}
}
try {
fileLock = new FileLock(clientDir, LOCK_FILENAME);
} catch (Exception e) {
// TODO - This shouldn't be here according to the interface
// See https://github.com/eclipse/paho.mqtt.java/issues/178
//throw new MqttPersistenceException(MqttPersistenceException.REASON_CODE_PERSISTENCE_IN_USE);
}
// Scan the directory for .backup files. These will
// still exist if the JVM exited during addMessage, before
// the new message was written to disk and the backup removed.
restoreBackups(clientDir);
}
}
/**
* Checks whether the persistence has been opened.
* @throws MqttPersistenceException if the persistence has not been opened.
*/
private void checkIsOpen() throws MqttPersistenceException {
if (clientDir == null) {
throw new MqttPersistenceException();
}
}
public void close() throws MqttPersistenceException {
synchronized (this) {
// checkIsOpen();
if (fileLock != null) {
fileLock.release();
}
if (getFiles().length == 0) {
clientDir.delete();
}
clientDir = null;
}
}
/**
* Writes the specified persistent data to the previously specified persistence directory.
* This method uses a safe overwrite policy to ensure IO errors do not lose messages.
* @param message The {@link MqttPersistable} message to be persisted
* @throws MqttPersistenceException if an exception occurs whilst persisting the message
*/
public void put(String key, MqttPersistable message) throws MqttPersistenceException {
checkIsOpen();
File file = new File(clientDir, key+MESSAGE_FILE_EXTENSION);
File backupFile = new File(clientDir, key+MESSAGE_FILE_EXTENSION+MESSAGE_BACKUP_FILE_EXTENSION);
if (file.exists()) {
// Backup the existing file so the overwrite can be rolled-back
boolean result = file.renameTo(backupFile);
if (!result) {
backupFile.delete();
file.renameTo(backupFile);
}
}
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(message.getHeaderBytes(), message.getHeaderOffset(), message.getHeaderLength());
if (message.getPayloadBytes()!=null) {
fos.write(message.getPayloadBytes(), message.getPayloadOffset(), message.getPayloadLength());
}
fos.getFD().sync();
fos.close();
if (backupFile.exists()) {
// The write has completed successfully, delete the backup
backupFile.delete();
}
}
catch (IOException ex) {
throw new MqttPersistenceException(ex);
}
finally {
if (backupFile.exists()) {
// The write has failed - restore the backup
boolean result = backupFile.renameTo(file);
if (!result) {
file.delete();
backupFile.renameTo(file);
}
}
}
}
public MqttPersistable get(String key) throws MqttPersistenceException {
checkIsOpen();
MqttPersistable result;
try {
File file = new File(clientDir, key+MESSAGE_FILE_EXTENSION);
FileInputStream fis = new FileInputStream(file);
int size = fis.available();
byte[] data = new byte[size];
int read = 0;
while (read<size) {
read += fis.read(data,read,size-read);
}
fis.close();
result = new MqttPersistentData(key, data, 0, data.length, null, 0, 0);
}
catch(IOException ex) {
throw new MqttPersistenceException(ex);
}
return result;
}
/**
* Deletes the data with the specified key from the previously specified persistence directory.
*/
public void remove(String key) throws MqttPersistenceException {
checkIsOpen();
File file = new File(clientDir, key+MESSAGE_FILE_EXTENSION);
if (file.exists()) {
file.delete();
}
}
/**
* Returns all of the persistent data from the previously specified persistence directory.
* @return all of the persistent data from the persistence directory.
* @throws MqttPersistenceException if an exception is thrown whilst getting the keys
*/
public Enumeration keys() throws MqttPersistenceException {
checkIsOpen();
File[] files = getFiles();
Vector result = new Vector(files.length);
for (int i=0;i<files.length;i++) {
String filename = files[i].getName();
String key = filename.substring(0,filename.length()-MESSAGE_FILE_EXTENSION.length());
result.addElement(key);
}
return result.elements();
}
private File[] getFiles() throws MqttPersistenceException {
checkIsOpen();
File[] files = clientDir.listFiles(getFilenameFilter());
if (files == null) {
throw new MqttPersistenceException();
}
return files;
}
private boolean isSafeChar(char c) {
return Character.isJavaIdentifierPart(c) || c=='-';
}
/**
* Identifies any backup files in the specified directory and restores them
* to their original file. This will overwrite any existing file of the same
* name. This is safe as a stray backup file will only exist if a problem
* occured whilst writing to the original file.
* @param dir The directory in which to scan and restore backups
*/
private void restoreBackups(File dir) throws MqttPersistenceException {
File[] files = dir.listFiles(new PersistanceFileFilter(MESSAGE_BACKUP_FILE_EXTENSION));
if (files == null) {
throw new MqttPersistenceException();
}
for (int i=0;i<files.length;i++) {
File originalFile = new File(dir,files[i].getName().substring(0,files[i].getName().length()-MESSAGE_BACKUP_FILE_EXTENSION.length()));
boolean result = files[i].renameTo(originalFile);
if (!result) {
originalFile.delete();
files[i].renameTo(originalFile);
}
}
}
public boolean containsKey(String key) throws MqttPersistenceException {
checkIsOpen();
File file = new File(clientDir, key+MESSAGE_FILE_EXTENSION);
return file.exists();
}
public void clear() throws MqttPersistenceException {
checkIsOpen();
File[] files = getFiles();
for (int i=0; i<files.length; i++) {
files[i].delete();
}
clientDir.delete();
}
}
可以看到我在上面加的两行中文注释,可以发现这个代码在运行的时候,是会去读取运行环境的属性,是否是可以写文件的
if (!dataDir.canWrite())
如果不可以,就会抛出异常而直接退出了
throw new MqttPersistenceException();
解决方法也是很简单的,对症下药就好了啊:改一下运行环境的目录属性,给它可读可写可执行权限即可:
FROM java8:1205
# 作者
MAINTAINER daopinz
#设置工作目录,在该指令后的 RUN、CMD、ENTRYPOINT, COPY、ADD 指令都会在该目录执行。如果该目录不存在,则会创建!
WORKDIR /opt/plugin/mqttadapter/
# 将jar包添加到容器中并更名为app.jar
COPY mqttAdaptor-1.0.0-SNAPSHOT.jar app.jar
# 运行jar包
RUN bash -c 'mkdir logs' &&\
bash -c 'chmod 777 -R /opt/plugin/mqttadapter'
ENTRYPOINT ["java","-Xms1024m","-Xmx1024m","-Xmn512m","-Xss256k","-server","-Djava.security.egd=file:/dev/./urandom","-jar","app.jar"]
微信公众号,搜索:zhangdaopin,也可方便快捷的看到本人的博客哦,谢谢~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南