refine delivery
parent
5931e3ce8f
commit
8fd65a83e4
|
@ -13,6 +13,7 @@ import (
|
||||||
clientv3 "go.etcd.io/etcd/client/v3"
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Delivery task delivery with ack and nack
|
||||||
type Delivery interface {
|
type Delivery interface {
|
||||||
Ack()
|
Ack()
|
||||||
Nack()
|
Nack()
|
||||||
|
@ -30,6 +31,7 @@ type deliver struct {
|
||||||
aliveCancel func()
|
aliveCancel func()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDelivery create the task delivery
|
||||||
func NewDelivery(ctx context.Context, client *clientv3.Client, key string, node string) (Delivery, error) {
|
func NewDelivery(ctx context.Context, client *clientv3.Client, key string, node string) (Delivery, error) {
|
||||||
d := &deliver{
|
d := &deliver{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
@ -64,15 +66,15 @@ func (d *deliver) assign(key string, node string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !resp.Succeeded {
|
if !resp.Succeeded {
|
||||||
return fmt.Errorf("key %s not exist or already assign ", key)
|
return fmt.Errorf("task %s not exist or already assign", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(resp.Responses) < 2 {
|
if len(resp.Responses) < 2 {
|
||||||
return fmt.Errorf("have no resp %s", key)
|
return fmt.Errorf("task %s tnx resp invalid, count=%d", key, len(resp.Responses))
|
||||||
}
|
}
|
||||||
getResp := resp.Responses[1].GetResponseRange()
|
getResp := resp.Responses[1].GetResponseRange()
|
||||||
if len(getResp.Kvs) == 0 {
|
if len(getResp.Kvs) == 0 || len(getResp.Kvs[0].Value) == 0 {
|
||||||
return fmt.Errorf("have no task %s", key)
|
return fmt.Errorf("task %s have no body", key)
|
||||||
}
|
}
|
||||||
kv := getResp.Kvs[0]
|
kv := getResp.Kvs[0]
|
||||||
|
|
||||||
|
@ -91,9 +93,11 @@ func (d *deliver) assign(key string, node string) error {
|
||||||
|
|
||||||
d.aliveCancel = aliveCancel
|
d.aliveCancel = aliveCancel
|
||||||
d.signature = signature
|
d.signature = signature
|
||||||
|
d.value = kv.Value
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ack acknowledged the task is done
|
||||||
func (d *deliver) Ack() {
|
func (d *deliver) Ack() {
|
||||||
defer d.aliveCancel()
|
defer d.aliveCancel()
|
||||||
|
|
||||||
|
@ -102,10 +106,11 @@ func (d *deliver) Ack() {
|
||||||
|
|
||||||
_, err := d.client.Delete(ctx, d.key, clientv3.WithPrefix())
|
_, err := d.client.Delete(ctx, d.key, clientv3.WithPrefix())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ERROR.Printf("ack task %s err: %s", d.value, err)
|
log.ERROR.Printf("ack task %s err: %s", d.key, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Nack negatively acknowledge the delivery of task should handle again
|
||||||
func (d *deliver) Nack() {
|
func (d *deliver) Nack() {
|
||||||
defer d.aliveCancel()
|
defer d.aliveCancel()
|
||||||
|
|
||||||
|
@ -119,10 +124,12 @@ func (d *deliver) Nack() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Signature return the task Signature
|
||||||
func (d *deliver) Signature() *tasks.Signature {
|
func (d *deliver) Signature() *tasks.Signature {
|
||||||
return d.signature
|
return d.signature
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Body return the task body
|
||||||
func (d *deliver) Body() []byte {
|
func (d *deliver) Body() []byte {
|
||||||
return d.value
|
return d.value
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// Package etcd is broker use etcd
|
||||||
package etcd
|
package etcd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -12,6 +13,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
"github.com/RichardKnop/machinery/v2/brokers/errs"
|
"github.com/RichardKnop/machinery/v2/brokers/errs"
|
||||||
"github.com/RichardKnop/machinery/v2/brokers/iface"
|
"github.com/RichardKnop/machinery/v2/brokers/iface"
|
||||||
"github.com/RichardKnop/machinery/v2/common"
|
"github.com/RichardKnop/machinery/v2/common"
|
||||||
|
@ -20,7 +23,6 @@ import (
|
||||||
"github.com/RichardKnop/machinery/v2/tasks"
|
"github.com/RichardKnop/machinery/v2/tasks"
|
||||||
clientv3 "go.etcd.io/etcd/client/v3"
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
"go.etcd.io/etcd/client/v3/concurrency"
|
"go.etcd.io/etcd/client/v3/concurrency"
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var haveNoTaskErr = errors.New("have no task")
|
var haveNoTaskErr = errors.New("have no task")
|
||||||
|
@ -37,7 +39,7 @@ type etcdBroker struct {
|
||||||
// New ..
|
// New ..
|
||||||
func New(ctx context.Context, conf *config.Config) (iface.Broker, error) {
|
func New(ctx context.Context, conf *config.Config) (iface.Broker, error) {
|
||||||
etcdConf := clientv3.Config{
|
etcdConf := clientv3.Config{
|
||||||
Endpoints: []string{conf.Lock},
|
Endpoints: []string{conf.Broker},
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
DialTimeout: time.Second * 5,
|
DialTimeout: time.Second * 5,
|
||||||
TLS: conf.TLSConfig,
|
TLS: conf.TLSConfig,
|
||||||
|
@ -116,9 +118,7 @@ func (b *etcdBroker) StartConsuming(consumerTag string, concurrency int, taskPro
|
||||||
|
|
||||||
task, err := b.nextTask(getQueue(b.GetConfig(), taskProcessor), consumerTag)
|
task, err := b.nextTask(getQueue(b.GetConfig(), taskProcessor), consumerTag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, haveNoTaskErr) {
|
log.ERROR.Printf("get next task failed: %s", err)
|
||||||
log.ERROR.Print(err)
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,140 +0,0 @@
|
||||||
package etcd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/RichardKnop/machinery/v2/log"
|
|
||||||
"go.etcd.io/etcd/api/v3/mvccpb"
|
|
||||||
clientv3 "go.etcd.io/etcd/client/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
type checkFunc func(*mvccpb.KeyValue) error
|
|
||||||
|
|
||||||
// deleteRevKey deletes a key by revision, returning false if key is missing
|
|
||||||
func deleteRevKey(ctx context.Context, kv clientv3.KV, key string, rev int64) error {
|
|
||||||
cmp := clientv3.Compare(clientv3.ModRevision(key), "=", rev)
|
|
||||||
req := clientv3.OpDelete(key)
|
|
||||||
txnresp, err := kv.Txn(ctx).If(cmp).Then(req).Commit()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 有写入或者删除竞争
|
|
||||||
if !txnresp.Succeeded {
|
|
||||||
return fmt.Errorf("delete key failed: %s: %w", key, haveNoTaskErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitPrefixEvents(ctx context.Context, c *clientv3.Client, prefix string, rev int64) (*clientv3.Event, error) {
|
|
||||||
wc := c.Watch(ctx, prefix, clientv3.WithPrefix(), clientv3.WithRev(rev))
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, haveNoTaskErr
|
|
||||||
|
|
||||||
case wresp := <-wc:
|
|
||||||
for _, ev := range wresp.Events {
|
|
||||||
if ev.Type != mvccpb.PUT {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return ev, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getItem(ctx context.Context, c *clientv3.Client, prefix string, opts []clientv3.OpOption, check checkFunc) (*mvccpb.KeyValue, error) {
|
|
||||||
resp, err := c.Get(ctx, prefix, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// nothing yet; wait on elements
|
|
||||||
if len(resp.Kvs) == 0 {
|
|
||||||
ev, err := waitPrefixEvents(ctx, c, prefix, resp.Header.Revision)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := check(ev.Kv); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := deleteRevKey(ctx, c, string(ev.Kv.Key), ev.Kv.ModRevision); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ev.Kv, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
kv := resp.Kvs[0]
|
|
||||||
if err := check(kv); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := deleteRevKey(ctx, c, string(kv.Key), kv.ModRevision); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return kv, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFirstItem(ctx context.Context, c *clientv3.Client, prefix string) ([]byte, error) {
|
|
||||||
// 按创建时间排序, 只返回单个数据
|
|
||||||
opts := clientv3.WithFirstRev()
|
|
||||||
|
|
||||||
check := func(*mvccpb.KeyValue) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
kv, err := getItem(ctx, c, prefix, opts, check)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return kv.Value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFirstETAItem(ctx context.Context, c *clientv3.Client, prefix string) ([]byte, error) {
|
|
||||||
// 按创建key排序, 只返回单个数据
|
|
||||||
opts := clientv3.WithFirstKey()
|
|
||||||
|
|
||||||
check := func(kv *mvccpb.KeyValue) error {
|
|
||||||
k := string(kv.Key)
|
|
||||||
if len(k) < 55 {
|
|
||||||
log.ERROR.Print("not valid eta key: %s", k)
|
|
||||||
return fmt.Errorf("not valid eta key: %s", k)
|
|
||||||
}
|
|
||||||
|
|
||||||
nsStr := k[36:55]
|
|
||||||
ns, err := strconv.ParseInt(nsStr, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parse key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t := time.Unix(0, ns)
|
|
||||||
if t.After(time.Now()) {
|
|
||||||
time.Sleep(time.Millisecond * 100)
|
|
||||||
return haveNoTaskErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
kv, err := getItem(ctx, c, prefix, opts, check)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return kv.Value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func putItem() {
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue