etcd是什么
- 分布式kv数据库
- raft一致性协议 + 选主
- wal日志先行保证数据一致性
- mvcc多版本并发控制kv历史记录查看
- 高可用,高可扩展
etcd能做什么
- 系统关键数据存储(配置中心)
- 服务注册与发现
- 分布式锁
- 发布订阅
- 事件监听
etcd服务发现与注册原理
- 创建一个对某目录(以/order-srv)的Watcher监听,通过阻塞持续租约保持对/order-srv目录的持续监听
/order-srv
├── order1
├── order2
├── order3
├── order4
└── order5
- 有一个新的order实例加入到被监听的目录/order-srv,此时/order-srv目录会发生addEvent,被监听器捕获
/order-srv
├── order1
├── order2
├── order3
├── order4
└── order5
└── orderNew
- 监听器捕获到addEvent事件后,通过raft一致性协议同主节点/order-srv目录下的数据到follower节点,通过ack成功响应数目来确定本次是不是成功
- 对每一个新加入目录/order-srv的实例启动一个心跳检测线程,如果心跳超时,则将该实例从/order-srv中删除,并通过raft协议在不同etcd节点间同步数据(order5)
/order-srv
├── order1
├── order2
├── order3
├── order4
└── orderNew
- 客户端从目录/order-srv随机获取一个order实例(负载均衡)
基于go实现etcd服务发现与注册
package main
import (
"context"
"log"
"time"
"go.etcd.io/etcd/clientv3"
)
type ServiceRegister struct {
cli *clientv3.Client
leaseID clientv3.LeaseID
keepAliveChan <-chan *clientv3.LeaseKeepAliveResponse
key string
val string
}
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,
}
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:%s val:%s success!", s.key, s.val)
return nil
}
func (s *ServiceRegister) ListenLeaseRespChan() {
for leaseKeepResp := range s.keepAliveChan {
log.Println("续约成功", leaseKeepResp)
}
log.Println("关闭续租")
}
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{"localhost:2379"}
ser, err := NewServiceRegister(endpoints, "/web/node1", "localhost:8000", 5)
if err != nil {
log.Fatalln(err)
}
go ser.ListenLeaseRespChan()
select {
}
}
package main
import (
"context"
"log"
"sync"
"time"
"github.com/coreos/etcd/mvcc/mvccpb"
"go.etcd.io/etcd/clientv3"
)
type ServiceDiscovery struct {
cli *clientv3.Client
serverList map[string]string
lock sync.Mutex
}
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(map[string]string),
}
}
func (s *ServiceDiscovery) WatchService(prefix string) error {
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))
}
go s.watcher(prefix)
return nil
}
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))
}
}
}
}
func (s *ServiceDiscovery) SetServiceList(key, val string) {
s.lock.Lock()
defer s.lock.Unlock()
s.serverList[key] = string(val)
log.Println("put key :", key, "val:", val)
}
func (s *ServiceDiscovery) DelServiceList(key string) {
s.lock.Lock()
defer s.lock.Unlock()
delete(s.serverList, key)
log.Println("del key:", key)
}
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
}
func (s *ServiceDiscovery) Close() error {
return s.cli.Close()
}
func main() {
var endpoints = []string{"localhost:2379"}
ser := NewServiceDiscovery(endpoints)
defer ser.Close()
ser.WatchService("/web/")
ser.WatchService("/gRPC/")
for {
select {
case <-time.Tick(10 * time.Second):
log.Println(ser.GetServices())
}
}
}