etcd
etcd简介
- go开发的开源的、分布式的、高可用的kv存储系统
- 可用于配置共享
- 可用于服务的注册和发现
特点:
- 完全复制:集群中每个节点都可以使用完整的存档
- 高可用: etcd可避免点点问题可网络故障
- 一致性:每次读取都会返回跨多主机的最新写入
- 简单:包括一个定义良好、面向用户的api(grpc)
- 安全:实现了可选的客户端证书身份验证的自动化TLS
- 可靠:使用raft算法实现了强一致性、高可用的服务存储目录
1、put与get
package main
import (
"context"
"fmt"
clientv3 "go.etcd.io/etcd/client/v3"
)
func main(){
cli,_:= clientv3.New(clientv3.Config{
Endpoints:[]string{"127.0.0.1:2379"}
})
defer cli.Close()
//添加kv
_,_=cli.Put(context.TODO,"张三","DSHHASDAS")
//根据key获取value
resp,_:=cli.Get(context.TODO,"张三")
for _,kv:=resp.Kvs{
fmt.Printf("%s->%s\n",kv.Key,kv.Value)
}
}
2、watch
package main
func main(){
cli,_:= clientv3.New(clientv3.Config{
Endpoints:[]string{"127.0.0.1:2379"}
})
defer cli.Close()
_,_=cli.Put(context.TODO(),"张三","DSHHASDAS")
resp,_:=cli.Watch(context.TODO,"张三")
}
3、设置一个租约
package main
import(
"context"
"fmt"
clientv3 "go.etcd.io/etcd/client/v3"
)
func main(){
cli,_:=clientv3.New(clientv3.Config{
Endpoints:[]string{127.0.0.1:2379}
})
//设置一个租约
lease,_:=cli.Grant(context.TODO,5)
//将租约与kv绑定,租约到期kv失效
_,_=cli.Put(context.TODO(),"ll","value",clientv3.WithLease(lease.ID))
resp,_:=cli.Get(context.TODO(),"ll")
for v:=range resp.Kvs{
fmt.Println(v.Key,v.Value)
}
}
4、keepAlive
package main
import(
"context"
"fmt"
clientv3 "go.etcd.io/etcd/client/v3"
)
func main(){
cli,_:=clientv3.New(clientv3.Config{
Endpoints:[]string{"127.0.0.1:2379"}
})
//创建一个租期
lease,_:=cli.Grant(context.TODO(),5)
respch,_:=cli.KeepAlive(context.TODO(),lease.ID)
for{
v:=<-respch
fmt.Println(v.ID)
}
}
5、分布式锁
package main
import (
"context"
"fmt"
"go.etcd.io/etcd/client/v3/concurrency"
clientv3 "go.etcd.io/etcd/client/v3"
)
func main() {
cli, err := clientv3.New(clientv3.Config{
Endpoints: []string{"123.249.105.59:2379"},
})
if err != nil {
// handle error!
fmt.Printf("connect to etcd failed, err:%v\n", err)
return
}
_, _ = cli.Put(context.TODO(), "key", "value")
session1, _ := concurrency.NewSession(cli)
mutex1 := concurrency.NewMutex(session1, "key")
session2, _ := concurrency.NewSession(cli)
mutex2 := concurrency.NewMutex(session2, "key")
_ = mutex1.Lock(context.TODO())
ch := make(chan struct{})
go func() {
defer close(ch)
//等待session1释放锁
err2 := mutex2.Lock(context.TODO())
if err2 != nil {
fmt.Println("mutex2 lock failed")
}
}()
err = mutex1.Unlock(context.TODO())
if err != nil {
fmt.Println("mutex1 unlock failed")
}
fmt.Println("mutex1 unlock")
<-ch
// session1的锁不释放,session2的上锁会一直阻塞
}
6、服务的naming与discovery
proto
syntax="proto3";
package nuts.echo.v1;
message EchoRequest {
string request=1;
}
message EchoResponse {
string response=1;
}
service EchoService{
rpc Echo(EchoRequest)returns(EchoResponse){}
}
register
package main
import (
"context"
"fmt"
"net"
clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/client/v3/naming/endpoints"
"google.golang.org/grpc"
echov1 "github.com/nuts/etcd_test/proto/service/nuts/echo/v1"
)
type Ser struct {
echov1.UnimplementedEchoServiceServer
}
func (receiver Ser) Echo(ctx context.Context, request *echov1.EchoRequest) (*echov1.EchoResponse, error) {
fmt.Printf("recv %s\n", request.GetRequest())
return &echov1.EchoResponse{Response: "hello!" + request.GetRequest()}, nil
}
func main() {
conn, err := net.Listen("tcp", ":8080")
if err != nil {
fmt.Println("create conn failed")
return
}
defer conn.Close()
server := grpc.NewServer()
//注册服务
echov1.RegisterEchoServiceServer(server, &Ser{})
cli, err := clientv3.New(clientv3.Config{
Endpoints: []string{"127.0.0.1:2379"},
})
if err != nil {
fmt.Println("create etcd client failed!")
return
}
defer cli.Close()
//向etcd注册一个服务终端
mg, err := endpoints.NewManager(cli, "nuts/v1/EchoService")
if err != nil {
fmt.Println("create terminal failed!")
return
}
err = mg.AddEndpoint(context.TODO(), "nuts/v1/EchoService/echo", endpoints.Endpoint{Addr: "127.0.0.1:8080"})
if err != nil {
fmt.Println(" add endpoint to etcd failed!")
return
}
_ = server.Serve(conn)
}
discovery
package main
import (
"context"
"fmt"
"google.golang.org/grpc/credentials/insecure"
echov1 "github.com/nuts/etcd_test/proto/service/nuts/echo/v1"
clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/client/v3/naming/resolver"
"google.golang.org/grpc"
)
func main() {
cli, _ := clientv3.NewFromURL("127.0.0.1:2379")
etcdResolver, err := resolver.NewBuilder(cli)
if err != nil {
fmt.Println("create resolver failed!")
return
}
conn, err := grpc.Dial("etcd:///nuts/v1/EchoService",
grpc.WithResolvers(etcdResolver),
//grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
fmt.Println("create grpc client failed!")
return
}
client := echov1.NewEchoServiceClient(conn)
resp, _ := client.Echo(context.TODO(), &echov1.EchoRequest{Request: "张三"})
fmt.Println(resp.GetResponse())
}