如何同步新的ActiveStorage镜像?

问题描述 投票:0回答:4

从 ActiveStorage 开始,您可以定义用于存储文件的镜像。

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

amazon:
  service: S3
  access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
  region: us-east-1
  bucket: mybucket

mirror:
  service: Mirror
  primary: local
  mirrors:
    - amazon
    - another_mirror

如果您在某个时间点后添加镜像,您必须小心复制所有文件,例如从“本地”到“亚马逊”或“another_mirror”。

  1. 有没有方便的方法来保持文件同步?
  2. 或者方法运行验证来检查所有文件是否在每个服务上可用?
ruby-on-rails ruby-on-rails-5 mirroring rails-activestorage
4个回答
17
投票

我有一些可能适合您的解决方案,其中一个适用于 Rails <= 6.0 and one for Rails >= 6.1:

首先,您需要迭代 ActiveStorage blob:

ActiveStorage::Blob.all.each do |blob|
  # work with blob
end

然后...

  1. 导轨<= 6.0

    您将需要 blob 的密钥、校验和以及磁盘上的本地文件。

    local_file = ActiveStorage::Blob.service.primary.path_for blob.key
    
    # I'm picking the first mirror as an example,
    # but you can select a specific mirror if you want
    mirror = blob.service.mirrors.first
    
    mirror.upload blob.key, File.open(local_file), checksum: blob.checksum
    

    如果镜像中已存在文件,您可能还想避免上传该文件。您可以通过这样做来做到这一点:

    mirror = blob.service.mirrors.first
    
    # If the file doesn't exist on the mirror, upload it
    unless mirror.exist? blob.key
      # Upload file to mirror
    end
    

    把它们放在一起,rake 任务可能看起来像:

    # lib/tasks/active_storage.rake
    
    namespace :active_storage do
    
      desc 'Ensures all files are mirrored'
      task mirror_all: [:environment] do
    
      # Iterate through each blob
      ActiveStorage::Blob.all.each do |blob|
    
        # We assume the primary storage is local
        local_file = ActiveStorage::Blob.service.primary.path_for blob.key
    
        # Iterate through each mirror
        blob.service.mirrors.each do |mirror|
    
          # If the file doesn't exist on the mirror, upload it
          mirror.upload(blob.key, File.open(local_file), checksum: blob.checksum) unless mirror.exist? blob.key
    
          end
        end
      end
    end
    

    您可能会遇到类似@Rystraum提到的情况,您可能需要从本地磁盘以外的其他地方进行镜像。在这种情况下,rake 任务可能如下所示:

    # lib/tasks/active_storage.rake
    
    namespace :active_storage do
    
      desc 'Ensures all files are mirrored'
      task mirror_all: [:environment] do
    
        # All services in our rails configuration
        all_services = [ActiveStorage::Blob.service.primary, *ActiveStorage::Blob.service.mirrors]
    
        # Iterate through each blob
        ActiveStorage::Blob.all.each do |blob|
    
          # Select services where file exists
          services = all_services.select { |file| file.exist? blob.key }
    
          # Skip blob if file doesn't exist anywhere
          next unless services.present?
    
          # Select services where file doesn't exist
          mirrors = all_services - services
    
          # Open the local file (if one exists)
          local_file = File.open(services.find{ |service| service.is_a? ActiveStorage::Service::DiskService }.path_for blob.key) if services.select{ |service| service.is_a? ActiveStorage::Service::DiskService }.any?
    
          # Upload local file to mirrors (if one exists)
          mirrors.each do |mirror|
            mirror.upload blob.key, local_file, checksum: blob.checksum
          end if local_file.present?
    
          # If no local file exists then download a remote file and upload it to the mirrors (thanks @Rystraum)
          services.first.open blob.key, checksum: blob.checksum do |temp_file|
            mirrors.each do |mirror|
              mirror.upload blob.key, temp_file, checksum: blob.checksum
            end
          end unless local_file.present?
    
        end
      end
    end
    

    虽然第一个 rake 任务回答了 OP 的问题,但后者的用途要广泛得多:

    • 它可以与任意服务组合一起使用
    • 不需要磁盘服务
    • 优先通过DiskServices上传
    • 避免额外存在?调用,因为我们每个 blob 每个服务只调用一次
  2. 导轨 > 6.1

    非常简单,只需在每个斑点上调用此...

    blob.mirror_later
    

    将其包装为 rake 任务如下所示:

    # lib/tasks/active_storage.rake
    
    namespace :active_storage do
    
      desc 'Ensures all files are mirrored'
      task mirror_all: [:environment] do
        ActiveStorage::Blob.all.each do |blob|
          blob.mirror_later
        end
      end
    end
    

6
投票

(03-11-2021) 在 Rails > 6.1.4.1 上,使用 active_storage > 6.1.4.1 并在以下范围内:

宝石文件:

gem 'azure-storage-blob', github: 'Azure/azure-storage-ruby'

配置/环境/生产.rb

 # Store uploaded files on the local file system (see config/storage.yml for options).
  config.active_storage.service = :mirror #:microsoft or #:amazon

配置/存储.yml:

amazon:
  service: S3
  access_key_id: XXX
  secret_access_key: XXX
  region: XXX
  bucket: XXX

microsoft:
  service: AzureStorage
  storage_account_name: YYY
  storage_access_key: YYY
  container: YYY

mirror:
  service: Mirror
  primary: amazon
  mirrors: [ microsoft ]

这确实工作:

ActiveStorage::Blob.find_each do |blob|
  blob.mirror_later
end && puts("Mirroring done!")

所做的工作是什么:

ActiveStorage::Blob.find_each do |blob|
  ActiveStorage::Blob.service.try(:mirror, blob.key, checksum: blob.checksum)
end && puts("Mirroring done!")

不确定为什么会这样,也许 Rails 的未来版本支持它,或者它需要额外的后台作业设置,或者它最终会发生(这对我来说从未发生过)。

TL;博士

如果您需要立即对整个存储进行镜像,请添加此 rake 任务并使用

bundle exec rails active_storage:mirror_all
:

在给定环境中执行它

lib/tasks/active_storage.rake

namespace :active_storage do
  desc 'Ensures all files are mirrored'
  task mirror_all: [:environment] do
    ActiveStorage::Blob.find_each do |blob|
      ActiveStorage::Blob.service.try(:mirror, blob.key, checksum: blob.checksum)
    end && puts("Mirroring done!")
  end
end

可选:
一旦镜像了所有 blob,如果您希望它们实际上从正确的存储中获得服务,您可能需要更改它们的所有服务名称:

namespace :active_storage do
  desc 'Change each blob service name to microsoft'
    task switch_to_microsoft: [:environment] do
      ActiveStorage::Blob.find_each do |blob|
        blob.service_name = 'microsoft'
        blob.save
    end && puts("All blobs will now be served from microsoft!")
  end
end

最后,更改:

生产.rb
中的config.active_storage.service=或将主镜像设为您希望将来上传到的镜像。


2
投票

我在https://stackoverflow.com/a/57579839/365218之上工作,因此 rake 任务不会假设该文件位于本地。

我从 S3 开始,出于成本考虑,我决定将文件移动到磁盘并使用 S3 和 Azure 作为镜像。

所以我的情况是,对于某些文件,我的主(磁盘)有时没有该文件,而我的完整存储库实际上位于我的第一个镜像上。

所以,有两件事:

  1. 将文件从 S3 移动到磁盘
  2. 添加了一个新镜像,并希望保持最新
namespace :active_storage do
  desc "Ensures all files are mirrored"
  task mirror_all: [:environment] do
    ActiveStorage::Blob.all.each do |blob|
      source_mirror = if blob.service.primary.exist? blob.key
                        blob.service.primary
                      else
                        blob.service.mirrors.find { |m| m.exist? blob.key }
                      end

      source_mirror.open(blob.key, checksum: blob.checksum) do |file|
        blob.service.primary.upload(blob.key, file, checksum: blob.checksum) unless blob.service.primary.exist? blob.key

        blob.service.mirrors.each do |mirror|
          next if mirror == source_mirror

          mirror.upload(blob.key, file, checksum: blob.checksum) unless mirror.exist? blob.key
        end
      end
    rescue StandardError
      puts blob.key.to_s
    end
  end
end

1
投票

所有内容都是根据 ActiveStorage 的密钥存储的,因此只要您的存储桶名称和文件名在传输中没有更改,您就可以将所有内容复制到新服务。 请参阅这篇文章了解如何复制内容。

© www.soinside.com 2019 - 2024. All rights reserved.