Конфигурация¶
Настройка подключений к базам данных.
Обзор¶
Конфигурация строится вокруг шардов. Каждый шард может иметь мастера и реплики:
graph TB
subgraph "Кластер (serverConf)"
subgraph "Шард 1"
M1[Master 1]
R1[Replica 1a]
R2[Replica 1b]
end
subgraph "Шард 2"
M2[Master 2]
R3[Replica 2]
end
end
Интерфейс конфигурации¶
type ConfigInterface interface {
GetLastUpdateTime() time.Time
GetBool(ctx context.Context, path string, dfl ...bool) bool
GetBoolIfExists(ctx context.Context, path string) (bool, bool)
GetDuration(ctx context.Context, path string, dfl ...time.Duration) time.Duration
GetDurationIfExists(ctx context.Context, path string) (time.Duration, bool)
GetInt(ctx context.Context, path string, dfl ...int) int
GetIntIfExists(ctx context.Context, path string) (int, bool)
GetString(ctx context.Context, path string, dfl ...string) string
GetStringIfExists(ctx context.Context, path string) (string, bool)
GetStrings(ctx context.Context, path string, dfl []string) []string
GetStruct(ctx context.Context, path string, valuePtr interface{}) (bool, error)
}
Дерево параметров¶
Для //ar:serverConf:mydb:
mydb/
├── Timeout # Таймаут по умолчанию (Duration)
├── PoolSize # Размер пула по умолчанию (int)
├── master # Адреса мастеров (string, через запятую)
├── replica # Адреса реплик (string, через запятую)
└── 1/ # Шард 1 (опционально)
├── Timeout # Таймаут шарда
├── PoolSize # Пул шарда
├── master # Мастера шарда
└── replica # Реплики шарда
Варианты конфигурации¶
Простой (один сервер)¶
С репликой¶
mydb/Timeout = 200ms
mydb/PoolSize = 10
mydb/master = "10.0.1.1:5432"
mydb/replica = "10.0.1.2:5432"
Несколько мастеров¶
Несколько мастеров
Запись идёт во все мастера. Нет транзакционной целостности между ними. Используйте для кэшей.
Шардирование¶
mydb/Timeout = 200ms
mydb/PoolSize = 10
mydb/1/master = "10.0.1.1:5432"
mydb/1/replica = "10.0.1.2:5432"
mydb/1/Timeout = 100ms
mydb/2/master = "10.0.2.1:5432"
mydb/2/replica = "10.0.2.2:5432"
mydb/2/PoolSize = 20
Реализация конфигурации¶
Статическая конфигурация¶
package config
import (
"context"
"time"
)
type ARConfig struct {
updatedAt time.Time
values map[string]string
}
func NewARConfig() *ARConfig {
return &ARConfig{
updatedAt: time.Now(),
values: map[string]string{
"mydb/Timeout": "200ms",
"mydb/PoolSize": "10",
"mydb/master": "127.0.0.1:5432",
},
}
}
func (c *ARConfig) GetLastUpdateTime() time.Time {
return c.updatedAt
}
func (c *ARConfig) GetStringIfExists(ctx context.Context, path string) (string, bool) {
v, ok := c.values[path]
return v, ok
}
func (c *ARConfig) GetString(ctx context.Context, path string, dfl ...string) string {
if v, ok := c.GetStringIfExists(ctx, path); ok {
return v
}
if len(dfl) > 0 {
return dfl[0]
}
return ""
}
func (c *ARConfig) GetDurationIfExists(ctx context.Context, path string) (time.Duration, bool) {
if v, ok := c.GetStringIfExists(ctx, path); ok {
d, err := time.ParseDuration(v)
if err == nil {
return d, true
}
}
return 0, false
}
func (c *ARConfig) GetDuration(ctx context.Context, path string, dfl ...time.Duration) time.Duration {
if d, ok := c.GetDurationIfExists(ctx, path); ok {
return d
}
if len(dfl) > 0 {
return dfl[0]
}
return 0
}
func (c *ARConfig) GetIntIfExists(ctx context.Context, path string) (int, bool) {
if v, ok := c.GetStringIfExists(ctx, path); ok {
var i int
if _, err := fmt.Sscanf(v, "%d", &i); err == nil {
return i, true
}
}
return 0, false
}
func (c *ARConfig) GetInt(ctx context.Context, path string, dfl ...int) int {
if i, ok := c.GetIntIfExists(ctx, path); ok {
return i
}
if len(dfl) > 0 {
return dfl[0]
}
return 0
}
func (c *ARConfig) GetBoolIfExists(ctx context.Context, path string) (bool, bool) {
return false, false
}
func (c *ARConfig) GetBool(ctx context.Context, path string, dfl ...bool) bool {
if len(dfl) > 0 {
return dfl[0]
}
return false
}
func (c *ARConfig) GetStrings(ctx context.Context, path string, dfl []string) []string {
return dfl
}
func (c *ARConfig) GetStruct(ctx context.Context, path string, valuePtr interface{}) (bool, error) {
return false, nil
}
Динамическая конфигурация¶
Для поддержки hot reload реализуйте GetLastUpdateTime():
type DynamicConfig struct {
mu sync.RWMutex
values map[string]string
updatedAt time.Time
}
func (c *DynamicConfig) Reload() {
c.mu.Lock()
defer c.mu.Unlock()
// Перезагрузка значений
c.values = loadFromSource()
c.updatedAt = time.Now()
}
func (c *DynamicConfig) GetLastUpdateTime() time.Time {
c.mu.RLock()
defer c.mu.RUnlock()
return c.updatedAt
}
ActiveRecord периодически проверяет GetLastUpdateTime() и пересоздаёт соединения при изменении.
Регистрация¶
package main
import (
"github.com/Educentr/go-activerecord/v3/pkg/activerecord"
)
func main() {
cfg := config.NewARConfig()
activerecord.RegisterConfig(cfg)
// Теперь можно работать с моделями
}
Метрики и логирование¶
// Регистрация метрик (Prometheus-совместимые)
metrics := NewPrometheusMetrics()
activerecord.RegisterMetrics(metrics)
// Регистрация логгера
logger := zerolog.New(os.Stdout)
activerecord.RegisterLogger(&logger)
Приоритет параметров¶
Параметры на уровне шарда переопределяют глобальные:
mydb/Timeout = 200ms # Глобальный
mydb/PoolSize = 10 # Глобальный
mydb/1/Timeout = 100ms # Переопределение для шарда 1
# mydb/1/PoolSize — использует глобальный (10)
mydb/2/PoolSize = 20 # Переопределение для шарда 2
# mydb/2/Timeout — использует глобальный (200ms)
Best Practices¶
1. Используйте таймауты¶
2. Настраивайте размер пула¶
3. Разделяйте чтение и запись¶
// Реплики для чтения, мастер для записи
"mydb/master": "master.db:5432"
"mydb/replica": "replica1.db:5432,replica2.db:5432"
4. Мониторьте соединения¶
Используйте метрики для отслеживания: - Количество активных соединений - Время ожидания соединения - Ошибки соединений
Следующие шаги¶
- Метрики — доступные метрики
- Архитектура — устройство системы