go get go.etcd.io/etcd/client/v3
config.go:
package config
const (
ServiceName = "/hello"
ServiceAddr = "127.0.0.1:9090"
EtcdServerAddr = "127.0.0.1:2379"
)
hello.proto:
syntax = "proto3";
package service;
option go_package = "./proto/service";
message Hello{
uint64 HelloID = 1;
string Msg = 2;
string SendTime = 3;
}
message HelloRequest{
Hello hello = 1;
}
message HelloResponse{
Hello Resp = 1;
int64 Code = 2;
}
service HelloService{
rpc SayHello(HelloRequest)returns(HelloResponse){}
}
在./proto/service目录下生成gRPC相关代码。
type HelloService struct {
}
var _ service.HelloServiceServer = (*HelloService)(nil)
func (h *HelloService) SayHello(ctx context.Context, req *service.HelloRequest) (*service.HelloResponse, error) {
log.Printf("[Server]:接收到消息...消息ID为%d,消息类型为%s,发送时间为%s", req.Hello.HelloID, req.Hello.Msg, req.Hello.SendTime)
return &service.HelloResponse{
Resp: &service.Hello{
HelloID: req.Hello.HelloID + 1,
Msg: "Hello from Server",
SendTime: time.Now().Format("2006-01-02 15:04:05"),
},
Code: 0,
}, nil
}
服务发现 discovery.go:
// EtcdDiscovery 服务发现
type EtcdDiscovery struct {
cli *clientv3.Client // etcd连接
serviceMap map[string]string // 服务列表(k-v列表)
lock sync.RWMutex // 读写互斥锁
}
func NewServiceDiscovery(endpoints []string) (*EtcdDiscovery, error) {
// 创建etcdClient对象
cli, err := clientv3.New(clientv3.Config{
Endpoints: endpoints,
DialTimeout: 5 * time.Second,
})
if err != nil {
return nil, err
}
return &EtcdDiscovery{
cli: cli,
serviceMap: make(map[string]string), // 初始化kvMap
}, nil
}
// ServiceDiscovery 读取etcd的服务并开启协程监听kv变化
func (e *EtcdDiscovery) ServiceDiscovery(prefix string) error {
// 根据服务名称的前缀,获取所有的注册服务
resp, err := e.cli.Get(context.Background(), prefix, clientv3.WithPrefix())
if err != nil {
return err
}
// 遍历key-value存储到本地map
for _, kv := range resp.Kvs {
e.putService(string(kv.Key), string(kv.Value))
}
// 开启监听协程,监听prefix的变化
go func() {
watchRespChan := e.cli.Watch(context.Background(), prefix, clientv3.WithPrefix())
log.Printf("watching prefix:%s now...", prefix)
for watchResp := range watchRespChan {
for _, event := range watchResp.Events {
switch event.Type {
case mvccpb.PUT: // 发生了修改或者新增
e.putService(string(event.Kv.Key), string(event.Kv.Value)) // ServiceMap中进行相应的修改或新增
case mvccpb.DELETE: //发生了删除
e.delService(string(event.Kv.Key)) // ServiceMap中进行相应的删除
}
}
}
}()
return nil
}
// SetService 新增或修改本地服务
func (s *EtcdDiscovery) putService(key, val string) {
s.lock.Lock()
s.serviceMap[key] = val
s.lock.Unlock()
log.Println("put key :", key, "val:", val)
}
// DelService 删除本地服务
func (s *EtcdDiscovery) delService(key string) {
s.lock.Lock()
delete(s.serviceMap, key)
s.lock.Unlock()
log.Println("del key:", key)
}
// GetService 获取本地服务
func (s *EtcdDiscovery) GetService(serviceName string) (string, error) {
s.lock.RLock()
serviceAddr, ok := s.serviceMap[serviceName]
s.lock.RUnlock()
if !ok {
return "", fmt.Errorf("can not get serviceAddr")
}
return serviceAddr, nil
}
// Close 关闭服务
func (e *EtcdDiscovery) Close() error {
return e.cli.Close()
}
服务注册 register.go:
// EtcdRegister 服务注册
type EtcdRegister struct {
etcdCli *clientv3.Client // etcdClient对象
leaseId clientv3.LeaseID // 租约id
}
// CreateLease 创建租约。expire表示有效期(s)
func (e *EtcdRegister) CreateLease(expire int64) error {
lease, err := e.etcdCli.Grant(context.Background(), expire)
if err != nil {
log.Println(err.Error())
return err
}
e.leaseId = lease.ID // 记录生成的租约Id
return nil
}
// BindLease 绑定租约。将租约与对应的key-value绑定
func (e *EtcdRegister) BindLease(key string, value string) error {
res, err := e.etcdCli.Put(context.Background(), key, value, clientv3.WithLease(e.leaseId))
if err != nil {
log.Println(err.Error())
return err
}
log.Printf("bind lease success %v \n", res)
return nil
}
// KeepAlive 获取续约通道 并 持续续租
func (e *EtcdRegister) KeepAlive() error {
keepAliveChan, err := e.etcdCli.KeepAlive(context.Background(), e.leaseId)
if err != nil {
log.Println(err.Error())
return err
}
go func(keepAliveChan <-chan *clientv3.LeaseKeepAliveResponse) {
for {
select {
case resp := <-keepAliveChan:
log.Printf("续约成功...leaseID=%d", resp.ID)
}
}
}(keepAliveChan)
return nil
}
// Close 关闭服务
func (e *EtcdRegister) Close() error {
log.Printf("close...\n")
// 撤销租约
e.etcdCli.Revoke(context.Background(), e.leaseId)
return e.etcdCli.Close()
}
// NewEtcdRegister 初始化etcd服务注册对象
func NewEtcdRegister(etcdServerAddr string) (*EtcdRegister, error) {
client, err := clientv3.New(clientv3.Config{
Endpoints: []string{etcdServerAddr},
DialTimeout: 3 * time.Second,
})
if err != nil {
log.Println(err.Error())
return nil, err
}
e := &EtcdRegister{
etcdCli: client,
}
return e, nil
}
// ServiceRegister 服务注册。expire表示过期时间,serviceName和serviceAddr分别是服务名与服务地址
func (e *EtcdRegister) ServiceRegister(serviceName, serviceAddr string, expire int64) (err error) {
// 创建租约
err = e.CreateLease(expire)
if err != nil {
return err
}
// 将租约与k-v绑定
err = e.BindLease(serviceName, serviceAddr)
if err != nil {
return err
}
// 持续续租
err = e.KeepAlive()
return err
}
server/main.go:
func main() {
etcdRegister, err := etcd.NewEtcdRegister(config.EtcdServerAddr)
if err != nil {
log.Fatal(err)
}
err = etcdRegister.ServiceRegister(config.ServiceName, config.ServiceAddr, 30)
if err != nil {
log.Fatal(err)
}
defer etcdRegister.Close()
listener, err := net.Listen("tcp", config.ServiceAddr)
if err != nil {
log.Fatal(err)
}
defer listener.Close()
server := grpc.NewServer()
service.RegisterHelloServiceServer(server, &serviceImpl.HelloService{})
err = server.Serve(listener)
if err != nil {
log.Fatal(err)
}
}
client/main.go:
func main() {
c := make(chan os.Signal, 1)
go func() {
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
}()
etcdDiscovery, err := etcd.NewServiceDiscovery([]string{config.EtcdServerAddr})
if err != nil {
log.Fatal(err)
}
defer etcdDiscovery.Close()
err = etcdDiscovery.ServiceDiscovery("/hello")
if err != nil {
log.Fatal(err)
}
var helloID uint64
for {
helloID++
serviceAddr, err := etcdDiscovery.GetService(config.ServiceName)
conn, err := grpc.Dial(serviceAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal(err)
}
cli := service.NewHelloServiceClient(conn)
req := &service.HelloRequest{
Hello: &service.Hello{
HelloID: helloID,
Msg: "Hello from Client",
SendTime: time.Now().Format("2006-01-02 15:04:05"),
},
}
resp, err := cli.SayHello(context.Background(), req)
if err != nil {
log.Fatal(err)
}
log.Printf("%+v", resp)
conn.Close()
time.Sleep(time.Second * 2)
}
}