go task scheduling 6 (etcd lease mechanism / automatic expiration)

Keywords: Go

It is very important to implement distributed optimistic locking. If the lock is out of order suddenly, the lock needs to be released automatically. So it takes a lifetime to lock it in an etcd.
Overdue demonstration:

package main

import (
    "context"
    "fmt"
    "go.etcd.io/etcd/clientv3"
    "time"
)

func main() {
    var (
        config  clientv3.Config
        client  *clientv3.Client
        err     error
        lease clientv3.Lease
        leaseGrantResp *clientv3.LeaseGrantResponse
        leaseId clientv3.LeaseID
        putResp *clientv3.PutResponse
        kv clientv3.KV
        getResp *clientv3.GetResponse
    )

    //Client Configuration
    config = clientv3.Config{
        Endpoints:   []string{"0.0.0.0:2379"}, //Cluster list
        DialTimeout: 5 * time.Second,
    }

    //Establishing Client
    if client, err = clientv3.New(config); err != nil {
        fmt.Println(err)
        return
    }

    //Apply for a lease
    lease = clientv3.NewLease(client)

    //Apply for a five-second lease
    if leaseGrantResp, err = lease.Grant(context.TODO(), 5); err != nil {
        fmt.Println(err)
        return
    }

    //Get the id of the lease
    leaseId = leaseGrantResp.ID

    //Get a subset of kv api
    kv = clientv3.NewKV(client)

    //put a kv and associate it with the lease to automatically expire after 10 seconds
    if putResp, err = kv.Put(context.TODO(), "/cron/lock/job1", "", clientv3.WithLease(leaseId)); err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println("Write successfully:", putResp.Header.Revision)

    //See if the key expires at regular intervals
    for {
        if getResp, err = kv.Get(context.TODO(), "/cron/lock/job1"); err != nil {
            fmt.Println(err)
            return
        }
        if getResp.Count == 0 {
            fmt.Println("kv Out of date")
            break
        }
        fmt.Println("It's not expired yet.:", getResp.Kvs)
        time.Sleep(time.Second)
    }
}

[root@bogon etcd]# go run demo6.go
Write successfully: 27
It's not expired yet.: [key:"/cron/lock/job1" create_revision:27 mod_revision:27 version:1 lease:7587837741646622005 ]
It's not expired yet.: [key:"/cron/lock/job1" create_revision:27 mod_revision:27 version:1 lease:7587837741646622005 ]
It's not expired yet.: [key:"/cron/lock/job1" create_revision:27 mod_revision:27 version:1 lease:7587837741646622005 ]
It's not expired yet.: [key:"/cron/lock/job1" create_revision:27 mod_revision:27 version:1 lease:7587837741646622005 ]
It's not expired yet.: [key:"/cron/lock/job1" create_revision:27 mod_revision:27 version:1 lease:7587837741646622005 ]
It's not expired yet.: [key:"/cron/lock/job1" create_revision:27 mod_revision:27 version:1 lease:7587837741646622005 ]
kv Out of date
[root@bogon etcd]#

When applying for a distributed lock, whoever grabs the key is grabbing the lock. If the lock is not released voluntarily, the lease should not expire. The main reason for the expiration of the lease is to release the lock automatically after the program is out of order, so as to prevent the abnormal exit of the program. If the program grabs the lock, we hope that the lock will never fail until we release it voluntarily:

package main

import (
    "context"
    "fmt"
    "go.etcd.io/etcd/clientv3"
    "time"
)

func main() {
    var (
        config  clientv3.Config
        client  *clientv3.Client
        err     error
        lease clientv3.Lease
        leaseGrantResp *clientv3.LeaseGrantResponse
        leaseId clientv3.LeaseID
        putResp *clientv3.PutResponse
        kv clientv3.KV
        getResp *clientv3.GetResponse
        keepResp *clientv3.LeaseKeepAliveResponse
        keepRespChan <-chan *clientv3.LeaseKeepAliveResponse //Read-only channel
    )

    //Client Configuration
    config = clientv3.Config{
        Endpoints:   []string{"0.0.0.0:2379"}, //Cluster list
        DialTimeout: 5 * time.Second,
    }

    //Establishing Client
    if client, err = clientv3.New(config); err != nil {
        fmt.Println(err)
        return
    }

    //Apply for a lease
    lease = clientv3.NewLease(client)

    //Apply for a five-second lease
    if leaseGrantResp, err = lease.Grant(context.TODO(), 5); err != nil {
        fmt.Println(err)
        return
    }

    //Get the id of the lease
    leaseId = leaseGrantResp.ID

    //(automatic renewal) When we apply for a lease, we can start a renewal.
    if keepRespChan, err = lease.KeepAlive(context.TODO(), leaseId); err != nil {
        fmt.Println(err)
        return
    }

    //Procedure for processing renewal response
    go func() {
        for {
            select {
            case keepResp = <-keepRespChan:
                if keepRespChan == nil {
                    fmt.Println("The lease has expired")
                    goto END
                } else { //Rents are renewed every second, so you get a response.
                    fmt.Println("Receiving automatic renewal response:", keepResp.ID)
                }
            }
        }
        END:
    }()

    //Get a subset of kv api
    kv = clientv3.NewKV(client)

    //put a kv and associate it with the lease to automatically expire after 10 seconds
    if putResp, err = kv.Put(context.TODO(), "/cron/lock/job1", "", clientv3.WithLease(leaseId)); err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println("Write successfully:", putResp.Header.Revision)

    //See if the key expires at regular intervals
    for {
        if getResp, err = kv.Get(context.TODO(), "/cron/lock/job1"); err != nil {
            fmt.Println(err)
            return
        }
        if getResp.Count == 0 {
            fmt.Println("kv Out of date")
            break
        }
        fmt.Println("It's not expired yet.:", getResp.Kvs)
        time.Sleep(time.Second)
    }
}

[root@bogon etcd]# go run demo7.go
Write successfully: 30
Receiving automatic renewal response: 7587837741646622039
It's not expired yet.: [key:"/cron/lock/job1" create_revision:29 mod_revision:30 version:2 lease:7587837741646622039 ]
It's not expired yet.: [key:"/cron/lock/job1" create_revision:29 mod_revision:30 version:2 lease:7587837741646622039 ]
Receiving automatic renewal response: 7587837741646622039
It's not expired yet.: [key:"/cron/lock/job1" create_revision:29 mod_revision:30 version:2 lease:7587837741646622039 ]
It's not expired yet.: [key:"/cron/lock/job1" create_revision:29 mod_revision:30 version:2 lease:7587837741646622039 ]
It's not expired yet.: [key:"/cron/lock/job1" create_revision:29 mod_revision:30 version:2 lease:7587837741646622039 ]
Receiving automatic renewal response: 7587837741646622039
It's not expired yet.: [key:"/cron/lock/job1" create_revision:29 mod_revision:30 version:2 lease:7587837741646622039 ]
It's not expired yet.: [key:"/cron/lock/job1" create_revision:29 mod_revision:30 version:2 lease:7587837741646622039 ]
Receiving automatic renewal response: 7587837741646622039
......

Posted by mantona on Sat, 20 Apr 2019 20:48:33 -0700