Backend платформа Go
Backend платформа Go
Цель интервью
Проверяем общие знания кандидата по конкретному языку, устройству
компьютера и работе сетей в глубину.
До интервью
● Создаем новый отчет;
● Заполняем поля «Кандидат», «Интервьюер(ы)» и «Дата».
После интервью
● «Причесываем» заметки по ответам кандидата;
● Заполняем поля «Ссылка на код», «Оценка уровня» и оставляем
комментарий на основе чего было принято это решение;
● Нажимаем «Сохраняить отчет»;
● Переходим на детальную страницу интервью по ссылке в календаре
(приглашении), добавляем ссылку на отчет в поле «Ссылка на итоги»,
выставляем «Уровень» и завершаем интервью.
Инструменты
● Для ведения протокола
● Для онлайн-кодинга с подсветкой или исполнением
● Онлайн whiteboard: [Link], Miro, Whimsical или Excalidraw
Рекомендуемые задачи
Указатели (easy)
Условие задачи
Что выведет следующая программа и почему?
package main
import "fmt"
Ответ:
Bob
Bob
Bob
Alice
Что проверяем
● Базовое понимание структур
● Понимание работы с указателями
Примечание
● Функция changeName принимает копию указателя и в копию указателя
записывается адрес нового объекта.
● There is no pass-by-reference in Go
package main
import "fmt"
func main() {
var max int
Что проверяем
● Базовое знание горутин
● Понимание областей видимости переменных
● Знание примитивов синхронизации
● Понимание проблем race conditions
Примечание
Проблема 1: Замыкание
Проблема 2: Синхронизация
Дополнительные вопросы:
● Какие есть инструменты (и есть ли) в go, которые могут помочь найти
проблемы выше (go vet, -race флаг и тд)
Дополнительные вопросы
● Как распараллелить пачку задач и дождаться завершения всех?
○ Работа с общей памятью, атомарность операций и необходимость
синхронизации
● Предположим, что сетевые запросы теперь могут выдать ошибку, как это
обработать?
○ Какие есть варианты? Показать цену в долларах или спятисотить
или ретраить?
● Какой таймаут правильно выставить на вызовы во внешние сервисы?
● Асинхронность:
○ Отличие между тредами, процессами и корутинами?
○ Как работает веб-сервер?
○ Как устроен шедулер, как устроены приоритеты, в какие моменты
корутины засыпают и просыпаются?
● Синхронизация:
○ Мьютексы и как ими пользоваться (лок на чтение и на запись,
отпускание локов после exception'ов)?
○ Атомарные операции, compare-and-swap etc, rw-мьютексы, спинлоки,
как оно работает внутри?
○ Синхронизация между процессами на одной машине и между
разными машинами, типовые способы ее избежать?
Заготовка кода
Go
Python
C++
C#
Java
NodeJS
Пример решения
Go
package main
[Link]()
return Snippet{
Price: priceRub,
Description: desc,
}
}
Python
C++
C#
Java
NodeJS
Дополнительные вопросы
● А что если нам теперь приходит параметр cityId и нам надо отдавать
погоду для него?
● Что будет если каждый раз ходить в микросервис?
○ каждый запрос будет обрабатываться по секунде, а это
дополнительный расход памяти, CPU и т.д
● Какой таймаут правильно выставить на ручку вызов aiWeatherForecast?
● Если человек додумался сделать кеш со словариком, то какие нештатные
ситуации могут случиться?
○ Прогрев кеша, протухание кеша, одновременное протухание кеша и
т.п..
○ Как предотвратить эти ситуации?
● Как выглядит HTTP-запрос и ответ
○ Method, URL, Headers, Body
○ Статусы HTTP-ответов (нужно принципиально отличать 200, 400 и
500, знать как их обрабатывать)
○ Путь запроса от пользовательского приложения до сервера и
обратно (шифрование, сокеты, буферы, DNS, IP, роутеры, MTU,
таблицы маршрутизации, фаерволы, почему витая пара витая)
Заготовки кода
Go
package main
func main() {
[Link]("/weather", func(w [Link], r *[Link]) {
[Link](w, "{\"temperature\":%d}\n", aiWeatherForecast())
})
if err := [Link](":3333", nil); err != nil {
panic(err)
}
}
Экспертные вопросы
● Какую структуру правильно выбрать для кэша?
● Как исключить гонки при паралельном доступе к структуре?
● Как инвалидировать кеш?
● Как реализовать прогрев кеша перед стартом приложения?
Python
import random
import time
import json
def ai_weather_forecast():
[Link](1)
return [Link](-30, 39)
@[Link]('/weather', methods=['GET'])
def get_weather():
return [Link]({'temperature': ai_weather_forecast()})
Экспертные вопросы
● Какую структуру правильно выбрать для кэша?
● Как исключить гонки при паралельном доступе к структуре?
● Как инвалидировать кеш?
● Как реализовать прогрев кеша перед стартом приложения?
C++
#include <cpprest/http_listener.h>
#include <cpprest/filestream.h>
#include <random>
#include <chrono>
int main() {
try {
http_listener listener(U("[Link]
[Link](methods::GET, handle_get);
[Link]().wait();
return 0;
}
Экспертные вопросы
● Какую структуру правильно выбрать для кэша?
○ Оптимально unordered_map, просто map тоже можно, отдельно
можно поговорить "что тут лучше"
○ Если предложит вектор, то рассказать, что city_id это не плотный
диапазон, а скажем внешний хэшкод
● Как исключить гонки при паралельном доступе к структуре кэша?
○ Канонично rwlock, можно mutex, но тогда выяснить "знаешь ли про
rwlock"?
○ Копнуть в понимание (или нет), что вставка в мапу под wlock может
приводить к небольшим паузам
○ На оценку 5+ понимание, что под high contention + fast requests mutex
это лучше rwlock, но 10 krps скорее всего not nigh enough
C#
namespace [Link];
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly IWeatherForecastService _weather;
public WeatherForecastController(IWeatherForecastService weather)
{
_weather = weather;
}
[HttpGet(Name = "weather")]
public async Task<int> Get()
{
return await _weather.Get();
}
}
public WeatherForecastService()
{
_rnd = new Random();
}
Экспертные вопросы
● Какую структуру правильно выбрать для кэша?
○ Что лучше для InMemory кэша mutex+map или ConcurrentDictionary?
● Как исключить гонки при паралельном доступе к структуре?
○ Какие примитивы синхронизации есть?
● Как инвалидировать кеш?
● Как реализовать прогрев кеша перед стартом приложения?
● Как запустить background service?
Java
OutputStream os = [Link]();
[Link]([Link]());
[Link]();
}
}
}
Экспертные вопросы
● Структура для кеша должна либо гарантировать thread safe операции, либо
реализация решения должна включать эти гарантии с помощью
synchronized операции либо других решений из пакета [Link]
● Прогрев кеша перед стартом приложения / актуализация для top N
запросов
● Инвалидация кеша по TTL или другому принципу
● Реализация парралелизма, простой Thread, CompletableFuture, возможные
эффекты
● Обработка ошибок и возвращаемый response клиенту
● Как реализовать лимиты по таймаутам для запроса
● Как реализовать кеш для N сервисов
NodeJS
Экспертные вопросы
● Какую структуру правильно выбрать для кэша?
● Как исключить гонки при паралельном доступе к структуре?
● Как инвалидировать кеш?
● Как реализовать прогрев кеша перед стартом приложения?
Пример решения
Go без локаций
package main
func updateWeather() {
value := aiWeatherForecast()
[Link]()
temperature = value
[Link]()
}
func main() {
ctx, cancel := [Link]([Link]())
defer cancel()
updateWeather()
for {
select {
case <-[Link]():
return
case <-[Link](1 * [Link]):
}
updateWeather()
}
}(ctx)
<-closed
}
Go с локациями
C++ с локациями
struct ParsedLocationRequest {
int cityId;
};
struct HttpResponse {
int status;
std::string body;
};
class MyWebServer
{
public:
void execute() {
while(true) {
swapStorage();
sleep(timer);
catch_signal();
}
}
private:
void swapStorage() {
std::unique_lock lock(storageMtx);
for(const auto& [cityId, weather] : storage_) {
oldStorage_[cityId] = weather;
}
storage_.clear();
}
struct Entry {
int weather{};
timestamp reqTime;
}
timer = 1m;
std::mutex storageMtx_;
};
// 10 krps
HttpResponse MyWebServer::Handle(ParsedLocationRequest req)
{
const auto& cityId = [Link];
const auto& reqTime = [Link];
std::shared_lock lock(storageMtx_);
auto it = oldStorage_.find(cityId);
if(it != oldStorage_.end()) {
if((reqTime - [Link] > 1h)) {
return updateStorage(cityId, [Link]);
} else {
return HttpResponse("200", it->[Link]);
}
}
return updateStorage(cityId, reqTime);
}
C# без локаций
using [Link];
using [Link];
namespace [Link];
[ApiController]
[Route("[controller]")]
public class SimpleWeatherForecastController : ControllerBase
{
private readonly IWeatherForecastService _weather;
[HttpGet(Name = "weather")]
public async Task<int> Get(CancellationToken token = default)
{
return await _weather.Get(token);
}
}
public interface IWeatherForecastService
{
Task<int> Get(CancellationToken token = default);
}
/// <summary>
/// Остановить обновление справочника
/// </summary>
public void StopUpdate()
{
_cancellationSource.Cancel();
}
/// <summary>
/// Начать обновление справочника
/// </summary>
public async Task StartUpdate()
{
_cancellationSource = new CancellationTokenSource();
using var tickTimer = new PeriodicTimer([Link](CalcTimeoutSeconds));
try
{
await _semaphoreSlim.WaitAsync(token);
_cache.Set(DefaultWeatherId, w,
new
MemoryCacheEntryOptions().SetAbsoluteExpiration([Link](CacheSeconds)));
return w;
}
finally
{
_semaphoreSlim.Release();
}
}
}
C# с локациями
using [Link];
using [Link];
namespace [Link];
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly IWeatherForecastService _weather;
/// <summary>
/// Получение прогноза погоды
/// </summary>
/// <param name="locationId"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet(Name = "GetWeather")]
public async Task<int> Get(int? locationId, CancellationToken token = default)
{
return await _weather.Get(locationId, token);
}
}
/// <summary>
/// Ручка получения прогноза погоды
/// </summary>
/// <param name="locationId">id локации</param>
/// <param name="token">токен отмены</param>
/// <returns>Температура</returns>
public async Task<int> Get(int? locationId, CancellationToken token = default)
{
var key = GetWeatherKey(locationId);
if (_cache.TryGetValue(key, out var weather))
{
return weather;
}
/// <summary>
/// Остановить обновление справочника
/// </summary>
public void StopUpdate()
{
_cancellationSource.Cancel();
}
/// <summary>
/// Начать обновление справочника
/// </summary>
public async Task StartUpdate()
{
_cancellationSource = new CancellationTokenSource();
using var tickTimer = new PeriodicTimer([Link](CalcTimeoutSeconds));
try
{
await [Link](token);
w = _rnd.Next(70) - 30;
_cache.Set(key, w,
new
MemoryCacheEntryOptions().SetAbsoluteExpiration([Link](CacheSeconds)));
return w;
}
finally
{
[Link]();
}
}
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
// use AtomicInteger
private int temperature;
updateWeatherByTtl();
}
@Override
public void handle(HttpExchange exchange) throws IOException {
String jsonResponse = "{\"temperature\": " + temperature + "}";
// handle non success cases and return error http code and response
[Link](200, [Link]());
OutputStream os = [Link]();
[Link]([Link]());
[Link]();
}
Java с локациями
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link].*;
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@Override
public void handle(HttpExchange exchange) throws IOException {
Integer locationId = parseLocationId(exchange);
if (locationId == null) {
sendResponse(
exchange,
400,
"{\"error\": \"Location id is not defined\"}"
);
return;
}
try {
sendResponse(
exchange,
200,
"{\"temperature\": " + getTemperature(locationId) + "}"
);
} catch (InterruptedException e) {
sendResponse(
exchange,
500,
"{\"error\": \"Receive actual weather is fault\"}"
);
}
}
if (temperature != null) {
return temperature;
}
Integer actualTemperature = aiWeatherForecast(locationId);
return actualTemperature;
}
if (![Link]()) {
return null;
}
return [Link]([Link](0).split("=")[1]);
}
function aiWeatherForecast() {
return [Link]([Link]() * 70) - 30
}
setInterval(() => {
temperature = aiWeatherForecast();
}, TTL);
NodeJS с локациями
function aiWeatherForecast(cityId) {
return [Link]([Link]() * 70) - 30
}
package main
import (
"fmt"
"time"
)
func networkRequest() {
[Link]([Link]) // Эмуляция сетевого запроса.
count++
}
func main() {
for i := 0; i < numRequests; i++ {
networkRequest()
}
[Link](count)
}
Что проверяем
● Общее понимание CPU и IO-bound задач
● Базовое знание горутин
● Знание примитивов синхронизации
Примечание
● Кандидат добавляет go – go networkRequest()
● Кандидат добавляет [Link]
● Кандидат защищает count локом из пакета sync (или использовать пакет
sync/atomic)
● Какие преимущества работы с atomic над mutex (рассказывает про
специальные инструкции процессоров и преимущества над мютексами)
● Спрашиваем за сколько по его мнению отработает программа теперь
(1-2ms норм ответ)
● А что если это настоящий сетевой запрос, не столкнемся ли мы с
какой-нибудь проблемой на практике? (лимит файловых дескрипторов)
Что проверяем
● Базовое знание горутин.
● Знание примитивов синхронизации
● Понимание работы context
● Паттерны межсервисного взаимодействия
Пример решения
package main
import (
"fmt"
"net/http"
"sync"
)
func main() {
var wg [Link]
[Link](2)
go func() {
defer [Link]()
resp, err := [Link]("[Link]
if err != nil {
[Link](err)
return
}
go func() {
defer [Link]()
resp, err := [Link]("[Link]
if err != nil {
[Link](err)
return
}
[Link]()
}
Возможные ошибки
● Запросы происходят не параллельно
● При использовании WaitGroup вызов [Link]() делается внутри горутины
● Нет проверки ошибок
Дополнительные вопросы
● Шедулинг горутин, преимущества горутин (что такое горутины в Go и как
они соотносятся с потоками операционной системы)
● А если URL адресов будет 500 и запустить запросы по ним в 500 горутинах,
будут и все они выполняться одновременно? Как это устроено “под
капотом”?
● Как изменить этот код чтобы можно было передавать любое количество
URL адресов?
● Как изменить этот код чтобы максимальное время его выполнения было 2
секунды?
● Advanced: Как изменить код, если мы захотим сделать утилиту демоном,
который умеет безопасно завершаться (graceful shutdown). Ожидаем
рассказ про обработку сигналов.
● Expert: Как можно оптимизировать создание TCP-соединений в HTTP/1.1?
(ожидаем ответ про keep-alive) Как можно управлять keep-alive в
HTTP-клиенте go? (ожидаем, что скажет про пул соединений, MaxIdleConns,
IdleConnTimeout) Для чего управлять пулом? (контроль нагрузки на сервис,
к которому обращаемся)
● Expert: Как в Linux понять, сколько у процесса открытых соединений?
Приходилось ли дебажить на таком уровне? (Ожидаем, что расскажет про
lsof, netstat или ss)
● Expert: Какие проблемы нужно учесть при запросах к удаленным
серверам? (Могут быть проблемы недоступности, приемлемым решением
было бы использование паттерна Circuit breaker).
package main
import (
"fmt"
"math/rand"
"time"
)
func init() {
[Link]([Link]().UnixNano())
}
return rnd
}
func main() {
[Link]("started")
[Link](predictableFunc())
}
Что проверяем
● Базовое знание горутин
● Применение каналов для передачи результата из горутины
● Понимание работы context
● Работа с select для мультиплексирования каналов
● Работа с временем выполнения операций (прообраз работы с метриками)
Вариант решения
const defaultTimeout = 1 * [Link]
select {
case res := <-resCh:
return res, nil
case <-[Link]():
// timeout
return 0, [Link]()
}
}
func main() {
[Link]("started")
Возможные ошибки
● При создании контекста забыл обработать [Link] - стоит
уточнить, зачем она нужна (ответ - чтобы корректно завершить работу
таймера внутри контекста и освободить ресурсы)
● При логгировании времени сделал что-то типа defer
[Link]("elapsed:", [Link]([Link]())) - нужно будет спросить
про работу defer и разрешение параметров вызова
● Создан небуферизированный канал - и горутина навсегда заблокировалась
на запись в случае таймаута
● Нет проверки ошибок
Дополнительные вопросы
● Как работает select, если в нескольких каналах одновременно есть данные
(ответ - недетерминировано, на порядок обработки нельзя закладываться)
Basic
● Легковесность
● Переключение происходит на уровне приложения (user space)
● Знает про GOMAXPROCS
● Знает про M:N модель
Advanced
Expert
● Менеджмент потоков
● Шедулинг горутин
● Менеджмент памяти
● Сборка мусора
Basic
Advanced
Expert
Basic
Advanced
Expert
● ?
Basic
Advanced
● nil-интерфейс
Basic
Advanced
Basic
Вопрос №9 Профилирование
Basic
Advanced
Expert