Go微服务: Gin框架搭建网关, 接入熔断器,链路追踪以及服务端接入限流和链路追踪

概述

  • 本文使用最简单和快速的方式基于Gin框架搭建一个微服务的网关调用微服务的场景
  • 网关作为客户端基于RPC调用某一服务端的服务并接入熔断和限流以及链路追踪
  • 具体场景:通过网关API查询购物车里的数据
  • 在最后,会贴上网关和购物车服务的代码仓库

服务端搭建


1 )目录结构

cart
├── domain
│      ├── model
│      │     └── cart.go
│      ├── repository
│      │     └── cart_repository.go
│      ├── service
│      │     └── cart_data_service.go
├── handler
│      └── cart.go
├── proto
│      ├── cart
│            ├── cart.proto
│            ├── cart.pb.go                  # 待生成
│            └── cart.pb.micro.go            # 待生成
├── go.mod
├── main.go
└── Makefile
  • 可以看到这是基于领域模型搭建的框架,默认是通过 go-micro生成的模板,之后进行修改的
  • proto 下的 pb.go 和 pb.micro.go 是通过 Makefile 中配置的 protoc 命令生成的
    • $ protoc --proto_path=. --micro_out=. --go_out=:. proto/cart/*.proto

2 ) 核心 main.go

package mainimport ("fmt""log""strconv""github.com/go-micro/plugins/v4/registry/consul"opentracingTool "github.com/go-micro/plugins/v4/wrapper/trace/opentracing""github.com/go-micro/plugins/v4/wrapper/ratelimiter/ratelimit"jujuratelimit "github.com/juju/ratelimit""github.com/opentracing/opentracing-go""go-micro.dev/v4""go-micro.dev/v4/registry""gitee.com/go-micro-services/cart/domain/repository""gitee.com/go-micro-services/cart/domain/service""gitee.com/go-micro-services/cart/handler"pbcart "gitee.com/go-micro-services/cart/proto/cart""gitee.com/go-micro-services/common""github.com/jinzhu/gorm"_ "github.com/jinzhu/gorm/dialects/mysql"
)var (serviceName     = "go.micro.service.cart"version         = "latest"host            = "127.0.0.1"port            = 8500address         = host + ":" + strconv.Itoa(port)mysqlConfigPath = "/micro/config/mysql"
)func main() {// 1. 指定注册中心consulReg := consul.NewRegistry(registry.Addrs(address),)// 2. 从配置中心获取mysql配置并创建处理mysqlConfig, consulConfigErr := common.GetConsulMysqlConfig(address, mysqlConfigPath)if consulConfigErr != nil {log.Fatal(consulConfigErr)}mysqlUrl := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local", mysqlConfig.User, mysqlConfig.Pwd, mysqlConfig.Host, mysqlConfig.Port, mysqlConfig.Database)db, dbErr := gorm.Open("mysql", mysqlUrl)if dbErr != nil {log.Fatal(dbErr)}defer db.Close()rp := repository.NewCartRepository(db)// 数据库表初始化,只执行一次, 如果本来就设计好了,则无需下面// rp.InitTable()// db.SingularTable(false) // true 则 表就是单数// 3. 链路追踪配置tracer, closer, tracerErr := common.NewTracer("go.micro.service.cart", "localhost:6831")if tracerErr != nil {log.Fatal(tracerErr)}defer closer.Close()opentracing.SetGlobalTracer(tracer)// 4. 创建服务实例cartDataService := service.NewCartDataService(rp)// 5. 创建服务和初始化srv := micro.NewService()srv.Init(micro.Name(serviceName),micro.Version(version),micro.Registry(consulReg),// micro.Address("0.0.0.0:8087"), // 暴露的服务地址 这里可用可不用指定micro.WrapHandler(opentracingTool.NewHandlerWrapper(opentracing.GlobalTracer())),       // 绑定链路追踪micro.WrapHandler(ratelimit.NewHandlerWrapper(jujuratelimit.NewBucket(50, 100), true)), // 添加限流)// 6. 注册 handlerif handlerErr := pbcart.RegisterCartHandler(srv.Server(), &handler.Cart{CartDataService: cartDataService}); handlerErr != nil {log.Fatal(handlerErr)}// 7. 运行服务if runErr := srv.Run(); runErr != nil {log.Fatal(runErr)}
}
  • 以下是一些注意事项
    • 服务初始化需要借助mysql,mysql的一些连接信息是从consul配置中心获取的
    • 在初始化的时候,需要先运行一次 rp.InitTable()db.SingularTable(false)
    • 上面 jujuratelimit.NewBucket(50, 100) 中的参数50, 100可以写入环境变量来获取,这个可以根据具体资源情况进行调节
  • 这里,购物车服务已经搭建完成,可以进行启动了
    • $ sudo go run main.go
  • 启动后,在consul中查看,发现已经注册成功了

网关搭建


1 )目录结构

api
├── conf
│      ├── app.ini                                 # 通用配置文件
│      ├── env.local.ini                           # 本地配置文件
│      ├── env.test.ini                            # 测试环境配置
│      ├── env.uat.ini                             # uat环境配置
│      ├── env.prod.ini                            # prod环境配置
├── controllers
│      ├── api
│           └── api.go
├── middlewares
│      └── tracer.go                               # 链路追踪中间件
├── routers
│      └── router.go                               # 路由配置
├── utils
│      ├── common.go                               # 工具包通用工具
│      ├── conf.go                                 # 读取配置工具
│      └── micro.go                                # 微服务工具
├── go.mod
├── main.go
└── Makefile

2 )通用配置信息

[app]
appName  = go.micro.api
appVersion = latest
appZeroHost = 0.0.0.0
appAddr  =  0.0.0.0:8080[consul]
address  = 127.0.0.1:8500[jaeger]
tracerAddr = 127.0.0.1:6831[hystrix]
hystrixPort = 9096

3 ) main.go 核心代码

package mainimport ("gitee.com/go-micro-services/api/middlewares""gitee.com/go-micro-services/api/routers""gitee.com/go-micro-services/api/utils""github.com/gin-gonic/gin""go-micro.dev/v4/web"
)func main() {// 1. 创建一个默认的路由引擎ginRouter := gin.Default()ginRouter.Use(middlewares.Trace()) // 加入 tracing 中间件routers.RoutersInit(ginRouter)// 2. web网关服务开启server := web.NewService(web.Name(utils.AppName),       // 服务名称web.Address(utils.AppAddr),    // 服务端口web.Handler(ginRouter),        // 服务路由web.Registry(utils.ConsulReg), // 注册中心)// 3. 启动server.Run()
}
  • 这里,web模块是go-micro中用于构建Web服务的部分
  • 它允许你使用标准的HTTP协议来暴露和调用微服务
  • web.NewService 函数的作用
    • 用于创建一个新的Web服务实例
    • 该函数接受一系列的配置参数
    • 用于定义服务的名称、地址、处理程序(即路由引擎)以及注册中心等信息
  • 下面是web.NewService函数中各个参数的意义
    • web.Name(utils.AppName): 设置服务的名称, 这通常用于服务发现和注册中心中的服务标识
    • web.Address(utils.AppAddr): 设置服务的监听地址和端口号, 这决定了服务应该在哪里监听传入的HTTP请求
    • web.Handler(ginRouter): 设置服务的路由处理程序, 这里传入的是基于gin框架的路由引擎实例,它定义了如何处理传入的HTTP请求
    • web.Registry(utils.ConsulReg): 设置服务的注册中心。这允许服务在启动时将自己注册到指定的注册中心(例如Consul),以便其他服务可以发现和调用它

4 )utils 包

  • utils.common.go

    package utilsimport ("fmt""os""gopkg.in/ini.v1"
    )// 读取通用配置
    func getConfig() *ini.File {appConfig, iniErr := ini.Load("conf/app.ini")if iniErr != nil {fmt.Printf("Fail to read file: %v", iniErr)os.Exit(1)}return appConfig
    }func init() {// 1. 读取配置文件appConfig := getConfig()// 2. 获取配置initConfig(appConfig)// 3. 初始化微服务initMicro(appConfig)
    }
    
  • utils/conf.go

    package utilsimport ("gopkg.in/ini.v1"
    )// 通用配置
    var AppName string
    var AppVersion string
    var AppAddr string
    var AppZeroHost string// 读取通用配置
    func initConfig(appConfig *ini.File) {AppName = appConfig.Section("app").Key("appName").String()         // 应用名称AppVersion = appConfig.Section("app").Key("appVersion").String()   // 应用版本AppAddr = appConfig.Section("app").Key("appAddr").String()         // 应用地址AppZeroHost = appConfig.Section("app").Key("appZeroHost").String() // 零地址
    }
    
  • utils/micro.go

    package utilsimport ("context""fmt""net""net/http""go-micro.dev/v4""go-micro.dev/v4/client""gopkg.in/ini.v1""gitee.com/go-micro-services/common""github.com/go-micro/plugins/v4/registry/consul"opentracingTool "github.com/go-micro/plugins/v4/wrapper/trace/opentracing""github.com/opentracing/opentracing-go"log "go-micro.dev/v4/logger""go-micro.dev/v4/registry""github.com/afex/hystrix-go/hystrix""github.com/go-micro/plugins/v4/wrapper/select/roundrobin"
    )// 微服务micro客户端
    var SrvClient client.Client
    var ConsulAddr string
    var ConsulReg registry.Registry
    var TracerAddr string
    var HystrixPort string// 读取通用配置
    func initMicro(appConfig *ini.File) {// 1. 配置注册中心ConsulAddr = appConfig.Section("consul").Key("address").String()ConsulReg := consul.NewRegistry(registry.Addrs(ConsulAddr),)// 2. 配置链路追踪 jaegerTracerAddr = appConfig.Section("jaeger").Key("tracerAddr").String()// 3. 配置熔断HystrixPort = appConfig.Section("hystrix").Key("hystrixPort").String()setHystrix()// 4. 创建服务srv := micro.NewService()srv.Init(micro.Name(AppName),micro.Version(AppVersion),micro.Registry(ConsulReg),// 绑定链路追踪micro.WrapHandler(opentracingTool.NewHandlerWrapper(opentracing.GlobalTracer())),// 添加熔断micro.WrapClient(NewClientHystrixWrapper()),// 添加负载均衡micro.WrapClient(roundrobin.NewClientWrapper()),)SrvClient = srv.Client() // 对外暴露
    }// 链路追踪配置
    func SetTracer() (opentracing.Tracer, error) {tracer, closer, err := common.NewTracer(AppName, TracerAddr)if err != nil {log.Fatal(err)}defer closer.Close()return tracer, err
    }// 熔断器 设置
    func setHystrix() {hystrixStreamHandler := hystrix.NewStreamHandler()hystrixStreamHandler.Start()go func() {// 启动端口err := http.ListenAndServe(net.JoinHostPort(AppZeroHost, HystrixPort), hystrixStreamHandler)if err != nil {log.Error(err)}}()
    }// 熔断器 type
    type clientWrapper struct {client.Client
    }// 熔断器 Call
    func (c *clientWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {return hystrix.Do(req.Service()+"."+req.Endpoint(), func() error {// run 正常执行fmt.Println(req.Service() + "." + req.Endpoint())return c.Client.Call(ctx, req, rsp, opts...)}, func(err error) error {fmt.Println(err)return err})
    }// 熔断器 Wrapper
    func NewClientHystrixWrapper() client.Wrapper {return func(i client.Client) client.Client {return &clientWrapper{i}}
    }
    

5 )路由

package routersimport ("gitee.com/go-micro-services/api/controllers/api""github.com/gin-gonic/gin"
)func RoutersInit(r *gin.Engine) {rr := r.Group("/api"){rr.GET("/findAll", api.ApiController{}.FindAll)}
}

6 ) 控制器

package apiimport ("context""fmt""strconv""gitee.com/go-micro-services/api/utils"cart "gitee.com/go-micro-services/cart/proto/cart""github.com/gin-gonic/gin""github.com/prometheus/common/log"
)type ApiController struct{}func (con ApiController) FindAll(c *gin.Context) {log.Info("接受到 /api/findAll 访问请求")// 1. 获取参数user_id_str := c.Query("user_id")userId, err := strconv.ParseInt(user_id_str, 10, 64)if err != nil {c.JSON(200, gin.H{"message": "参数异常","success": false,})return}fmt.Println(userId)// 2. rpc 远程调用:获取购物车所有商品cartClient := cart.NewCartService("go.micro.service.cart", utils.SrvClient)cartAll, err := cartClient.GetAll(context.TODO(), &cart.CartFindAll{UserId: userId})fmt.Println(cartAll)fmt.Println("-----")c.JSON(200, gin.H{"data":    cartAll,"success": true,})
}

7 ) 服务启动准备

  • 这里可以看到,基于gin框架来搭建了一个网关
  • 这里可以访问 /api/findAll?user_id=xxx 来调用购物车的微服务
  • 现在我们准备下数据,在数据库中初始化一条数据
  • 现在就可以通过:网关ip:8080/api/findAll?user_id=1 来调用了

8 )服务启动

  • $ sudo go run main.go
  • 可以看到,服务已经启动了

服务调用与测试


1 ) 调用

  • 访问网关ip:8080/api/findAll?user_id=1
  • 可以看到,调用成功

2 )查看链路追踪

  • 在 JAEGER UI 上, 比如本机访问:http://127.0.0.1:16686/search
  • 可以看到链路追踪上多了2个服务
  • 我们可以看下 api 的相关调用
  • 再看下 cart 的服务调用
  • 当然以上都是通常的调用,如果存在复杂的调用关系或出错信息,也可以从这里看出来

3 )测试限流

  • 在已搭建好的熔断面板上可以查看熔断功能:http://127.0.0.1:9002/hystrix
  • 因为熔断器是在客户端,也就是网关层接入的,所以,上面填入 网关ip:/hystrix.stream,比如上面的:192.168.1.7:9096/hystrix.stream
  • 好,现在我们频繁访问,刷新API,来测试下
  • 可见峰值不超过 60%,限流成功

总结

  • 从上面可见,我们基于gin框架搭建网关服务和微服务基本已经调通了
  • 相关原理和更多细节参考
    • 1 ) jaeger 链路追踪
      • https://blog.csdn.net/Tyro_java/article/details/137754812
    • 2 )hystrix 熔断
      • https://blog.csdn.net/Tyro_java/article/details/137777246
    • 3 ) ratelimit 限流
      • https://blog.csdn.net/Tyro_java/article/details/137780273
  • 源码
    • 服务端 cart 服务:https://gitee.com/go-micro-services/cart
    • 客户端 网关api 服务: https://gitee.com/go-micro-services/api

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

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

相关文章

HTML常用标签-布局相关标签

布局标签 div标签 俗称"块",主要用于划分页面结构,做页面布局 自己独占一行的元素&#xff0c;设置宽高生效 span标签 俗称"层",主要用于划分元素范围,配合CSS做页面元素样式的修饰 不会自己独占一行的元素&#xff0c;设置宽高不生效 代码 <div style&…

【HR】阿里三板斧--20240514

参考https://blog.csdn.net/haydenwang8287/article/details/113541512 头部三板斧 战略能不能落地、文化能不能得到传承、人才能不能得到保障。 头部三板斧适用的核心场景有三个&#xff1a;一是战略不靠谱&#xff1b;二是组织效率低、不聚心&#xff1b;三是人才跟不上。对…

二、服务器配置修改

二、服务器配置修改 1 防火墙相关配置 systemctl status firewalld systemctl enable firewalld systemctl start firewalld firewall-cmd --reload firewall-cmd --list-all# 开启端口 firewall-cmd --zonepublic --add-port6030-6060/tcp --permanent firewall-cmd --zonep…

如何将公众号添加到CSDN个人主页

1. 创作中心- 推广管理 输入个人公众号名字并开启微信公众号推广 2. 将公众号的二维码图片加入拓展信息 个人主页的左下角就能看到推广 如果希望能看到是二维码 操作如下&#xff1a; 写篇文章贴上二维码 然后点击鼠标右键获得此页面链接 &#xff0c;例如我的个人公众号 htt…

定时器的理论和使用

文章目录 一、定时器理论1.1定时器创建和使用 二、定时器实践2.1周期触发定时器2.2按键消抖 一、定时器理论 定时器是一种允许在特定时间间隔后或在将来的某个时间点调用回调函数的机制。对于需要周期性任务或延迟执行任务的嵌入式应用程序特别有用。 软件定时器&#xff1a; …

【C++语言】动态内存管理

文章目录 前言内存管理数据存储位置C语言动态内存管理方式C动态内存管理方式&#xff1a;new/deleteoperator new与operator delete函数new和delete的实现原理定位new表达式&#xff08;了解&#xff09;常见面试题 总结C语言系列学习目录 前言 本章要介绍的是动态内存管理&am…

ORACLE ODA一体机存储节点电源故障的分析处理

近期&#xff0c;某用户的ORACLE ODA一体机在例行机房巡检时出现亮黄灯告警&#xff1b;用户反馈次问题后我们立刻通过远程方式&#xff0c;登陆ODA的控制台进行查看&#xff1b; 对于ODA一体机&#xff08;2个计算节点1个存储节点&#xff09;&#xff0c;计算节点可以通过il…

nginx 发布静态资源

一. nginx 发布静态资源 在nginx中nginx.conf配置文件中添加内容如下&#xff1a; server {listen 90;server_name localhost;# 配置静态资源文件&#xff0c;就可以访问了location / {root /home/fooie-shop;index index.html;}# 配置音频和图片资源location /imoo…

深入了解 npm:Node.js 包管理工具详解

文章目录 一、npm 基本概念1.1 什么是 npm&#xff1f;1.2 package.json 文件 二、npm 常用命令2.1 初始化项目2.2 安装依赖2.2.1 安装单个包2.2.2 全局安装包2.2.3 安装开发依赖 2.3 移除依赖2.4 更新依赖2.5 查看已安装的包2.6 发布包 三、npm 高级用法3.1 使用 npm scripts3…

数据结构之二叉树详解[1]

在前面我们介绍了堆和二叉树的基本概念后&#xff0c;本篇文章将带领大家深入学习链式二叉树。 1.预备知识 2.二叉树结点的创建 3.二叉树的遍历 3.1前序遍历 3.2中序遍历 3.3 后序遍历 4.统计二叉树的结点个数 5.二叉树叶子结点的个数 6.二叉树第k层的结点个数 7.总结 …

589.N叉树的前序遍历

刷算法题&#xff1a; 第一遍&#xff1a;1.看5分钟&#xff0c;没思路看题解 2.通过题解改进自己的解法&#xff0c;并且要写每行的注释以及自己的思路。 3.思考自己做到了题解的哪一步&#xff0c;下次怎么才能做对(总结方法) 4.整理到自己的自媒体平台。 5.再刷重复的类…

高级个人主页

高级个人主页 效果图部分代码领取源码下期更新预报 效果图 部分代码 <!DOCTYPE html> <html lang"en"><head><meta charset"utf-8" name"viewport" content"widthdevice-width, initial-scale1, maximum-scale1, use…

数据驱动测试在接口测试和网站测试中的应用

什么是数据驱动测试 据驱动测试是一种测试方法&#xff0c;其中测试数据和测试逻辑是分开的&#xff0c;测试数据被存储在外部源中&#xff08;如Excel表格、JSON文件、数据库等&#xff09;&#xff0c;测试逻辑则独立于测试数据。在测试过程中&#xff0c;测试数据被读取并传…

代码随想录算法训练营第五十三天

今天同事说他要离职啦&#xff0c;还挣挺多的&#xff0c;我也慢慢努力吧&#xff01;&#xff01; 儿子似乎有点斜颈&#xff0c;还好不是很大的病&#xff0c;儿子也开始面对人生的苦难啦。都好好加油生活&#xff01; 1143.最长公共子序列 二维可以理解一点。 class Solut…

用面向对象的思想编写实时嵌入式C程序

实时嵌入式系统的软件一般由C语言编写&#xff0c;程序结构基本上都是这样的&#xff1a; // 主程序 int main(void) {init(); // 初始化while(1){tick(); // 业务逻辑}return 0; }// 计时器 static unsigned int g_timer_tick_cnt 0; // 时钟中断回调 void isr_time…

[c++]多态的分析

多态详细解读 多态的概念多态的构成条件 接口继承和实现继承: 多态的原理:动态绑定和静态绑定 多继承中的虚函数表 多态的概念 -通俗的来说&#xff1a;当不同的对象去完成某同一行为时&#xff0c;会产生不同的状态。 多态的构成条件 必须通过基类的指针或者引用调用虚函数1虚…

超级简单的地图操作工具开发可疑应急,地图画点,画线,画区域,获取地图经纬度等

使用echars的地图画点,画线,画区域,获取地图经纬度等 解压密码:10086007 地图也是用临时的bmap.js和china.js纯离线二选一 一共就这么多文件 画点,画线,画区域 点击地图获取经纬度-打印到控制台,这样就能渲染航迹,多变形,结合其他算法算圆等等操作 下载资源:https://download…

Databend 开源周报第 144 期

Databend 是一款现代云数仓。专为弹性和高效设计&#xff0c;为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务&#xff1a;https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 了解 Databend …

Redis-详解(基础)

文章目录 什么是Redis&#xff1f;用Redis的特点&#xff1f;用Redis可以实现哪些功能&#xff1f;Redis的常用数据类型有哪些?Redis的常用框架有哪些?本篇小结 更多相关内容可查看 什么是Redis&#xff1f; Redis&#xff08;Remote DictionaryServer&#xff09;是一个开源…

【AI学习】对指令微调(instruction tuning)的理解

前面对微调&#xff08;Fine-tuning&#xff09;的学习中&#xff0c;提到指令微调。当时&#xff0c;不清楚何为指令微调&#xff0c;也一直没来得及仔细学习。 什么是指令微调&#xff1f;LLM经过预训练后&#xff0c;通过指令微调提升模型的指令遵循能力。所谓指令&#xf…