Выполнение метода Ruby внутри сеанса Net::SSH

Ruby 1.9.3, net-ssh 2.9.2

Я работаю над проектом, в котором мне нужно разделить один и тот же каталог (и его подкаталоги) на двух разных серверах (локальном и удаленном). Оттуда мне нужно скопировать самые новые/недавно измененные файлы на правильный сервер и удалить с удаленного, если файл отсутствует на локальном.

ПРИМЕЧАНИЕ. Я не могу использовать rsync. Мы делаем резервные копии каталогов, связанных с Asterisk, в GlusterFS. При тысячах файлов rsync сравнивает локальный том с томом Gluster очень медленно (когда нам это нужно менее 1 минуты).

Вот мой текущий код. Я опускаю свою работу по копированию/удалению файлов, так как хочу делать это по одному шагу за раз.

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 '{$1=$2=$3=$4=$5=$6=$7=$8=""; print $0}'`.strip
    date = `ls --full-time "#{file}" | awk '{print $6, $7, $8}'`.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?


person onebree    schedule 13.08.2015    source источник
comment
Почему бы вам не использовать предназначенные для этого инструменты? rsync может многое сделать, если все это проверено в бою и является стандартной частью систем *nix.   -  person the Tin Man    schedule 13.08.2015
comment
Я часто получаю это в чате SO. Добавление причины к вопросу. Но в основном rsync + gluster + много файлов = медленно.   -  person onebree    schedule 13.08.2015
comment
rsync будет намного быстрее, чем то, что вы пишете на Ruby. rsync — это скомпилированный код, написанный для определенной цели. Если скорость не важна, вы можете сделать это в Ruby, и он сделает свою работу. Вы также можете посмотреть на lsyncd, который работает в фоновом режиме, отслеживая изменения файлов и немедленно синхронизируя их. . У меня есть каталоги, содержащие 1800+ подкаталогов и 764K+ файлов, и я поддерживаю зеркала с помощью lsyncd. Он обрабатывается почти в реальном времени (синхронизируется в течение нескольких секунд).   -  person the Tin Man    schedule 14.08.2015


Ответы (3)


Код Ruby, работающий внутри блока net::ssh, по-прежнему работает на вашем компьютере (включая методы, запускающие такие команды, как system или обратные кавычки)

Чтобы выполнить команду на удаленном сервере, вам нужно использовать session.exec или session.exec! (последний блокирует, первый требует запуска цикла событий ssh). Вы также можете явно открыть канал и выполнить там команду — эти методы являются обертками совести.

Специальной поддержки для удаленного запуска ruby ​​не предусмотрено. Конечно, вы можете использовать exec! для запуска ruby ​​на другом компьютере (при условии, что он установлен), но это все.

person Frederick Cheung    schedule 14.08.2015
comment
Это не отвечает на мой вопрос. Пока SSH подключен к другому компьютеру, мне нужно выполнить метод, который я определил в своей программе выше. Как открыть канал? Документы (веб-сайт v1) меня немного сбивают с толку. - person onebree; 14.08.2015
comment
Net ssh v1 древний. В v2 вы открываете канал с помощью метода open_channel, но, как я уже сказал, вы можете выполнять только команды оболочки на удаленной машине, а не методы ruby ​​(за исключением случаев, когда ваша команда оболочки может запускать ruby) - person Frederick Cheung; 14.08.2015
comment
Документы v1 были для меня лучше, так как они были разбиты на главы. Для полного синтаксиса я прочитал документы v2. Теперь я понимаю - я не читал это так, как вы объяснили это в своем комментарии выше. - person onebree; 14.08.2015
comment
Спасибо. Я согласился с тем, что вы сказали, что я не могу запускать методы, только команды оболочки. - person onebree; 14.08.2015

Я на 100% НЕ троллю вас ... но ... ваш синопсис - та самая причина, по которой был создан rsync. Перемещение файлов между серверами с возможностью сравнения, но эффективно.

IMO немного ошибочно думать, что вы можете сделать лучше, чем 20-летний боевой проверенный код C. Который FWIW будет выполняться намного быстрее, чем рубиновый код. Вероятно, поэтому так много сторонников rsync в качестве решения.

Хотя rsync является однопоточным... спросите себя, почему это так... то, что вы можете использовать многопоточность в ruby, не означает, что вы должны это делать. Это откроет совершенно другого спагетти-монстра, и вскоре вам придется «обрабатывать» дубликаты или неправильные версии. См. обсуждение MongoDB атомарности. Вы даже не приблизитесь к атомному в рубине, так что это БУДЕТ проблемой.

Я бы обязательно использовал потокобезопасный язык, если вы хотите пойти по этому пути, по крайней мере, jRuby. Потокобезопасность FWIW была одной из многих причин, по которым Хосе создал Эликсир, так как он был раздражен тем, что Ruby не имеет его на самом деле.

Однако IMO что-то не так с вашим подходом, и вам нужно сделать несколько шагов назад и взглянуть на проблему целостно, например. возможно, есть аналогичное решение для GlusterFS, которое может обрабатывать дедупликацию на уровне FS, или, возможно, вам нужно обрабатывать добавление файлов через API или какую-то систему очередей, которая будет обрабатывать файлы в последовательном порядке. Это может потребовать больших изменений, чем вы хотите или можете сделать, поэтому, если бы это был я, я бы не стал просто ковбойски кодировать что-то на рубине, потому что какой-нибудь разработчик когда-нибудь в конечном итоге прыгнет в этот код и мгновенно фейспалм .

Многопоточная rsync не ruby

Единственное решение, которое я могу легко придумать, — это сосредоточиться на ускорении передачи rsync.

  1. Возможно, вместо этого вы можете ускорить rsync с помощью потоков.

  2. Или используйте подход этого человека. Это похоже на проблему с GlusterFS, но rsync с правильным флагом/сигналами может сделать дифференциальная синхронизация лучше. Тогда ваши ruby-скрипты смогут забрать файлы из главного источника.

person engineerDave    schedule 13.08.2015
comment
Я знаю, что вы не троллите, но мне поручили эту задачу, потому что rsync и GlusterFS в нашем случае плохо сочетаются друг с другом. - person onebree; 14.08.2015
comment
Спасибо за совет. Я проголосовал за вас, потому что согласен, и я продолжаю запускать rsync в потоках. Я все еще буду заниматься своим текущим проектом, но я хочу больше понять rsync. - person onebree; 14.08.2015
comment
Хотя я чувствую вашу боль по поводу наследования проектов с сумасшедшими параметрами от mgmt. удачи! - person engineerDave; 14.08.2015

принятый ответ помог мне прийти к следующему решению. Зная, что session.exec!() запускает только команды оболочки, я решил разделить метод (см. вопрос) на несколько шагов в блоке 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 '{$1=$2=$3=$4=$5=$6=$7=$8=""; print $0}')).strip
      date = session.exec!(%Q(ls --full-time "#{BRICK_PATH}/#{file}" | awk '{print $6, $7, $8}')).strip
      @files[server][name] = DateTime.parse(date)
    end
  end
end

Я пока не знаю, окажется ли это быстрее (необходимо запустить тест), но это определенно лучше, чем использование SSH в нескольких system() вызовах.

person onebree    schedule 14.08.2015
comment
Если скорость имеет значение, подумайте о том, чтобы просто выполнить одну команду ls, а не передавать ее через awk, а затем проанализировать ее в Ruby, чтобы извлечь имя и дату. String#split, вероятно, будет достаточно, чтобы заменить большую часть того, что делает awk. - person Wayne Conrad; 14.08.2015