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

相关推荐
小二·11 小时前
Go 语言系统编程与云原生开发实战(第9篇)安全加固实战:认证授权 × 数据加密 × 安全审计(生产级落地)
安全·云原生·golang
小高Baby@11 小时前
Go中常用字段说明
后端·golang·gin
Whoami!11 小时前
⓫⁄₁₀ ⟦ OSCP ⬖ 研记 ⟧ Windows权限提升 ➱ 动态链接库(dll)文件劫持(下)
windows·网络安全·信息安全·dll劫持
罗马尼亚硬拉11 小时前
tensile/reference/environment-variables
人工智能·windows·深度学习
小二·11 小时前
Go 语言系统编程与云原生开发实战(第8篇)消息队列实战:Kafka 事件驱动 × CQRS 架构 × 最终一致性(生产级落地)
云原生·golang·kafka
程序员徐师兄20 小时前
Windows JDK11 下载安装教程,适合新手
java·windows·jdk11 下载安装·jdk11 下载教程
编码者卢布1 天前
【App Service】Java应用上传文件功能部署在App Service Windows上报错 413 Payload Too Large
java·开发语言·windows
多来哈米1 天前
openclaw在Windows部署
windows·openclaw
视觉AI1 天前
【踩坑实录】Windows ICS 共享网络下,国产化盒子 SSH 连接异常的完整分析
网络·windows·ssh
NBhhbYyOljP1 天前
LabVIEW与西门子PLC S7200SMART 12001500 300 400
golang