在 Net::SSH 会话中执行 Ruby 方法

Executing a Ruby method inside a Net::SSH session

Ruby 1.9.3,网络 ssh 2.9.2

我正在做一个项目,我需要在两个不同的服务器(本地和远程)上比较相同的目录(及其子目录)。从那里,我需要将 newest/recently 修改后的文件复制到正确的服务器,如果文件不存在于本地,则从远程删除。

注意:我无法使用 rsync。我们正在将与 Asterisk 相关的目录备份到 GlusterFS。在数千个文件中,rsync 将本地卷与 Gluster 卷进行比较非常慢(当我们需要它时不到 1 分钟)。

这是我当前的代码。我省略了 copying/removing 个文件的工作,因为我想一次迈出这一步。

require 'thread'
require 'date'
require 'rubygems'
require 'net/ssh'

SERVERS = ['local17', 'development']
CLIENT = SERVERS[0]
CLIENT_PATH = '/home/hstevens/temp_gfs'
BRICK_PATH = '/export/hunter_test'

@files = {
  SERVERS[0] => {},
  SERVERS[1] => {}
}

def grab_filenames_and_dates(files, server)
  files.reject { |x| File.directory? x }
  files.each do |file|
    name = `ls --full-time "#{file}" | awk '{========""; print [=12=]}'`.strip
    date = `ls --full-time "#{file}" | awk '{print , , }'`.strip
    @files[server][name] = DateTime.parse(date)
  end
end

# Collect diff information on all servers
ls_threads = SERVERS.map do |server|
  Thread.new do
    if server == CLIENT
      files = Dir.glob("#{CLIENT_PATH}/**/*")
      grab_filenames_and_dates(files, server)
    else
      Net::SSH.start(server, 'hstevens') do |session|
        files = session.exec!(%Q(ruby -e 'puts Dir.glob("#{BRICK_PATH}/**/*")')).split("\n")
        grab_filenames_and_dates(files, server)
      end
    end
  end
end
ls_threads.each(&:join)

当我 运行 我的程序时,它适用于本地服务器 (CLIENT/local17),但在远程服务器上失败。我尝试了调试语句(将 pwd 打印到控制台`,看起来虽然该方法是在 Net::SSH 会话块内调用的,但它正在我的本地服务器上运行。

ls: cannot access /export/hunter_test/sorttable.js: No such file or directory
ls: cannot access /export/hunter_test/sorttable.js: No such file or directory
./gluster_rsync.rb:36:in `parse': invalid date (ArgumentError)
    from ./gluster_rsync.rb:36:in `block in grab_filenames_and_dates'
    from ./gluster_rsync.rb:33:in `each'
    from ./gluster_rsync.rb:33:in `grab_filenames_and_dates'
    from ./gluster_rsync.rb:53:in `block (3 levels) in <main>'
    from /usr/local/lib/ruby/gems/1.9.1/gems/net-ssh-2.9.2/lib/net/ssh.rb:215:in `start'
    from ./gluster_rsync.rb:51:in `block (2 levels) in <main>'

如何在 Net::SSH 会话中正确包装方法调用?

我 100% 不是在逗你……但是……你的概要正是创建 rsync 的原因。在具有差异功能但高效的服务器之间移动文件。

IMO 认为您可以比经过 20 年实战测试的 C 代码做得更好,这有点误导了。哪个 FWIW 的执行速度比 ruby 代码快得多。这可能就是为什么这么多人团结起来将 rsync 作为解决方案的原因。

尽管 rsync 是单线程的...问问自己为什么会这样...仅仅因为您可以在 ruby 中使用多线程并不意味着您应该这样做。它会打开一个完整的其他意大利面条怪物,您很快就会发现自己的任务是 "handling" 重复或不正确的版本。请参阅 MongoDB 关于原子性的讨论。你甚至不会在 ruby 中接近原子,所以这将是一个问题。

如果您想走那条路,我肯定会使用线程安全语言,至少是 jRuby。 FWIW 线程安全是 Jose 创建 Elixir 的众多原因之一,因为他对 ruby 没有真正拥有它感到愤怒。

但是在我看来,您的方法有问题,您需要后退几步并从整体上看待问题,例如也许有一个与 GlusterFS 类似的解决方案可以处理 FS 级别的重复数据删除,或者您可能需要通过 API 或某种排队系统来处理文件添加,这些系统将按顺序处理文件。它可能需要比你愿意或可以做的更大的改变,所以如果是我,我会犹豫是否只在 ruby 中编写牛仔代码,因为一些开发人员最终会跳入该代码总有一天,立即捂脸。

多线程rsync不是ruby

我能轻松想到的唯一解决方案是专注于使 rsync 传输更快。

  1. 也许你可以 speed rsync up 用线程代替

  2. 还是用这个人的方法吧。这似乎是 GlusterFS 的问题,但 rsync with the proper flag/signals 可以更好地进行差异同步。然后您的 ruby 脚本可以从主源中获取文件。

Ruby 运行 在 net::ssh 块内的代码在您的计算机上仍然 运行(这包括 运行 命令的方法,例如 system 或反引号)

要在远程服务器上执行命令,您需要使用 session.execsession.exec!(后者是阻塞的,前者需要您 运行 ssh 事件循环)。您还可以显式打开一个通道并在那里执行命令——这些方法是良心包装器。

远程 运行ning ruby 没有特殊支持。你当然可以在另一台机器上使用 exec! 到 运行 ruby(假设它已安装)但仅此而已

帮助我得出以下解决方案。知道 session.exec!() 只有 运行s shell 命令,我决定将方法(参见问题)拆分为 SSH 块中的多个步骤。

Thread.new do
  files = nil
  Net::SSH.start(server, 'hstevens') do |session|
    files = session.exec!(%Q(cd "#{BRICK_PATH}" ; ruby -e 'puts Dir.glob("**/*")')).split("\n")
    files.delete_if { |x| File.directory? x }
    files.each do |file|
      name = session.exec!(%Q(ls --full-time "#{BRICK_PATH}/#{file}" | awk '{========""; print [=10=]}')).strip
      date = session.exec!(%Q(ls --full-time "#{BRICK_PATH}/#{file}" | awk '{print , , }')).strip
      @files[server][name] = DateTime.parse(date)
    end
  end
end

我还不知道这是否证明速度更快(需要 运行 基准),但在几次 system() 调用中它肯定比 SSH-ing 要好。