Grafana нужна для гибкой визуализации данных из Prometheus и других хранилищ. Она позволяет:
- Создавать интерактивные панели мониторинга.
- Настраивать уведомления (alerts).
- Подключаться к разным источникам данных.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Да, знаком. 12FA (12-Factor App) — это набор принципов, созданных разработчиками Heroku для построения масштабируемых, надежных и удобных в развертывании SaaS-приложений. Эти принципы особенно полезны при разработке облачных сервисов (Cloud-Native).
У приложения должна быть единая кодовая база (один репозиторий), независимо от количества развертываний (production, staging, dev).
Все зависимости должны явно указываться в
go.mod
/ go.sum
(для Go). Никаких глобальных зависимостей в системе. Конфигурация должна храниться в переменных окружения, а не в коде.
export DATABASE_URL="postgres://user:pass@host:5432/db"
Внешние сервисы (БД, кэш, API) должны быть заменяемыми и подключаться через URL (без хардкода).
Сборка, релиз и запуск должны быть разделены. Например, Docker-контейнеры для каждой стадии.
Приложение должно быть бесстатичным (не хранить файлы локально, использовать БД, S3 и т. д.).
Приложение должно быть самодостаточным и слушать порт (например, через
http.ListenAndServe
). Масштабируемость должна обеспечиваться горизонтальным масштабированием (разделением на процессы).
Приложение должно быстро запускаться и корректно завершаться (например, ловить SIGTERM).
Среды разработки и продакшена должны быть максимально похожи.
Логи должны писаться в стандартный вывод и обрабатываться внешними системами (ELK, Loki, Grafana).
Скрипты администрирования (миграции, отладка) должны выполняться как отдельные процессы.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Это императивный язык программирования. Код пишется в виде последовательных команд, а не деклараций (как в SQL или Terraform). Однако в некоторых случаях (например, в Kubernetes) Go может использоваться в декларативном стиле.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Каналы — это мощные инструменты для обмена данными между горутинами, обеспечивающие синхронизацию и безопасную коммуникацию. Основное различие между буферизированными и небуферизированными каналами заключается в их поведении при отправке и получении данных.
Не имеют внутренней емкости, т.е. они не могут хранить значения. Эти каналы требуют, чтобы отправитель и получатель были готовы обмениваться данными одновременно. Если одна сторона не готова, другая будет заблокирована:
Отправка данных в него блокирует отправителя до тех пор, пока получатель не прочитает данные из канала.
Получение данных из него блокирует получателя до тех пор, пока другая горутина не отправит данные в канал.
ch := make(chan int) // Создание небуферизированного канала
go func() {
val := <-ch // Блокируется, ожидая данные
fmt.Println("Received:", val)
}()
ch <- 3 // Блокируется, пока данные не будут получены
Имеют внутреннюю емкость, что позволяет хранить одно или несколько элементов без непосредственного получателя данных. Отправка или получение данных работает следующим образом:
Отправка блокируется, только если буфер заполнен. До этого момента данные могут быть отправлены без блокировки, даже если получатель не готов их принять.
Получение из буферизированного канала блокируется, только если канал пуст. Если в канале есть данные, получение происходит без блокировки.
ch := make(chan int, 2) // Создание буферизированного канала с емкостью 2
ch <- 1 // Отправка данных без блокировки
ch <- 2 // Отправка данных без блокировки
go func() {
val := <-ch // Получение данных без блокировки
fmt.Println("Received:", val)
}()
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Стандартный логгер (log из stdlib):
- Не поддерживает уровни логирования (INFO, ERROR).
- Не форматирует логи по умолчанию.
- Не поддерживает ротацию логов.
Для продакшена обычно используют logrus или zap.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
EXPLAIN
— это команда в SQL, которая показывает, как база данных выполняет SQL-запрос. Она помогает оптимизировать запросы, анализируя их план выполнения. Когда вы пишете
EXPLAIN
перед SQL-запросом, база данных не выполняет его, а показывает пошаговый процесс выполнения. Это помогает понять: Какие индексы используются
Какие таблицы сканируются
Какие соединения (
JOIN
) выполняются Оценить производительность запроса
EXPLAIN SELECT * FROM users WHERE age > 30;
Выход (пример)
Seq Scan on users (cost=0.00..25.00 rows=10 width=100)
Добавляет реальное время выполнения запроса
EXPLAIN ANALYZE SELECT * FROM users WHERE age > 30;
Использование индексов
CREATE INDEX idx_users_age ON users(age);
Правильный порядок
JOIN
EXPLAIN SELECT * FROM orders JOIN users ON orders.user_id = users.id;
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Да, популярные ORM для Go:
- GORM – самый распространенный, удобный, но может быть медленным.
- Ent – более производительный, но требует генерации кода.
- SQLX – не ORM, но расширяет database/sql и упрощает работу с SQL.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
В Go есть несколько способов преобразования строки в число и числа в строку.
Используется пакет
strconv
и функция strconv.Atoi()
или strconv.ParseInt()
. package main
import (
"fmt"
"strconv"
)
func main() {
str := "42"
num, err := strconv.Atoi(str) // Преобразуем строку в int
if err != nil {
fmt.Println("Ошибка:", err)
return
}
fmt.Println("Число:", num) // Выведет: Число: 42
}
Пример 2:
strconv.ParseInt()
(гибкое преобразование) num, err := strconv.ParseInt("1234", 10, 64)
// "1234" → строка, 10 → десятичная система, 64 → int64
if err != nil {
fmt.Println("Ошибка:", err)
} else {
fmt.Println("Число:", num) // 1234
}
Используется
strconv.Itoa()
или strconv.FormatInt()
. num := 42
str := strconv.Itoa(num) // 42 → "42"
fmt.Println("Строка:", str) // "42"
Пример 2:
strconv.FormatInt()
(int64 → строка) num := int64(12345)
str := strconv.FormatInt(num, 10) // 10 — десятичная система
fmt.Println("Строка:", str) // "12345"
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Prometheus в Go-программах собирает такие стандартные метрики:
- Go runtime метрики (go_gc_duration_seconds, go_memstats_heap_alloc_bytes).
- HTTP-метрики (если используется prometheus/client_golang).
- Метрики запросов (http_requests_total, request_duration_seconds).
- Производительность CPU и памяти (process_cpu_seconds_total, process_resident_memory_bytes).
Для их сбора нужно подключить prometheus/client_golang и зарегистрировать метрики в /metrics.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
SSL (Secure Sockets Layer) — это протокол, который обеспечивает безопасность передачи данных в интернете, используя шифрование. Он был разработан для защиты данных, передаваемых между клиентом (например, веб-браузером) и сервером (например, веб-сайтом), от перехвата и манипуляций.
Защита данных от перехвата и чтения посторонними лицами путем их шифрования.
Подтверждение подлинности сервера (и иногда клиента) с помощью цифровых сертификатов, что позволяет клиенту убедиться, что он подключен к настоящему серверу.
Проверка того, что данные не были изменены во время передачи, с помощью контрольных сумм и хеш-функций.
Клиент инициирует соединение с сервером, запрашивая защищенное соединение.
Сервер отправляет свой цифровой сертификат, который содержит его публичный ключ и информацию о сервере.
Клиент проверяет сертификат, используя доверенные центры сертификации (CA), чтобы удостовериться в подлинности сервера.
Клиент и сервер используют асимметричное шифрование для обмена ключами сеанса, которые затем используются для симметричного шифрования данных в течение сессии.
Все данные, передаваемые между клиентом и сервером, шифруются с использованием симметричных ключей, обеспечивая безопасность передачи.
При посещении веб-сайта с использованием HTTPS (например, https://example.com), SSL обеспечивает шифрование и безопасность данных, передаваемых между вашим браузером и сервером.
Никогда не был выпущен публично из-за серьезных уязвимостей.
Выпущен в 1995 году, но вскоре был признан небезопасным из-за множества уязвимостей.
Выпущен в 1996 году, значительно улучшил безопасность, но со временем также был признан устаревшим из-за уязвимостей (например, POODLE-атака).
SSL был заменен протоколом TLS, который является его преемником и предлагает улучшенную безопасность. Текущие версии TLS (1.2 и 1.3) используются вместо SSL.
TLS обеспечивает более сильное шифрование, лучшее управление сессионными ключами и устранение уязвимостей, найденных в SSL.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
1. Легковесность – горутины потребляют меньше памяти, чем системные потоки.
2. Планировщик Go – позволяет управлять миллионами горутин без явного управления потоками.
3. Простота работы с конкурентностью – нет необходимости использовать mutex для синхронизации, благодаря каналам.
4. Низкие затраты на переключение контекста – переключение между горутинами быстрее, чем между потоками.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
В многопоточных (параллельных) программах горутины (goroutines) могут одновременно изменять одни и те же данные. Если не синхронизировать доступ, это приведёт к гонке данных (data race), когда несколько потоков читают/пишут одно и то же значение одновременно.
Используется для блокировки критической секции кода, чтобы в один момент только одна горутина могла изменять данные.
package main
import (
"fmt"
"sync"
)
var (
counter int
mutex sync.Mutex
)
func increment(wg *sync.WaitGroup) {
defer wg.Done()
mutex.Lock() // Блокируем доступ
counter++ // Изменяем данные
mutex.Unlock() // Разблокируем доступ
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Println("Итоговый счетчик:", counter) // 1000
}
Позволяет нескольким горутинам читать данные одновременно, но блокирует запись.
var (
data int
mutex sync.RWMutex
)
func readData(wg *sync.WaitGroup) {
defer wg.Done()
mutex.RLock() // Разрешаем чтение
fmt.Println("Читаем данные:", data)
mutex.RUnlock()
}
func writeData(wg *sync.WaitGroup) {
defer wg.Done()
mutex.Lock() // Блокируем на запись
data++
mutex.Unlock()
}
Позволяет дождаться завершения всех горутин без блокировки данных.
var wg sync.WaitGroup
wg.Add(2) // Ожидаем 2 горутины
go func() {
defer wg.Done()
fmt.Println("Горутина 1 завершилась")
}()
go func() {
defer wg.Done()
fmt.Println("Горутина 2 завершилась")
}()
wg.Wait() // Ждём завершения всех горутин
fmt.Println("Все горутины завершены")
Атомарные операции быстрее мьютексов и гарантируют безопасное обновление переменных без гонок данных.
import "sync/atomic"
var counter int64
func incrementAtomic() {
atomic.AddInt64(&counter, 1) // Атомарное увеличение
}
В Go рекомендуется избегать блокировок и использовать каналы для передачи данных между горутинами.
package main
import "fmt"
func main() {
ch := make(chan int) // Канал для передачи данных
go func() {
ch <- 42 // Отправляем данные
}()
data := <-ch // Получаем данные
fmt.Println("Получено:", data)
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
1. Отсутствие привязки к конкретному ядру – горутины исполняются планировщиком Go, а не напрямую ОС.
2. Необходимость контроля за утечками – забытые горутины продолжают выполняться, потребляя ресурсы.
3. Ограниченная поддержка системных вызовов – при блокирующих операциях Go может выделять новый поток ОС.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Слайс (
slice
) — это динамический массив, который ссылается на часть массива в памяти. В отличие от массивов (array
), слайсы могут изменять размер. Слайс в Go — это структура
type SliceHeader struct {
Data uintptr // Указатель на массив в памяти
Len int // Длина слайса (количество элементов)
Cap int // Вместимость (capacity) — сколько элементов может вместить без перевыделения памяти
}
Пример структуры слайса
arr := [5]int{1, 2, 3, 4, 5}
s := arr[1:4] // Берём срез от 2-го до 4-го элемента
fmt.Println(s) // [2 3 4]
Есть несколько способов создать слайс:
Способ 1: Срез массива
arr := [5]int{10, 20, 30, 40, 50}
s := arr[1:4] // [20 30 40]
Способ 2: Использование
make()
s := make([]int, 3, 5) // Длина 3, вместимость 5
fmt.Println(s, len(s), cap(s)) // [0 0 0] 3 5
Способ 3: Литерал (инициализация значениями)
s := []int{1, 2, 3}
fmt.Println(s) // [1 2 3]
Слайсы можно изменять, используя
append()
. s := []int{1, 2, 3}
s = append(s, 4, 5)
fmt.Println(s) // [1 2 3 4 5]
Когда
append()
увеличивает slice
, Go использует оптимизированный алгоритм роста:- Если
cap < 1024
, слайс удваивает размер (cap *= 2
). - Если
cap >= 1024
, рост идёт примерно на 25% (cap += cap / 4
). s := []int{}
for i := 0; i < 10; i++ {
s = append(s, i)
fmt.Printf("Len: %d, Cap: %d\n", len(s), cap(s))
}
Выход (пример)
Len: 1, Cap: 1
Len: 2, Cap: 2
Len: 3, Cap: 4
Len: 5, Cap: 8
Len: 9, Cap: 16
Так как слайсы хранят ссылку на массив, возможны побочные эффекты.
arr := [5]int{1, 2, 3, 4, 5}
s1 := arr[:3] // [1 2 3]
s2 := arr[2:] // [3 4 5]
s2[0] = 100 // Меняем первый элемент s2
fmt.Println(s1) // [1 2 100] ❗️ s1 тоже изменился
Решение: используйте
copy()
для создания нового массива. s1 := []int{1, 2, 3}
s2 := make([]int, len(s1))
copy(s2, s1) // Копируем данные
s2[0] = 100
fmt.Println(s1) // [1 2 3] ✅ Оригинал не изменился
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
- Читать данные, пока буфер не станет пустым.
- Использовать range, чтобы получить оставшиеся элементы.
- Проверять закрытие с помощью второго параметра (ok).
Записывать в закрытый канал нельзя – это приведет к panic.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Контексты (context.Context) представляют собой интерфейс, который используется для передачи мета-данных, управления сроками действия и отменой операций в иерархии вызовов функций. Основная цель контекста — обеспечение способа для остановки выполнения программы (например, запросов или подпроцессов) по требованию. Это особенно полезно в сетевых приложениях, где вам может потребоваться прервать выполнение операции, которая больше не требуется или занимает слишком много времени.
Контекст предназначен для безопасной передачи по горутинам, что означает, что он может быть передан между различными частями вашей программы, выполняющимися в разных горутинах.
Контекст может быть отменен, что автоматически сигнализирует всем получателям этого контекста о необходимости остановить свою работу. Это обеспечивается через канал, который закрывается, когда контекст отменяется.
Контекст может иметь установленный таймаут или дедлайн, после достижения которого он автоматически отменяется.
Контексты могут нести значения — пары ключ-значение, которые можно устанавливать и получать. Эти значения обычно используются для передачи данных, специфичных для запроса, таких как идентификаторы сессий или токены авторизации.
🟠context.Background()
Возвращает пустой контекст, который никогда не отменяется. Обычно используется в основной функции и при инициализации.
context.TODO()
Используется для указания, что должен быть предоставлен подходящий контекст. Обычно применяется в разработке и при рефакторинге.
context.WithCancel(parent Context) (ctx Context, cancel CancelFunc)
Создает новый контекст с возможностью отмены.
context.WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
Создает контекст, который автоматически отменяется в указанный
deadline
.context.WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
Аналогичен
WithDeadline
, но устанавливает время жизни контекста на основе заданного таймаута.func operation1(ctx context.Context) {
select {
case <-time.After(5 * time.Second):
fmt.Println("operation1 completed")
case <-ctx.Done():
fmt.Println("operation1 cancelled")
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
go operation1(ctx)
// Дожидаемся завершения или отмены операции
<-ctx.Done()
if err := ctx.Err(); err != nil {
fmt.Println("main:", err)
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Проверить второй параметр при чтении:
- Если ok == false – канал закрыт, а 0 не является значением.
- Если ok == true – это действительное значение 0, переданное в канал.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Go (или Golang) обладает рядом преимуществ, которые делают его популярным.
Был разработан с упором на простоту синтаксиса и лаконичность. Это делает язык легким для изучения и чтения кода. Минимализм языка позволяет разработчикам сосредоточиться на решении задач, а не на сложностях синтаксиса.
Компилируется в машинный код, что обеспечивает высокую производительность выполнения программ. Производительность Go сопоставима с производительностью программ, написанных на C или C++, благодаря низкоуровневой оптимизации компилятора.
Одним из ключевых преимуществ является встроенная поддержка параллелизма и конкурентности. С помощью горутин и каналов разработчики могут легко создавать многопоточные приложения.
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 3; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
Имеет строгую систему типов, которая помогает предотвращать ошибки на этапе компиляции. В языке отсутствуют неявные преобразования типов, что снижает вероятность ошибок. Также Go управляет памятью с помощью встроенного сборщика мусора (garbage collector), что предотвращает утечки памяти.
Стандартных инструментов
Поставляется с богатым набором встроенных инструментов для разработки, таких как:
go fmt
для автоматического форматирования кода.go test
для запуска тестов.go build
и go run
для сборки и выполнения программ.go doc
для генерации документации.Поддерживает компиляцию кода для различных платформ и операционных систем. Это делает его удобным для разработки кроссплатформенных приложений.
Обширна и покрывает многие аспекты разработки, такие как работа с сетью, работа с файлами, веб-разработка и многое другое. Это позволяет разработчикам быстро начинать работу, не тратя время на поиск и интеграцию сторонних библиотек.
Имеет большое и активное сообщество, а также поддерживается компанией Google. Это гарантирует наличие множества ресурсов для обучения и решения возникающих вопросов, а также активное развитие и улучшение языка.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Можно использовать канал done, который закроется или передаст значение при завершении работы горутины. Например, через select можно отслеживать завершение.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
В контексте gRPC (Google Remote Procedure Call) unary и stream — это два разных типа взаимодействия между клиентом и сервером.
это стандартный запрос-ответ:
Клиент отправляет одно сообщение → сервер отвечает одним сообщением.
service UserService {
rpc GetUserInfo(UserRequest) returns (UserResponse);
}
Go-реализация Unary RPC
func (s *server) GetUserInfo(ctx context.Context, req *pb.UserRequest) (*pb.UserResponse, error) {
user := &pb.UserResponse{
Id: req.Id,
Name: "John Doe",
Email: "john@example.com",
}
return user, nil // Обычный ответ
}
В streaming RPC передача данных идёт потоком. gRPC поддерживает три вида стримов:
Клиент отправляет один запрос → сервер возвращает поток ответов.
service UserService {
rpc GetUserActivity(UserRequest) returns (stream ActivityResponse);
}
Go-реализация Server Streaming
func (s *server) GetUserActivity(req *pb.UserRequest, stream pb.UserService_GetUserActivityServer) error {
activities := []string{"Login", "Upload File", "Logout"}
for _, activity := range activities {
err := stream.Send(&pb.ActivityResponse{Message: activity})
if err != nil {
return err
}
time.Sleep(time.Second) // Имитация задержки
}
return nil
}
Клиент отправляет поток данных → сервер отвечает одним ответом.
service UploadService {
rpc UploadFile(stream FileChunk) returns (UploadResponse);
}
Go-реализация Client Streaming
func (s *server) UploadFile(stream pb.UploadService_UploadFileServer) error {
var totalSize int64
for {
chunk, err := stream.Recv()
if err == io.EOF {
return stream.SendAndClose(&pb.UploadResponse{Size: totalSize})
}
if err != nil {
return err
}
totalSize += chunk.Size
}
}
Клиент и сервер обмениваются данными в потоке одновременно.
service ChatService {
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}
Go-реализация Bi-directional Streaming
func (s *server) Chat(stream pb.ChatService_ChatServer) error {
for {
msg, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
response := &pb.ChatMessage{Text: "Echo: " + msg.Text}
if err := stream.Send(response); err != nil {
return err
}
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Использовать close(channel). После этого:
- Новые записи приведут к panic.
- Чтение продолжится, пока не будет пустым.
- Чтение из пустого закрытого канала даст zero-value.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM