windows golang,consul,grpc学习

Cousul

1.安装consul,直接去官网下载。

Consul官网:https://www.consul.io/

Consul下载地址:https://developer.hashicorp.com/consul/install?product_intent=consul

windows下载

2.运行consul

复制代码
consul agent -dev

具体配置可以看这篇文章。 https://blog.csdn.net/qq_45774406/article/details/138251814

运行成功之后就可以在localhost:8500看到consul界面了。

具体服务

现在使用golang在本地通过协程启动多个服务,并注册到consul。
1.定义proto

先新建一个目录proto,然后新建service.proto,复制下面的内容进去

bash 复制代码
syntax = "proto3";

package proto;

option go_package = "./proto";

// 定义示例服务
service ExampleService {
    // 简单的Echo方法
    rpc Echo (EchoRequest) returns (EchoResponse);
}

// Echo请求消息
message EchoRequest {
    string message = 1;
}

// Echo响应消息
message EchoResponse {
    string message = 1;
    string server_id = 2; 
}

2.把proto转为go文件

需要安装以下插件:

go

protoc

protoc-gen-go

安装方法和proto文件说明参考

https://blog.csdn.net/weixin_42413966/article/details/128497429

安装好之后运行下面的语句,会自动生成service_grpc.pb.go和service.pb.go

bash 复制代码
 protoc --go_out=. --go-grpc_out=. proto/service.proto

service.pb.go 包含了Protocol Buffers消息类型的定义和序列化相关的代码。

service_grpc.pb.go 包含了gRPC服务相关的代码,包括客户端和服务端的接口定义、方法实现等。

3.服务注册

下面是服务注册的代码

main.go

bash 复制代码
package main

import (
	"consul/pkg/service"
	"context"
	"fmt"
	"log"
	"os"
	"os/signal"
	"syscall"
)

func main() {
	// 创建一个带取消的上下文
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// 创建服务实例
	srv, err := service.NewService(
		"service-1",       // 服务ID
		"example-service", // 服务名称
		8080,              // HTTP端口
		50051,             // gRPC端口
		"localhost:8500",  // Consul地址
		func() {
			fmt.Println("服务已启动:")
			fmt.Println("- HTTP服务地址:http://localhost:8080")
			fmt.Println("- gRPC服务地址:localhost:50051")
		},
	)
	if err != nil {
		log.Fatalf("创建服务失败: %v", err)
	}

	// 设置信号处理
	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

	// 启动服务
	go func() {
		if err := srv.Start(ctx); err != nil {
			log.Printf("服务运行错误: %v", err)
			cancel()
		}
	}()

	// 等待信号
	<-sigCh
	cancel()
	log.Println("正在关闭服务...")
}

service.go

bash 复制代码
package service

import (
	"consul/pkg/registry"
	"consul/proto"
	"context"
	"fmt"
	"log"
	"net"
	"net/http"
	"sync"

	"github.com/gin-gonic/gin"
	"google.golang.org/grpc"
)

type Service struct {
	ID        string
	Name      string
	HTTPAddr  string
	HTTPPort  int
	GRPCAddr  string
	GRPCPort  int
	Registry  *registry.ServiceRegistry
	httpSrv   *http.Server
	grpcSrv   *grpc.Server
	wg        sync.WaitGroup
	onStarted func()
}

func NewService(id, name string, httpPort, grpcPort int, consulAddr string, onStarted func()) (*Service, error) {
	reg, err := registry.NewServiceRegistry(consulAddr)
	if err != nil {
		return nil, err
	}

	return &Service{
		ID:        id,
		Name:      name,
		HTTPAddr:  "localhost",
		HTTPPort:  httpPort,
		GRPCAddr:  "localhost",
		GRPCPort:  grpcPort,
		Registry:  reg,
		onStarted: onStarted,
	}, nil
}

func (s *Service) Start(ctx context.Context) error {
	// 创建一个等待组来等待服务启动
	var startWg sync.WaitGroup
	startWg.Add(2) // 等待HTTP和gRPC服务

	// 启动HTTP服务
	s.wg.Add(1)
	go func() {
		defer s.wg.Done()
		s.startHTTP(&startWg)
	}()

	// 启动gRPC服务
	s.wg.Add(1)
	go func() {
		defer s.wg.Done()
		s.startGRPC(&startWg)
	}()

	// 注册服务到Consul
	err := s.Registry.RegisterService(
		s.ID,
		s.Name,
		s.HTTPAddr,
		s.HTTPPort,
		s.GRPCPort,
		[]string{"http", "grpc"},
	)
	if err != nil {
		return err
	}

	// 等待服务启动完成
	startWg.Wait()
	if s.onStarted != nil {
		s.onStarted()
	}

	// 等待上下文取消
	<-ctx.Done()
	return s.Stop()
}

func (s *Service) Stop() error {
	// 从Consul注销服务
	if err := s.Registry.DeregisterService(s.ID); err != nil {
		log.Printf("注销服务失败: %v", err)
	}

	// 停止HTTP服务
	if s.httpSrv != nil {
		if err := s.httpSrv.Shutdown(context.Background()); err != nil {
			log.Printf("停止HTTP服务失败: %v", err)
		}
	}

	// 停止gRPC服务
	if s.grpcSrv != nil {
		s.grpcSrv.GracefulStop()
	}

	// 等待所有服务停止
	s.wg.Wait()
	return nil
}

func (s *Service) startHTTP(wg *sync.WaitGroup) {
	router := gin.Default()

	// 健康检查端点
	router.GET("/health", func(c *gin.Context) {
		c.JSON(200, gin.H{"status": "ok"})
	})

	// 示例API端点
	router.GET("/api/echo", func(c *gin.Context) {
		msg := c.Query("message")
		c.JSON(200, gin.H{
			"message":   msg,
			"server_id": s.ID,
		})
	})

	// Greeter服务HTTP端点
	router.GET("/helloworld/:name", func(c *gin.Context) {
		name := c.Param("name")
		reply, err := (&GreeterImpl{ID: s.ID}).SayHello(c, &proto.HelloRequest{Name: name})
		if err != nil {
			c.JSON(500, gin.H{"error": err.Error()})
			return
		}
		c.JSON(200, reply)
	})
	s.httpSrv = &http.Server{
		Addr:    fmt.Sprintf("%s:%d", s.HTTPAddr, s.HTTPPort),
		Handler: router,
	}

	// 创建监听器
	lis, err := net.Listen("tcp", s.httpSrv.Addr)
	if err != nil {
		log.Printf("HTTP服务监听失败: %v", err)
		return
	}

	// 通知服务已启动
	wg.Done()

	if err := s.httpSrv.Serve(lis); err != http.ErrServerClosed {
		log.Printf("HTTP服务错误: %v", err)
	}
}

func (s *Service) startGRPC(wg *sync.WaitGroup) {
	lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.GRPCAddr, s.GRPCPort))
	if err != nil {
		log.Printf("gRPC服务监听失败: %v", err)
		return
	}

	s.grpcSrv = grpc.NewServer()
	// 注册gRPC服务实现
	proto.RegisterExampleServiceServer(s.grpcSrv, &grpcImpl{ID: s.ID})
	// 注册Greeter服务
	proto.RegisterGreeterServer(s.grpcSrv, &GreeterImpl{ID: s.ID})

	// 通知服务已启动
	wg.Done()

	if err := s.grpcSrv.Serve(lis); err != nil {
		log.Printf("gRPC服务错误: %v", err)
	}
}

以及注册consul用到的consul.go

bash 复制代码
package registry

import (
	"fmt"
	"log"

	"github.com/hashicorp/consul/api"
)

type ServiceRegistry struct {
	client *api.Client
}

// NewServiceRegistry 创建一个新的服务注册器
func NewServiceRegistry(consulAddr string) (*ServiceRegistry, error) {
	config := api.DefaultConfig()
	config.Address = consulAddr

	client, err := api.NewClient(config)
	if err != nil {
		return nil, fmt.Errorf("创建Consul客户端失败: %v", err)
	}

	return &ServiceRegistry{client: client}, nil
}

// RegisterService 注册服务到Consul
func (r *ServiceRegistry) RegisterService(serviceID, serviceName string, address string, httpPort int, grpcPort int, tags []string) error {
	// 在Meta中添加gRPC端口信息
	meta := map[string]string{
		"grpc_port": fmt.Sprintf("%d", grpcPort),
	}

	reg := &api.AgentServiceRegistration{
		ID:      serviceID,
		Name:    serviceName,
		Address: address,
		Port:    httpPort,
		Tags:    tags,
		Meta:    meta,
		Check: &api.AgentServiceCheck{
			HTTP:     fmt.Sprintf("http://%s:%d/health", address, httpPort),
			Interval: "10s",
			Timeout:  "5s",
		},
	}

	err := r.client.Agent().ServiceRegister(reg)
	if err != nil {
		return fmt.Errorf("注册服务失败: %v", err)
	}

	log.Printf("服务 %s 已成功注册到Consul", serviceID)
	return nil
}

// DeregisterService 从Consul注销服务
func (r *ServiceRegistry) DeregisterService(serviceID string) error {
	err := r.client.Agent().ServiceDeregister(serviceID)
	if err != nil {
		return fmt.Errorf("注销服务失败: %v", err)
	}

	log.Printf("服务 %s 已从Consul注销", serviceID)
	return nil
}

启动成功,分别监听了 http 8080和grpc 50051端口

然后consul里面也能够看到

现在在另外一个目录下新建一个项目,用来连接consul然后进行grpc请求。

新建consul_client目录,然后在此目录下新建proto目录。

将之前的proto文件放到proto目录下。

然后执行

bash 复制代码
protoc --go_out=. --go-grpc_out=. --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative ./proto/example.proto

然后新建main.go

bash 复制代码
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	pb "consul_client/proto"

	consulapi "github.com/hashicorp/consul/api"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func main() {
	// 创建 Consul 客户端配置
	config := consulapi.DefaultConfig()
	config.Address = "http://localhost:8500"

	// 创建 Consul 客户端
	client, err := consulapi.NewClient(config)
	if err != nil {
		log.Fatal("创建 Consul 客户端失败:", err)
	}

	// 查询服务
	services, _, err := client.Health().Service("example-service", "", true, nil)
	if err != nil {
		log.Fatal("获取服务失败:", err)
	}

	if len(services) == 0 {
		log.Fatal("没有找到可用的服务实例")
	}

	// 获取服务实例信息
	service := services[0].Service

	// 打印服务元数据
	fmt.Println("服务元数据:")
	for key, value := range service.Meta {
		fmt.Printf("  %s: %s\n", key, value)
	}

	// 尝试从元数据中获取 gRPC 端口
	grpcPort := service.Port
	if port, ok := service.Meta["grpc_port"]; ok {
		fmt.Sscanf(port, "%d", &grpcPort)
	} else {
		log.Fatal("无法获取 gRPC 端口")
	}

	serviceAddr := fmt.Sprintf("%s:%d", service.Address, grpcPort)
	fmt.Printf("找到 gRPC 服务地址: %s\n", serviceAddr)

	// 创建 gRPC 连接
	conn, err := grpc.NewClient(serviceAddr,
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		// 使用 WithTimeout 的替代方案
		grpc.WithConnectParams(grpc.ConnectParams{
			MinConnectTimeout: 5 * time.Second,
		}),
	)
	if err != nil {
		log.Fatal("无法连接到 gRPC 服务:", err)
	}
	defer conn.Close()

	// 创建 gRPC 客户端
	grpcClient := pb.NewExampleServiceClient(conn)

	// 设置上下文超时
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	// 发送 Echo 请求
	response, err := grpcClient.Echo(ctx, &pb.EchoRequest{
		Message: "Hello from gRPC client!",
	})
	if err != nil {
		log.Fatal("调用 Echo 服务失败:", err)
	}

	fmt.Printf("服务响应: Message=%s, ServerID=%s\n", response.Message, response.ServerId)
}

运行一下,可以看到已经通过consul找到了对应的地址和端口,并且按proto的格发送了请求并得到了回复。

相关推荐
tadus_zeng7 小时前
Windows C++ 排查死锁
c++·windows
EverestVIP7 小时前
VS中动态库(外部库)导出与使用
开发语言·c++·windows
lmryBC499 小时前
golang接口-interface
java·前端·golang
浮尘笔记10 小时前
go-zero使用elasticsearch踩坑记:时间存储和展示问题
大数据·elasticsearch·golang·go
抛物线.11 小时前
inhibitor_tool
windows
宋冠巡11 小时前
Windows安装Docker(Docker Desktop)
windows·docker·容器
冷琅辞12 小时前
Go语言的嵌入式网络
开发语言·后端·golang
淬渊阁13 小时前
windows技术基础知识
windows
徐小黑ACG16 小时前
GO语言 使用protobuf
开发语言·后端·golang·protobuf
niandb19 小时前
The Rust Programming Language 学习 (九)
windows·rust