Pull to refresh

KISS + Ruby = нагрузим сервер по быстрому

Reading time 8 min
Views 1.6K
Опишу я тут историю как на коленках сделал «нагрузочное» тестирование сервиса, и некоторые соображения по поводу Ruby;)

Жила-была себе одна большая система, жила уже не первый год, не первый релиз.
Система представляет из себя центральные сервера, через которые бегает информация и набор desktop приложений которые эту информацию и порождают/потребляют, так сказать gateways и destination.

Однажды с сервером случилась беда, для локализации дефекта нужно было нагрузить сервер — имитировать работу БОЛЬШОГО кол-ва одновременных активных клиентов.



Клиенты общаются с серверами посредством «Windows Communication Foundation» aka WCF.

Как в любой серьезной системе с клиентами, в наличии был эмулятор, который через GUI давал возможность дергать все методы и смотреть ответы.
В эмуляторе даже был специальный режим который умел прикидываться живыми клиентами, как настоящими (дергать нужные методы в нужном порядке), даже умел делать это многопоточно.

Собственно это затянувшееся вступление, перейдем к деталям.
Надо было сделать нагрузку в 6000 работающих клиентов.
Каждый из них проявляет активность раз в 10 секунд.
у каждого свой логин/пароль.

Один эмулятор оказался в состоянии запустить 250 потоков/клиентов Итого для запуска нужно было 24 копии эмулятора, на куче машин (в зависимости от мощности от 1 до 4 штук на машину).
Однако сказали мужуки, но надо значит надо, запустили, и первый этап на этом закрыли, подтвердили теорию и отложили за редкостью ситуации.

Потом через время опять возникла необходимость в нагрузке, а человек который это все пускал — отсутствовал, привлекли меня, но т.к. был вечер пятницы и т.д., решили отложить до понедельника.

Собственно я не девелопер — я тестировщик/QA, но с разными странными бэкграундами.

Решил я дома посмотреть, а можно ли упростить это дело, можно ли создать нагрузку «более другими» методами.

Взял эмулятор, взял Fiddler2 (Fiddler is a Web Debugging Proxy which logs all HTTP (S) traffic between your computer and the Internet), и посмотрел что делается.

Увидел я там XML который бегает в обе стороны, и волшебные слово SOAP.

О, сказал я, хороший повод наконец с ним познакомиться поближе.

Заодно и проверить, можно ли такие вещи делать на Ruby.
Почему Ruby?
ну во первых потому что красиво, это раз,
во вторых Ruby + WATIR после этой связки я и увлекся руби (думаю отдельно про это написать)
в третьих — мультиплатфоменность, это для меня было важным фактором т.к. дома, где и проходила первая фаза исследований — OS X (Hackintosh), на работе Windows, ну и Linux нам тоже пригодился в реальной жизни.

Поискал библиотеку подходящую, нашлись soap4r, Savon, выбрал последнюю, т.к. показалась полегче. Поставил, начались игры.

На удивление быстро получился первый работающий код, задача была получить запрос выглядящий как запрос родного эмулятора.
  1. def data_available(client,id,password)
  2.   _xml=%Q|<?xml version="1.0" encoding="utf-8"?>
  3.   <soap:Envelope
  4.         xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
  5.         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  6.         xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  7.         <soap:Body>
  8.                 <DataAvailable xmlns="Company.ESD">
  9.                         <ID>#{id}</deviceID>
  10.                         <Password>#{password}</Password>
  11.                 </DataAvailable>
  12.         </soap:Body>
  13.   </soap:Envelope>
  14.   |  
  15.   _xml.gsub!(/\r|\n/,"")
  16.  
  17.   response = client.request "Company.ESD/Data/DataAvailable","xmlns" => "Company.ESD" do
  18.     soap.xml=_xml
  19.   end
  20.   response  
  21. end
  22.  
  23. def do_test(id,pwd)
  24.   client = Savon::Client.new do
  25.     wsdl.endpoint = "https://superserver.super.com/DDService/Data.svc"
  26.     wsdl.namespace = "https://superserver.super.com"
  27.     http.read_timeout = 90
  28.     http.open_timeout = 90
  29.   end
  30.  
  31.   r=data_available(client,id,pwd).to_hash
  32. end


т.е. есть один клиент, ну что, пора сделать МНОГО клиентов открываем документацию находим пример, применяем
run_client — код одного «клиента»

  1. def run_test(clnt_id)
  2.         pwd=get_pwd(clnt_id)
  3.         id=get_id(clnt_id)
  4.         while true
  5.                 do_test(id,pwd)
  6.                 sleep 9
  7.         end
  8. end
  9.  
  10. clients=Array.new
  11. 1.upto(10) do |client_id|
  12.   client << Thread.new do
  13.     puts "Start: #{client_id}"
  14.     run_client(t_cnt)
  15.   end
  16. end
  17. clients.map {|t| t.join}


запустили — УРА, работает.
попробуем 1000, опс, на экране запуск ~130 потоков и тормоза;(
похоже Ruby не очень хочет пускать много потоков,
ну в общем то не вопрос, нам важен результат.
пустим в одном файле 100 штук, а файлов таких будет 10, вот и имеем 1000 клиентов.
пробуем — ура работает!!!, но проц грузим мягко скажем не подески, начинаем думать что делать, нам то надо еще больше!

тут вспоминаем что клиент проявляет активность раз в 10 секунд
т.е. мы можем пустить один поток который спокойно может обработать 10 последовательных клиентов, на каждого по секунде, в результати и получим активность каждого клиента раз в 10 секунд.
ура, удалось уменьшить кол-во потоков в 10 раз, уже радует, т.е. на 1000 клиентов надо всего 100 потоков, а это уже реально…

получили такой запускатель
  1. def do_for_time(time_in_sec,&block)
  2.  time_end=Time.now+time_in_sec-0.01
  3.  yield
  4.  time_rest=time_end-Time.now
  5.  sleep time_rest if time_rest>=0.1
  6. end
  7.  
  8. def run_10(start_id)
  9.     clinets=Array.new
  10.     1.upto(10) do |nn|
  11.       id=get_id[start_id+nn]
  12.       pwd=get_pwd[start_id+nn]
  13.       clinets << [id,pwd]
  14.     end
  15.    
  16.     clnt = HTTPClient.new
  17.     while true do
  18.       clinets.each do |client|
  19.         id=client[0]
  20.         pwd=client[1]
  21.         do_for_time(1) do
  22.            data_available(clnt,id,pwd)
  23.            update_status(clnt,id,pwd)
  24.         end
  25.       end
  26.     end
  27. end


Кстати, забавная функция do_for_time (эх, люблю я блоки в руби!)
мы ее вызываем с параметром сколько секунд она должна выполняться, и передаем ей блок который надо выполнить, и она вместе с выполнением блока будет работать столько секунд сколько просят (считаем что вызов функций блоке много меньше чем время ожидания)
, но проц все еще грузим, надо с этим что-то делать.

Посмотрел я еще раз на оригинальные запросы и понял, так зачем нам нужен SOAP?
это же в конце концов не более чем POST запрос c зараннее известным XML!

сказано — сделано, переписали наши функции, выкинули SOAP, получили 
  1. def check_data(http_client,id,pwd)
  2.     uri='https://superserver.super.com/DDService/Data.svc'
  3.     soap=%Q|<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><DataAvailable xmlns="company.ESD"><ID>#{id}</ID><Password>#{pwd}</Password></DataAvailable></soap:Body></soap:Envelope>
  4.   |
  5.   headers = {  
  6.     'Content-Type' => 'text/xml;charset=UTF-8',
  7.     'SOAPAction' => 'company.EDC/IData/ ',
  8.   }
  9.  
  10.   http_client.post uri, soap, headers
  11. end


ожидаемо новая версия кушала МНОГО меньше проца, т.к. не занималась не нужными глупостями.

3000 клиентов живут достаточно легко…

тут домашние игры я прекратил, т.к. уперся в несеметричный канал ADSL, да и отдыхать то надо;)

в понедельник продолжил на работе, на быстром канале.
, а тут начинаются чудеса, грустные
виндовый Ruby 1.8.7 жрет 100% 2х ядерного cpu при 1000 клиентов, опаньки…

для проверки в виртуалке (на этой же машине) ставлю ubuntu, даю ей два ядра, и пускаю тоже самое, и…
в общем чудеса, в ВИРТУАЛКЕ под линухом имеем 5000 клиентов.

далнейие эксперементы:
ставми jruby, у него по описанию правильные треды, пускаем 1000 — 20% загрузки — УРА
пускаем второй файл с 1000 и опаньки, загрузка 2х ядер 80%, терпимо, но не очень понятно;(

в итоге, 2000 на машине подошли для нашей задачи (надо было пускать на удаленной тачке, виртуалку там поставить было нельзя)
Задачу решили, сервер нагрузили.
Поигрался с SOAP, забавно, не очень сложно.
Времени потратил, сумарно часов 8.

выводы:
1. «Keep it simple, Stupid!» — в нашем случае упрощение помогло решить задачу, уход от сложного SOAP к просто http requests спасло положение.
2. RUBY можно использовать для таких задач, мне не пришлось воевать с языком, он просто позволил мне сделать то что я хотел;) (почти не создавая проблем)
3. использование scripting language — сильно ускоряет время разбора с проблеммой, т.к. дает легко эксперементировать, это быстро и просто.
4. мультиплатформенность — рулит
5. теперь можно при необходимости переписать это все на C#, может и на одной машине будет много клиентов, но оно пока не надо, задача получилась разовая.
6. i love ruby!
______________________
Текст подготовлен в Редакторе Блогов от © SoftCoder.ru
Tags:
Hubs:
+24
Comments 25
Comments Comments 25

Articles