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
......