code analysis
github.com/tal-tech/go-zero@v1.2.3/core/discov/publisher.go
package discov import ( "github.com/tal-tech/go-zero/core/discov/internal" "github.com/tal-tech/go-zero/core/lang" "github.com/tal-tech/go-zero/core/logx" "github.com/tal-tech/go-zero/core/proc" "github.com/tal-tech/go-zero/core/syncx" "github.com/tal-tech/go-zero/core/threading" clientv3 "go.etcd.io/etcd/client/v3" ) type ( // PubOption defines the method to customize a Publisher. PubOption func(client *Publisher) // A Publisher can be used to publish the value to an etcd cluster on the given key. Publisher struct { endpoints []string key string fullKey string id int64 value string lease clientv3.LeaseID quit *syncx.DoneChan pauseChan chan lang.PlaceholderType resumeChan chan lang.PlaceholderType } ) // NewPublisher returns a Publisher. // endpoints is the hosts of the etcd cluster. // key:value are a pair to be published. // opts are used to customize the Publisher. func NewPublisher(endpoints []string, key, value string, opts ...PubOption) *Publisher { publisher := &Publisher{ endpoints: endpoints, key: key, value: value, quit: syncx.NewDoneChan(), pauseChan: make(chan lang.PlaceholderType), resumeChan: make(chan lang.PlaceholderType), } for _, opt := range opts { opt(publisher) } return publisher } // KeepAlive keeps key:value alive. func (p *Publisher) KeepAlive() error { cli, err := internal.GetRegistry().GetConn(p.endpoints) if err != nil { return err } p.lease, err = p.register(cli) if err != nil { return err } proc.AddWrapUpListener(func() { p.Stop() }) return p.keepAliveAsync(cli) } // Pause pauses the renewing of key:value. func (p *Publisher) Pause() { p.pauseChan <- lang.Placeholder } // Resume resumes the renewing of key:value. func (p *Publisher) Resume() { p.resumeChan <- lang.Placeholder } // Stop stops the renewing and revokes the registration. func (p *Publisher) Stop() { p.quit.Close() } func (p *Publisher) keepAliveAsync(cli internal.EtcdClient) error { ch, err := cli.KeepAlive(cli.Ctx(), p.lease) if err != nil { return err } threading.GoSafe(func() { for { select { case _, ok := <-ch: if !ok { p.revoke(cli) if err := p.KeepAlive(); err != nil { logx.Errorf("KeepAlive: %s", err.Error()) } return } case <-p.pauseChan: logx.Infof("paused etcd renew, key: %s, value: %s", p.key, p.value) p.revoke(cli) select { case <-p.resumeChan: if err := p.KeepAlive(); err != nil { logx.Errorf("KeepAlive: %s", err.Error()) } return case <-p.quit.Done(): return } case <-p.quit.Done(): p.revoke(cli) return } } }) return nil } func (p *Publisher) register(client internal.EtcdClient) (clientv3.LeaseID, error) { resp, err := client.Grant(client.Ctx(), TimeToLive) if err != nil { return clientv3.NoLease, err } lease := resp.ID if p.id > 0 { p.fullKey = makeEtcdKey(p.key, p.id) } else { p.fullKey = makeEtcdKey(p.key, int64(lease)) } _, err = client.Put(client.Ctx(), p.fullKey, p.value, clientv3.WithLease(lease)) return lease, err } func (p *Publisher) revoke(cli internal.EtcdClient) { if _, err := cli.Revoke(cli.Ctx(), p.lease); err != nil { logx.Error(err) } } // WithPubEtcdAccount provides the etcd username/password. func WithPubEtcdAccount(user, pass string) PubOption { return func(pub *Publisher) { internal.AddAccount(pub.endpoints, user, pass) } } // WithId customizes a Publisher with the id. func WithId(id int64) PubOption { return func(publisher *Publisher) { publisher.id = id } }
The function of this file is to register the Etcd service. A struct: Publisher is defined in the file
Publisher struct { endpoints []string key string fullKey string id int64 value string lease clientv3.LeaseID quit *syncx.DoneChan pauseChan chan lang.PlaceholderType resumeChan chan lang.PlaceholderType }
- Endpoints: address of EDCT
- Key: the key prefix of the service in etcd, taken from the configuration file
- fullkey: if id is set for the key in etcd, it is a combination of key and id; If no id is set, it is a combination of key and lease
- id: if set, it is used to generate fullKey
- Value: the value of etcd, which is the address of the RPC service
- Lease: lease id of etcd
- quit: used to exit the operation
- pauseChan: used to suspend the service registration. If suspended, the lease of etcd will be cancelled. After the suspension, there are two subsequent operations to restart or close
- resumeChan: used to restart after service suspension
Publisher provides the following public methods
func NewPublisher(endpoints []string, key, value string, opts ...PubOption) *Publisher func (p *Publisher) KeepAlive() error func (p *Publisher) Pause() func (p *Publisher) Resume() func (p *Publisher) Stop()
- Newpublisher: construction method of publish
- KeepAlive(): service publishing and connection
- Pause(): the service is suspended
- Resume(): Service restart
- Stop(): service stop
The core method is the KeepAlive() method. Its code is as follows
func (p *Publisher) KeepAlive() error { cli, err := internal.GetRegistry().GetConn(p.endpoints) if err != nil { return err } p.lease, err = p.register(cli) if err != nil { return err } proc.AddWrapUpListener(func() { p.Stop() }) return p.keepAliveAsync(cli) }
line2-5: etcd connection operation
line7-10: create lease
line12-14: I'm not sure about this. It should be added to the listening queue. If the Rpc service is stopped, the service also needs to be stopped
line 16: asynchronous listening is mainly used to listen to the connection with etcd. If the connection is broken, it needs to be restarted; There is also the command to listen for pause, restart and stop
Let's first look at the line7-10 register method
func (p *Publisher) register(client internal.EtcdClient) (clientv3.LeaseID, error) { resp, err := client.Grant(client.Ctx(), TimeToLive) if err != nil { return clientv3.NoLease, err } lease := resp.ID if p.id > 0 { p.fullKey = makeEtcdKey(p.key, p.id) } else { p.fullKey = makeEtcdKey(p.key, int64(lease)) } _, err = client.Put(client.Ctx(), p.fullKey, p.value, clientv3.WithLease(lease)) return lease, err }
line2-7: create etcd lease. The expiration time of TimeToLive is 10 seconds
line8-12: create a fullKey. If the id is set, the fullKey will be generated based on the key and id; If the id is not set, it is generated with key and lease id
line13: store value in etcd and bind it to the newly created lease
Look again at the line 16 keepAliveAsync method
func (p *Publisher) keepAliveAsync(cli internal.EtcdClient) error { ch, err := cli.KeepAlive(cli.Ctx(), p.lease) if err != nil { return err } threading.GoSafe(func() { for { select { case _, ok := <-ch: if !ok { p.revoke(cli) if err := p.KeepAlive(); err != nil { logx.Errorf("KeepAlive: %s", err.Error()) } return } case <-p.pauseChan: logx.Infof("paused etcd renew, key: %s, value: %s", p.key, p.value) p.revoke(cli) select { case <-p.resumeChan: if err := p.KeepAlive(); err != nil { logx.Errorf("KeepAlive: %s", err.Error()) } return case <-p.quit.Done(): return } case <-p.quit.Done(): p.revoke(cli) return } } }) return nil }
line2-5: lease regular renewal
line7: start a Ctrip monitor
line10-17: listen to etcd keeplive status. If the returned value is not true, first cancel the current lease and then re KeepLive()
line18-29: monitor whether to pause the operation through Publisher's pauseChan. If you need to pause, first cancel the current lease, and then monitor whether to restart or stop the subsequent operation through resumeChan and quit. If you restart, restart keepLive()
line30-32: monitor whether there is a stop operation. If it is necessary to stop, directly cancel the current lease
demo display
Use go zero to create a user's rpc service. The specific creation steps skim over, and finally in etc / user Yaml is configured as follows
Name: user.rpc ListenOn: 127.0.0.1:8081 Etcd: Hosts: - 172.16.1.36:12379 Key: user.rpc
Then start the user service. After successful startup, go to etcd to check
./etcdctl --endpoints=127.0.0.1:12379 get user.rpc --prefix
The result is