曹德军 發表於 2022-4-21 17:21:00

go etcd服务发现

<h2><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px; color: rgba(255, 0, 0, 1)">&nbsp;一.etcd简介</span></h2>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px">  <span style="font-size: 15px">etcd 是一个分布式键值对存储系统,由coreos 开发,内部采用&nbsp;raft 协议作为一致性算法,用于可靠、快速地保存关键数据,并提供访问。通过分布式锁、leader选举和写屏障(write barriers),来实现可靠的分布式协作。etcd集群是为高可用、持久化数据存储和检索而准备。</span></span></p>
<h3><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><span style="font-size: 15px">  <span style="color: rgba(51, 153, 102, 1)">概念词汇</span></span></span></h3>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><span style="font-size: 15px">    Raft:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;etcd所采用的保证分布式系统强一致性的算法。<br>    Node:&nbsp; &nbsp; &nbsp; &nbsp;一个Raft状态机实例。<br>    Member:&nbsp; 一个etcd实例。它管理着一个Node,并且可以为客户端请求提供服务。<br>    Cluster:&nbsp; &nbsp; 由多个Member构成、可以协同工作的etcd集群。<br>    Peer:&nbsp; &nbsp; &nbsp; &nbsp; 对同一个etcd集群中另外一个Member的称呼。<br>    Client:&nbsp; &nbsp; &nbsp; 向etcd集群发送HTTP请求的客户端。<br>    WAL:&nbsp; &nbsp; &nbsp; &nbsp; 预写式日志,etcd用于持久化存储的日志格式。<br>    snapshot: etcd防止WAL文件过多而设置的快照,存储etcd数据状态。<br>    Proxy:&nbsp; &nbsp; &nbsp; &nbsp;etcd的一种模式,为etcd集群提供反向代理服务。<br>    Leader:&nbsp; &nbsp; &nbsp;Raft算法中,通过竞选而产生的、处理所有数据提交的节点。<br>    Follower:&nbsp; 竞选失败的节点作为Raft中的从属节点,为算法提供强一致性保证。<br>    Candidate:当Follower超过一定时间接收不到Leader的心跳时转变为Candidate开始竞选。<br>    Term:&nbsp; &nbsp; &nbsp; &nbsp; 某个节点成为Leader到下一次竞选时间,称为一个Term。<br>    Index:&nbsp; &nbsp; &nbsp; &nbsp;数据项编号。Raft中通过Term和Index来定位数据</span></span></p>
<h3><strong><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><span style="font-size: 15px">  <span style="color: rgba(51, 153, 102, 1)">应用场景</span></span></span></strong></h3>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><span style="font-size: 15px">    服务发现</span></span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><span style="font-size: 15px">    </span></span><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><span style="font-size: 15px">消息发布与订阅</span></span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><span style="font-size: 15px">    </span></span><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><span style="font-size: 15px">负载均衡</span></span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><span style="font-size: 15px">    </span></span><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><span style="font-size: 15px">分布式通知与协调</span></span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><span style="font-size: 15px">    </span></span><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><span style="font-size: 15px">分布式锁、分布式队列</span></span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><span style="font-size: 15px">    </span></span><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><span style="font-size: 15px">集群监控与Leader竞选</span></span></p>
<h3><strong><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><span style="font-size: 15px">  <span style="color: rgba(51, 153, 102, 1)">etcd与redis</span></span></span></strong></h3>
<p><strong><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><span style="font-size: 15px">    </span></span></strong><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px"><strong>etcd:</strong>&nbsp;<em>用于共享配置和服务发现的分布式一致键值存储.&nbsp;</em>etcd 是一种分布式键值存储, 它提供了一种跨机器集群存储数据的可靠方式. etcd 在网络分区期间优雅地处理 master 选举, 并且会容忍机器故障.</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">    <strong>redis:&nbsp;</strong>&nbsp;<em>持久化在磁盘上的内存数据库,&nbsp;</em>Redis 是一个开源、BSD 许可的高级键值存储. 它通常被称为数据结构服务器, 因为键可以包含字符串、散列、列表、集合和排序集合.</span></p>
<h2><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px; color: rgba(255, 0, 0, 1)"><strong>二.etcd安装</strong></span></h2>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><strong>  </strong><span style="font-size: 15px">采用二进制安装,解压后将etcd和etcdctl二进制文件复制到/user/bin/下即可:</span></span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px">wget https:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">github.com/coreos/etcd/releases/download/v3.5.1/etcd-v3.5.1-linux-amd64.tar.gz</span></span></pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><strong>  </strong><span style="font-size: 15px; color: rgba(0, 128, 0, 1)">版本查看:</span></span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px">root@master ~ &gt;<span style="color: rgba(0, 0, 0, 1)"> etcdctl version
etcdctl version: </span><span style="color: rgba(128, 0, 128, 1)">3.5</span>.<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">
API version: </span><span style="color: rgba(128, 0, 128, 1)">3.5</span></span></pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><strong>  </strong><span style="font-size: 15px; color: rgba(0, 128, 0, 1)">etcd常用命令:</span></span></p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_1fc0e6c6-a70b-456e-8a7a-a7d72e48203e" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_1fc0e6c6-a70b-456e-8a7a-a7d72e48203e" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_1fc0e6c6-a70b-456e-8a7a-a7d72e48203e" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 0, 1)">COMMANDS:
    alarm disarm      Disarms all alarms
    alarm list      Lists all alarms
    auth disable      Disables authentication
    auth enable      Enables authentication
    auth status      Returns authentication status
    check datascale      Check the memory usage of holding data for different workloads on a given server endpoint.
    check perf      Check the performance of the etcd cluster
    compaction      Compacts the event history in etcd
    defrag            Defragments the storage of the etcd members with given endpoints
    del            Removes the specified key </span><span style="color: rgba(0, 0, 255, 1)">or</span><span style="color: rgba(0, 0, 0, 1)"> range of keys [key, range_end)
    elect            Observes </span><span style="color: rgba(0, 0, 255, 1)">and</span><span style="color: rgba(0, 0, 0, 1)"> participates in leader election
    endpoint hashkv      Prints the KV history hash for each endpoint in </span>--<span style="color: rgba(0, 0, 0, 1)">endpoints
    endpoint health      Checks the healthiness of endpoints specified in `</span>--<span style="color: rgba(0, 0, 0, 1)">endpoints` flag
    endpoint status      Prints out the status of endpoints specified in `</span>--<span style="color: rgba(0, 0, 0, 1)">endpoints` flag
    get            Gets the key </span><span style="color: rgba(0, 0, 255, 1)">or</span><span style="color: rgba(0, 0, 0, 1)"> a range of keys
    help            Help about any command
    lease grant      Creates leases
    lease keep</span>-<span style="color: rgba(0, 0, 0, 1)">alive    Keeps leases alive (renew)
    lease list      List all active leases
    lease revoke      Revokes leases
    lease timetolive    Get lease information
    lock            Acquires a named lock
    make</span>-<span style="color: rgba(0, 0, 0, 1)">mirror      Makes a mirror at the destination etcd cluster
    member add      Adds a member into the cluster
    member list      Lists all members in the cluster
    member promote      Promotes a non</span>-<span style="color: rgba(0, 0, 0, 1)">voting member in the cluster
    member remove      Removes a member from the cluster
    member update      Updates a member in the cluster
    move</span>-<span style="color: rgba(0, 0, 0, 1)">leader      Transfers leadership to another etcd cluster member.
    put            Puts the given key into the store
    role add      Adds a new role
    role delete      Deletes a role
    role get      Gets detailed information of a role
    role grant</span>-<span style="color: rgba(0, 0, 0, 1)">permission    Grants a key to a role
    role list      Lists all roles
    role revoke</span>-<span style="color: rgba(0, 0, 0, 1)">permission    Revokes a key from a role
    snapshot restore    Restores an etcd member snapshot to an etcd directory
    snapshot save      Stores an etcd node backend snapshot to a given file
    snapshot status       Gets backend snapshot status of a given file
    txn            Txn processes all the requests in one transaction
    user add      Adds a new user
    user delete      Deletes a user
    user get      Gets detailed information of a user
    user grant</span>-<span style="color: rgba(0, 0, 0, 1)">role      Grants a role to a user
    user list      Lists all users
    user passwd      Changes password of user
    user revoke</span>-<span style="color: rgba(0, 0, 0, 1)">role    Revokes a role from a user
    version            Prints the version of etcdctl
    watch            Watches events stream on keys </span><span style="color: rgba(0, 0, 255, 1)">or</span><span style="color: rgba(0, 0, 0, 1)"> prefixes

OPTIONS:
      </span>--cacert=<span style="color: rgba(128, 128, 128, 1)">"</span><span style="color: rgba(128, 128, 128, 1)">"                verify certificates of TLS-enabled secure servers using this CA bundle</span>
      --cert=<span style="color: rgba(128, 128, 128, 1)">"</span><span style="color: rgba(128, 128, 128, 1)">"                  identify secure client using this TLS certificate file</span>
      --command-timeout=5s            timeout for <span style="color: rgba(0, 0, 255, 1)">short</span><span style="color: rgba(0, 0, 0, 1)"> running command (excluding dial timeout)
      </span>--debug[=<span style="color: rgba(0, 0, 255, 1)">false</span>]                enable client-<span style="color: rgba(0, 0, 0, 1)">side debug logging
      </span>--dial-timeout=<span style="color: rgba(0, 0, 0, 1)">2s                dial timeout for client connections
</span>-d, --discovery-srv=<span style="color: rgba(128, 128, 128, 1)">"</span><span style="color: rgba(128, 128, 128, 1)">"            domain name to query for SRV records describing cluster endpoints</span>
      --discovery-srv-name=<span style="color: rgba(128, 128, 128, 1)">"</span><span style="color: rgba(128, 128, 128, 1)">"            service name to query when using DNS discovery</span>
      --endpoints=[<span style="color: rgba(128, 0, 128, 1)">127.0</span>.<span style="color: rgba(128, 0, 128, 1)">0.1</span>:<span style="color: rgba(128, 0, 128, 1)">2379</span><span style="color: rgba(0, 0, 0, 1)">]      gRPC endpoints
</span>-h, --help[=<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">]                help for etcdctl
      </span>--hex[=<span style="color: rgba(0, 0, 255, 1)">false</span>]                print <span style="color: rgba(0, 0, 255, 1)">byte</span><span style="color: rgba(0, 0, 0, 1)"> strings as hex encoded strings
      </span>--insecure-discovery[=<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">]      accept insecure SRV records describing cluster endpoints
      </span>--insecure-skip-tls-verify[=<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">]    skip server certificate verification (CAUTION: this option should be enabled only for testing purposes)
      </span>--insecure-transport[=<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">]      disable transport security for client connections
      </span>--keepalive-time=<span style="color: rgba(0, 0, 0, 1)">2s            keepalive time for client connections
      </span>--keepalive-timeout=<span style="color: rgba(0, 0, 0, 1)">6s            keepalive timeout for client connections
      </span>--key=<span style="color: rgba(128, 128, 128, 1)">"</span><span style="color: rgba(128, 128, 128, 1)">"                  identify secure client using this TLS key file</span>
      --password=<span style="color: rgba(128, 128, 128, 1)">"</span><span style="color: rgba(128, 128, 128, 1)">"                password for authentication (if this option is used, --user option shouldn't include password)</span>
      --user=<span style="color: rgba(128, 128, 128, 1)">"</span><span style="color: rgba(128, 128, 128, 1)">"                  username[:password] for authentication (prompt if password is not supplied)</span>
-w, --write-out=<span style="color: rgba(128, 128, 128, 1)">"</span><span style="color: rgba(128, 128, 128, 1)">simple"            set the output format (fields, json, protobuf, simple, table)</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><strong>  </strong><span style="font-size: 15px; color: rgba(0, 128, 0, 1)">指定ip端口启动etcd:</span></span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px">etcd --listen-client-urls http://<span style="color: rgba(128, 0, 128, 1)">0.0</span>.<span style="color: rgba(128, 0, 128, 1)">0.0</span>:<span style="color: rgba(128, 0, 128, 1)">2379</span> --advertise-client-urls http://<span style="color: rgba(128, 0, 128, 1)">0.0</span>.<span style="color: rgba(128, 0, 128, 1)">0.0</span>:<span style="color: rgba(128, 0, 128, 1)">2379</span> --listen-peer-urls http://<span style="color: rgba(128, 0, 128, 1)">0.0</span>.<span style="color: rgba(128, 0, 128, 1)">0.0</span>:<span style="color: rgba(128, 0, 128, 1)">2380</span></span></pre>
</div>
<h2><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px; color: rgba(255, 0, 0, 1)"><strong>三.go操作etcd</strong></span></h2>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><strong>  <span style="font-size: 15px; color: rgba(0, 128, 0, 1)">put和get</span></strong></span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;"><span style="font-size: 14px">func main() {
        cli, err := clientv3.New(clientv3.Config{
                Endpoints:   []string{"192.168.79.134:2379"}, // etcd节点,因为使用的单节点,所以这里只有一个
                DialTimeout: 5 * time.Second,               //超时时间
        })
        if err != nil {
                fmt.Println(err)
        }
        fmt.Println(" connect to etcd success")
        defer cli.Close()
        // put
        // 设置超时时间
        ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
        _, err = cli.Put(ctx, "test", "hello")
        cancel()
        if err != nil {
                fmt.Printf("put to etcd failed, err:%v\n", err)
                return
        }
        // get
        ctx, cancel = context.WithTimeout(context.Background(), time.Second)
        resp, err := cli.Get(ctx, "test")
        cancel()
        if err != nil {
                fmt.Printf("get from etcd failed, err:%v\n", err)
                return
        }
        for _, ev := range resp.Kvs {
                fmt.Printf("%s:%s\n", ev.Key, ev.Value)
        }
}

// connect to etcd success
// test:hello
</span></pre>
</div>
<p>  <span style="font-size: 15px; font-family: &quot;Microsoft YaHei&quot;; color: rgba(0, 128, 0, 1)"><strong>watch</strong></span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">func main() {
        cli, err := clientv3.New(clientv3.Config{
                Endpoints:   []string{"192.168.79.134:2379"},
                DialTimeout: 5 * time.Second,
        })
        if err != nil {
                fmt.Printf("connect to etcd failed, err:%v\n", err)
                return
        }
        fmt.Println("connect to etcd success")
        defer cli.Close()
        // watch
        // Watch(ctx context.Context, key string, opts ...OpOption) WatchChan
        rch := cli.Watch(context.Background(), "test")
        for wresp := range rch {
                for _, ev := range wresp.Events {
                        fmt.Printf("Type: %s Key:%s Value:%s\n", ev.Type, ev.Kv.Key, ev.Kv.Value)
                }
        }
}
</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">    监听test的变化,Watch返回的是一个WatchResponse的管道类型,所以我们可以用for循环取值,每当test发生变化,watch就会发现并做相应操作:</span></p>
<blockquote>
<pre><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 14px">type WatchResponse struct {</span><br><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 14px">   Header pb.ResponseHeader</span><br><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 14px">   Events []*storagepb.Event</span><br><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 14px">   CompactRevision int64</span><br><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 14px">   Canceled bool</span><br><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 14px">}</span></pre>
<pre><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 14px">type Event struct {</span><br><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 14px">   Type Event_EventType `protobuf:"varint,1,opt,name=type,proto3,enum=storagepb.Event_EventType" json:"type,omitempty"`</span><br><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 14px">   Kv *KeyValue `protobuf:"bytes,2,opt,name=kv" json:"kv,omitempty"`</span><br><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 14px">}</span></pre>
</blockquote>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;"><span style="font-size: 14px">root@master~ &gt; etcdctl put test world
OK
root@master~ &gt; etcdctl del test      
1

// $ go run watch.go
// connect to etcd success
// Type: PUT Key:test Value:world
// Type: DELETE Key:test Value:</span> </pre>
</div>
<h2><span style="color: rgba(255, 0, 0, 1)"><strong><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px">四.go中安装etcd v3的坑</span></strong></span></h2>
<p>  <span style="background-color: rgba(0, 255, 0, 1)"><strong><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">***建议直接使用clientv3的3.5版本,与grpc最新版本兼容,安装:&nbsp;go get go.etcd.io/etcd/client/v3@v3.5.4,详细版本迭代及操作建议参考官方说明:&nbsp;<em>https://github.com/etcd-io/etcd/tree/main/client/v3</em></span></strong></span></p>
<p><span style="background-color: rgba(0, 255, 0, 1)"><strong><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px"><span style="background-color: rgba(255, 255, 255, 1)">  </span>***如果受go版本或公司大环境影响不能使用最新版本,可以参考下边操作</span></strong></span></p>
<p>  <span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">1.当我们直接使用go get&nbsp;github.com/coreos/etcd/clientv3或者go get&nbsp;go.etcd.io/etcd时,会自动安装etcd2.3.8版本,一个很久的版本,所以在安装时一定要指定版本, 如:&nbsp;go get&nbsp;github.com/coreos/etcd/clientv3@v3.3.25</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">  2.必须安装有grpc v1.26.0版本. 如果装有多个版本的grpc,需要在go.mod中需添加下边代码或者直接不使用go mod tidy导包,而是通过go get&nbsp;google.golang.org/grpc@v1.26.0和go get&nbsp;github.com/coreos/etcd/clientv3@v3.3.25完成导包</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;"><span style="font-size: 14px">replace google.golang.org/grpc v1.38.0 =&gt; google.golang.org/grpc v1.26.0</span> </pre>
</div>
<h2><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px; color: rgba(255, 0, 0, 1)"><strong>五.etcd实现服务注册和发现</strong></span></h2>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px; color: rgba(255, 0, 0, 1)">  <span style="color: rgba(0, 128, 0, 1)"><strong><span style="font-size: 15px">方法汇总:</span></strong></span></span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px; color: rgba(255, 0, 0, 1)"><span style="color: rgba(0, 128, 0, 1)"><strong><span style="font-size: 15px">    </span></strong></span></span><span style="font-family: &quot;Microsoft YaHei&quot;">clientv3.New:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 创建etcdv3客户端(</span><span style="font-size: 15px">func New(cfg Config) (*Client, error)</span><span style="font-family: &quot;Microsoft YaHei&quot;">)</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">    clientv3.Config:&nbsp; &nbsp; &nbsp;创建客户端时使用的配置</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">    </span><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">Grant:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 初始化一个新租约(Grant(ctx context.Context, ttl int64) (*LeaseGrantResponse, error))</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">    Put:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 注册服务并绑定租约</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">    </span><span style="font-family: &quot;Microsoft YaHei&quot;"><span style="font-size: 15px">KeepAlive</span>:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<span style="font-size: 15px">设置续租,定期发送续租请求</span>(</span><span style="font-size: 15px; font-family: &quot;Microsoft YaHei&quot;">KeepAlive(ctx context.Context, id LeaseID) (&lt;-chan *LeaseKeepAliveResponse, error))</span></p>
<p><span style="font-size: 15px; font-family: &quot;Microsoft YaHei&quot;">    </span><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">Revoke:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 撤销租约</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">    Get:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 获取服务</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">    </span><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">Watch</span><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 监控服务</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">  <span style="color: rgba(0, 128, 0, 1)"><strong>实现流程:</strong></span></span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px"><span style="color: rgba(0, 128, 0, 1)"><strong>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<img src="https://img2022.cnblogs.com/blog/1497368/202204/1497368-20220422092531973-293341951.png" alt="" loading="lazy"></strong></span></span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">  <span style="color: rgba(0, 128, 0, 1)"><strong>实现代码:</strong></span></span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px"><span style="color: rgba(0, 128, 0, 1)"><strong>    <span style="color: rgba(0, 0, 0, 1)">1.服务注册</span></strong></span></span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px"><span style="color: rgba(0, 128, 0, 1)"><strong><span style="color: rgba(0, 0, 0, 1)">      </span></strong><span style="color: rgba(0, 0, 0, 1)">注册一个前缀为/web的服务:</span></span></span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;"><span style="font-size: 14px">package main

import (
        "context"
        "github.com/coreos/etcd/clientv3"
        "log"
        "time"
)

//ServiceRegister 创建租约注册服务
type ServiceRegister struct {
        cli   *clientv3.Client //etcd client
        leaseID clientv3.LeaseID //租约ID
        //租约keepalieve相应chan
        keepAliveChan &lt;-chan *clientv3.LeaseKeepAliveResponse
        key         string //key
        val         string //value
}

//NewServiceRegister 新建注册服务
func NewServiceRegister(endpoints []string, key, val string, lease int64) (*ServiceRegister, error) {
        cli, err := clientv3.New(clientv3.Config{
                Endpoints:   endpoints,
                DialTimeout: 5 * time.Second,
        })
        if err != nil {
                log.Fatal(err)
        }

        ser := &amp;ServiceRegister{
                cli: cli,
                key: key,
                val: val,
        }

        //申请租约设置时间keepalive并注册服务
        if err := ser.putKeyWithLease(lease); err != nil {
                return nil, err
        }

        return ser, nil
}

//设置租约
func (s *ServiceRegister) putKeyWithLease(lease int64) error {
        //设置租约时间
        resp, err := s.cli.Grant(context.Background(), lease)
        if err != nil {
                return err
        }
        //注册服务并绑定租约
        _, err = s.cli.Put(context.Background(), s.key, s.val, clientv3.WithLease(resp.ID))
        if err != nil {
                return err
        }
        //设置续租 定期发送需求请求
        leaseRespChan, err := s.cli.KeepAlive(context.Background(), resp.ID)

        if err != nil {
                return err
        }
        s.leaseID = resp.ID
        log.Println(s.leaseID)
        s.keepAliveChan = leaseRespChan
        log.Printf("Put key:%sval:%ssuccess!", s.key, s.val)
        return nil
}

//ListenLeaseRespChan 监听 续租情况
func (s *ServiceRegister) ListenLeaseRespChan() {
        for leaseKeepResp := range s.keepAliveChan {
                log.Println("续约成功", leaseKeepResp)
        }
        log.Println("关闭续租")
}

// Close 注销服务
func (s *ServiceRegister) Close() error {
        //撤销租约
        if _, err := s.cli.Revoke(context.Background(), s.leaseID); err != nil {
                return err
        }
        log.Println("撤销租约")
        return s.cli.Close()
}

func main() {
        var endpoints = []string{"192.168.79.134:2379"}
        ser, err := NewServiceRegister(endpoints, "/web", "192.168.1.51:8000", 5)
        if err != nil {
                log.Fatalln(err)
        }
        //监听续租相应chan
        go ser.ListenLeaseRespChan()
        select {
        case &lt;-time.After(20 * time.Second):
                ser.Close()
        }
}
</span></pre>
</div>
<p>    <strong><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">2.服务发现  </span></strong></p>
<p><strong><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">      </span></strong><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">通过/web前缀发现服务,并持续监测/web服务的变化</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;"><span style="font-size: 14px">package main

import (
        "context"
        "log"
        "sync"
        "time"

        "github.com/coreos/etcd/clientv3"
        "github.com/coreos/etcd/mvcc/mvccpb"
)

//ServiceDiscovery 服务发现
type ServiceDiscovery struct {
        cli      *clientv3.Client//etcd client
        serverList mapstring //服务列表
        lock       sync.Mutex
}

//NewServiceDiscovery新建发现服务
func NewServiceDiscovery(endpoints []string) *ServiceDiscovery {
        cli, err := clientv3.New(clientv3.Config{
                Endpoints:   endpoints,
                DialTimeout: 5 * time.Second,
        })
        if err != nil {
                log.Fatal(err)
        }

        return &amp;ServiceDiscovery{
                cli:      cli,
                serverList: make(mapstring),
        }
}

//WatchService 初始化服务列表和监视
func (s *ServiceDiscovery) WatchService(prefix string) error {
        //根据前缀获取现有的key
        resp, err := s.cli.Get(context.Background(), prefix, clientv3.WithPrefix())
        if err != nil {
                return err
        }

        for _, ev := range resp.Kvs {
                s.SetServiceList(string(ev.Key), string(ev.Value))
        }

        //监视前缀,修改变更的server
        go s.watcher(prefix)
        return nil
}

//watcher 监听前缀
func (s *ServiceDiscovery) watcher(prefix string) {
        rch := s.cli.Watch(context.Background(), prefix, clientv3.WithPrefix())
        log.Printf("watching prefix:%s now...", prefix)
        for wresp := range rch {
                for _, ev := range wresp.Events {
                        switch ev.Type {
                        case mvccpb.PUT: //修改或者新增
                                s.SetServiceList(string(ev.Kv.Key), string(ev.Kv.Value))
                        case mvccpb.DELETE: //删除
                                s.DelServiceList(string(ev.Kv.Key))
                        }
                }
        }
}

//SetServiceList 新增服务地址
func (s *ServiceDiscovery) SetServiceList(key, val string) {
        s.lock.Lock()
        defer s.lock.Unlock()
        s.serverList = string(val)
        log.Println("put key :", key, "val:", val)
}

//DelServiceList 删除服务地址
func (s *ServiceDiscovery) DelServiceList(key string) {
        s.lock.Lock()
        defer s.lock.Unlock()
        delete(s.serverList, key)
        log.Println("del key:", key)
}

//GetServices 获取服务地址
func (s *ServiceDiscovery) GetServices() []string {
        s.lock.Lock()
        defer s.lock.Unlock()
        addrs := make([]string, 0)

        for _, v := range s.serverList {
                addrs = append(addrs, v)
        }
        return addrs
}

//Close 关闭服务
func (s *ServiceDiscovery) Close() error {
        return s.cli.Close()
}

func main() {
        var endpoints = []string{"192.168.79.134:2379"}
        ser := NewServiceDiscovery(endpoints)
        defer ser.Close()
        _ = ser.WatchService("/web")
        for {
                select {
                case &lt;-time.Tick(10 * time.Second):
                        log.Println(ser.GetServices())
                }
        }
}</span></pre>
</div>
<p>    <span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px"><strong>3.测试</strong></span></p>
<p><img src="https://img2022.cnblogs.com/blog/1497368/202204/1497368-20220421171736951-495182345.png" alt="" loading="lazy"></p>
<h2><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><strong>六.grpc注册etcd集群</strong></span></h2>
<p>  <em><strong><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">在使用grpc作为服务,并用etcd作为服务发现工具时要注意grpc版本.</span></strong></em></p>
<p><em><strong><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">&nbsp; &nbsp; &nbsp; <span style="background-color: rgba(0, 255, 0, 1)">示例链接:&nbsp;<span style="background-color: rgba(0, 255, 0, 1)">https://github.com/UUPthub/grpc-etcdCluster</span></span></span></strong></em></p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/wangtaobiu/p/16173610.html
頁: [1]
查看完整版本: go etcd服务发现