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

Представьте, что у вас есть служба, которая получает запрос, выполняет сложные вычисления, а затем возвращает ответ. Что-то, что связано с обработкой ЦП и не требует дополнительной информации из других источников. Представьте, что процесс занимает, скажем, 20 мсек.

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

Это похоже на мойку автомобилей. У нас есть несколько автомоек. Каждая машина может мыть машину за фиксированный промежуток времени, две машины могут мыть две машины параллельно, и, если подъезжает больше машин, они ждут своей очереди. Время, которое имеет значение для владельца автомобиля, - это общее время простоя в очереди и мытья. А наши автовладельцы не хотят ждать больше 300 мс.

Итак, мы можем начать нашу модель с определения автомобиля. Мы действительно заботимся только о времени прибытия и времени, когда закончится мойка и машина будет готова.

type car struct {
   arrival time.Time
   done time.Time
}

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

func (sim *simulation) carwash(wg *sync.WaitGroup, carQueue <-chan *car){
    defer wg.Done()
    for c := range carQueue {
        sim.processCar(c)
        sim.done(c)
    }
}

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

carQueue := make(chan *car, sim.numWashes*100)
wg := &sync.WaitGroup{}
wg.Add(sim.numWashes)
for i := 0; i < sim.numWashes; i++ {
    go sim.carwash(wg, carQueue)
}

Мы предполагаем, что автомобили прибывают случайным образом с установленной нами скоростью. К счастью, умные люди разработали, что мы можем смоделировать это как процесс Пуассона (http://www.math.uah.edu/stat/poisson/), а милые люди с Голанга дали нам функцию (rand.ExpFloat64 ( )), что дает нам случайное время между поступлениями для пуассоновского процесса. Таким образом, мы можем легко вычислить подходящее случайное время ожидания, пока следующая машина не подъедет к мойке и не встанет в очередь.

for i := 0; i < sim.iterations; i++ {
    // Wait for the next car to arrive and add it to the queue
    time.Sleep(time.Duration(rand.ExpFloat64()) * time.Second / time.Duration(sim.arrivalRate))
    c := &car{arrival: time.Now()}
    carQueue <- c
 }

Итак, давайте запустим моделирование. Если у нас есть один процессор и наша задача занимает 20 мс, тогда мы должны иметь возможность выполнять 50 запросов в секунду, верно?

Processing time min=20.757644ms, max=2.289756103s, mean=1.877248319s, stddev=35.377296ms

К сожалению, случайное поступление означает, что вы очень быстро создаете очереди, и если мы попытаемся поддерживать 50 запросов в секунду, наше среднее время обработки возрастет до 1,9 секунды, а максимальная задержка, которую мы видим, еще выше.

Неудивительно, что мы не можем работать со 100% ЦП и обеспечивать быстрое время отклика на случайные поступления. Если мы хотим, чтобы время отклика оставалось

Картина улучшается по мере того, как мы получаем процессоры, и мы можем распределить риски. С 4 процессорами мы можем обрабатывать 110 запросов в секунду или 2200 мс обработки из доступных 4000 мс. Среднее использование 55%. С 8 ЦП мы можем управлять примерно 62% загрузкой ЦП, прежде чем достигнем потолка в 300 мс.

Мы также увидели бы более высокую среднюю загрузку ЦП, если бы сократили время выполнения задачи. Я разместил свой код для этого на github по адресу https://github.com/philpearl/washsim на случай, если вы заинтересованы в работе с другими сценариями. Он вдохновлен примером для simpy, но simpy умнее моего примера, поскольку simpy контролирует время в моделировании, поэтому, если вы серьезно относитесь к моделированию чего-то, это может быть лучшим местом для начала.

Днем Фил использует свою суперсилу кодирования для борьбы с преступностью на ravelin.com. Если вам понравилась эта статья, нажмите маленькую кнопку в виде сердечка, так как Фил получает питание в основном от точек доступа в Интернет.