golang微服务实践:服务注册与服务发现-Etcd的使⽤
为什么?
为什么会有服务注册和服务发现?在它以前我们是怎么做的?
举个例⼦:
⽐如我们做MySQL读写分离,就在本地配置⼀个⽂件,然后程序读取这个配置⽂件⾥的数据进⾏数据库读写分离的设置。
但是随着业务发展迅速,业务模块越来越多,数据也越来越多,MySQL数据库也越来越多,需要读取MySQL服务的业务模块也越来越多。每次增加MySQL实例,每个业务模块都要⼿动去写⼀次本地配置。
想⼀想这⾥有什么问题?可以改进吗?
每次⼿动写配置,是不是很浪费时间,能不能做到⾃动⼀些?能不能做到只配置⼀次,业务模块就可以⾃动读取更新后的配置?于是我们就开始动脑筋了,就开始琢磨解决的办法了。
在计算机领域,没有什么是增加⼀个中间层解决不了的问题,有的话,“那就在增加⼀层”,对,最后那句是我说的- -!。
于是就想:能不能把所有的配置集中存放,程序模块的配置更新由各个程序⾃动的来配置中⼼读取,更新配置。⽽不是每次⼿动的增加每个程序模块的配置。
于是我们就增加了⼀个“中间层”- 配置中⼼,集中存储,管理分散在各个程序中的配置。配置中⼼就向外⾯提供配置服务。
不过Etcd应该属于服务配置中⼼,属于更加细化的配置中⼼。
还有更加专注的配置中⼼,⽐如Apollo,Nacos,disconf等等。
是什么?
什么是服务注册和服务发现?
服务注册 :就是向配置中⼼注册⼀个配置的功能。
服务发现:就是发现配置中⼼增加、修改、删除了配置的功能。发现配置的异动情况,让更新配置。
会遇到哪些问题?
服务发现和服务注册怎么解决问题以及会遇到哪些问题?
1. 怎么标识⼀个服务?
在计算机⾥,标识⼀个计算机中运⾏的程序可以⽤ip+端⼝形式来标识,配置服务也可以⽤这种⽅式。
2. 怎么发现⼀个配置服务的异动?
配置更新,删除(下线),增加了,怎么发现配置的这些异动情况?⼀种是长轮询,⼀种是⼼跳,每隔多长时间检查⼀次,还有发布订阅模式。
3. 服务是否可⽤?
这种就要检查服务的可⽤情况了,⼀般是健康检查。
... ...
等等问题
怎么解决
业内主要的⼏个解决⽅案ZooKeeper,Etcd,Consul ,等。我们介绍etcd的使⽤。
et cd介绍
是CoreOS团队于2013年6⽉发起的开源项⽬,它的⽬标是构建⼀个⾼可⽤的分布式键值(key-value)数据库。etcd内部采⽤raft协议作为⼀致性算法,etcd基于Go语⾔实现。
我也有⼀些它的使⽤情况。
它是⼀个kv键值的数据库:
所以可以存储数据,那么就可以存储配置数据。
它有watch功能:
所以可以发现配置的异动情况。变化时候可以起到通知作⽤。
它有key TTL功能:
etcdv3可以通过lease租约,设置服务⽣存周期TTL,让key⾃动过期,通过KeepAlive定期续租,避免过期。当然,它还可以做分布式锁、分布式队列等等其他功能,你可以google去学习它的其他功能。
例⼦
注册服务和获取服务
// PutValueDemo 插⼊值demo,配置⾥⾯put相当于注册⼀个服务(服务注册),get相当于获取⼀个服务(服务发现)
func (demo EtcdDemo) PutValueDemo(client *clientv3.Client) {
fmt.Println("\n...put ")
fmt.Println("put value: ", demo)
_, err := demo.PutValue(client)
if err != nil {
fmt.Println("failed to put value, err: ", err)
return
}
getVal, err := demo.GetValue(client)
if err != nil {
fmt.Println("get val err: ", err)
return
}
fmt.Println("get key:",string(getVal.Kvs[0].Key),",value:", string(getVal.Kvs[0].Value), ", Revision: ", getVal.Header.Revision)
}
如果你单独在main函数⾥测试PutValueDemo(),
client, err := clientv3.New(clientv3.Config{
Endpoints:  []string{"127.0.0.1:2379"},
DialTimeout: time.Second * 5,
})
if err != nil {
fmt.Println("failed to connect etcd: ", err)
return
}
defer client.Close()
demo := EtcdDemo{Key: "test1", Val: "val1"}
demo.PutValueDemo(client)
你会发现:
Revision 的值,每次都是增加的。
get val: val1 ,key: test1 , Revision:  1
get val: val1 ,key: test1 , Revision:  2
这也是etcd的⼀个特性,可以⽤个来做分布式锁。
watch 监测服务变化
// watchDemo 监听key的变化
func (demo EtcdDemo) WatchDemo(client *clientv3.Client) {
fmt.Println("\n...")
stopChan := make(chan interface{}) // 是否停⽌信号
go func() {
watchChan := client.Watch(context.TODO(), demo.Key, clientv3.WithPrefix())
for {
select {
case result := <- watchChan:
for _, event := range result.Events {
fmt.Printf("%s %q : %q\n", event.Type, event.Kv.Key, event.Kv.Value)
}
case <-stopChan:
fmt.Println("")
return
}
}
}()
for i := 0; i < 5; i++ {
var demo EtcdDemo
demo.Key = fmt.Sprintf("key_%02d", i)
demo.Val = strconv.Itoa(i)
demo.PutValue(client)
}
time.Sleep(time.Second * 1)
stopChan <- 1 //停⽌watch,在插⼊就不会监听到了
}
运⾏后:
...
putResp Revision:  13
PUT "key_00" : "0"
PUT "key_01" : "1"
putResp Revision:  14
PUT "key_02" : "2"
putResp Revision:  15
PUT "key_03" : "3"
putResp Revision:  16
PUT "key_04" : "4"
putResp Revision:  17
Revision单调递增变化的。
watch 可以监测到put变化的情况。
租约lease
// LeaseDemo 租约
func (demo EtcdDemo) LeaseDemo(client *clientv3.Client) {
fmt.Println("\n...")
lease, err := client.Grant(context.TODO(), 2) //创建⼀个租约
if err != nil {
fmt.Println("grant err: ", err)
return
}
testKey := "testleasekey"
/
/ 给这个testkey⼀个 2秒的TTL租约
client.Put(context.TODO(), testKey, "testvalue", clientv3.WithLease(lease.ID))
getVal, err := client.Get(context.TODO(), testKey)
if err != nil {
fmt.Println("get val err: ", err)
return微服务注册中心有哪些
}
vallen := len(getVal.Kvs)
fmt.Println("before time sleep, val len: ", vallen)
fmt.Println("sleep 4 seconds")
time.Sleep(4 * time.Second) //睡眠4秒,让租约过期
getVal, _ = client.Get(context.TODO(), testKey)
vallen = len(getVal.Kvs)
fmt.Println("after 4 seconds, val len: ", vallen)
}
运⾏:
...
before time sleep, val len:  1
sleep 4 seconds
after 4 seconds, val len:  0 //这⾥租约到期,删掉了这个key和val 完整代码在
参考

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。