【Go】微服务架构下实现etcd服务注册与服务发现

中心网关:gateway

四个微服务:user、message、note、relationship

1 中心网关实现服务发现

1.1 设计EtcdDiscovery类

go 复制代码
package entity

import (
	"context"
	"fmt"
	clientv3 "go.etcd.io/etcd/client/v3"
	"gonote/gateway/internal/option"
	messageService "gonote/message/service"
	noteService "gonote/note/service"
	relationshipService "gonote/relationship/service"
	userService "gonote/user/service"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"sync"
	"time"
)

type EtcdDiscovery struct {
	etcdClient *clientv3.Client
	serviceMap map[string]interface{}
	mu         sync.RWMutex
}

func NewEtcdDiscovery(ip string, port int) (*EtcdDiscovery, error) {
	etcdCli, err := clientv3.New(clientv3.Config{
		Endpoints:   []string{fmt.Sprintf("%v:%v", ip, port)},
		DialTimeout: time.Second * 3,
	})
	if err != nil {
		return nil, err
	}
	
	return &EtcdDiscovery{
		etcdClient: etcdCli,
		serviceMap: make(map[string]interface{}),
	}, nil
}

func (ed *EtcdDiscovery) Start(serviceNames []string) {
	for _, serviceName := range serviceNames {
		resp, err := ed.etcdClient.Get(context.TODO(), serviceName)
		if err != nil {
			panic(err)
		}
		addr := string(resp.Kvs[0].Value)

		conn, err := grpc.Dial(addr,
			grpc.WithTransportCredentials(insecure.NewCredentials()))
		if err != nil {
			panic(err)
		}

		// etcd存储的是各个微服务的监听地址,通过监听地址创建服务实例
		switch serviceName {
		case option.User:
			grpcCli := userService.NewUserServiceClient(conn)
			ed.serviceMap[serviceName] = grpcCli
		case option.Relationship:
			grpcCli := relationshipService.NewRelationshipServiceClient(conn)
			ed.serviceMap[serviceName] = grpcCli
		case option.Note:
			grpcCli := noteService.NewNoteServiceClient(conn)
			ed.serviceMap[serviceName] = grpcCli
		case option.Message:
			grpcCli := messageService.NewMessageServiceClient(conn)
			ed.serviceMap[serviceName] = grpcCli
		}
		
		// 开启协程,监听etcd的变化,动态维护各个grpc服务实例
		go ed.watch(serviceName)
	}
}

func (ed *EtcdDiscovery) watch(serviceName string) {
	watchChan := ed.etcdClient.Watch(context.TODO(), serviceName)
	for event := range watchChan {
		for _, e := range event.Events {
			if e.Type == clientv3.EventTypePut {
				addr := string(e.Kv.Value)
				conn, err := grpc.Dial(addr,
					grpc.WithTransportCredentials(insecure.NewCredentials()))
				if err != nil {
					continue
				}
				ed.mu.Lock()
				switch serviceName {
				case option.User:
					grpcCli := userService.NewUserServiceClient(conn)
					ed.serviceMap[serviceName] = grpcCli
				case option.Relationship:
					grpcCli := relationshipService.NewRelationshipServiceClient(conn)
					ed.serviceMap[serviceName] = grpcCli
				case option.Note:
					grpcCli := noteService.NewNoteServiceClient(conn)
					ed.serviceMap[serviceName] = grpcCli
				case option.Message:
					grpcCli := messageService.NewMessageServiceClient(conn)
					ed.serviceMap[serviceName] = grpcCli
				}
				ed.mu.Unlock()
			} else if e.Type == clientv3.EventTypeDelete {
				ed.mu.Lock()
				delete(ed.serviceMap, serviceName)
				ed.mu.Unlock()
			}
		}
	}
}

func (ed *EtcdDiscovery) GetServiceAddr(serviceName string) interface{} {
	ed.mu.RLock()
	defer ed.mu.RUnlock()

	return ed.serviceMap[serviceName]
}

1.2 在web启动时,初始化EtcdDiscovery

go 复制代码
package main

import (
	"gonote/gateway/internal"
	"gonote/gateway/internal/option"
	"gonote/gateway/internal/util"
)

func init() {
	util.InitLogger()

	util.InitWebSocketServer(64)

	err := util.InitRedis()
	if err != nil {
		panic(err)
	}

	util.InitKafka(option.Topic)

	util.InitEtcdDiscovery([]string{
		option.User,
		option.Relationship,
		option.Note,
		option.Message})
}

func main() {
	engine := internal.Router()

	if err := engine.Run("0.0.0.0:9090"); err != nil {
		panic(err)
	}
}

1.3 调用EtcdDiscovery获取服务实例

举个用户注册的例子:

go 复制代码
func UserLogin(c *gin.Context) {
	em := c.PostForm("email")
	pwd := c.PostForm("pwd")

    // 获取服务实例
	cli := util.EtcdDiscovery.GetServiceAddr(option.User).(service.UserServiceClient)
   
    // 调用服务
	_, err := cli.UserLogin(context.TODO(), &service.User{
		Email: em,
		Pwd:   pwd,
	})

	if err != nil {
		c.JSON(200, gin.H{
			"code": 1,
			"msg":  err.Error(),
		})
		return
	}

	// 生成jwt令牌
	token, err := util.GenToken(em)
	if err != nil {
		c.JSON(200, gin.H{
			"code": 1,
			"msg":  err.Error(),
		})
		return
	}

    // session维护长连接
	session := sessions.Default(c)
	session.Set("email", em)
	err = session.Save()
	if err != nil {
		c.JSON(200, gin.H{
			"code": 1,
			"msg":  err.Error(),
		})
		return
	}

	c.JSON(200, gin.H{
		"code": 0,
		"data": token,
	})
}

2 微服务端进行服务注册

user业务对应的微服务:

go 复制代码
func init() {
	util.InitLogger()

	err := util.InitDB()
	if err != nil {
		panic(err)
	}

	util.InitKafka(option.Topic)

	util.InitEtcdCli()
}

func main() {
	addr := option.IP + ":" + option.Port
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		panic(err)
	}
	defer ln.Close()

	defer util.EtcdCli.Close()
	defer util.KafkaCli.Close()

    // 服务注册
	_, err = util.EtcdCli.Put(context.TODO(), "user", addr)
	if err != nil {
		panic(err)
	}

	server := grpc.NewServer()

	service.RegisterUserServiceServer(server, &service.UserServiceImpl{})

	if err = server.Serve(ln); err != nil {
		panic(err)
	}
}

通过etcd命令查看相关注册信息

相关推荐
米丘19 小时前
微前端之 Web Components 完全指南
微服务·html
霸道流氓气质3 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
霸道流氓气质4 天前
Spring Boot 微服务性能优化完全指南
spring boot·微服务·性能优化
地瓜伯伯4 天前
从MESI缓存一致性协议讲透synchronized的底层
java·spring boot·spring·spring cloud·微服务·springcloud
Devin~Y4 天前
大厂 Java 面试实录:从音视频内容社区到 AI RAG 的全链路技术设计
java·spring boot·redis·spring cloud·微服务·kafka·音视频
递归尽头是星辰4 天前
AI 访问数据仓库:从直连到微服务化
数据仓库·人工智能·微服务·dataagent·ai数据治理
就改了4 天前
Windows 环境 SkyWalking 完整实操教程
windows·微服务·skywalking
至乐活着5 天前
Docker Compose多服务编排实战:从零搭建Node.js+MySQL+Redis全栈应用
docker·微服务·devops·容器编排·compose
就改了5 天前
微服务异步场景链路断裂完整解决方案
微服务·云原生·架构
山东点狮信息科技有限公司5 天前
点狮OA-企业级 OA 办公自动化系统架构设计与实践
spring cloud·微服务·性能优化·架构·系统架构