当前位置: 首页 > news >正文

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

写在前面

最近稍微重构了之前写的 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.UserRequestPanicIfUserError(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.UserRequestif 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分支来进行感受这一次的改变。

相关文章:

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

写在前面 最近稍微重构了之前写的 grpc-todolist 模块 项目地址:https://github.com/CocaineCong/grpc-todoList 1. 项目结构改变 与之前的目录有很大的区别 1.1 grpc_todolist 项目总体 1.1.1 改变前 grpc-todolist/ ├── api-gatway // 网关模块 ├── ta…...

单板硬件设计:存储器SD卡( NAND FLASH)

在单板设计中,无论是涉及到一个简易的CPU、MCU小系统或者是复杂的单板设计,都离不开存储器设计: 1、存储器介绍 存储器的分类大致可以划分如下: ROM和RAM指的都是半导体存储器,ROM在系统停止供电的时候仍然可以保持数…...

C++实现日期类Date(超详细)

个人主页:平行线也会相交💪 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【C之路】💌 本专栏旨在记录C的学习路线,望对大家有所帮助🙇‍ 希望我们一起努力、成长&…...

实验室检验系统源码,集检验业务、质量控制、报告、统计分析、两癌等模块于一体

云 LIS 系统针对区域化 LIS 而设计,依托底层云架构,将传统的 LIS 功能模块进行“云化”。 该系统是集检验业务、科室管理、质量控制、报告、统计分析、两癌等模块于一体的数据检验信息平台。通过计算机联网,实现各类仪器数据结果的实时自动接…...

学习RHCSA的day.03

目录 2.6 Linux系统的目录结构 2.7 目录操作命令 2.8 文件操作命令 2.6 Linux系统的目录结构 1、Linux目录结构的特点 分区加载于目录结构: 使用树形目录结构来组织和管理文件。整个系统只有一个位于根分区的一个根目录(树根)、一棵树。…...

电子邮件协议(SMTP,MIME,POP3,IMAP)

SMTP 关键词: 电子邮件协议:SMTP简单邮件传输协议,负责将邮件上传到服务器,采用TCP的25端口,C/S工作。仅传送ASCII码文本 详细介绍: SMTP是一种提供可靠且有效的电子邮件传输的协议。SMTP是建立在FTP文件传输服务上…...

Golang笔记:使用embed包将静态资源嵌入到程序中

文章目录 目的使用演示//go:embed 指令在WebServer中应用总结 目的 Golang编译程序默认是静态编译,会把相关的库都打包到一起,这在分发部署使用时非常方便。不过如果项目中用到的外部的静态资源文件,通常就需要将这些资源和程序一起拷贝分发…...

ImportError: cannot import name ‘OldCsv‘ from ‘pyflink.table.descriptors‘

我最近开始使用flink用于数据处理。 当我尝试执行table api 用于计数时 我不能导入OldCsv and FileSystem from pyflink.table.descriptors. I have also downloaded apache-flink using: pip install apache-flink [rootmaster flink]# pip3 list | grep flink apache-fli…...

YouCompleteMe(YCM)安装

vim在各个linux版本中是个比较好编辑器,反正nano我是用不惯。但这个ycm的安装也是不断的在变,现在的安装比之前要简单的多,基本个几命令就搞定了,而且 也不用关心系统里有没有vim,ycm已经可以自动安装。具体安装步骤如下&#xff…...

day33_css

今日内容 零、 复习昨日 一、CSS 零、 复习昨日 见代码 一 、引言 1.1CSS概念 ​ 层叠样式表(英文全称:Cascading Style Sheets)是一种用来表现HTML(标准通用标记语言的一个应用)或XML(标准通用标记语言的一个子集)等文…...

10个最流行的向量数据库【AI】

矢量数据库是一种将数据存储为高维向量的数据库,高维向量是特征或属性的数学表示。 每个向量都有一定数量的维度,范围从几十到几千不等,具体取决于数据的复杂性和粒度。 推荐:用 NSDT场景设计器 快速搭建3D场景。 矢量数据库&…...

vite3+vue3 项目打包优化二 —— 依赖分包策略

在没有配置构建工具的分包功能时,构建出来的文件将无比巨大且是独立的一个 js 和 css 文件(如下图),这样本地加载文件时会存在巨大的压力。 默认情况下,浏览器重复请求相同名称的静态资源时,会直接使用缓存…...

中国社科院与美国杜兰大学金融管理硕士——与时间赛跑,充分利用每一分钟

不管你愿不愿意,时间总是在不经意间流去。林清玄在《和时间赛跑》中写道:“虽然我知道人永远跑不过时间,但是可以比原来快跑几步。那几步虽然很小很小,但作用却很大很大”。是的,我们需要与时间赛跑,充分利…...

什么是Dirichlet分布?

Dirichlet分布是一种概率分布,用于描述多维随机变量的概率分布。它是一个连续分布,通常用于处理具有多种可能取值的离散型随机变量。在LDA模型中,Dirichlet分布通常被用作先验分布,用来表示主题的概率分布和单词的概率分布。 Dir…...

web前端开发需要哪些技术?学前端顺序千万千万不要搞错啦!

宝子们,下午好,之前给大家分享了前端岗位的前景规划,小源看的出来,还是有不少宝子想入行前端的! 那除了会面试,还要有充足丰富的知识储备,需要什么技术,怎么样做才能找到高薪工作呢&…...

【AFNetWorking源码(二)AFURLSessionManger和AFHTTPSessionManager】

前言 学习了Mananger的初始化和以GET请求为例的过程,发现整个过程离不开AFHTTPSessionManager和AFURLSessionManger的某些方法。这两个是AFN的重要的网络通信模块内容,对它们作揖详细的学习。 AFURLSessionManager和AFHTTPSessionManager都是AFNetwork…...

编程不头秃,Google「AI程序员」来了,聊天就能敲代码

上周 Google 在 I/O 大会宣布了一个能够辅助编程的聊天机器人 Codey,现在它终于上线 Google Colab 啦! 🌟 Codey 是基于 Google 目前最新的大语言模型 PaLM 2 运行,有着强大的语言理解和编程能力。 Codey 有这些功能&#xff1…...

【数据结构与算法】基础数据结构

文章目录 数组概述动态数组二维数组局部性原理越界检查 链表概述单向链表单向链表(带哨兵)双向链表(带哨兵)环形链表(带哨兵) 队列概述链表实现环形数组实现 栈概述链表实现数组实现应用 双端队列概述链表实…...

k8s系列(四)——资源对象

k8s系列四——资源对象 pod概念 思考:为什么k8s会引出pod这个概念,容器不能解决么? 我的理解:一组密切相关的服务使用容器的话,如果他们的镜像不在一个容器里的话,那么就需要配置反向代理进行通信&#xf…...

JavaScript如何使用for循环

JavaScript 是一门非常有趣的编程语言,它可以让我们在浏览器中创建交互式的 Web 应用程序。在 JavaScript 中,我们可以使用 for 循环来迭代一个数组或对象,从而执行一系列的操作。下面是一些关于 for 循环的有趣的用法和例子。 为什么要使用…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...

SciencePlots——绘制论文中的图片

文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...

从零实现富文本编辑器#5-编辑器选区模型的状态结构表达

先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

页面渲染流程与性能优化

页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

Typeerror: cannot read properties of undefined (reading ‘XXX‘)

最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...

如何在网页里填写 PDF 表格?

有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据&#xff…...

10-Oracle 23 ai Vector Search 概述和参数

一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...

基于Java+MySQL实现(GUI)客户管理系统

客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...