【Golang/gRPC/Nacos】在golang中将gRPC和Nacos结合使用

Nacos与gRPC

前言

关于这部分,前段时间我在看文档以及视频教程的时候,怎么都想不明白,到底为什么要用gRPC是什么,他在项目中应该充当什么样的角色?Nacos又是如何和他结合的?

于是我就决定去看看一些小项目是如何实现的这个功能,现在将我最近学到的分享给大家。

正文

在正文开始之前,我们要先知道Nacos和gRPC在本篇内容中,会涉及到的作用:

gRPC
  • gRPC 允许服务之间无缝通信,像调用本地函数一样调用远程服务的功能。
Nacos
  • 服务在启动时将自身注册到 Nacos,包含 IP、端口和服务名称,其他服务可以通过服务名称查询可用实例。
正片

在这里,我们以一个实现了用户注册/登陆的接口为例子,进行讲解。

micro # 项目根目录
└── app # 应用逻辑目录
├── api # 定义API接口
│ └── user.go # 用户相关的API实现
├── client # 客户端功能实现
│ ├── nacos # Nacos配置相关
│ │ └── nacos.go # Nacos客户端实现
│ └── user # 用户客户端功能
│ └── proto # 用户相关的proto定义
│ └── userclient.go # 用户客户端的实现
├── cmd # 应用入口
│ └── main.go # 启动程序的主文件
├── Middleware # 中间件处理
│ └── Token.go # 处理Token的中间件
├── model # 数据模型定义
│ └── model.go # 数据结构和模型实现
├── router # 路由功能实现
│ └── router.go # 路由定义和管理
└── utils # 工具函数
└── jwt.go # JWT处理相关函数
└── response # 响应相关定义
├── Errors.go # 错误处理
├── Rsp.go # 通用响应结构
└── rspModel.go # 响应模型定义
└── server # 服务器端逻辑
└── user # 用户功能实现
├── conf # 配置文件
│ ├── conf.go # 配置读取逻辑
│ └── db.yaml # 数据库配置文件
├── dao # 数据访问对象
│ ├── init.go # 初始化数据库
│ └── user.go # 用户相关的数据库操作
└── proto # proto文件定义
├── user.pb.go # 编译后的用户proto文件
├── user.proto # 用户定义的proto文件
└── user_grpc.pb.go # gRPC编译后的用户proto文件
└── main.go # 服务器的主入口/同时实现了相应的业务逻辑函数

结构如上,首先我们需要知道,在app层,我们实现了基本的路由逻辑,随后client相当于我们的客户端,我们通过client的grpc客户端和server进行通信,并且拿到server上面实现的业务逻辑函数,从而调用相关的函数,从而实现完整用户的注册/登陆功能。

了解完这个demo的基本结构以后,下面我来细说。

proto部分
syntax = "proto3";package user;option go_package = "2025/01January/20250121/micro/proto;user";message RegisterRequest {string username = 1;string password = 2;
}message LoginRequest {string username = 1;string password = 2;
}message RegisterResponse {bool success = 1;string message = 2;
}message LoginResponse {bool success = 1;string message = 2;string token = 3;
}service User {rpc Login(LoginRequest) returns(LoginResponse);rpc Register(RegisterRequest) returns(RegisterResponse);
}

首先我来解释解释这上面都是什么意思,LoginRequest 以及RegisterRequest都相当于一个请求的结构体,上面带有请求的信息,我们可以看到信息就是用户名和密码,随后就是我们的回复信息Response,上面带有请求的结果以及信息,接下来只需要在控制台输入相应的指令就好了

protoc --go_out=. --go-grpc_out=. user.proto

随后就会产生相应的go文件,这里产生的文件我们并不需要理会它,这些文件实现了通信的内部逻辑,而且我们由于我们只需要按照规则简单的定义.proto文件就可以了,所以遵循这个规则的我们就可以轻易的实现通信,并且不需要关心内部的通信逻辑。

gRPC部分

随后我们需要做的是什么呢?在接下来我们需要将这个文件复制到server端一份,以保证两端可以相互通信,然后我们需要将我们定义在.proto文件中的函数方法进行重写,比如:

type UserServer struct {pb.UnimplementedUserServer
}func (U *UserServer) Register(_ context.Context, req *pb.RegisterRequest) (*pb.RegisterResponse, error) {user := model.User{Name:     req.Username,Password: req.Password,}if len(user.Password) < 5 || len(user.Password) > 20 {return &pb.RegisterResponse{Success: false,Message: "PasswordLength",}, response.ErrPasswordLength}if len(user.Name) < 5 || len(user.Name) > 20 {return &pb.RegisterResponse{Success: false,Message: "NameLength",}, response.ErrNameLength}if err := dao.Register(user); err != nil {return &pb.RegisterResponse{Success: false,Message: err.Error(),}, err}return &pb.RegisterResponse{Success: true,Message: "ok",}, nil
}

这部分实现了相应的服务端的逻辑,就相当于我们在单层架构中的service层的作用,在该方法中,我们调用了dao层的函数,以便于实现完整的注册逻辑。

既然重写了相应的方法函数,这并不是完事大吉了,我们还需要在真正运行代码的时候让两端进行通信,此时就需要我们创建相应的grpc客户端和服务端。

	// 设置监听地址和端口listener, err := net.Listen("tcp", ":10001")if err != nil {log.Fatalf("Failed to listen: %v", err)}grpcServer := grpc.NewServer(grpc.Creds(insecure.NewCredentials()))// 注册服务pb.RegisterUserServer(grpcServer, &UserServer{})

考虑到此处我们的重点是gRPC通信和nacos服务注册,我们就采取了不安全的通信方式。

此处我们设置了监听的端口,UserServer是实现了相对应的接口的结构体,使用 pb.RegisterUserServer 函数将 UserServer 实例注册到 gRPC 服务器。这使得通过 gRPC 客户端调用该服务时,可以调用您在 UserServer 中实现的方法。

此处还不着急使用nacos注册服务,我们先来看看客户端如何通信。

	// 连接到 gRPC 服务UserConn, err = grpc.Dial(addr, grpc.WithInsecure())if err != nil {log.Fatalf("failed to connect to gRPC service: %v", err)return}UserClient = pb.NewUserClient(UserConn)

此处的addr是我们对应的server的地址+端口号,也是采取了不安全的通信,然后这个UserClient可以这样理解,他作为一个对象,在通信之后,能够作为UserServer对象来调用服务端实现的方法,就是如此,所以我们要在app层创建一个UserClient实例来完成我们的调用。

nacos部分

nacos是什么?他是一个服务注册中心,服务在启动时将自身注册到 Nacos,包含 IP、端口和服务名称,其他服务可以通过服务名称查询可用实例,于是我们在服务端将当前启动服务的ip端口以及服务名称等注册到了nacos,在客户端,我们就可以通过服务的名称等信息查找相应服务对应的信息,这样我们就不需要以硬编码的形式将服务对应的IP端口写在客户端,而且当我们在服务端改变IP和端口时,也是将最新的IP端口注册到nacos中,这样就不需要担心修改客户端的问题了,因为客户端的IP和端口是以变量的形式保存的,保证了灵活性,而当有多个相同服务注册时,客户端可以从多个可用实例中选择,提高系统的整体性能与可靠性。

下面让我们来看看是如何实现的。

首先是在服务端:

	nacos.RegisterServiceInstance(nacos.Client, vo.RegisterInstanceParam{Ip:          "127.0.0.1",                          // 根据实际情况填写Port:        10001,                                // gRPC服务的端口ServiceName: "UserTest",                           // 服务名称GroupName:   "GroupTest",                          // 分组名称ClusterName: "cluster-a",                          // 集群名称Weight:      10,                                   // 权重Enable:      true,                                 // 是否启用Healthy:     true,                                 // 是否健康Ephemeral:   true,                                 // 是否为临时实例Metadata:    map[string]string{"idc": "shanghai"}, // 元数据信息})

在启动服务端时,我们通过函数调用,将服务的地址等信息注册到nacos的相应位置,关于nacos服务的增删查改可以看看我之前发的博客。

这样就算注册完成了,启动完服务端,我们返回来看客户端:

param := vo.GetServiceParam{ServiceName: "UserTest",            // 替换为你的服务名称GroupName:   "GroupTest",           // 根据需要设置Clusters:    []string{"cluster-a"}, // 集群名称}service, err := nacos.Client.GetService(param)if err != nil {log.Fatalf("can't discover the service: %v", err)return}// 获取第一个服务实例的地址if len(service.Hosts) == 0 {log.Fatal("no healthy instance found for service 'user'")return}addr := fmt.Sprintf("%s:%d", service.Hosts[0].Ip, service.Hosts[0].Port)

此处我们只需要将搜索信息放入param中,然后根据我们的信息查找这个服务是否注册到了nacos中,如果没注册,就说明服务端还没启动,就停止调用,如果发现了这个服务,我们便可以获取这个服务的地址,然后根据地址,连接gRPC服务,这样就算完成了!

最后,启动服务端,将服务注册进nacos,同时开启grpc服务端,监听端口,然后启动客户端,发送post命令,看到令人满意的结果,就算结束了。

如果想要详细的代码,可以看我的github-Some-WORKs仓库


结语

以上便是我关于nacos和gRPC如何结合起来实现服务注册调用的想法,希望对你会有帮助,如果有问题,欢迎提出来!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/5177.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RTMP|RTSP播放器只解码视频关键帧功能探讨

技术背景 我们在做RTMP|RTSP直播播放器的时候&#xff0c;遇到过这样的技术诉求&#xff0c;在一些特定的应用场景中&#xff0c;可能只需要关键帧的信息&#xff0c;例如视频内容分析系统&#xff0c;可能只对关键帧进行分析&#xff0c;以提取特征、检测对象或场景变化。鉴于…

2024年度总结-CSDN

2024年CSDN年度总结 Author&#xff1a;OnceDay Date&#xff1a;2025年1月21日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 文章目录 2024年CSDN年度总结1. 整体回顾2…

【Node.js]

一、概述 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境 &#xff0c;使用了一个事件驱动、非阻塞式I/O模型&#xff0c; 让JavaScript 运行在服务端的开发平台&#xff0c;它让JavaScript成为与PHP、Python、Perl、Ruby等服务端语言平起平坐的脚本语言。 官网地…

【基于无线电的数据通信链】Link 11 仿真测试

〇、废话 Link 11 仿真测试 涉及多个方面&#xff0c;包括信号仿真、协议模拟、数据链路层的仿真以及网络性能评估等。Link 11 是一种基于 HF&#xff08;高频&#xff09; 或 UHF&#xff08;超高频&#xff09; 波段的无线通信协议&#xff0c;主要用于军事通信系统中。为了…

iOS 网络请求: Alamofire 结合 ObjectMapper 实现自动解析

引言 在 iOS 开发中&#xff0c;网络请求是常见且致其重要的功能之一。从获取资料到上传数据&#xff0c;出色的网络请求框架能夠大大提升开发效率。 Alamofire 是一个极具人气的 Swift 网络请求框架&#xff0c;提供了便据的 API 以完成网络请求和响应处理。它支持多种请求类…

分布式多卡训练(DDP)踩坑

多卡训练最近在跑yolov10版本的RT-DETR&#xff0c;用来进行目标检测。 单卡训练语句&#xff08;正常运行&#xff09;&#xff1a; python main.py多卡训练语句&#xff1a; 需要通过torch.distributed.launch来启动&#xff0c;一般是单节点&#xff0c;其中CUDA_VISIBLE…

RV1126+FFMPEG推流项目(8)AENC音频编码模块

本节分享的是AENC音频编码模块&#xff0c;是负责在AI模块通道里面取出收集到的音频数据&#xff0c;进行编码。了解AENC模块之前&#xff0c;先来看一个数据结构“RV1126_AENC_CONFIG”&#xff0c;这个数据结构是自己封装的&#xff0c;里面有AENC通道号&#xff0c;和内部描…

智能新浪潮:亚马逊云科技发布Amazon Nova模型

在2024亚马逊云科技re:Invent全球大会上&#xff0c;亚马逊云科技宣布推出新一代基础模型Amazon Nova&#xff0c;其隶属于Amazon Bedrock&#xff0c;这些模型精准切入不同领域&#xff0c;解锁多元业务可能&#xff0c;为人工智能领域带来革新。 带你认识一起了解Amazon Nova…

【Prometheus】PromQL进阶用法

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

C++《AVL树》

在之前的学习当中我们已经了解了二叉搜索树&#xff0c;并且我们知道二叉搜索树的查找效率是无法满足我们的要求&#xff0c;当二叉树为左或者右斜树查找的效率就很低下了&#xff0c;那么这本篇当中我们就要来学习对二叉搜索树进行优化的二叉树——AVL树。在此会先来了解AVL树…

微信小程序:实现单选,多选,通过变量控制单选/多选

一、实现单选功能 微信小程序提供了 radio 组件来实现单选功能。radio 组件需要配合 radio-group 使用。 1. WXML 代码 <radio-group bindchange"onRadioChange"><label wx:for"{{items}}" wx:key"id"><radio value"{{it…

《Effective Java》学习笔记——第1部分 创建对象和销毁对象的最佳实践

文章目录 第1部分 创建和销毁对象一、前言二、创建和销毁对象最佳实践内容1. 优先使用工厂方法而非直接使用构造器2. 避免创建不必要的对象3. 避免使用过多的构造器4. 避免使用原始类型&#xff08;Raw Types&#xff09;5. 避免创建对象的过度依赖6. 清理资源和关闭对象&#…

解决conda create速度过慢的问题

问题 构建了docker容器 想在容器中创建conda环境&#xff0c;但是conda create的时候速度一直很慢 解决办法 宿主机安装的是anaconda 能正常conda create,容器里安装的是miniforge conda create的时候速度一直很慢&#xff0c;因为容器和宿主机共享网络了&#xff0c;宿主机…

【知识分享】PCIe5.0 TxRx 电气设计参数汇总

目录 0 引言 1 参考时钟--Refclk 2 发射端通道设计 3 发送均衡技术 4 接收端通道设计 5 接收均衡技术 6 结语 7 参考文献 8 扩展阅读 0 引言 PCI Express Base Specification 5.0的电气规范中&#xff0c;关键技术要点如下&#xff1a; 1. 支持2.5、5.0、8.0、16.0和3…

Java 的初认识(一)

好久不见兄弟们&#xff01;之前更新完 C 语言的内容之后呢&#xff0c;我是做了一个“ 短暂 ”的休息昂&#xff0c;当然我自己的学习是没有停歇的&#xff0c;只是在更新博客这上面休息了一下&#xff0c;主要还是想让自己先把这部分的知识掌握透彻了之后&#xff0c;再来为大…

2024年美赛C题评委文章及O奖论文解读 | AI工具如何影响数学建模?从评委和O奖论文出发-O奖论文做对了什么?

模型假设仅仅是简单陈述吗&#xff1f;允许AI的使用是否降低了比赛难度&#xff1f;还在依赖机器学习的模型吗&#xff1f;处理题目的方法有哪些&#xff1f;O奖论文的优点在哪里&#xff1f; 本文调研了当年赛题的评委文章和O奖论文&#xff0c;这些问题都会在文章中一一解答…

C语言练习(17)

两个乒乓球队进行比赛&#xff0c;各出3人。甲队为A、B、C 3人&#xff0c;乙队为X、Y、Z 3人&#xff0c;并抽签决定比赛名单。有人向队员打听比赛的名单&#xff0c;A说他不和X比&#xff0c;C说他不和X、Z比&#xff0c;请编程序找出3对选手的对阵名单。 #include <stdi…

【回忆迷宫——处理方法+DFS】

题目 代码 #include <bits/stdc.h> using namespace std; const int N 250; int g[N][N]; bool vis[N][N]; int dx[4] {0, 0, -1, 1}; int dy[4] {-1, 1, 0, 0}; int nx 999, ny 999, mx, my; int x 101, y 101; //0墙 (1空地 2远方) bool jud(int x, int y) {if…

Flowable 审核功能封装

文章目录 引言I 查询当前用户需要审核的数据列表整体逻辑根据组获取任务数据根据审核人获取任务数据II 进行审核整体逻辑III 审核历史查询IV 流程图查看流程进度思路根据任务 ID 获取任务进度流程图引言 流程引擎功能封装 : 审核列表数据查询进行审核的整体逻辑:获取任务 Id,…

Java-数据结构-二叉树习题(2)

第一题、平衡二叉树 ① 暴力求解法 &#x1f4da; 思路提示&#xff1a; 该题要求我们判断给定的二叉树是否为"平衡二叉树"。 平衡二叉树指&#xff1a;该树所有节点的左右子树的高度相差不超过 1。 也就是说需要我们会求二叉树的高&#xff0c;并且要对节点内所…