goworker

repository·master·Indexed 25 days ago

https://github.com/benmanns/goworker

A Resque-compatible, Go-based background worker that processes jobs pushed into a Redis queue by Ruby Resque clients. It provides a framework for registering worker functions, configuring concurrency and Redis connectivity via WorkerSettings or CLI flags, and programmatically enqueuing jobs using the Job and Payload structures.

Tokens
3.8K
Snippets
9
Records
21
Agent score
83%

What's inside goworker

  1. Register and run a background worker

    master

    To create a worker, define a function with the signature func(string, ...interface{}) error. Register this function using goworker.Register("ClassName", functionName) and start the worker loop by calling goworker.Work().

    package main
    
    import (
    	"fmt"
    	"github.com/benmanns/goworker"
    )
    
    func myFunc(queue string, args ...interface{}) error {
    	fmt.Printf("From %s, %v\n", queue, args)
    	return nil
    }
    
    func init() {
    	goworker.Register("MyClass", myFunc)
    }
    
    func main() {
    	if err := goworker.Work(); err != nil {
    		fmt.Println("Error:", err)
    	}
    }
  2. Define custom flags for workers

    master
    You can define your own custom flags for use within your workers. To ensure they are processed correctly alongside goworker's internal flags, define your flags and call flag.Parse() before calling goworker.Main().
  3. Handle potential job loss and failure modes

    master

    goworker does not guarantee job safety during process shutdown.

    • Idempotency: Workers must be idempotent and tolerant to job loss.
    • KILL/System Failure: If the process is killed with KILL or a system failure, the job currently in the poller's buffer may be lost.
    • Heroku/TERM signals: On platforms like Heroku, you have 10 seconds after a TERM signal before a KILL is sent. Jobs must finish within this window or they may be lost.
    • Recovery: Lost jobs can sometimes be recovered from Redis under the key resque:worker:<hostname>:<process-id>-<worker-id>:<queues>, which contains a JSON object with queue, run_at, and payload. However, manual processing is required and there is no guarantee the job hasn't already finished.
  4. Handle job arguments with type assertions

    master

    Worker functions receive arguments as a slice of interface{}. Because Resque/goworker often uses JSON for payloads, you should use Go type assertions (specifically with json.Number for numeric values) to convert arguments into usable types.

    // Expecting (int, string, float64)
    func myFunc(queue, args ...interface{}) error {
    	idNum, ok := args[0].(json.Number)
    	if !ok {
    		return errorInvalidParam
    	}
    	id, err := idNum.Int64()
    	if err != nil {
    		return errorInvalidParam
    	}
    	name, ok := args[1].(string)
    	if !ok {
    		return errorInvalidParam
    	}
    	weightNum, ok := args[2].(json.Number)
    	if !ok {
    		return errorInvalidParam
    	}
    	weight, err := weightNum.Float64()
    	if err != nil {
    		return errorInvalidParam
    	}
    	doSomething(id, name, weight)
    	return nil
    }
  5. Configure goworker using WorkerSettings

    master

    You can customize worker behavior by creating a goworker.WorkerSettings struct and passing it to goworker.SetSettings(settings). This allows you to configure the Redis URI, connection limits, concurrency, and specific queues.

    package main
    
    import (
    	"fmt"
    	"github.com/benmanns/goworker"
    )
    
    func myFunc(queue string, args ...interface{}) error {
    	fmt.Printf("From %s, %v\n", queue, args)
    	return nil
    }
    
    func init() {
    	settings := goworker.WorkerSettings{
    		URI:            "redis://localhost:6379/",
    		Connections:    100,
    		Queues:         []string{"myqueue", "delimited", "queues"},
    		UseNumber:      true,
    		ExitOnComplete: false,
    		Concurrency:    2,
    		Namespace:      "resque:",
    		Interval:       5.0,
    	}
    	goworker.SetSettings(settings)
    	goworker.Register("MyClass", myFunc)
    }
    
    func main() {
    	if err := goworker.Work(); err != nil {
    		fmt.Println("Error:", err)
    	}
    }
  6. Enqueue jobs via Go

    master

    You can programmatically enqueue jobs into a specific queue using goworker.Enqueue with a *goworker.Job object.

    goworker.Enqueue(&goworker.Job{
        Queue: "myqueue",
        Payload: goworker.Payload{
            Class: "MyClass",
            Args: []interface{}{"hi", "there"},
        },
    })
  7. Configure goworker using WorkerSettings

    master

    Use the WorkerSettings struct to define the behavior of the worker process. You can apply these settings using SetSettings(settings WorkerSettings) before calling Init() or Work().

    Fields:

    • QueuesString: A string representation of the queues to process.
    • Queues: The parsed queue configuration.
    • IntervalFloat: Float representation of the polling interval.
    • Interval: The parsed polling interval.
    • Concurrency: Number of concurrent workers to spawn.
    • Connections: Number of Redis connections to maintain in the pool.
    • URI: The Redis connection URI.
    • Namespace: The Redis namespace.
    • ExitOnComplete: If true, the process exits once queues are empty.
    • IsStrict: Whether to use strict queue processing.
    • UseNumber: Whether to use numeric identifiers.
    • SkipTLSVerify: Whether to skip TLS verification.
    • TLSCertPath: Path to the TLS certificate.
  8. Graceful shutdown and signal handling

    master
    To stop goworker, send a QUIT, TERM, or INT signal to the process. This will immediately stop job polling. Any jobs currently running (up to the value of $CONCURRENCY) will continue to run until they finish.
  9. Reference: goworker CLI flags

    master

    The following flags control the operation of the goworker client. Note that -queues is the only required flag.

    * `-queues="comma,delimited,queues"` — This is the only required flag. The recommended practice is to separate your Resque workers from your goworkers with different queues. Otherwise, Resque worker classes that have no goworker analog will cause the goworker process to fail the jobs. Because of this, there is no default queue, nor is there a way to select all queues (à la Resque's `*` queue). If you have multiple queues you can assign them weights. A queue with a weight of 2 will be checked twice as often as a queue with a weight of 1: `-queues='high=2,low=1'`.
    * `-interval=5.0` — Specifies the wait period between polling if no job was in the queue the last time one was requested.
    * `-concurrency=25` — Specifies the number of concurrently executing workers. This number can be as low as 1 or rather comfortably as high as 100,000, and should be tuned to your workflow and the availability of outside resources.
    * `-connections=2` — Specifies the maximum number of Redis connections that goworker will consume between the poller and all workers. There is not much performance gain over two and a slight penalty when using only one. This is configurable in case you need to keep connection counts low for cloud Redis providers who limit plans on `maxclients`.
    * `-uri=redis://localhost:6379/` — Specifies the URI of the Redis database from which goworker polls for jobs. Accepts URIs of the format `redis://user:pass@host:port/db` or `unix:///path/to/redis.sock`. The flag may also be set by the environment variable `$($REDIS_PROVIDER)` or `$REDIS_URL`. E.g. set `$REDIS_PROVIDER` to `REDISTOGO_URL` on Heroku to let the Redis To Go add-on configure the Redis database.
    * `-namespace=resque:` — Specifies the namespace from which goworker retrieves jobs and stores stats on workers.
    * `-exit-on-complete=false` — Exits goworker when there are no jobs left in the queue. This is helpful in conjunction with the `time` command to benchmark different configurations.
  10. Initialize the goworker process with Init()

    master

    The Init() function initializes the goworker process, including the logger, flags, and the Redis connection pool.

    While Work() calls this automatically, you can call Init() manually if you need to access goworker configuration or functions (like GetConn()) without starting the full worker loop. If you call Init() manually, you must call Close() to clean up resources.

    if err := goworker.Init(); err != nil {
    	fmt.Println("Error:", err)
    }
    defer goworker.Close()
  11. Clean up goworker resources with Close()

    master

    The Close() function cleans up initialized resources, such as the Redis connection pool.

    If you are using Work(), cleanup is handled automatically. If you are using Init() manually to access configuration or connections, you must call Close() to prevent resource leaks.