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的格发送了请求并得到了回复。

相关推荐
cpsvps_net4 小时前
美国服务器环境下Windows容器工作负载智能弹性伸缩
windows
甄超锋4 小时前
Java ArrayList的介绍及用法
java·windows·spring boot·python·spring·spring cloud·tomcat
apocelipes5 小时前
下划线字段在golang结构体中的应用
golang
cpsvps7 小时前
美国服务器环境下Windows容器工作负载基于指标的自动扩缩
windows
网硕互联的小客服10 小时前
Apache 如何支持SHTML(SSI)的配置方法
运维·服务器·网络·windows·php
etcix10 小时前
implement copy file content to clipboard on Windows
windows·stm32·单片机
许泽宇的技术分享10 小时前
Windows MCP.Net:基于.NET的Windows桌面自动化MCP服务器深度解析
windows·自动化·.net
非凡ghost11 小时前
AMS PhotoMaster:全方位提升你的照片编辑体验
windows·学习·信息可视化·软件需求
mortimer13 小时前
一次与“顽固”外部程序的艰难交锋:subprocess 调用exe踩坑实录
windows·python·ai编程
gameatp15 小时前
从 Windows 到 Linux 服务器的全自动部署教程(免密登录 + 压缩 + 上传 + 启动)
linux·服务器·windows