负载均衡服务器如何保证上传文件同步

负载服务器Z,应用服务器A 和B ,从A上传的附件,如何在B上下载下来?

这个问题我的解决思路如下:
  服务器A、B 上传附件的时候,将这个附件备份到服务器Z ,当A、B下载文件的时候,首先会在自己服务器的目录下寻找,如果找不到,就会从服务器Z 上下载一份到当前服务器。

这里主要介绍一下重写上传、下载的方法时应该添加的代码

上传文件,异步操作

new Thread(() -> {
   SftpCustom fu = new SftpCustom();
   fu.upload(file.getAbsolutePath(), getFileName(fileDescr));
   fu.closeChannel();
}).start();

下载文件,先从当前服务器寻找

String tmpPath = roots[0].getPath() + '/' + getFileName(fileDescr);
File file2 = new File(tmpPath);
if (file2.exists()) {
    return FileUtils.openInputStream(file2);
}

SftpCustom fu = new SftpCustom();
fu.download(getFileName(fileDescr), tmpPath);
file2 = new File(tmpPath);
inputStream = FileUtils.openInputStream(file2);
fu.closeChannel();
return inputStream;

cuba 框架中重写上传文件类FileStorage.java 的代码如下:

package com.haulmont.cuba.core.app.custom;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.haulmont.cuba.core.app.FileStorageAPI;
import com.haulmont.cuba.core.app.ServerConfig;
import com.haulmont.cuba.core.entity.FileDescriptor;
import com.haulmont.cuba.core.global.*;
import com.haulmont.cuba.core.sys.AppContext;
import com.haulmont.cuba.core.sys.SecurityContext;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static com.haulmont.bali.util.Preconditions.checkNotNullArgument;

public class FileStorage implements FileStorageAPI {

    private final Logger log = LoggerFactory.getLogger(FileStorage.class);

    @Inject
    protected UserSessionSource userSessionSource;

    @Inject
    protected TimeSource timeSource;

    @Inject
    protected Configuration configuration;

    protected boolean isImmutableFileStorage;

    protected ExecutorService writeExecutor = Executors.newFixedThreadPool(5,
            new ThreadFactoryBuilder().setNameFormat("FileStorageWriter-%d").build());

    protected volatile File[] storageRoots;

    @PostConstruct
    public void init() {
        this.isImmutableFileStorage = configuration.getConfig(ServerConfig.class).getImmutableFileStorage();
    }

    /**
     * INTERNAL. Don't use in application code.
     */
    public File[] getStorageRoots() {
        if (storageRoots == null) {
            String conf = configuration.getConfig(ServerConfig.class).getFileStorageDir();
            if (StringUtils.isBlank(conf)) {
                String dataDir = configuration.getConfig(GlobalConfig.class).getDataDir();
                File dir = new File(dataDir, "filestorage");
                dir.mkdirs();
                storageRoots = new File[]{dir};
            } else {
                List<File> list = new ArrayList<>();
                for (String str : conf.split(",")) {
                    str = str.trim();
                    if (!StringUtils.isEmpty(str)) {
                        File file = new File(str);
                        if (!list.contains(file))
                            list.add(file);
                    }
                }
                storageRoots = list.toArray(new File[list.size()]);
            }
        }
        return storageRoots;
    }

    @Override
    public long saveStream(final FileDescriptor fileDescr, final InputStream inputStream) throws FileStorageException {
        checkFileDescriptor(fileDescr);

        File[] roots = getStorageRoots();

        // Store to primary storage

        checkStorageDefined(roots, fileDescr);
        checkPrimaryStorageAccessible(roots, fileDescr);

        File dir = getStorageDir(roots[0], fileDescr);
        dir.mkdirs();
        checkDirectoryExists(dir);

        final File file = new File(dir, getFileName(fileDescr));
        checkFileExists(file);

        long size = 0;
        OutputStream os = null;
        try {
            os = FileUtils.openOutputStream(file);
            size = IOUtils.copyLarge(inputStream, os);
            os.flush();
            writeLog(file, false);

            new Thread(() -> {
                SftpCustom fu = new SftpCustom();
                fu.upload(file.getAbsolutePath(), getFileName(fileDescr));
                fu.closeChannel();
            }).start();

        } catch (IOException e) {
            IOUtils.closeQuietly(os);
            FileUtils.deleteQuietly(file);

            throw new FileStorageException(FileStorageException.Type.IO_EXCEPTION, file.getAbsolutePath(), e);
        } finally {
            IOUtils.closeQuietly(os);
        }

        // Copy file to secondary storages asynchronously

        final SecurityContext securityContext = AppContext.getSecurityContext();
        for (int i = 1; i < roots.length; i++) {
            if (!roots[i].exists()) {
                log.error("Error saving {} into {} : directory doesn't exist", fileDescr, roots[i]);
                continue;
            }

            File copyDir = getStorageDir(roots[i], fileDescr);
            final File fileCopy = new File(copyDir, getFileName(fileDescr));

            writeExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        AppContext.setSecurityContext(securityContext);
                        FileUtils.copyFile(file, fileCopy, true);
                        writeLog(fileCopy, false);
                    } catch (Exception e) {
                        log.error("Error saving {} into {} : {}", fileDescr, fileCopy.getAbsolutePath(), e.getMessage());
                    } finally {
                        AppContext.setSecurityContext(null);
                    }
                }
            });
        }

        return size;
    }

    protected void checkFileExists(File file) throws FileStorageException {
        if (file.exists() && isImmutableFileStorage)
            throw new FileStorageException(FileStorageException.Type.FILE_ALREADY_EXISTS, file.getAbsolutePath());
    }

    protected void checkDirectoryExists(File dir) throws FileStorageException {
        if (!dir.exists())
            throw new FileStorageException(FileStorageException.Type.STORAGE_INACCESSIBLE, dir.getAbsolutePath());
    }

    protected void checkPrimaryStorageAccessible(File[] roots, FileDescriptor fileDescr) throws FileStorageException {
        if (!roots[0].exists()) {
            log.error("Inaccessible primary storage at {}", roots[0]);
            throw new FileStorageException(FileStorageException.Type.STORAGE_INACCESSIBLE, fileDescr.getId().toString());
        }
    }

    protected void checkStorageDefined(File[] roots, FileDescriptor fileDescr) throws FileStorageException {
        if (roots.length == 0) {
            log.error("No storage directories defined");
            throw new FileStorageException(FileStorageException.Type.STORAGE_INACCESSIBLE, fileDescr.getId().toString());
        }
    }

    @Override
    public void saveFile(final FileDescriptor fileDescr, final byte[] data) throws FileStorageException {
        checkNotNullArgument(data, "File content is null");
        saveStream(fileDescr, new ByteArrayInputStream(data));
    }

    protected synchronized void writeLog(File file, boolean remove) {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

        StringBuilder sb = new StringBuilder();
        sb.append(df.format(timeSource.currentTimestamp())).append(" ");
        sb.append("[").append(userSessionSource.getUserSession().getUser()).append("] ");
        sb.append(remove ? "REMOVE" : "CREATE").append(" ");
        sb.append("\"").append(file.getAbsolutePath()).append("\"\n");

        File rootDir;
        try {
            rootDir = file.getParentFile().getParentFile().getParentFile().getParentFile();
        } catch (NullPointerException e) {
            log.error("Unable to write log: invalid file storage structure", e);
            return;
        }
        File logFile = new File(rootDir, "storage.log");
        try {
            try (FileOutputStream fos = new FileOutputStream(logFile, true)) {
                IOUtils.write(sb.toString(), fos, StandardCharsets.UTF_8.name());
            }
        } catch (IOException e) {
            log.error("Unable to write log", e);
        }
    }

    @Override
    public void removeFile(FileDescriptor fileDescr) throws FileStorageException {
        checkFileDescriptor(fileDescr);

        File[] roots = getStorageRoots();
        if (roots.length == 0) {
            log.error("No storage directories defined");
            return;
        }

        for (File root : roots) {
            File dir = getStorageDir(root, fileDescr);
            File file = new File(dir, getFileName(fileDescr));
            if (file.exists()) {
                if (!file.delete()) {
                    throw new FileStorageException(FileStorageException.Type.IO_EXCEPTION, "Unable to delete file " + file.getAbsolutePath());
                } else {
                    writeLog(file, true);
                }
            }
        }
    }

    protected void checkFileDescriptor(FileDescriptor fd) {
        if (fd == null || fd.getCreateDate() == null) {
            throw new IllegalArgumentException("A FileDescriptor instance with populated 'createDate' attribute must be provided");
        }
    }

    @Override
    public InputStream openStream(FileDescriptor fileDescr) throws FileStorageException {
        checkFileDescriptor(fileDescr);

        File[] roots = getStorageRoots();
        if (roots.length == 0) {
            log.error("No storage directories available");
            throw new FileStorageException(FileStorageException.Type.FILE_NOT_FOUND, fileDescr.getId().toString());
        }

        InputStream inputStream = null;
        for (File root : roots) {
            File dir = getStorageDir(root, fileDescr);

            File file = new File(dir, getFileName(fileDescr));
            if (!file.exists()) {
                log.error("File " + file + " not found");
                continue;
            }

            try {
                inputStream = FileUtils.openInputStream(file);
                break;
            } catch (IOException e) {
                log.error("Error opening input stream for " + file, e);
            }
        }
        if (inputStream != null) {
            return inputStream;
        } else {
            try {
                String tmpPath = roots[0].getPath() + '/' + getFileName(fileDescr);
                File file2 = new File(tmpPath);
                if (file2.exists()) {
                    return FileUtils.openInputStream(file2);
                }

                SftpCustom fu = new SftpCustom();
                fu.download(getFileName(fileDescr), tmpPath);
                file2 = new File(tmpPath);
                inputStream = FileUtils.openInputStream(file2);
                fu.closeChannel();
                return inputStream;
            } catch (Exception e) {
                throw new FileStorageException(FileStorageException.Type.FILE_NOT_FOUND, fileDescr.getId().toString());
            }
        }
    }

    @Override
    public byte[] loadFile(FileDescriptor fileDescr) throws FileStorageException {
        InputStream inputStream = openStream(fileDescr);
        try {
            return IOUtils.toByteArray(inputStream);
        } catch (IOException e) {
            throw new FileStorageException(FileStorageException.Type.IO_EXCEPTION, fileDescr.getId().toString(), e);
        } finally {
            IOUtils.closeQuietly(inputStream);
        }
    }

    @Override
    public boolean fileExists(FileDescriptor fileDescr) {
        checkFileDescriptor(fileDescr);

        File[] roots = getStorageRoots();
        for (File root : roots) {
            File dir = getStorageDir(root, fileDescr);
            File file = new File(dir, getFileName(fileDescr));
            if (file.exists()) {
                return true;
            }
        }
        return false;
    }

    /**
     * INTERNAL. Don't use in application code.
     */
    public File getStorageDir(File rootDir, FileDescriptor fileDescriptor) {
        checkNotNullArgument(rootDir);
        checkNotNullArgument(fileDescriptor);

        Calendar cal = Calendar.getInstance();
        cal.setTime(fileDescriptor.getCreateDate());
        int year = cal.get(Calendar.YEAR);
        int month = cal.get(Calendar.MONTH) + 1;
        int day = cal.get(Calendar.DAY_OF_MONTH);

        return new File(rootDir, year + "/"
                + StringUtils.leftPad(String.valueOf(month), 2, '0') + "/"
                + StringUtils.leftPad(String.valueOf(day), 2, '0'));
    }

    public static String getFileName(FileDescriptor fileDescriptor) {
        return fileDescriptor.getId().toString() + "." + fileDescriptor.getExtension();
    }

    @PreDestroy
    protected void stopWriteExecutor() {
        writeExecutor.shutdown();
    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FastDFS 是一个高性能、轻量级的分布式文件系统,它主要用于存储大规模的文件。在 FastDFS 中,文件同步速度可以通过多种参数进行优化,以下是一些常用的参数: 1. tracker_server:Tracker 服务器地址,可以设置多个,用逗号分隔。Tracker 服务器是 FastDFS 的中心控制节点,负责协调存储节点之间的文件同步负载均衡等工作。 2. tracker_port:Tracker 服务器端口号,默认为 22122。如果 Tracker 服务器与存储节点不在同一台机器上,则需要设置该参数。 3. anti_steal_token:是否启用防盗链功能,默认为 false。如果开启防盗链功能,则需要在客户端请求文件时携带一个 token,否则请求会被拒绝。 4. secret_key:防盗链 token 的密钥,需要与 Tracker 服务器上的 secret_key 保持一致。 5. group_name:存储组名称,可以设置多个,用逗号分隔。存储组是 FastDFS 存储节点的逻辑分组,用于实现存储节点的负载均衡和故障转移。 6. connect_timeout:连接超时时间,单位为毫秒,默认为 2000 毫秒。 7. network_timeout:网络超时时间,单位为毫秒,默认为 3000 毫秒。 8. charset:字符集,默认为 UTF-8。 9. http.tracker_http_port:Tracker HTTP 端口号,默认为 8080。如果需要使用 HTTP 协议上传和下载文件,则需要设置该参数。 10. http.anti_steal_token:是否启用 HTTP 防盗链功能,默认为 false。如果开启 HTTP 防盗链功能,则需要在客户端请求文件时携带一个 token,否则请求会被拒绝。 11. http.secret_key:HTTP 防盗链 token 的密钥,需要与 Tracker 服务器上的 secret_key 保持一致。 可以通过调整这些参数来优化 FastDFS 的文件同步速度。具体的优化策略需要根据实际情况进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值