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,也可方便快捷的看到本人的博客哦,谢谢~

 

posted @   zhangdaopin  阅读(417)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示