Files
orbit/internal/config/config.go

160 lines
6.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package config
import (
"errors"
"fmt"
core "git.yuntuo.hk/k2safe/orbit/model"
"net"
"os"
"path/filepath"
"strconv"
"time"
"git.yuntuo.hk/k2safe/orbit/internal/retention"
)
type Config struct {
ArchiveRoot string // ArchiveRoot 私有持久归档卷绝对路径,空表示关闭后台执行器。
ArchiveStorageID core.ID // ArchiveStorageID 32位小写十六进制卷身份,各节点须挂载同一物理卷。
ArchiveMaxBytes int64 // ArchiveMaxBytes 全卷文件预算,默认10GiB。
ArchiveTickInterval time.Duration // ArchiveTickInterval 后台扫描间隔,默认10秒。
RetentionDefaults retention.Policy // RetentionDefaults 未保存数据库策略时使用的部署默认值。
HTTPAddr string
MySQLDSN string
MaxOpen int
MaxIdle int
MetricsAddr string
WorkflowTickInterval time.Duration // WorkflowTickInterval Weave 持久化推进扫描间隔,范围100ms–1m。
ScheduleTickInterval time.Duration // ScheduleTickInterval 计划扫描间隔,范围100ms–1m。
RecoveryTickInterval time.Duration // RecoveryTickInterval 执行租约恢复扫描间隔,范围100ms–1m。
RuntimeBatchLimit int // RuntimeBatchLimit 每个后台批次的候选上限,范围1–100。
ShutdownTimeout time.Duration // ShutdownTimeout 优雅排空和关闭窗口,范围1s–5m。
WorkerConnectionLimit int // WorkerConnectionLimit 单节点 Worker WebSocket 连接上限,范围1–4096。
EventConnectionLimit int // EventConnectionLimit 单节点 Event WebSocket 连接上限,范围1–4096。
DispatchWorkers int // DispatchWorkers 单个 Hub 并发执行数据库领取调用的协程数,范围1–256。
DispatchCapacity int // DispatchCapacity 单个 Hub 领取调用的有界内存排队槽位,范围1–65536。
}
func Load() (Config, error) { return FromEnv(os.Getenv) }
func FromEnv(get func(string) string) (Config, error) {
c := Config{
ArchiveRoot: get("ORBIT_ARCHIVE_ROOT"), ArchiveStorageID: core.ID(get("ORBIT_ARCHIVE_STORAGE_ID")), ArchiveMaxBytes: 10 << 30, ArchiveTickInterval: 10 * time.Second,
HTTPAddr: get("ORBIT_HTTP_ADDR"),
MySQLDSN: get("ORBIT_MYSQL_DSN"),
MaxOpen: 20,
MaxIdle: 5,
MetricsAddr: get("ORBIT_METRICS_ADDR"),
WorkflowTickInterval: time.Second,
ScheduleTickInterval: time.Second,
RecoveryTickInterval: 2 * time.Second,
RuntimeBatchLimit: 100,
ShutdownTimeout: 10 * time.Second,
WorkerConnectionLimit: 128,
EventConnectionLimit: 128,
DispatchWorkers: 4,
DispatchCapacity: 128,
}
if c.HTTPAddr == "" {
c.HTTPAddr = "127.0.0.1:8080"
}
if _, _, err := net.SplitHostPort(c.HTTPAddr); err != nil {
return Config{}, errors.New("invalid ORBIT_HTTP_ADDR")
}
if c.MySQLDSN == "" {
return Config{}, errors.New("ORBIT_MYSQL_DSN is required")
}
for _, field := range []struct {
name string
dst *int
}{{"ORBIT_DB_MAX_OPEN", &c.MaxOpen}, {"ORBIT_DB_MAX_IDLE", &c.MaxIdle}} {
if raw := get(field.name); raw != "" {
n, err := strconv.Atoi(raw)
if err != nil || n < 0 {
return Config{}, fmt.Errorf("%s must be a nonnegative integer", field.name)
}
*field.dst = n
}
}
if c.MaxOpen <= 0 || c.MaxIdle > c.MaxOpen {
return Config{}, errors.New("database pool requires 0 <= max_idle <= max_open and max_open > 0")
}
for _, field := range []struct {
name string
dst *time.Duration
}{{"ORBIT_WORKFLOW_TICK_INTERVAL", &c.WorkflowTickInterval}, {"ORBIT_SCHEDULE_TICK_INTERVAL", &c.ScheduleTickInterval}, {"ORBIT_RECOVERY_TICK_INTERVAL", &c.RecoveryTickInterval}} {
if raw := get(field.name); raw != "" {
d, err := time.ParseDuration(raw)
if err != nil || d < 100*time.Millisecond || d > time.Minute {
return Config{}, fmt.Errorf("%s must be a duration from 100ms to 1m", field.name)
}
*field.dst = d
}
}
if raw := get("ORBIT_RUNTIME_BATCH_LIMIT"); raw != "" {
n, err := strconv.Atoi(raw)
if err != nil || n < 1 || n > 100 {
return Config{}, errors.New("ORBIT_RUNTIME_BATCH_LIMIT must be an integer from 1 to 100")
}
c.RuntimeBatchLimit = n
}
if raw := get("ORBIT_SHUTDOWN_TIMEOUT"); raw != "" {
d, err := time.ParseDuration(raw)
if err != nil || d < time.Second || d > 5*time.Minute {
return Config{}, errors.New("ORBIT_SHUTDOWN_TIMEOUT must be a duration from 1s to 5m")
}
c.ShutdownTimeout = d
}
for _, field := range []struct {
name string
dst *int
min int
max int
}{
{"ORBIT_WORKER_CONNECTION_LIMIT", &c.WorkerConnectionLimit, 1, 4096},
{"ORBIT_EVENT_CONNECTION_LIMIT", &c.EventConnectionLimit, 1, 4096},
{"ORBIT_DISPATCH_WORKERS", &c.DispatchWorkers, 1, 256},
{"ORBIT_DISPATCH_CAPACITY", &c.DispatchCapacity, 1, 65536},
} {
if raw := get(field.name); raw != "" {
n, err := strconv.Atoi(raw)
if err != nil || n < field.min || n > field.max {
return Config{}, fmt.Errorf("%s must be an integer from %d to %d", field.name, field.min, field.max)
}
*field.dst = n
}
}
if c.MetricsAddr != "" {
_, port, err := net.SplitHostPort(c.MetricsAddr)
n, parseErr := strconv.Atoi(port)
if err != nil || parseErr != nil || n < 0 || n > 65535 {
return Config{}, errors.New("invalid ORBIT_METRICS_ADDR")
}
}
if c.ArchiveRoot != "" || c.ArchiveStorageID != "" {
if !filepath.IsAbs(c.ArchiveRoot) || c.ArchiveStorageID.Validate() != nil {
return Config{}, errors.New("ORBIT_ARCHIVE_ROOT and ORBIT_ARCHIVE_STORAGE_ID must be configured together")
}
}
if raw := get("ORBIT_ARCHIVE_MAX_BYTES"); raw != "" {
n, err := strconv.ParseInt(raw, 10, 64)
if err != nil || n < 1<<20 || n > 1<<40 {
return Config{}, errors.New("ORBIT_ARCHIVE_MAX_BYTES must be 1MiB to 1TiB")
}
c.ArchiveMaxBytes = n
}
if raw := get("ORBIT_ARCHIVE_TICK_INTERVAL"); raw != "" {
d, err := time.ParseDuration(raw)
if err != nil || d < time.Second || d > time.Minute {
return Config{}, errors.New("ORBIT_ARCHIVE_TICK_INTERVAL must be 1s to 1m")
}
c.ArchiveTickInterval = d
}
var err error
c.RetentionDefaults, err = retention.FromEnv(get)
if err != nil {
return Config{}, err
}
return c, nil
}