SeaweedFSの簡単なセットアップと利用

ユーザーがアップロードする写真や添付ファイルのような多数の小さなファイルを保存する必要があるため、SeaweedFSの導入を検討しました。 以下に、セットアップ手順と基本的なファイル操作のためのC#コード例を示します。

SeaweedFSのセットアップ

SeaweedFSは、公式GitHubリポジトリからリリース版をダウンロードして構築できます。

1. マスターノードの起動

まず、コマンドプロンプトでweed.exeのあるディレクトリに移動し、以下のコマンドを実行してマスターノードを起動します。

weed master

正常に起動すると、Webブラウザでhttp://localhost:9333にアクセスして、マスターノードの管理画面を確認できます。

2. ボリュームノードの起動

次に、データ保存用のボリュームノードを起動します。マスターノードと同様ですが、パラメータが異なります。

weed volume -dir="C:\seaweedfs\data" -max=5 -mserver="localhost:9333" -port=9090 -index=leveldb
  • -dir: ファイルの保存ディレクトリを指定します。
  • -max: ボリュームごとの最大ファイル数を設定します。
  • -mserver: マスターノードのアドレスを指定します。
  • -port: ボリュームノードのポート番号を指定します。
  • -index=leveldb: インデックスをLevelDBに保存し、ディスクに永続化します。メモリ上に保存する場合はmemoryを指定します。

ボリュームノードが起動すると、マスターノードの管理画面(http://localhost:9333)にその情報が表示されます。 複数のボリュームノードを起動することも可能で、その場合は異なるポート番号を指定します。

weed volume -dir="C:\seaweedfs\data2" -max=5 -mserver="localhost:9333" -port=9091 -index=leveldb

これで、マスターノードとボリュームノードの基本的なセットアップが完了しました。

C#によるファイル操作

以下は、SeaweedFSとの連携を行うための基本的なC#クラスです。ファイルアップロード、ダウンロード、更新、削除などの操作を実装しています。


using System;
using System.IO;
using System.Net.Http;
using System.Net;
using RestSharp;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

// SeaweedFSのAPIエンドポイント定義(一部抜粋)
public static class RequestPathStrategy
{
    public const string AssignFileKey = "/submit";
    public const string LookupVolume = "/vol/lookup";
}

// ファイルキー取得結果
public class AssignFileKeyResult
{
    [JsonProperty("fid")]
    public string FId { get; set; }
    [JsonProperty("publicUrl")]
    public string PublicUrl { get; set; }
    [JsonProperty("url")]
    public string Url { get; set; }
    [JsonProperty("granted")]
    public bool Granted { get; set; }
}

// ボリューム検索結果
public class LookupVolumeResult
{
    [JsonProperty("locations")]
    public VolumeLocation[] Locations { get; set; }
}

public class VolumeLocation
{
    [JsonProperty("publicUrl")]
    public string PublicUrl { get; set; }
    [JsonProperty("url")]
    public string Url { get; set; }
    [JsonProperty("lastModified")]
    public DateTime LastModified { get; set; }
    [JsonProperty("size")]
    public long Size { get; set; }
}

// ファイルハンドル情報
public class FileHandleStatus
{
    public string FileId { get; set; }
    public long Size { get; set; }
    public string TargetUrl { get; set; }

    public FileHandleStatus(string fileId, long size, string targetUrl)
    {
        FileId = fileId;
        Size = size;
        TargetUrl = targetUrl;
    }
}

// バイト配列レスポンス
public class BytesResponse
{
    public byte[] bytes { get; set; }
}

// JSONレスポンスラッパー
public class JsonResponse
{
    public string Json { get; set; }
    public HttpStatusCode StatusCode { get; set; }

    public JsonResponse(string json, HttpStatusCode statusCode)
    {
        Json = json;
        StatusCode = statusCode;
    }
}

public interface ISeaweedFSService
{
    FileHandleStatus SaveFileByStream(string fileName, Stream stream, out string errMsg);
    FileHandleStatus UpdateFileByStream(string fileId, string fileName, Stream stream, out string errMsg);
    BytesResponse GetFileBytes(string fileId);
    bool DeleteFile(string fileId, out string errMsg);
}

public class SeaweedFSService : ISeaweedFSService
{
    private readonly string _masterUrl;
    private readonly bool _usePublicUrl;

    // 設定サービスなどからマスターURLと公開URL使用フラグを取得
    public SeaweedFSService(string masterUrl, bool usePublicUrl = false)
    {
        _masterUrl = masterUrl;
        _usePublicUrl = usePublicUrl;
    }

    // ファイルキーを割り当てる
    private AssignFileKeyResult AssignFileKey()
    {
        var url = $"{_masterUrl}{RequestPathStrategy.AssignFileKey}";
        var client = new RestClient(url);
        var request = new RestRequest("", Method.GET);
        IRestResponse<AssignFileKeyResult> response = client.Execute<AssignFileKeyResult>(request);

        if (response.IsSuccessful && response.Data != null)
        {
            return response.Data;
        }
        else
        {
            // エラーハンドリング
            return null;
        }
    }

    // ファイルをストリームから保存する
    public FileHandleStatus SaveFileByStream(string fileName, Stream stream, out string errMsg)
    {
        errMsg = string.Empty;
        var fileKeyResult = AssignFileKey();
        if (fileKeyResult == null)
        {
            errMsg = "Failed to get file key.";
            return null;
        }

        var uploadUrl = _usePublicUrl ? fileKeyResult.PublicUrl : fileKeyResult.Url;
        var fullUploadUrl = $"http://{uploadUrl}/{fileKeyResult.FId}";

        var size = UploadFile(fullUploadUrl, fileKeyResult.FId, fileName, stream, out errMsg);
        if (size > 0)
        {
            return new FileHandleStatus(fileKeyResult.FId, size, uploadUrl);
        }
        else
        {
            return null;
        }
    }

    // ファイルをストリームから更新する
    public FileHandleStatus UpdateFileByStream(string fileId, string fileName, Stream stream, out string errMsg)
    {
        errMsg = string.Empty;
        var targetUrl = GetVolumeUrl(fileId);
        if (string.IsNullOrEmpty(targetUrl))
        {
            errMsg = "Failed to get volume URL for file update.";
            return null;
        }

        var fullUploadUrl = $"http://{targetUrl}/{fileId}";
        var size = UploadFile(fullUploadUrl, fileId, fileName, stream, out errMsg);

        if (size > 0)
        {
            return new FileHandleStatus(fileId, size, targetUrl);
        }
        else
        {
            return null;
        }
    }

    // ファイルの内容をバイト配列で取得する
    public BytesResponse GetFileBytes(string fileId)
    {
        var targetUrl = GetVolumeUrl(fileId);
        if (string.IsNullOrEmpty(targetUrl))
        {
            return null;
        }

        var downloadUrl = $"http://{targetUrl}/{fileId}";
        try
        {
            var client = new RestClient(downloadUrl);
            var request = new RestRequest("", Method.GET);
            byte[] fileBytes = client.DownloadData(request);
            return new BytesResponse { bytes = fileBytes };
        }
        catch (Exception ex)
        {
            // エラーログ記録など
            return null;
        }
    }

    // ファイルを削除する
    public bool DeleteFile(string fileId, out string errMsg)
    {
        errMsg = string.Empty;
        var targetUrl = GetVolumeUrl(fileId);
        if (string.IsNullOrEmpty(targetUrl))
        {
            errMsg = "Failed to get volume URL for file deletion.";
            return false;
        }

        var deleteUrl = $"http://{targetUrl}/{fileId}";
        try
        {
            var client = new RestClient(deleteUrl);
            var request = new RestRequest("", Method.DELETE);
            var response = client.Execute(request);

            // 200 OK または 404 Not Found を成功とみなす(既に削除されている場合)
            if (response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.NotFound)
            {
                return true;
            }
            else
            {
                errMsg = $"Deletion failed. Status code: {response.StatusCode}, Response: {response.Content}";
                return false;
            }
        }
        catch (Exception ex)
        {
            errMsg = $"An error occurred during deletion: {ex.Message}";
            return false;
        }
    }

    // fileIdに基づいてボリュームのURLを取得する
    private string GetVolumeUrl(string fileId)
    {
        var lookupUrl = $"{_masterUrl}{RequestPathStrategy.LookupVolume}?volumeId={fileId}";
        try
        {
            var client = new RestClient(lookupUrl);
            var request = new RestRequest("", Method.GET);
            var response = client.Execute(request);

            if (response.IsSuccessful && !string.IsNullOrEmpty(response.Content))
            {
                var lookupResult = JsonConvert.DeserializeObject<LookupVolumeResult>(response.Content);
                if (lookupResult?.Locations != null && lookupResult.Locations.Length > 0)
                {
                    var location = lookupResult.Locations[0];
                    return _usePublicUrl ? location.PublicUrl : location.Url;
                }
            }
        }
        catch (Exception ex)
        {
            // エラーログ記録など
        }
        return string.Empty;
    }

    // ファイルを実際にアップロードする
    private long UploadFile(string uploadUrl, string fileId, string filename, Stream inputStream, out string outErrMsg)
    {
        outErrMsg = string.Empty;
        try
        {
            var client = new RestClient(uploadUrl);
            var request = new RestRequest("", Method.POST);

            byte[] fileBytes = new byte[inputStream.Length];
            inputStream.Read(fileBytes, 0, fileBytes.Length);
            inputStream.Seek(0, SeekOrigin.Begin); // ストリームの位置をリセット

            request.AddFile(filename, fileBytes, filename, "application/octet-stream"); // ContentTypeは適宜変更

            var response = client.Execute(request);

            if (response.IsSuccessful)
            {
                var result = JObject.Parse(response.Content);
                var size = result["size"]?.Value<long>();
                return size ?? 0;
            }
            else
            {
                outErrMsg = $"Upload failed. Status code: {response.StatusCode}, Response: {response.Content}";
                return 0;
            }
        }
        catch (Exception ex)
        {
            outErrMsg = $"An error occurred during upload: {ex.Message}";
            return 0;
        }
    }
}
    

このコードは、SeaweedFSの公式ドキュメントに基づいていますが、実際の使用シナリオに合わせて調整されています。 この概要が、SeaweedFSの導入と利用の参考になれば幸いです。

タグ: SeaweedFS 分散ファイルシステム ストレージ C# API

5月22日 09:51 投稿