771 lines
40 KiB
Go
771 lines
40 KiB
Go
package worker
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/gogf/gf/v2/database/gredis"
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"github.com/gogf/gf/v2/os/gtime"
|
|
"github.com/gogf/gf/v2/util/gmode"
|
|
|
|
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/batch"
|
|
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/client/upstream"
|
|
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consts"
|
|
rabbitmqconsumer "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consumer/rabbitmq"
|
|
zsetconsumer "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consumer/zset"
|
|
fullsyncarchive "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/fullsync/archive"
|
|
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/fullsync/httpapi"
|
|
fullsyncruntime "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/fullsync/runtime"
|
|
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/fullsync/supply"
|
|
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/library/tdengine"
|
|
dailyusageproducer "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/producer/dailyusage"
|
|
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/producer/imei"
|
|
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/producer/monthlyusage"
|
|
monthlyhistoryproducer "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/producer/monthlyusagehistory"
|
|
simstatusproducer "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/producer/simstatus"
|
|
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/queue/zset"
|
|
batchrepository "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/repository/batch"
|
|
cancelrepository "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/repository/cancel"
|
|
datarepository "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/repository/data"
|
|
imeirepository "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/repository/imei"
|
|
inboundrepository "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/repository/inbound"
|
|
lifecyclerepository "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/repository/lifecycle"
|
|
monthlyusagerepository "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/repository/monthlyusage"
|
|
simstatusrepository "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/repository/simstatus"
|
|
trafficusagerepository "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/repository/trafficusage"
|
|
unsubrepository "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/repository/unsub"
|
|
addpackageservice "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/addpackage"
|
|
allocateservice "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/allocate"
|
|
balanceservice "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/balance"
|
|
cancelservice "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/cancel"
|
|
dailyusageservice "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/dailyusage"
|
|
dataservice "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/data"
|
|
imeiservice "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/imei"
|
|
inboundservice "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/inbound"
|
|
lifecycleservice "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/lifecycle"
|
|
monthlyusageservice "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/monthlyusage"
|
|
monthlyhistoryservice "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/monthlyusagehistory"
|
|
packagecustomerservice "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/packagecustomer"
|
|
renewchangeservice "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/renewchange"
|
|
rolesyncservice "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/rolesync"
|
|
simstatusservice "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/simstatus"
|
|
trafficcutoffconfigservice "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/trafficcutoffconfig"
|
|
trafficusageservice "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/trafficusage"
|
|
unsubservice "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/unsub"
|
|
warningconfigservice "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/warningconfig"
|
|
workorderservice "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/workorder"
|
|
)
|
|
|
|
const (
|
|
shutdownTimeout = 10 * time.Second
|
|
consumerRestartInitialDelay = 2 * time.Second
|
|
consumerRestartMaxDelay = 30 * time.Second
|
|
consumerRestartBackoffResetAfter = time.Minute
|
|
)
|
|
|
|
const monthlyUsageTemplateQPSConfigKey = "trafficUsageFullSync.consumer.templateQPS"
|
|
|
|
func loadMonthlyUsageTemplateQPS(ctx context.Context) map[string]float64 {
|
|
value, err := g.Cfg().Get(ctx, monthlyUsageTemplateQPSConfigKey)
|
|
if err != nil {
|
|
g.Log().Warningf(ctx, "load monthly usage template QPS config failed: %+v", err)
|
|
return nil
|
|
}
|
|
if value == nil || value.IsNil() {
|
|
return nil
|
|
}
|
|
var configured map[string]float64
|
|
if err = value.Scan(&configured); err != nil {
|
|
g.Log().Warningf(ctx, "parse monthly usage template QPS config failed: %+v", err)
|
|
return nil
|
|
}
|
|
return configured
|
|
}
|
|
|
|
func loadSimStatusTemplateQPS(ctx context.Context) map[string]float64 {
|
|
value, err := g.Cfg().Get(ctx, "simStatusQueueSync.consumer.templateQPS")
|
|
if err != nil || value == nil || value.IsNil() {
|
|
return nil
|
|
}
|
|
var configured map[string]float64
|
|
if err = value.Scan(&configured); err != nil {
|
|
g.Log().Warningf(ctx, "parse sim status template QPS config failed: %+v", err)
|
|
return nil
|
|
}
|
|
return configured
|
|
}
|
|
|
|
func loadImeiTemplateQPS(ctx context.Context) map[string]float64 {
|
|
value, err := g.Cfg().Get(ctx, "imeiFullSync.consumer.templateQPS")
|
|
if err != nil || value == nil || value.IsNil() {
|
|
return nil
|
|
}
|
|
var configured map[string]float64
|
|
if err = value.Scan(&configured); err != nil {
|
|
g.Log().Warningf(ctx, "parse imei template QPS config failed: %+v", err)
|
|
return nil
|
|
}
|
|
return configured
|
|
}
|
|
|
|
func loadSimStatusRunPrefix(ctx context.Context) (string, error) {
|
|
prefix := strings.TrimSpace(g.Cfg().MustGet(ctx, "simStatusQueueSync.redisKeyPrefix", "sim-status:").String())
|
|
if prefix == "" {
|
|
return "", gerror.New("sim status redisKeyPrefix must be nonempty")
|
|
}
|
|
return prefix, nil
|
|
}
|
|
|
|
func loadDailyUsageTemplateQPS(ctx context.Context) map[string]float64 {
|
|
value, err := g.Cfg().Get(ctx, "trafficUsageDailySync.consumer.templateQPS")
|
|
if err != nil || value == nil || value.IsNil() {
|
|
return nil
|
|
}
|
|
var configured map[string]float64
|
|
if err = value.Scan(&configured); err != nil {
|
|
g.Log().Warningf(ctx, "parse daily usage template QPS config failed: %+v", err)
|
|
return nil
|
|
}
|
|
return configured
|
|
}
|
|
|
|
func loadDailyUsageRunPrefix(ctx context.Context) (string, error) {
|
|
prefix := strings.TrimSpace(g.Cfg().MustGet(ctx, "trafficUsageDailySync.redisKeyPrefix", "daily:").String())
|
|
if prefix == "" {
|
|
return "", gerror.New("daily usage redisKeyPrefix must be nonempty")
|
|
}
|
|
return prefix, nil
|
|
}
|
|
|
|
func loadImeiRunPrefix(ctx context.Context) (string, error) {
|
|
prefix := strings.TrimSpace(g.Cfg().MustGet(ctx, "imeiFullSync.redisKeyPrefix", "imei:").String())
|
|
if prefix == "" {
|
|
return "", gerror.New("imei redisKeyPrefix must be nonempty")
|
|
}
|
|
return prefix, nil
|
|
}
|
|
|
|
func loadMonthlyHistoryRunPrefix(ctx context.Context) (string, error) {
|
|
prefix := strings.TrimSpace(g.Cfg().MustGet(ctx, "trafficUsageMonthlyHistorySync.redisKeyPrefix", "monthly-history:").String())
|
|
if prefix == "" {
|
|
return "", gerror.New("monthly usage history redisKeyPrefix must be nonempty")
|
|
}
|
|
return prefix, nil
|
|
}
|
|
|
|
func loadMonthlyHistoryTemplateQPS(ctx context.Context) map[string]float64 {
|
|
value, err := g.Cfg().Get(ctx, "trafficUsageMonthlyHistorySync.consumer.templateQPS")
|
|
if err != nil || value == nil || value.IsNil() {
|
|
return nil
|
|
}
|
|
var configured map[string]float64
|
|
if err = value.Scan(&configured); err != nil {
|
|
g.Log().Warningf(ctx, "parse monthly usage history template QPS config failed: %+v", err)
|
|
return nil
|
|
}
|
|
return configured
|
|
}
|
|
|
|
func superviseZSetConsumer(ctx context.Context, eventPrefix string, run func(context.Context) error) {
|
|
retryDelay := consumerRestartInitialDelay
|
|
for {
|
|
startedAt := time.Now()
|
|
runErr := run(ctx)
|
|
if ctx.Err() != nil {
|
|
return
|
|
}
|
|
if time.Since(startedAt) >= consumerRestartBackoffResetAfter {
|
|
retryDelay = consumerRestartInitialDelay
|
|
}
|
|
|
|
errorText := ""
|
|
if runErr != nil {
|
|
errorText = runErr.Error()
|
|
}
|
|
g.Log().Warningf(ctx, "event=%s_consumer status=restart_scheduled retryDelayMs=%d error=%q", eventPrefix, retryDelay.Milliseconds(), errorText)
|
|
|
|
timer := time.NewTimer(retryDelay)
|
|
select {
|
|
case <-ctx.Done():
|
|
timer.Stop()
|
|
return
|
|
case <-timer.C:
|
|
}
|
|
|
|
retryDelay *= 2
|
|
if retryDelay > consumerRestartMaxDelay {
|
|
retryDelay = consumerRestartMaxDelay
|
|
}
|
|
}
|
|
}
|
|
|
|
func loadFullSyncArchivePrefixes(ctx context.Context) ([]string, error) {
|
|
trafficPrefix := strings.TrimSpace(g.Cfg().MustGet(ctx, "trafficUsageFullSync.redisKeyPrefix", "flow:").String())
|
|
if trafficPrefix == "" {
|
|
trafficPrefix = "flow:"
|
|
}
|
|
statusPrefix, err := loadSimStatusRunPrefix(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dailyPrefix, err := loadDailyUsageRunPrefix(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
monthlyHistoryPrefix, err := loadMonthlyHistoryRunPrefix(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
imeiPrefix, err := loadImeiRunPrefix(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Duplicate prefixes would make Catalog and SimpleReplay read the same run
|
|
// hash twice. Deduplicate while preserving the traffic-first order.
|
|
prefixes := make([]string, 0, 5)
|
|
seen := make(map[string]struct{}, 5)
|
|
for _, prefix := range []string{trafficPrefix, statusPrefix, dailyPrefix, monthlyHistoryPrefix, imeiPrefix} {
|
|
if _, exists := seen[prefix]; exists {
|
|
continue
|
|
}
|
|
seen[prefix] = struct{}{}
|
|
prefixes = append(prefixes, prefix)
|
|
}
|
|
return prefixes, nil
|
|
}
|
|
|
|
func Run(ctx context.Context) error {
|
|
workerCtx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
|
|
defer stop()
|
|
|
|
if err := initialize(workerCtx); err != nil {
|
|
return err
|
|
}
|
|
|
|
var (
|
|
upstreamClient = upstream.New()
|
|
usageRepository = trafficusagerepository.New()
|
|
trafficUsage = trafficusageservice.New(upstreamClient, usageRepository)
|
|
workOrder = workorderservice.New()
|
|
balance = balanceservice.New()
|
|
inboundRepo = inboundrepository.New()
|
|
inboundExecutor = inboundservice.NewExecutorWithUpstream(inboundRepo, upstreamClient)
|
|
lifecycleRepo = lifecyclerepository.New()
|
|
lifecycleExecutor = lifecycleservice.NewExecutor(lifecycleRepo, upstreamClient)
|
|
cancelRepo = cancelrepository.New()
|
|
cancelExecutor = cancelservice.NewExecutor(cancelRepo)
|
|
renewChangeRepo = renewchangeservice.NewRepository()
|
|
addPackageCardExecutor = addpackageservice.NewCardExecutor(renewChangeRepo)
|
|
addPackagePoolExecutor = addpackageservice.NewPoolExecutor(renewChangeRepo)
|
|
renewPrepareExecutor = renewchangeservice.NewPrepareExecutor(renewChangeRepo)
|
|
prepareExecutor = addpackageservice.NewPrepareMux(renewChangeRepo, renewPrepareExecutor)
|
|
renewExecutor = renewchangeservice.NewRenewExecutor(renewChangeRepo)
|
|
changeExecutor = renewchangeservice.NewChangeExecutor(renewChangeRepo)
|
|
dataRepo = datarepository.New()
|
|
dataExecutor = dataservice.NewExecutor(dataRepo, upstreamClient)
|
|
unsubRepo = unsubrepository.New()
|
|
unsubExecutor = unsubservice.NewExecutor(unsubRepo)
|
|
restartExecutor = lifecycleservice.NewRestartExecutor(lifecycleRepo, upstreamClient)
|
|
batchRepository = batchrepository.New()
|
|
batchDispatcher = batch.NewDispatcher(lifecycleExecutor)
|
|
batchRuntime = batch.NewRuntime(
|
|
batchRepository,
|
|
batchDispatcher,
|
|
batch.WithExecutionTimeout(g.Cfg().MustGet(workerCtx, "rabbitmq.batchExecute.executionTimeout", "0s").Duration()),
|
|
batch.WithFinalizationTimeout(g.Cfg().MustGet(workerCtx, "rabbitmq.batchExecute.finalizationTimeout", "10s").Duration()),
|
|
)
|
|
allocateExecutor = allocateservice.NewExecutor()
|
|
warningConfigExecutor = warningconfigservice.NewExecutor()
|
|
trafficCutoffConfigExecutor = trafficcutoffconfigservice.NewExecutor()
|
|
packageCustomerSync = packagecustomerservice.New()
|
|
)
|
|
var (
|
|
monthlyProducer *monthlyusage.Producer
|
|
monthlyConsumer *zsetconsumer.Consumer
|
|
statusProducer *simstatusproducer.Producer
|
|
statusConsumer *zsetconsumer.Consumer
|
|
dailyProducer *dailyusageproducer.Producer
|
|
dailyConsumer *zsetconsumer.Consumer
|
|
monthlyHistoryProducer *monthlyhistoryproducer.Producer
|
|
monthlyHistoryConsumer *zsetconsumer.Consumer
|
|
imeiProducer *imei.Producer
|
|
imeiConsumer *zsetconsumer.Consumer
|
|
)
|
|
if g.Cfg().MustGet(workerCtx, "trafficUsageFullSync.enabled", false).Bool() {
|
|
prefix := g.Cfg().MustGet(workerCtx, "trafficUsageFullSync.redisKeyPrefix", "flow:").String()
|
|
store, err := zset.New(g.Redis(), prefix)
|
|
if err != nil {
|
|
return gerror.Wrap(err, "initialize monthly usage Redis store failed")
|
|
}
|
|
store.EnableArchiveIndex(g.Cfg().MustGet(workerCtx, "fullSyncOps.archive.enabled", false).Bool())
|
|
producerConfig, configErr := loadMonthlyUsageProducerConfig(workerCtx)
|
|
if configErr != nil {
|
|
return configErr
|
|
}
|
|
var supplyTracker *supply.Tracker
|
|
if g.Cfg().MustGet(workerCtx, "trafficUsageFullSync.supplyObservation.enabled", false).Bool() {
|
|
supplyTracker = supply.NewTracker(g.Cfg().MustGet(workerCtx, "trafficUsageFullSync.consumer.enabled", false).Bool())
|
|
producerConfig.SupplyObservation = supplyTracker
|
|
}
|
|
monthlyProducer, err = monthlyusage.New(&monthlyusage.DatabaseScanner{}, store, producerConfig)
|
|
if err != nil {
|
|
return gerror.Wrap(err, "initialize monthly usage producer failed")
|
|
}
|
|
monthlyUsageClient := upstream.NewWithCurrentUsageClient(
|
|
g.Cfg().MustGet(workerCtx, "trafficUsageFullSync.consumer.chanClientName", "").String(),
|
|
g.Cfg().MustGet(workerCtx, "trafficUsageFullSync.consumer.chanClientTimeout", "90s").Duration(),
|
|
)
|
|
usageHandler, handlerErr := monthlyusageservice.NewHandler(monthlyUsageClient, monthlyusagerepository.NewRepository())
|
|
if handlerErr != nil {
|
|
return gerror.Wrap(handlerErr, "initialize monthly usage handler failed")
|
|
}
|
|
monthlyConsumer, err = zsetconsumer.New(store, usageHandler, zsetconsumer.Config{
|
|
SupplyObservation: supplyTracker,
|
|
LockKey: g.Cfg().MustGet(workerCtx, "trafficUsageFullSync.consumer.lockKey", "flow:consumer:active").String(),
|
|
LockTTL: g.Cfg().MustGet(workerCtx, "trafficUsageFullSync.consumer.lockTTL", "1m").Duration(),
|
|
PollInterval: g.Cfg().MustGet(workerCtx, "trafficUsageFullSync.consumer.pollInterval", "1s").Duration(),
|
|
LeaseTTL: g.Cfg().MustGet(workerCtx, "trafficUsageFullSync.consumer.leaseTTL", "3m").Duration(),
|
|
TaskTimeout: g.Cfg().MustGet(workerCtx, "trafficUsageFullSync.consumer.taskTimeout", "2m").Duration(),
|
|
ClaimBatch: g.Cfg().MustGet(workerCtx, "trafficUsageFullSync.consumer.claimBatch", 100).Int(),
|
|
MobileConcurrency: g.Cfg().MustGet(workerCtx, "trafficUsageFullSync.consumer.mobileConcurrency", 30).Int(),
|
|
TelecomConcurrency: g.Cfg().MustGet(workerCtx, "trafficUsageFullSync.consumer.telecomConcurrency", 20).Int(),
|
|
GenericConcurrency: g.Cfg().MustGet(workerCtx, "trafficUsageFullSync.consumer.genericConcurrency", 1).Int(),
|
|
GenericQPS: g.Cfg().MustGet(workerCtx, "trafficUsageFullSync.consumer.genericQPS", 1.5).Float64(),
|
|
TemplateQPS: loadMonthlyUsageTemplateQPS(workerCtx),
|
|
RoundRetryEnabled: g.Cfg().MustGet(workerCtx, "trafficUsageFullSync.retry.enabled", true).Bool(),
|
|
MaxRounds: g.Cfg().MustGet(workerCtx, "trafficUsageFullSync.retry.maxRounds", 5).Int(),
|
|
FailureRetention: g.Cfg().MustGet(workerCtx, "trafficUsageFullSync.retry.failureRetention", "720h").Duration(),
|
|
ShouldRetryNextRound: func(result zsetconsumer.Result) bool {
|
|
return !result.Success && result.Retryable
|
|
},
|
|
})
|
|
if err != nil {
|
|
return gerror.Wrap(err, "initialize monthly usage consumer failed")
|
|
}
|
|
if g.Cfg().MustGet(workerCtx, "trafficUsageFullSync.consumer.enabled", false).Bool() {
|
|
go superviseZSetConsumer(workerCtx, "monthly_usage", monthlyConsumer.Run)
|
|
}
|
|
}
|
|
if g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.enabled", false).Bool() {
|
|
prefix, prefixErr := loadDailyUsageRunPrefix(workerCtx)
|
|
if prefixErr != nil {
|
|
return prefixErr
|
|
}
|
|
store, err := zset.New(g.Redis(), prefix)
|
|
if err != nil {
|
|
return gerror.Wrap(err, "initialize daily usage Redis store failed")
|
|
}
|
|
store.EnableArchiveIndex(g.Cfg().MustGet(workerCtx, "fullSyncOps.archive.enabled", false).Bool())
|
|
dailyScanMode, dailyScanConcurrency, scanErr := loadHistoryUsageScanSettings(workerCtx, "trafficUsageDailySync")
|
|
if scanErr != nil {
|
|
return scanErr
|
|
}
|
|
dailyProducer, err = dailyusageproducer.New(&dailyusageproducer.DatabaseScanner{}, store, dailyusageproducer.Config{
|
|
ScanMode: dailyScanMode,
|
|
ScanConcurrency: dailyScanConcurrency,
|
|
PageSize: g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.producer.pageSize", 100).Int(),
|
|
GlobalHighWaterMark: int64(g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.producer.globalHighWaterMark", 0).Int64()),
|
|
ChannelHighWaterMark: int64(g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.producer.channelHighWaterMark", 0).Int64()),
|
|
PollInterval: g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.producer.pollInterval", "2s").Duration(),
|
|
RunLockKey: g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.producer.runLockKey", "daily:producer:active").String(),
|
|
RunLockTTL: g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.producer.runLockTTL", "5m").Duration(),
|
|
RoundRetryEnabled: g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.retry.enabled", true).Bool(),
|
|
MaxRounds: g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.retry.maxRounds", 3).Int(),
|
|
FailureRetention: g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.retry.failureRetention", "720h").Duration(),
|
|
})
|
|
if err != nil {
|
|
return gerror.Wrap(err, "initialize daily usage producer failed")
|
|
}
|
|
dailyHandler, handlerErr := dailyusageservice.NewHandler(upstream.New(), trafficusagerepository.New())
|
|
if handlerErr != nil {
|
|
return gerror.Wrap(handlerErr, "initialize daily usage handler failed")
|
|
}
|
|
dailyConsumer, err = zsetconsumer.New(store, dailyHandler, zsetconsumer.Config{
|
|
RunQueueEnabled: true,
|
|
RoundRetryEnabled: g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.retry.enabled", true).Bool(),
|
|
MaxRounds: g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.retry.maxRounds", 3).Int(),
|
|
FailureRetention: g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.retry.failureRetention", "720h").Duration(),
|
|
ShouldRetryNextRound: dailyusageservice.ShouldRetryNextRound,
|
|
BlockedTasksAsFailures: true,
|
|
EventPrefix: "daily_usage",
|
|
LockKey: g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.consumer.lockKey", "daily:consumer:active").String(),
|
|
LockTTL: g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.consumer.lockTTL", "1m").Duration(),
|
|
PollInterval: g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.consumer.pollInterval", "1s").Duration(),
|
|
LeaseTTL: g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.consumer.leaseTTL", "3m").Duration(),
|
|
TaskTimeout: g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.consumer.taskTimeout", "2m").Duration(),
|
|
ClaimBatch: g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.consumer.claimBatch", 100).Int(),
|
|
MobileConcurrency: g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.consumer.mobileConcurrency", 30).Int(),
|
|
TelecomConcurrency: g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.consumer.telecomConcurrency", 8).Int(),
|
|
GenericConcurrency: g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.consumer.genericConcurrency", 1).Int(),
|
|
GenericQPS: g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.consumer.genericQPS", 1.5).Float64(),
|
|
TemplateQPS: loadDailyUsageTemplateQPS(workerCtx),
|
|
})
|
|
if err != nil {
|
|
return gerror.Wrap(err, "initialize daily usage consumer failed")
|
|
}
|
|
if g.Cfg().MustGet(workerCtx, "trafficUsageDailySync.consumer.enabled", false).Bool() {
|
|
go superviseZSetConsumer(workerCtx, "daily_usage", dailyConsumer.Run)
|
|
}
|
|
}
|
|
if g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.enabled", false).Bool() {
|
|
prefix, prefixErr := loadMonthlyHistoryRunPrefix(workerCtx)
|
|
if prefixErr != nil {
|
|
return prefixErr
|
|
}
|
|
store, err := zset.New(g.Redis(), prefix)
|
|
if err != nil {
|
|
return gerror.Wrap(err, "initialize monthly usage history Redis store failed")
|
|
}
|
|
store.EnableArchiveIndex(g.Cfg().MustGet(workerCtx, "fullSyncOps.archive.enabled", false).Bool())
|
|
monthlyHistoryScanMode, monthlyHistoryScanConcurrency, scanErr := loadHistoryUsageScanSettings(workerCtx, "trafficUsageMonthlyHistorySync")
|
|
if scanErr != nil {
|
|
return scanErr
|
|
}
|
|
monthlyHistoryProducer, err = monthlyhistoryproducer.New(&monthlyhistoryproducer.DatabaseScanner{}, store, monthlyhistoryproducer.Config{
|
|
ScanMode: monthlyHistoryScanMode,
|
|
ScanConcurrency: monthlyHistoryScanConcurrency,
|
|
PageSize: g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.producer.pageSize", 100).Int(),
|
|
GlobalHighWaterMark: int64(g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.producer.globalHighWaterMark", 0).Int64()),
|
|
ChannelHighWaterMark: int64(g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.producer.channelHighWaterMark", 0).Int64()),
|
|
PollInterval: g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.producer.pollInterval", "2s").Duration(),
|
|
RunLockKey: g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.producer.runLockKey", "monthly-history:producer:active").String(),
|
|
RunLockTTL: g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.producer.runLockTTL", "5m").Duration(),
|
|
RoundRetryEnabled: g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.retry.enabled", true).Bool(),
|
|
MaxRounds: g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.retry.maxRounds", 3).Int(),
|
|
FailureRetention: g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.retry.failureRetention", "720h").Duration(),
|
|
})
|
|
if err != nil {
|
|
return gerror.Wrap(err, "initialize monthly usage history producer failed")
|
|
}
|
|
monthlyHistoryHandler, handlerErr := monthlyhistoryservice.NewHandler(upstream.New(), trafficusagerepository.New())
|
|
if handlerErr != nil {
|
|
return gerror.Wrap(handlerErr, "initialize monthly usage history handler failed")
|
|
}
|
|
monthlyHistoryConsumer, err = zsetconsumer.New(store, monthlyHistoryHandler, zsetconsumer.Config{
|
|
RunQueueEnabled: true,
|
|
RoundRetryEnabled: g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.retry.enabled", true).Bool(),
|
|
MaxRounds: g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.retry.maxRounds", 3).Int(),
|
|
FailureRetention: g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.retry.failureRetention", "720h").Duration(),
|
|
ShouldRetryNextRound: monthlyhistoryservice.ShouldRetryNextRound,
|
|
BlockedTasksAsFailures: true,
|
|
EventPrefix: "monthly_usage_history",
|
|
LockKey: g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.consumer.lockKey", "monthly-history:consumer:active").String(),
|
|
LockTTL: g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.consumer.lockTTL", "1m").Duration(),
|
|
PollInterval: g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.consumer.pollInterval", "1s").Duration(),
|
|
LeaseTTL: g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.consumer.leaseTTL", "3m").Duration(),
|
|
TaskTimeout: g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.consumer.taskTimeout", "2m").Duration(),
|
|
ClaimBatch: g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.consumer.claimBatch", 100).Int(),
|
|
MobileConcurrency: g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.consumer.mobileConcurrency", 30).Int(),
|
|
TelecomConcurrency: g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.consumer.telecomConcurrency", 8).Int(),
|
|
GenericConcurrency: g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.consumer.genericConcurrency", 1).Int(),
|
|
GenericQPS: g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.consumer.genericQPS", 1.5).Float64(),
|
|
TemplateQPS: loadMonthlyHistoryTemplateQPS(workerCtx),
|
|
})
|
|
if err != nil {
|
|
return gerror.Wrap(err, "initialize monthly usage history consumer failed")
|
|
}
|
|
if g.Cfg().MustGet(workerCtx, "trafficUsageMonthlyHistorySync.consumer.enabled", false).Bool() {
|
|
go superviseZSetConsumer(workerCtx, "monthly_usage_history", monthlyHistoryConsumer.Run)
|
|
}
|
|
}
|
|
if g.Cfg().MustGet(workerCtx, "simStatusQueueSync.enabled", false).Bool() {
|
|
prefix, prefixErr := loadSimStatusRunPrefix(workerCtx)
|
|
if prefixErr != nil {
|
|
return prefixErr
|
|
}
|
|
store, err := zset.New(g.Redis(), prefix)
|
|
if err != nil {
|
|
return gerror.Wrap(err, "initialize sim status Redis store failed")
|
|
}
|
|
store.EnableArchiveIndex(g.Cfg().MustGet(workerCtx, "fullSyncOps.archive.enabled", false).Bool())
|
|
statusProducer, err = simstatusproducer.New(&simstatusproducer.DatabaseScanner{}, store, simstatusproducer.Config{
|
|
PageSize: g.Cfg().MustGet(workerCtx, "simStatusQueueSync.producer.pageSize", 1000).Int(),
|
|
GlobalHighWaterMark: int64(g.Cfg().MustGet(workerCtx, "simStatusQueueSync.producer.globalHighWaterMark", 0).Int64()),
|
|
PollInterval: g.Cfg().MustGet(workerCtx, "simStatusQueueSync.producer.pollInterval", "1s").Duration(),
|
|
RunLockKey: g.Cfg().MustGet(workerCtx, "simStatusQueueSync.producer.runLockKey", "sim-status:producer:active").String(),
|
|
RunLockTTL: g.Cfg().MustGet(workerCtx, "simStatusQueueSync.producer.runLockTTL", "5m").Duration(),
|
|
})
|
|
if err != nil {
|
|
return gerror.Wrap(err, "initialize sim status producer failed")
|
|
}
|
|
statusHandler, handlerErr := simstatusservice.NewHandler(
|
|
upstream.New(),
|
|
simstatusrepository.NewRepository(),
|
|
g.Cfg().MustGet(workerCtx, "simStatusQueueSync.consumer.networkRetries", 2).Int(),
|
|
)
|
|
if handlerErr != nil {
|
|
return gerror.Wrap(handlerErr, "initialize sim status handler failed")
|
|
}
|
|
statusConsumer, err = zsetconsumer.New(store, statusHandler, zsetconsumer.Config{
|
|
RunQueueEnabled: true,
|
|
RoundRetryEnabled: g.Cfg().MustGet(workerCtx, "simStatusQueueSync.retry.enabled", true).Bool(),
|
|
MaxRounds: g.Cfg().MustGet(workerCtx, "simStatusQueueSync.retry.maxRounds", 3).Int(),
|
|
FailureRetention: g.Cfg().MustGet(workerCtx, "simStatusQueueSync.retry.failureRetention", "720h").Duration(),
|
|
ShouldRetryNextRound: simstatusservice.ShouldRetryNextRound,
|
|
BlockedTasksAsFailures: true,
|
|
EventPrefix: "sim_status_queue",
|
|
LockKey: g.Cfg().MustGet(workerCtx, "simStatusQueueSync.consumer.lockKey", "sim-status:consumer:active").String(),
|
|
LockTTL: g.Cfg().MustGet(workerCtx, "simStatusQueueSync.consumer.lockTTL", "1m").Duration(),
|
|
PollInterval: g.Cfg().MustGet(workerCtx, "simStatusQueueSync.consumer.pollInterval", "1s").Duration(),
|
|
LeaseTTL: g.Cfg().MustGet(workerCtx, "simStatusQueueSync.consumer.leaseTTL", "3m").Duration(),
|
|
TaskTimeout: g.Cfg().MustGet(workerCtx, "simStatusQueueSync.consumer.taskTimeout", "2m").Duration(),
|
|
ClaimBatch: g.Cfg().MustGet(workerCtx, "simStatusQueueSync.consumer.claimBatch", 100).Int(),
|
|
MobileConcurrency: g.Cfg().MustGet(workerCtx, "simStatusQueueSync.consumer.mobileConcurrency", 100).Int(),
|
|
TelecomConcurrency: g.Cfg().MustGet(workerCtx, "simStatusQueueSync.consumer.telecomConcurrency", 8).Int(),
|
|
GenericConcurrency: g.Cfg().MustGet(workerCtx, "simStatusQueueSync.consumer.genericConcurrency", 1).Int(),
|
|
GenericQPS: g.Cfg().MustGet(workerCtx, "simStatusQueueSync.consumer.genericQPS", 1.5).Float64(),
|
|
TemplateQPS: loadSimStatusTemplateQPS(workerCtx),
|
|
FailurePoolAlertThreshold: g.Cfg().MustGet(workerCtx, "simStatusQueueSync.consumer.failurePoolAlertThreshold", 50000).Int64(),
|
|
})
|
|
if err != nil {
|
|
return gerror.Wrap(err, "initialize sim status consumer failed")
|
|
}
|
|
if g.Cfg().MustGet(workerCtx, "simStatusQueueSync.consumer.enabled", false).Bool() {
|
|
go superviseZSetConsumer(workerCtx, "sim_status_queue", statusConsumer.Run)
|
|
}
|
|
}
|
|
if g.Cfg().MustGet(workerCtx, "imeiFullSync.enabled", false).Bool() {
|
|
prefix, prefixErr := loadImeiRunPrefix(workerCtx)
|
|
if prefixErr != nil {
|
|
return prefixErr
|
|
}
|
|
store, err := zset.New(g.Redis(), prefix)
|
|
if err != nil {
|
|
return gerror.Wrap(err, "initialize imei Redis store failed")
|
|
}
|
|
store.EnableArchiveIndex(g.Cfg().MustGet(workerCtx, "fullSyncOps.archive.enabled", false).Bool())
|
|
// g.Redis may be initialized from g.Cfg without registering GetConfig.
|
|
leaseConfig, configured := gredis.GetConfig()
|
|
if !configured {
|
|
value, configErr := g.Cfg().Get(workerCtx, "redis.default")
|
|
if configErr != nil {
|
|
return gerror.Wrap(configErr, "load IMEI lease Redis configuration failed")
|
|
}
|
|
leaseConfig, err = gredis.ConfigFromMap(value.Map())
|
|
if err != nil {
|
|
return gerror.Wrap(err, "parse IMEI lease Redis configuration failed")
|
|
}
|
|
}
|
|
imeiProducer, err = imei.New(&imei.DatabaseScanner{}, store, imei.Config{
|
|
PageSize: g.Cfg().MustGet(workerCtx, "imeiFullSync.producer.pageSize", 1000).Int(),
|
|
GlobalHighWaterMark: int64(g.Cfg().MustGet(workerCtx, "imeiFullSync.producer.globalHighWaterMark", 0).Int64()),
|
|
PollInterval: g.Cfg().MustGet(workerCtx, "imeiFullSync.producer.pollInterval", "1s").Duration(),
|
|
RunLockKey: g.Cfg().MustGet(workerCtx, "imeiFullSync.producer.runLockKey", "imei:producer:active").String(),
|
|
RunLockTTL: g.Cfg().MustGet(workerCtx, "imeiFullSync.producer.runLockTTL", "5m").Duration(),
|
|
LeaseRedisConfig: leaseConfig,
|
|
})
|
|
if err != nil {
|
|
return gerror.Wrap(err, "initialize imei producer failed")
|
|
}
|
|
imeiHandler, handlerErr := imeiservice.NewHandler(
|
|
upstream.NewWithImeiClient(
|
|
g.Cfg().MustGet(workerCtx, "imeiFullSync.consumer.chanClientName", "").String(),
|
|
g.Cfg().MustGet(workerCtx, "imeiFullSync.consumer.chanClientTimeout", "30s").Duration(),
|
|
),
|
|
imeirepository.NewRepository(),
|
|
g.Cfg().MustGet(workerCtx, "imeiFullSync.consumer.networkRetries", 2).Int(),
|
|
)
|
|
if handlerErr != nil {
|
|
return gerror.Wrap(handlerErr, "initialize imei handler failed")
|
|
}
|
|
imeiConsumer, err = zsetconsumer.New(store, imeiHandler, zsetconsumer.Config{
|
|
RunQueueEnabled: true,
|
|
RoundRetryEnabled: g.Cfg().MustGet(workerCtx, "imeiFullSync.retry.enabled", true).Bool(),
|
|
MaxRounds: g.Cfg().MustGet(workerCtx, "imeiFullSync.retry.maxRounds", 5).Int(),
|
|
FailureRetention: g.Cfg().MustGet(workerCtx, "imeiFullSync.retry.failureRetention", "720h").Duration(),
|
|
ShouldRetryNextRound: imeiservice.ShouldRetryNextRound,
|
|
BlockedTasksAsFailures: true,
|
|
EventPrefix: "imei_queue",
|
|
LockKey: g.Cfg().MustGet(workerCtx, "imeiFullSync.consumer.lockKey", "imei:consumer:active").String(),
|
|
LockTTL: g.Cfg().MustGet(workerCtx, "imeiFullSync.consumer.lockTTL", "1m").Duration(),
|
|
PollInterval: g.Cfg().MustGet(workerCtx, "imeiFullSync.consumer.pollInterval", "1s").Duration(),
|
|
LeaseTTL: g.Cfg().MustGet(workerCtx, "imeiFullSync.consumer.leaseTTL", "3m").Duration(),
|
|
TaskTimeout: g.Cfg().MustGet(workerCtx, "imeiFullSync.consumer.taskTimeout", "2m").Duration(),
|
|
ClaimBatch: g.Cfg().MustGet(workerCtx, "imeiFullSync.consumer.claimBatch", 100).Int(),
|
|
MobileConcurrency: g.Cfg().MustGet(workerCtx, "imeiFullSync.consumer.mobileConcurrency", 100).Int(),
|
|
TelecomConcurrency: g.Cfg().MustGet(workerCtx, "imeiFullSync.consumer.telecomConcurrency", 8).Int(),
|
|
GenericConcurrency: g.Cfg().MustGet(workerCtx, "imeiFullSync.consumer.genericConcurrency", 1).Int(),
|
|
GenericQPS: g.Cfg().MustGet(workerCtx, "imeiFullSync.consumer.genericQPS", 1.5).Float64(),
|
|
TemplateQPS: loadImeiTemplateQPS(workerCtx),
|
|
// 失败池是唯一随卡数线性增长的每卡结构(单条约 1KB),
|
|
// 且会保留整个 failureRetention。超阀值只告警不截断。
|
|
FailurePoolAlertThreshold: g.Cfg().MustGet(workerCtx, "imeiFullSync.consumer.failurePoolAlertThreshold", 50000).Int64(),
|
|
})
|
|
if err != nil {
|
|
return gerror.Wrap(err, "initialize imei consumer failed")
|
|
}
|
|
if g.Cfg().MustGet(workerCtx, "imeiFullSync.consumer.enabled", false).Bool() {
|
|
go superviseZSetConsumer(workerCtx, "imei_queue", imeiConsumer.Run)
|
|
}
|
|
}
|
|
var rabbitMQConsumers *rabbitmqconsumer.Consumers
|
|
if monthlyProducer != nil {
|
|
rabbitMQConsumers = rabbitmqconsumer.NewWithPackageCustomerSyncAndMonthlyUsage(trafficUsage, workOrder, balance, packageCustomerSync, monthlyProducer, batchRuntime)
|
|
} else {
|
|
rabbitMQConsumers = rabbitmqconsumer.NewWithPackageCustomerSync(trafficUsage, workOrder, balance, packageCustomerSync, batchRuntime)
|
|
}
|
|
if statusProducer != nil {
|
|
rabbitMQConsumers.WithSimStatusQueue(statusProducer, statusProducer)
|
|
}
|
|
if dailyProducer != nil {
|
|
rabbitMQConsumers.WithDailyUsageStart(dailyProducer)
|
|
}
|
|
if monthlyHistoryProducer != nil {
|
|
rabbitMQConsumers.WithMonthlyHistoryStart(monthlyHistoryProducer)
|
|
}
|
|
if imeiProducer != nil {
|
|
rabbitMQConsumers.WithImeiQueue(imeiProducer)
|
|
}
|
|
rabbitMQConsumers.WithDataReviewQuery(dataExecutor)
|
|
var (
|
|
fullSyncHTTP *httpapi.Server
|
|
fullSyncArchives []*fullsyncarchive.Service
|
|
)
|
|
{ // The authenticated control plane is always available.
|
|
prefixes, err := loadFullSyncArchivePrefixes(workerCtx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stores := make([]*zset.Store, 0, len(prefixes))
|
|
for _, prefix := range prefixes {
|
|
store, e := zset.New(g.Redis(), prefix)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
stores = append(stores, store)
|
|
}
|
|
archiveRoot := g.Cfg().MustGet(workerCtx, "fullSyncOps.archive.rootDir", "/app/logs/full-sync").String()
|
|
repositories := make([]*fullsyncruntime.Repository, 0, len(stores))
|
|
for _, store := range stores {
|
|
repo, e := fullsyncruntime.New(store, archiveRoot)
|
|
if e != nil {
|
|
return gerror.Wrap(e, "initialize full-sync runtime repository failed")
|
|
}
|
|
repositories = append(repositories, repo)
|
|
}
|
|
catalog, err := fullsyncruntime.NewCatalog(repositories...)
|
|
if err != nil {
|
|
return gerror.Wrap(err, "initialize full-sync run catalog failed")
|
|
}
|
|
simple, err := fullsyncruntime.NewSimpleReplayWithDefault(g.Cfg().MustGet(workerCtx, "fullSyncOps.replay.maxRounds", fullsyncruntime.DefaultReplayMaxRounds).Int(), stores...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Only Admin exposes replay to users. This listener stays inside the
|
|
// Docker network; the Worker service must not publish port 18081.
|
|
fullSyncHTTP, err = httpapi.NewServer(httpapi.Config{Addr: ":18081", ShutdownTimeout: g.Cfg().MustGet(workerCtx, "fullSyncOps.control.shutdownTimeout", "10s").Duration()}, httpapi.Dependencies{Runs: catalog, List: catalog, Failures: catalog, Replays: repositories[0], Actions: repositories[0], Simple: simple})
|
|
if err != nil {
|
|
return gerror.Wrap(err, "initialize full-sync control server failed")
|
|
}
|
|
if err := fullSyncHTTP.Start(workerCtx); err != nil {
|
|
return gerror.Wrap(err, "start full-sync control server failed")
|
|
}
|
|
simple.Start(workerCtx)
|
|
}
|
|
if g.Cfg().MustGet(workerCtx, "fullSyncOps.archive.enabled", false).Bool() {
|
|
prefixes, err := loadFullSyncArchivePrefixes(workerCtx)
|
|
if err != nil {
|
|
return gerror.Wrap(err, "load full-sync archive Redis prefixes failed")
|
|
}
|
|
for _, prefix := range prefixes {
|
|
store, err := zset.New(g.Redis(), prefix)
|
|
if err != nil {
|
|
return gerror.Wrapf(err, "initialize full-sync archive Redis store failed for prefix %q", prefix)
|
|
}
|
|
store.EnableArchiveIndex(true)
|
|
repo, err := fullsyncruntime.New(store, g.Cfg().MustGet(workerCtx, "fullSyncOps.archive.rootDir", "/app/logs/full-sync").String())
|
|
if err != nil {
|
|
return gerror.Wrapf(err, "initialize full-sync archive repository failed for prefix %q", prefix)
|
|
}
|
|
service := fullsyncarchive.NewService(fullsyncarchive.Config{Root: g.Cfg().MustGet(workerCtx, "fullSyncOps.archive.rootDir", "/app/logs/full-sync").String(), Interval: g.Cfg().MustGet(workerCtx, "fullSyncOps.archive.pollInterval", "10s").Duration(), InitialBackoff: g.Cfg().MustGet(workerCtx, "fullSyncOps.archive.retryMin", "5s").Duration(), MaxBackoff: g.Cfg().MustGet(workerCtx, "fullSyncOps.archive.retryMax", "5m").Duration()}, repo, repo, repo)
|
|
if err := service.Start(workerCtx); err != nil {
|
|
return gerror.Wrapf(err, "start full-sync archive service failed for prefix %q", prefix)
|
|
}
|
|
fullSyncArchives = append(fullSyncArchives, service)
|
|
}
|
|
}
|
|
batchDispatcher.Register(consts.BatchTypeInbound, inboundExecutor)
|
|
batchDispatcher.Register(consts.BatchTypeAllocate, allocateExecutor)
|
|
batchDispatcher.Register(consts.BatchTypeCardAddPackage, addPackageCardExecutor)
|
|
batchDispatcher.Register(consts.BatchTypePoolAddPackage, addPackagePoolExecutor)
|
|
batchDispatcher.Register(consts.BatchTypePrepare, prepareExecutor)
|
|
batchDispatcher.Register(consts.BatchTypeRenew, renewExecutor)
|
|
batchDispatcher.Register(consts.BatchTypeChange, changeExecutor)
|
|
batchDispatcher.Register(consts.BatchTypeDataCut, dataExecutor)
|
|
batchDispatcher.Register(consts.BatchTypeDataStart, dataExecutor)
|
|
batchDispatcher.Register(consts.BatchTypeDataStop, dataExecutor)
|
|
// Resume durable APN sub-batches independently of RabbitMQ delivery and process restarts.
|
|
if reconciler, ok := dataExecutor.(interface{ RunReconciler(context.Context) error }); ok {
|
|
go func() {
|
|
if err := reconciler.RunReconciler(workerCtx); err != nil && workerCtx.Err() == nil {
|
|
g.Log().Errorf(workerCtx, "APN operation reconciler stopped: %v", err)
|
|
}
|
|
}()
|
|
}
|
|
batchDispatcher.Register(consts.BatchTypeSimCardUnsub, unsubExecutor)
|
|
batchDispatcher.Register(consts.BatchTypeSimCardRestart, restartExecutor)
|
|
batchDispatcher.Register(consts.BatchTypeSimCardCancel, cancelExecutor)
|
|
batchDispatcher.Register(consts.BatchTypeSimWarningConfig, warningConfigExecutor)
|
|
batchDispatcher.Register(consts.BatchTypeTrafficCutoffConfig, trafficCutoffConfigExecutor)
|
|
if err := rabbitMQConsumers.Start(workerCtx); err != nil {
|
|
_ = rabbitMQConsumers.Stop(context.Background())
|
|
return gerror.Wrap(err, "start RabbitMQ consumers failed")
|
|
}
|
|
|
|
stopRoleSync, err := rolesyncservice.Start(workerCtx)
|
|
if err != nil {
|
|
_ = rabbitMQConsumers.Stop(context.Background())
|
|
return gerror.Wrap(err, "start role sync Worker failed")
|
|
}
|
|
defer stopRoleSync()
|
|
|
|
g.Log().Info(workerCtx, "iotlcm worker started")
|
|
<-workerCtx.Done()
|
|
g.Log().Info(ctx, "iotlcm worker stopping")
|
|
stopRoleSync()
|
|
|
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
|
|
defer cancel()
|
|
if err := rabbitMQConsumers.Stop(shutdownCtx); err != nil {
|
|
return gerror.Wrap(err, "stop RabbitMQ consumers failed")
|
|
}
|
|
for _, service := range fullSyncArchives {
|
|
if err := service.Stop(); err != nil {
|
|
return gerror.Wrap(err, "stop full-sync archive service failed")
|
|
}
|
|
}
|
|
if fullSyncHTTP != nil {
|
|
if err := fullSyncHTTP.Wait(); err != nil {
|
|
return gerror.Wrap(err, "stop full-sync control server failed")
|
|
}
|
|
}
|
|
if err := tdengine.Close(); err != nil {
|
|
return gerror.Wrap(err, "close TDengine failed")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func initialize(ctx context.Context) error {
|
|
if err := g.DB().PingMaster(); err != nil {
|
|
return gerror.Wrap(err, "connect database failed")
|
|
}
|
|
if err := gtime.SetTimeZone("Asia/Shanghai"); err != nil {
|
|
return gerror.Wrap(err, "set timezone failed")
|
|
}
|
|
|
|
mode := g.Cfg().MustGet(ctx, "system.gfmode", gmode.NOT_SET).String()
|
|
for _, supported := range []string{gmode.DEVELOP, gmode.TESTING, gmode.STAGING, gmode.PRODUCT} {
|
|
if mode == supported {
|
|
gmode.Set(mode)
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|