github.com/artisanhe/tools@v1.0.1-0.20210607022958-19a8fef2eb04/mq/agent/agent.go (about)

     1  package agent
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"os"
     9  	"reflect"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/gomodule/redigo/redis"
    14  	"github.com/google/uuid"
    15  	"gopkg.in/robfig/cron.v2"
    16  
    17  	"github.com/artisanhe/tools/conf"
    18  	"github.com/artisanhe/tools/conf/presets"
    19  	"github.com/artisanhe/tools/courier"
    20  	"github.com/artisanhe/tools/env"
    21  	"github.com/artisanhe/tools/mq"
    22  	mq_redis "github.com/artisanhe/tools/mq/redis"
    23  	"github.com/artisanhe/tools/reflectx"
    24  	"github.com/artisanhe/tools/timelib"
    25  )
    26  
    27  var (
    28  	InvalidDeferTaskTime = errors.New("延迟执行任务时间错误")
    29  )
    30  
    31  type Agent struct {
    32  	Name string
    33  
    34  	CentreChannel string `conf:"env"`
    35  
    36  	Protocol       string
    37  	Host           string           `conf:"env,upstream"`
    38  	Port           int32            `conf:"env"`
    39  	Password       presets.Password `conf:"env"`
    40  	ConnectTimeout time.Duration
    41  	ReadTimeout    time.Duration
    42  	WriteTimeout   time.Duration
    43  	IdleTimeout    time.Duration
    44  	MaxActive      int
    45  	MaxIdle        int
    46  	Wait           bool
    47  	DB             int
    48  
    49  	NumWorkers int `conf:"env"`
    50  
    51  	Prefix string
    52  
    53  	pool  *redis.Pool
    54  	queue *mq.JobQueue
    55  }
    56  
    57  func (Agent) DockerDefaults() conf.DockerDefaults {
    58  	return conf.DockerDefaults{
    59  		"Host": "redis", //conf.RancherInternal("tool-deps", "redis"),
    60  		"Port": 36379,
    61  	}
    62  }
    63  
    64  func getDefaultName() string {
    65  	serviceName := "anonymous"
    66  	if projectName, exists := os.LookupEnv("PROJECT_NAME"); exists {
    67  		serviceName = projectName
    68  	}
    69  	return strings.ToLower(fmt.Sprintf("%s.%s", serviceName, env.GetRuntimeEnv()))
    70  }
    71  
    72  func (Agent) MarshalDefaults(v interface{}) {
    73  	if agent, ok := v.(*Agent); ok {
    74  		if agent.CentreChannel == "" {
    75  			agent.CentreChannel = "service-task-mgr.dev"
    76  		}
    77  		if agent.Name == "" {
    78  			agent.Name = getDefaultName()
    79  		}
    80  		if agent.Prefix == "" {
    81  			agent.Prefix = "mq-"
    82  		}
    83  		if agent.NumWorkers == 0 {
    84  			agent.NumWorkers = 1
    85  		}
    86  		if agent.Protocol == "" {
    87  			agent.Protocol = "tcp"
    88  		}
    89  		if agent.Port == 0 {
    90  			agent.Port = 6379
    91  		}
    92  		if agent.Password == "" {
    93  			agent.Password = "redis"
    94  		}
    95  		if agent.ConnectTimeout == 0 {
    96  			agent.ConnectTimeout = 10 * time.Second
    97  		}
    98  		if agent.ReadTimeout == 0 {
    99  			agent.ReadTimeout = 10 * time.Second
   100  		}
   101  		if agent.WriteTimeout == 0 {
   102  			agent.WriteTimeout = 10 * time.Second
   103  		}
   104  		if agent.IdleTimeout == 0 {
   105  			agent.IdleTimeout = 240 * time.Second
   106  		}
   107  		if agent.MaxActive == 0 {
   108  			agent.MaxActive = 5
   109  		}
   110  		if agent.MaxIdle == 0 {
   111  			agent.MaxIdle = 3
   112  		}
   113  		if !agent.Wait {
   114  			agent.Wait = true
   115  		}
   116  		if agent.DB == 0 {
   117  			agent.DB = 10
   118  		}
   119  	}
   120  }
   121  
   122  func (agent *Agent) GetPool() *redis.Pool {
   123  	return agent.pool
   124  }
   125  
   126  func (agent *Agent) initialPool() {
   127  	agent.pool = &redis.Pool{
   128  		Dial: func() (redis.Conn, error) {
   129  			return redis.Dial(
   130  				agent.Protocol,
   131  				fmt.Sprintf("%s:%d", agent.Host, agent.Port),
   132  				redis.DialConnectTimeout(agent.ConnectTimeout),
   133  				redis.DialReadTimeout(agent.ReadTimeout),
   134  				redis.DialWriteTimeout(agent.WriteTimeout),
   135  				redis.DialPassword(agent.Password.String()),
   136  				redis.DialDatabase(agent.DB),
   137  			)
   138  		},
   139  		MaxIdle:     agent.MaxIdle,
   140  		MaxActive:   agent.MaxActive,
   141  		IdleTimeout: agent.IdleTimeout,
   142  		Wait:        true,
   143  	}
   144  }
   145  
   146  func (agent *Agent) RegisterReceiver(receiver func(status *mq.TaskStatus) error) {
   147  	agent.queue.RegisterReceiver(receiver)
   148  }
   149  
   150  func (agent *Agent) StartReceiver() {
   151  	agent.queue.StartReceiver(agent.NumWorkers)
   152  }
   153  
   154  func (agent *Agent) StartWorker() {
   155  	agent.queue.StartWorker(agent.Name, agent.NumWorkers)
   156  }
   157  
   158  func (agent *Agent) Cancel(id string) error {
   159  	return agent.queue.Cancel(id)
   160  }
   161  
   162  func (agent *Agent) ListChannel() ([]string, error) {
   163  	return agent.queue.ListChannel()
   164  }
   165  
   166  func (agent *Agent) ListSubject(channel string) ([]string, error) {
   167  	return agent.queue.ListSubject(channel)
   168  }
   169  
   170  func SubjectAndDataFromValue(v interface{}) (string, []byte, error) {
   171  	subject := reflectx.IndirectType(reflect.TypeOf(v)).Name()
   172  	data, err := MarshalData(v)
   173  	return subject, data, err
   174  }
   175  
   176  func (agent *Agent) SendTask(task *mq.Task) error {
   177  	return agent.queue.SendTask(task)
   178  }
   179  
   180  type CronTableInfo struct {
   181  	CronTableID string                 `json:"cronTableID"`
   182  	Channel     string                 `json:"channel"`
   183  	Subject     string                 `json:"subject"`
   184  	Spec        string                 `json:"spec"`
   185  	Args        string                 `json:"args"`
   186  	NextTime    timelib.MySQLTimestamp `json:"nextTime"`
   187  	Desc        string                 `json:"desc"`
   188  }
   189  
   190  func (agent *Agent) RegisterCron(subject string, spec string) error {
   191  	cronTable := &CronTableInfo{}
   192  	cronTable.CronTableID = fmt.Sprintf("%s-%s", agent.Name, subject)
   193  	cronTable.Subject = subject
   194  	cronTable.Channel = agent.Name
   195  	cronTable.Spec = spec
   196  	cronTable.Desc = cronTable.CronTableID
   197  
   198  	bytes, _ := json.Marshal(cronTable)
   199  	_, err := agent.Publish(agent.CentreChannel, "SubjectCreateOrUpdateCron", bytes)
   200  	return err
   201  }
   202  
   203  func (agent *Agent) Next(subject string, args interface{}) (*mq.Task, error) {
   204  	var data []byte
   205  	if args != nil {
   206  		data, _ = json.Marshal(args)
   207  	}
   208  	return agent.queue.Next(agent.Name, subject, data)
   209  }
   210  
   211  func (agent *Agent) Defer(subject string, nextTime time.Time, args interface{}) (*mq.Task, error) {
   212  	cronTable := &CronTableInfo{}
   213  	cronTable.CronTableID = uuid.New().String()
   214  	cronTable.Channel = agent.Name
   215  	cronTable.Subject = subject
   216  	cronTable.NextTime = timelib.MySQLTimestamp(nextTime)
   217  
   218  	if args != nil {
   219  		data, _ := json.Marshal(args)
   220  		cronTable.Args = string(data)
   221  	}
   222  
   223  	bytes, _ := json.Marshal(cronTable)
   224  	return agent.Publish(agent.CentreChannel, "SubjectCreateOrUpdateCron", bytes)
   225  }
   226  
   227  func (agent *Agent) Publish(chanel, subject string, data []byte) (*mq.Task, error) {
   228  	return agent.queue.Publish(chanel, subject, data)
   229  }
   230  
   231  func (agent *Agent) Register(subject string, job mq.Job) {
   232  	agent.queue.Register(subject, job)
   233  }
   234  
   235  func (agent *Agent) RegisterRoutes(routes ...*courier.Route) {
   236  	for _, route := range routes {
   237  		if len(route.Operators) == 0 {
   238  			continue
   239  		}
   240  
   241  		operatorMetas := courier.ToOperatorMetaList(route.Operators...)
   242  
   243  		lastOpIndex := len(operatorMetas) - 1
   244  		lastOpMeta := operatorMetas[lastOpIndex]
   245  		subject := lastOpMeta.Type.Name()
   246  
   247  		if cronDescriber, ok := lastOpMeta.Operator.(mq.CronDescriber); ok {
   248  			spec := cronDescriber.CronSpec()
   249  			_, err := cron.Parse(spec)
   250  			if err != nil {
   251  				panic(err)
   252  			}
   253  			if agent.RegisterCron(subject, spec); err != nil {
   254  				panic(err)
   255  			}
   256  		}
   257  
   258  		agent.queue.Register(subject, func(task *mq.Task) (interface{}, error) {
   259  			ctx := context.Background()
   260  
   261  			for i, opMeta := range operatorMetas {
   262  				op := reflect.New(opMeta.Type).Interface().(courier.IOperator)
   263  
   264  				if err := UnmarshalData(task.Data, op); err != nil {
   265  					return nil, err
   266  				}
   267  
   268  				ret, err := op.Output(ctx)
   269  				if err != nil {
   270  					return nil, err
   271  				}
   272  
   273  				if i != lastOpIndex {
   274  					if ctxProvider, ok := op.(courier.IContextProvider); ok {
   275  						ctx = context.WithValue(ctx, ctxProvider.ContextKey(), ret)
   276  					}
   277  					continue
   278  				}
   279  
   280  				return ret, nil
   281  			}
   282  
   283  			return nil, nil
   284  		})
   285  	}
   286  }
   287  
   288  func (agent *Agent) initialQueue() {
   289  	agent.queue = mq.NewJobQueue(
   290  		mq_redis.NewRedisBroker(agent.pool, agent.Prefix),
   291  		mq_redis.NewRedisBackend(agent.pool, agent.Prefix),
   292  	)
   293  }
   294  
   295  func (agent *Agent) Init() {
   296  	agent.initialPool()
   297  	agent.initialQueue()
   298  }