go etcd服务发现
<h2><span style="font-family: "Microsoft YaHei"; font-size: 16px; color: rgba(255, 0, 0, 1)"> 一.etcd简介</span></h2><p><span style="font-family: "Microsoft YaHei"; font-size: 16px"> <span style="font-size: 15px">etcd 是一个分布式键值对存储系统,由coreos 开发,内部采用 raft 协议作为一致性算法,用于可靠、快速地保存关键数据,并提供访问。通过分布式锁、leader选举和写屏障(write barriers),来实现可靠的分布式协作。etcd集群是为高可用、持久化数据存储和检索而准备。</span></span></p>
<h3><span style="font-family: "Microsoft YaHei"; 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: "Microsoft YaHei"; font-size: 16px"><span style="font-size: 15px"> Raft: etcd所采用的保证分布式系统强一致性的算法。<br> Node: 一个Raft状态机实例。<br> Member: 一个etcd实例。它管理着一个Node,并且可以为客户端请求提供服务。<br> Cluster: 由多个Member构成、可以协同工作的etcd集群。<br> Peer: 对同一个etcd集群中另外一个Member的称呼。<br> Client: 向etcd集群发送HTTP请求的客户端。<br> WAL: 预写式日志,etcd用于持久化存储的日志格式。<br> snapshot: etcd防止WAL文件过多而设置的快照,存储etcd数据状态。<br> Proxy: etcd的一种模式,为etcd集群提供反向代理服务。<br> Leader: Raft算法中,通过竞选而产生的、处理所有数据提交的节点。<br> Follower: 竞选失败的节点作为Raft中的从属节点,为算法提供强一致性保证。<br> Candidate:当Follower超过一定时间接收不到Leader的心跳时转变为Candidate开始竞选。<br> Term: 某个节点成为Leader到下一次竞选时间,称为一个Term。<br> Index: 数据项编号。Raft中通过Term和Index来定位数据</span></span></p>
<h3><strong><span style="font-family: "Microsoft YaHei"; 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: "Microsoft YaHei"; font-size: 16px"><span style="font-size: 15px"> 服务发现</span></span></p>
<p><span style="font-family: "Microsoft YaHei"; font-size: 16px"><span style="font-size: 15px"> </span></span><span style="font-family: "Microsoft YaHei"; font-size: 16px"><span style="font-size: 15px">消息发布与订阅</span></span></p>
<p><span style="font-family: "Microsoft YaHei"; font-size: 16px"><span style="font-size: 15px"> </span></span><span style="font-family: "Microsoft YaHei"; font-size: 16px"><span style="font-size: 15px">负载均衡</span></span></p>
<p><span style="font-family: "Microsoft YaHei"; font-size: 16px"><span style="font-size: 15px"> </span></span><span style="font-family: "Microsoft YaHei"; font-size: 16px"><span style="font-size: 15px">分布式通知与协调</span></span></p>
<p><span style="font-family: "Microsoft YaHei"; font-size: 16px"><span style="font-size: 15px"> </span></span><span style="font-family: "Microsoft YaHei"; font-size: 16px"><span style="font-size: 15px">分布式锁、分布式队列</span></span></p>
<p><span style="font-family: "Microsoft YaHei"; font-size: 16px"><span style="font-size: 15px"> </span></span><span style="font-family: "Microsoft YaHei"; font-size: 16px"><span style="font-size: 15px">集群监控与Leader竞选</span></span></p>
<h3><strong><span style="font-family: "Microsoft YaHei"; 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: "Microsoft YaHei"; font-size: 16px"><span style="font-size: 15px"> </span></span></strong><span style="font-family: "Microsoft YaHei"; font-size: 15px"><strong>etcd:</strong> <em>用于共享配置和服务发现的分布式一致键值存储. </em>etcd 是一种分布式键值存储, 它提供了一种跨机器集群存储数据的可靠方式. etcd 在网络分区期间优雅地处理 master 选举, 并且会容忍机器故障.</span></p>
<p><span style="font-family: "Microsoft YaHei"; font-size: 15px"> <strong>redis: </strong> <em>持久化在磁盘上的内存数据库, </em>Redis 是一个开源、BSD 许可的高级键值存储. 它通常被称为数据结构服务器, 因为键可以包含字符串、散列、列表、集合和排序集合.</span></p>
<h2><span style="font-family: "Microsoft YaHei"; font-size: 16px; color: rgba(255, 0, 0, 1)"><strong>二.etcd安装</strong></span></h2>
<p><span style="font-family: "Microsoft YaHei"; 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: "Microsoft YaHei"; 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 ~ ><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: "Microsoft YaHei"; 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: "Microsoft YaHei"; 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: "Microsoft YaHei"; font-size: 16px; color: rgba(255, 0, 0, 1)"><strong>三.go操作etcd</strong></span></h2>
<p><span style="font-family: "Microsoft YaHei"; 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: "Microsoft YaHei"; 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: "Microsoft YaHei"; font-size: 15px"> 监听test的变化,Watch返回的是一个WatchResponse的管道类型,所以我们可以用for循环取值,每当test发生变化,watch就会发现并做相应操作:</span></p>
<blockquote>
<pre><span style="font-family: "Microsoft YaHei"; font-size: 14px">type WatchResponse struct {</span><br><span style="font-family: "Microsoft YaHei"; font-size: 14px"> Header pb.ResponseHeader</span><br><span style="font-family: "Microsoft YaHei"; font-size: 14px"> Events []*storagepb.Event</span><br><span style="font-family: "Microsoft YaHei"; font-size: 14px"> CompactRevision int64</span><br><span style="font-family: "Microsoft YaHei"; font-size: 14px"> Canceled bool</span><br><span style="font-family: "Microsoft YaHei"; font-size: 14px">}</span></pre>
<pre><span style="font-family: "Microsoft YaHei"; font-size: 14px">type Event struct {</span><br><span style="font-family: "Microsoft YaHei"; 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: "Microsoft YaHei"; font-size: 14px"> Kv *KeyValue `protobuf:"bytes,2,opt,name=kv" json:"kv,omitempty"`</span><br><span style="font-family: "Microsoft YaHei"; font-size: 14px">}</span></pre>
</blockquote>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;"><span style="font-size: 14px">root@master~ > etcdctl put test world
OK
root@master~ > 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: "Microsoft YaHei"; 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: "Microsoft YaHei"; font-size: 15px">***建议直接使用clientv3的3.5版本,与grpc最新版本兼容,安装: go get go.etcd.io/etcd/client/v3@v3.5.4,详细版本迭代及操作建议参考官方说明: <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: "Microsoft YaHei"; font-size: 15px"><span style="background-color: rgba(255, 255, 255, 1)"> </span>***如果受go版本或公司大环境影响不能使用最新版本,可以参考下边操作</span></strong></span></p>
<p> <span style="font-family: "Microsoft YaHei"; font-size: 15px">1.当我们直接使用go get github.com/coreos/etcd/clientv3或者go get go.etcd.io/etcd时,会自动安装etcd2.3.8版本,一个很久的版本,所以在安装时一定要指定版本, 如: go get github.com/coreos/etcd/clientv3@v3.3.25</span></p>
<p><span style="font-family: "Microsoft YaHei"; font-size: 15px"> 2.必须安装有grpc v1.26.0版本. 如果装有多个版本的grpc,需要在go.mod中需添加下边代码或者直接不使用go mod tidy导包,而是通过go get google.golang.org/grpc@v1.26.0和go get 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 => google.golang.org/grpc v1.26.0</span> </pre>
</div>
<h2><span style="font-family: "Microsoft YaHei"; font-size: 16px; color: rgba(255, 0, 0, 1)"><strong>五.etcd实现服务注册和发现</strong></span></h2>
<p><span style="font-family: "Microsoft YaHei"; 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: "Microsoft YaHei"; 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: "Microsoft YaHei"">clientv3.New: 创建etcdv3客户端(</span><span style="font-size: 15px">func New(cfg Config) (*Client, error)</span><span style="font-family: "Microsoft YaHei"">)</span></p>
<p><span style="font-family: "Microsoft YaHei"; font-size: 15px"> clientv3.Config: 创建客户端时使用的配置</span></p>
<p><span style="font-family: "Microsoft YaHei"; font-size: 15px"> </span><span style="font-family: "Microsoft YaHei"; font-size: 15px">Grant: 初始化一个新租约(Grant(ctx context.Context, ttl int64) (*LeaseGrantResponse, error))</span></p>
<p><span style="font-family: "Microsoft YaHei"; font-size: 15px"> Put: 注册服务并绑定租约</span></p>
<p><span style="font-family: "Microsoft YaHei"; font-size: 15px"> </span><span style="font-family: "Microsoft YaHei""><span style="font-size: 15px">KeepAlive</span>: <span style="font-size: 15px">设置续租,定期发送续租请求</span>(</span><span style="font-size: 15px; font-family: "Microsoft YaHei"">KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAliveResponse, error))</span></p>
<p><span style="font-size: 15px; font-family: "Microsoft YaHei""> </span><span style="font-family: "Microsoft YaHei"; font-size: 15px">Revoke: 撤销租约</span></p>
<p><span style="font-family: "Microsoft YaHei"; font-size: 15px"> Get: 获取服务</span></p>
<p><span style="font-family: "Microsoft YaHei"; font-size: 15px"> </span><span style="font-family: "Microsoft YaHei"; font-size: 15px">Watch</span><span style="font-family: "Microsoft YaHei"; font-size: 15px">: 监控服务</span></p>
<p><span style="font-family: "Microsoft YaHei"; font-size: 15px"> <span style="color: rgba(0, 128, 0, 1)"><strong>实现流程:</strong></span></span></p>
<p><span style="font-family: "Microsoft YaHei"; font-size: 15px"><span style="color: rgba(0, 128, 0, 1)"><strong> <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: "Microsoft YaHei"; font-size: 15px"> <span style="color: rgba(0, 128, 0, 1)"><strong>实现代码:</strong></span></span></p>
<p><span style="font-family: "Microsoft YaHei"; 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: "Microsoft YaHei"; 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 <-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 := &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 <-time.After(20 * time.Second):
ser.Close()
}
}
</span></pre>
</div>
<p> <strong><span style="font-family: "Microsoft YaHei"; font-size: 15px">2.服务发现 </span></strong></p>
<p><strong><span style="font-family: "Microsoft YaHei"; font-size: 15px"> </span></strong><span style="font-family: "Microsoft YaHei"; 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 &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 <-time.Tick(10 * time.Second):
log.Println(ser.GetServices())
}
}
}</span></pre>
</div>
<p> <span style="font-family: "Microsoft YaHei"; 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: "Microsoft YaHei"; font-size: 16px"><strong>六.grpc注册etcd集群</strong></span></h2>
<p> <em><strong><span style="font-family: "Microsoft YaHei"; font-size: 15px">在使用grpc作为服务,并用etcd作为服务发现工具时要注意grpc版本.</span></strong></em></p>
<p><em><strong><span style="font-family: "Microsoft YaHei"; font-size: 15px"> <span style="background-color: rgba(0, 255, 0, 1)">示例链接: <span style="background-color: rgba(0, 255, 0, 1)">https://github.com/UUPthub/grpc-etcdCluster</span></span></span></strong></em></p>
<p> </p><br><br>
来源:https://www.cnblogs.com/wangtaobiu/p/16173610.html
頁:
[1]