【Go微服务开发】gin+grpc+etcd 重构 grpc-todolist 项目

news2024/10/6 0:33:35

写在前面

最近稍微重构了之前写的 grpc-todolist 模块
项目地址:https://github.com/CocaineCong/grpc-todoList

1. 项目结构改变

与之前的目录有很大的区别

1.1 grpc_todolist 项目总体

1.1.1 改变前

grpc-todolist/
├── api-gatway  // 网关模块
├── task		// task模块
└── user		// user模块

v1版本的项目结构是分成了三个模块,网关,task,user模块。每个模块下各自读取配置文件,各自进行服务注册。

http 请求进入网关之后,网关开始转发并调用 rpc 请求,user、task模块接受到 rpc 调用之后,开始进行操作业务逻辑处理,结果返回给网关,网关再次对客户端进行 resp 响应。

基本思想没有问题,但是这种结构的问题是重复代码过多,很多代码是可以复用的,就没必要都写。

1.1.2 改变后

我们抽离出各个微服务模块下相同的内容,比如config,pkg,proto的内容,并且新建一个app文件夹,放置所有的gateway、task、user模块中所独有的,并且启动文件都放到各个模块下的cmd中。

grpc-todolist/
├── app                   // 各个微服务
│   ├── gateway           // 网关
│   ├── task              // 任务模块微服务
│   └── user              // 用户模块微服务
├── bin                   // 编译后的二进制文件模块
├── config                // 配置文件
├── consts                // 定义的常量
├── doc                   // 接口文档
├── idl                   // protoc文件
│   └── pb                // 放置生成的pb文件
├── logs                  // 放置打印日志模块
├── pkg                   // 各种包
│   ├── e                 // 统一错误状态码
│   ├── discovery         // etcd服务注册、keep-alive、获取服务信息等等
│   ├── res               // 统一response接口返回
│   └── util              // 各种工具、JWT、Logger等等..
└── types                 // 定义各种结构体

1.2 gateway网关模块

网关模块单单只是处理http请求,没有任何的业务逻辑,所以基本都是大量的中间件的使用。比如jwt鉴权,限流,etcd的服务发现等等…

gateway/
├── cmd                   // 启动入口
├── internal              // 业务逻辑(不对外暴露)
│   ├── handler           // 视图层
│   └── service           // 服务层
│       └── pb            // 放置生成的pb文件
├── logs                  // 放置打印日志模块
├── middleware            // 中间件
├── routes                // http 路由模块
└── rpc                   // rpc 调用

1.3. 各微服务模块

各个微服务的结构比较简单,因为各微服务模块都是处于一种被调用的状态,在该模块下聚焦好业务即可。

user/
├── cmd                    // 启动入口
└── internal               // 业务逻辑(不对外暴露)
    ├── service            // 业务服务
    └── repository         // 持久层
        └── db             // 视图层
          ├── dao          // 对数据库进行操作
          └── model        // 定义数据库的模型

1.4 项目的总结模块

  • 抽离出 proto 成 idl ,抽离pkg,config等公共模块。
  • 简化各个微服务模块的结构。

2. 代码层级的改变

2.1 RPC的调用方式

2.1.1 改变前

在v1版本中,我们是将我们的微服务的服务实例放到 gin.Key

func InitMiddleware(service []interface{}) gin.HandlerFunc {
	return func(context *gin.Context) {
		// 将实例存在gin.Keys中
		context.Keys = make(map[string]interface{})
		context.Keys["user"] = service[0]
		context.Keys["task"] = service[1]
		context.Next()
	}
}

通过断言的方式取出这个服务实例

func UserRegister(ginCtx *gin.Context) {
	var userReq service.UserRequest
	PanicIfUserError(ginCtx.Bind(&userReq))
	// 从gin.Key中取出服务实例
	userService := ginCtx.Keys["user"].(service.UserServiceClient)
	userResp, err := userService.UserRegister(context.Background(), &userReq)
	PanicIfUserError(err)
	r := res.Response{
		Data:   userResp,
		Status: uint(userResp.Code),
		Msg:    e.GetMsg(uint(userResp.Code)),
	}
	ginCtx.JSON(http.StatusOK, r)
}

这种结构是没有什么问题,但是就是缺少 rpc调度 那个味道。

2.1.2 改变后

我们可以新建一个rpc的文件来存储rpc相关,例如下面这个代码,是对下游的 UserRegister 进行调用。

func UserRegister(ctx context.Context, req *userPb.UserRequest) (resp *userPb.UserCommonResponse, err error) {
	r, err := UserClient.UserRegister(ctx, req)
	if err != nil {
		return
	}

	if r.Code != e.SUCCESS {
		err = errors.New(r.Msg)
		return
	}

	return
}

然后在 handler 这里调用 rpc 进行操作。而不需要把服务实例放到ctx中,去取服务实例来进行调用。

func UserRegister(ctx *gin.Context) {
	var userReq pb.UserRequest
	if err := ctx.Bind(&userReq); err != nil {
		ctx.JSON(http.StatusBadRequest, ctl.RespError(ctx, err, "绑定参数错误"))
		return
	}
	r, err := rpc.UserRegister(ctx, &userReq)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, ctl.RespError(ctx, err, "UserRegister RPC服务调用错误"))
		return
	}

	ctx.JSON(http.StatusOK, ctl.RespSuccess(ctx, r))
}

2.2 Makefile文件编写

makefile文件是起到项目的快速启动和关闭的作用

2.2.1 proto文件的快速生成

这里使用protocprotoc-go-inject-tag的命令来生成.pb.go文件。

.PHONY: proto
proto:
	@for file in $(IDL_PATH)/*.proto; do \
		protoc -I $(IDL_PATH) $$file --go-grpc_out=$(IDL_PATH)/pb --go_out=$(IDL_PATH)/pb; \
	done
	@for file in $(shell find $(IDL_PATH)/pb/* -type f); do \
		protoc-go-inject-tag -input=$$file; \
	done

protoc命令:用于生成pb.go,grpc.go文件。
protoc-go-inject-tag 命令:用于重写pb.go文件中的tag,使得能加入json:“xxx” form:"xxx"等tag来进行操作。

例如如下proto文件,如果没有 protoc-go-inject-tag 那么我们生成的 NickName 中就是大写,也就是接受参数是大写,但我们一般是使用小写来接受参数。

message UserRequest{
  // @inject_tag: json:"nick_name" form:"nick_name" uri:"nick_name"
  string NickName=1;
  // @inject_tag: json:"user_name" form:"user_name" uri:"user_name"
  string UserName=2;
  // @inject_tag: json:"password" form:"password" uri:"password"
  string Password=3;
  // @inject_tag: json:"password_confirm" form:"password_confirm" uri:"password_confirm"
  string PasswordConfirm=4;
}

当然,除了这个解决方法我们可以将NickName写成nickname小写,也是可以的,protoc会自动在代码层面,将nickname变成Nickname,代码层面还是可以调用的,并且接受参数还可以是小写。例如这样

message UserRequest{
  string nick_name=1;
  string user_name=2;
  string password=3;
  string password_confirm=4;
}

看个人喜好吧…

2.2.2 环境的快速启动

快速启动环境

.PHONY: env-up
env-up:
	docker-compose up -d

这里会根据compose文件,来进行环境的快速启动。

在这里插入图片描述
创建的时候,已经是创建了数据库了,所以我们只需要去到各个模块下的cmd文件夹下进行启动微服务即可,例如到 app/user/cmd,启动这个main.go就可以启动user模块了。

快速关闭环境

.PHONY: env-down
env-down:
	docker-compose down

在这里插入图片描述

以上就是这次grpc重构过程中的重要更新,大家可以把项目clone下来,自己跑跑,切换一下v1,v2分支来进行感受这一次的改变。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【小白版】最简单的 goland package 教程包括自定义包的使用

一、Hello World 最简单的教程,就需要从最简单的事情开始说起: mkdir myappcd myappgo mod init myapp // myapp是主项目名 这行命令将生成一个go.mod文件,这个文件会记录所有的包的依赖关系,一个空的go.mod只有项目名称和go版本…

智能指针详解

概念 在c中,动态内存的管理式通过一对运算符来完成的:new,在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化;delete,接受一个动态对象的指针,销毁该对象,并…

gitlab建立新分支提交,cherry-pick部分更新

gitlab介绍 GitLab是一个基于Git的在线代码托管和协作平台,提供源代码管理、单元测试、CI/CD构建、代码审查等功能。它是一个开放源代码的Git仓库管理系统,使用 Ruby on Rails 构建GitLab 不仅具有自己的 Git 仓库管理系统,还具有很多其他的…

AI 加持的代码编写实战:快速实现 Nginx 配置格式化工具

本篇文章聊聊如何使用 GPT 快速完成一个开源小项目,解决实际的问题,顺手点亮 GitHub 上 Nginx 开源社区的贡献者图标。 “Talk is Cheap,Show you the Code。” 写在前面 整理了一篇本该上个月就发出的内容。 前段时间,有个投…

浅谈JDK8的垃圾回收器

JDK1.8默认使用Parallel Scavenge作为年轻代的垃圾回收器,使用Parallel Old作为老年代的垃圾回收器,又称为PS MarkSweep。 Parallel Scavenge 收集器 Parallel Scavenge收集器又称为吞吐量优先收集器,和ParNew收集器类似,是一个新生代收集器。…

【OpenCV DNN】Flask 视频监控目标检测教程 01

欢迎关注『OpenCV DNN Youcans』系列,持续更新中 【OpenCV DNN】Flask 视频监控目标检测教程 01 【OpenCV DNN】Flask 视频监控目标检测教程 01 1. 面向Python程序的Web框架2. Flask 框架的安装与使用2.1 Flask 安装2.2 Flask 框架例程2.3 绑定IP和端口2.4 Flask路…

2023-5-20基于52单片机的智能家居系统(蓝牙)

资料已上传在微信公众号:风吹摇铃 奔赴星海 此系统可根据开发板原理图搭配外载模块实现功能,也可以根据原理图焊接或者PCB焊接。 注意:根据开发板搭载外部模块实现功能,需根据开发板原理图修改代码 0、整理及编写了19个常用的5…

NameServer路由注册与发现

NameServer在RocketMQ中主要承担的就是路由的管理、服务注册、以及服务的发现。在RocketMQ这承担着很重要的责任。 整体架构: 消息生产者在发送消息前需要考虑的问题就是,我需要发给谁?地址在哪儿?对于消费者也一样。那么NameSer…

软件工程 | 期末复习

一、软件与软件危机 1、软件发展经历三个阶段:程序设计、程序系统、软件工程 2、软件的概念:软件是计算机系统与硬件相互依存的另一部分,包括程序、数据以及相关文档的完整集合,软件程序数据文档 数据:使程序能够适…

测试人员转型是大势所趋:我的十年经验告诉我,你必须要行动起来了。

做测试十多年,有不少人问过我下面问题: 现在的手工测试真的不行了吗? 测试工程师,三年多快四年的经验,入门自动化测试需要多久? 自学自动化测试到底需要学哪些东西? 不得不说,随着行…

学习open62541 --- [76] 使用智能指针处理内存释放问题

在使用监测项时,一般都会加一个context,然后在回调函数里使用这个context,这就需要保证context的内存空间在执行回调函数时是有效的。往往有以下三种方法: 使用静态内存空间:使用static创建静态变量,然后把…

【Python 爬虫常见的报错及其解决方法】零基础也能轻松掌握的学习路线与参考资料

Python 爬虫被广泛应用于数据采集和分析。然而,爬虫在运行过程中常常会遇到各种问题和错误,降低了爬虫效率、准确性和可靠性。因此掌握爬虫常见报错及其解决方法是非常关键的。本文将介绍 Python 爬虫常见的报错及其解决方法,并提供参考资料和…

内存泄漏的原因,内存泄漏如何避免?内存泄漏如何定位?

1. 内存溢出 内存溢出 OOM (out of memory),是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个int,但给它存了long才能存下的数,那就是内存溢出。 2. 内存泄…

PyTorch LSTM和LSTMP的原理及其手写复现

PyTorch LSTM和LSTMP的原理及其手写复现 0、前言全部参数的细致介绍代码实现Reference 0、前言 关于LSTM的原理以及公式其实在这篇博客一步一步详解LSTM网络【从RNN到LSTM到GRU等,直至attention】讲的非常清晰明了了。 这里就是写出LSTM的pytorch的实现,…

【随笔记】全志 T507 PF4 引脚无法被正常设置为中断模式的问题分析

相关信息 硬件平台:全志T507 系统版本:Android 10 / Linux 4.9.170 问题描述:PF4 无法通过标准接口设置为中断模式,PF1、PF2、PF3、PF5 都可以。 分析过程 一开始以为是引脚被其它驱动占用引起,或者该引脚不具备中断…

高光谱成像技术在果蔬品质检测中的应用

在当前市场经济背景下,食品安全问题是消费者最为关心的问题之一,尤其是果蔬产品,农药残留问题和品质问题直接关系着消费者的权益和人身安全。针对传统化学检测的缺陷,本文结合高光谱成像技术,对其在果蔬品质与安全无损…

【C++】多态的概念/重写/虚表/抽象类

多态 多态的概念多态的定义和实现重写抽象类多态的原理虚表的构建原理虚函数的调用原理 多态的概念 多态就是多种形态,传递不同的对象,会调用不同的方法。 多态的定义和实现 那么在C语法中,多态是如何实现的呢? 我们首先要在继承…

vue学习 - 基础篇

初始工程结构 这里我们使用script标签从cdn获取vue.js, 而不是使用脚手架vue-cli, 因为cdn比较方便一点, 也不用配置node之类的比较麻烦 index.html <!DOCTYPE html> <html><head><title>VueJS Course</title><link rel"stylesheet"…

第三篇、基于Arduino uno,用oled0.96寸屏幕显示dht11温湿度传感器的温度和湿度信息——结果导向

0、结果 说明&#xff1a;先来看看拍摄的显示结果&#xff0c;如果是你想要的&#xff0c;可以接着往下看。 1、外观 说明&#xff1a;本次使用的oled是0.96寸的&#xff0c;别的规格的屏幕不一定适用本教程&#xff0c;一般而言有显示白色、蓝色和蓝黄一起显示的&#xff0…

RabbitMQ日常使用小结

一、使用场景 削峰、解耦、异步。 基于AMQP(高级消息队列协议)协议来统一数据交互,通过channel(网络信道)传递信息。erlang语言开发&#xff0c;并发量12000&#xff0c;支持持久化&#xff0c;稳定性好&#xff0c;集群不支持动态扩展。 RabbitMQ的基本概念 二、组成及工作流…