问题描述

原先seaweedfs集群因为不规范的操作,导致几个卷中的数据不同步,在文件请求的时候如果请求被负载均衡到存在问题的卷上,就会导致404或者500错误。因此需要进行文件系统的检查与修复,因为修复看起来有点困难而且数据量也不大,大约只有几十GB,所以直接创建新的seaweedfs集群,通过脚本导出所有文件并且导入新集群。

检查

检查脚本如下

const VERBOSE = 1; // 1显示所有日志,0只显示错误的文件
const URL = "127.0.0.1"; // 检查的ip,发博客隐藏一下实际ip
const PORT = "80"; // 检查的端口
const STARTPATH = "/"; // 起始路径

const axios = require("axios");

async function getOriginList(url, port, startPath) {
  let data = null;
  await axios
    .get(`http://${url}:${port}${startPath}`, {
      headers: { Accept: "application/json" },
    })
    .then((res) => {
      if (VERBOSE == 1) console.log(`success path [200]: ${startPath}`);
      data = res.data;
    })
    .catch((err) =>
      console.log(`error path [${err.response.status}]: ${startPath}`)
    );
  return data;
}

function checkAllFile(url, port, startPath) {
  getOriginList(url, port, startPath)
    .then((originList) => {
      const folderList = [];
      if (!originList) return;
      originList.Entries.forEach((entry) => {
        if (entry.chunks) {
          // 有chunks说明是文件
          checkFileStatus(url, port, entry.FullPath).then();
        } else {
          // 所有文件夹都没有chunks
          folderList.push(entry.FullPath);
        }
      });
      folderList.forEach((folderPath) => {
        // 递归查询
        checkAllFile(url, port, folderPath);
      });
    })
    .catch((err) => {
      return;
    });
}
async function checkFileStatus(url, port, filePath) {
  // 发送请求
  // 状态码不为200,任务文件状态异常
  let clens = [];
  let code = [];
  for (let i = 0; i < 3; i++) {
    await axios
      .get(`http://${url}:${port}${filePath}`)
      .then((res) => {
        clens.push(res.headers["content-length"]);
        code.push(200);
      })
      .catch((err) => {
        clens.push(-1);
        try {
          code.push(err.response.status);
        } catch (error) {
          code.push(-1);
        }
      });
  }
  if (
    clens.length === 3 &&
    clens[0] !== -1 &&
    clens[0] === clens[1] &&
    clens[1] === clens[2]
  )
    if (VERBOSE == 1) console.log(`success: ${filePath}, length: ${clens[0]}`);
    else
      console.log(
        `
      error: ${filePath}
      content-length info:
      0[${code[0]}]: ${clens[0]}
      1[${code[1]}]: ${clens[1]}
      2[${code[2]}]: ${clens[2]}
      `
      );
}

checkAllFile(URL, PORT, STARTPATH);

执行脚本后如果有error输出说明文件系统存在问题。

迁移

经过检查,文件系统有大量的错误,但是好消息是所有文件都不是完全无法访问。因此下一步进行迁移。
首先先进入seaweedfs数据库,我使用navicat导出了filemeta表的filename和directory列。json的数据结构为

{
  RECORDS:[
    {
      name: string,
      directory: string
    },
    ...
  ]
}

接下来使用这个RECORD数组进行迁移。每次进行批量请求,并且上传到新的文件系统中。

require("events").EventEmitter.defaultMaxListeners = 0; // 防止内存限制报错
const axios = require("axios");
const FormData = require("form-data");
const fs = require("fs");
const batch = 500; // 批大小
const startIndex = 0; // 开始位置
const OLD_SERVER = {
  host: "172.0.0.1",
  port: 80,
};
const NEW_SERVER = {
  host: "172.0.0.1",
  port: 80,
};

console.log("loading filemeta...");
const rawFileMeta = fs.readFileSync("filemeta.json");
const fileMeta = JSON.parse(rawFileMeta)["RECORDS"];
console.log("load finish");

axios.interceptors.response.use(undefined, (err) => {
  var config = err.config;
  // 如果配置不存在或未设置重试选项,则拒绝
  if (!config || !config.retry) return Promise.reject(err);

  // 设置变量以跟踪重试次数
  config.__retryCount = config.__retryCount || 0;

  // 判断是否超过总重试次数
  if (config.__retryCount >= config.retry) {
    // 返回错误并退出自动重试
    return Promise.reject(err);
  }

  // 增加重试次数
  config.__retryCount += 1;

  // 创建新的Promise
  var backoff = new Promise((resolve) => {
    setTimeout(() => resolve(), config.retryDelay || 1);
  });

  // 返回重试请求
  return backoff.then(() => {
    return axios(config);
  });
});

/**
 * 因为数据库提取出来的数据包括了文件夹,在文件夹上上传文件会发生异常
 */
function isFile(res) {
  if (res.data.headers["transfer-encoding"] === "chunked") return false;
  else return true;
}

async function move(oldServer, newServer, path) {
  const res = await axios
    .get(`http://${oldServer.host}:${oldServer.port}${encodeURI(path)}`, {
      retry: 10,
      retryDelay: 1000,
      timeout: 10000,
      responseType: "stream",
    })
    .catch(() => {
      console.log("download fail");
      fs.appendFileSync("errorfile.list", `${path}\n`);
    });
  if (res && !isFile(res)) {
    return;
  }
  const formData = new FormData();
  formData.append("file", res.data);
  await axios
    .post(
      `http://${newServer.host}:${newServer.port}${encodeURI(path)}`,
      formData,
      {
        headers: formData.getHeaders(),
        retry: 0,
        retryDelay: 500,
        timeout: 10000,
      }
    )
    .then()
    .catch((err) => {
      console.log("upload fail");
      fs.appendFileSync("errorfile.list", `${path}\n`);
    });
}

async function start(startIndex) {
  let i = startIndex;
  for (; i < fileMeta.length; ) {
    const qlist = [];
    let j = 0;
    for (; j < batch; j++) {
      const q = move(
        OLD_SERVER,
        NEW_SERVER,
        `${fileMeta[i].directory}/${fileMeta[i].name}`
      );
      qlist.push(q);
      if (i < fileMeta.length) i++;
    }
    await Promise.all(qlist).catch((err) => {
      console.log(err);
      console.log(`error range: ${i - batch}-${i}`);
    });
    console.log(`${i}/${fileMeta.length}`);
  }
}

start(startIndex);

这段代码有很大的局限性,遇到15M以上的文件会上传失败,因此还需要另写一个脚本处理大文件。

最后再次进行测试


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!