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 main

import (
	"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            = 8500
	address         = 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. 注册 handler
	if 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 main

import (
	"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 utils
    
    import (
    	"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 utils
    
    import (
    	"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 utils
    
    import (
    	"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. 配置链路追踪 jaeger
    	TracerAddr = 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 routers

import (
	"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 api

import (
	"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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

端口号查询进程PID

情况1:由于 idea 突然闪退,导致正在 debug 的 Java 进程没结束掉,端口还在占用,重新 debug 不了,所以需要到任务管理器把进程结束掉 但问题是如果当任务管理器进程同时有多个 Java 进程在运行(而且名字一样…

CSS表格特殊样式

列组样式 使用colgroup与col标签配合可以定义列祖样式&#xff1a;例 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><style>table,tr,th,td{border: 1px solid #000;}table{border-collapse: coll…

java导出excel动态加载多sheet多复杂表头

java导出excel动态加载多sheet多复杂表头 实体实现类sheet方法业务工具方法实现效果 实体 import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import lombok.ToString; import lombok.experimental.Accessors;import java.io.Serializable; import ja…

IPSSL证书:为特定IP地址通信数据保驾护航

IPSSL证书&#xff0c;顾名思义&#xff0c;是专为特定IP地址设计的SSL证书。它不仅继承了传统SSL证书验证网站身份、加密数据传输的基本功能&#xff0c;还特别针对通过固定IP地址进行通信的场景提供了强化的安全保障。在IP地址直接绑定SSL证书的模式下&#xff0c;它能够确保…

tomcat--目录结构和文件组成

目录结构 目录说明bin服务启动&#xff0c;停止等相关程序和文件conf配置文件lib库目录logs日志记录webapps应用程序&#xff0c;应用部署目录workjsp编译后的结果文件&#xff0c;建议提前预热访问 /usr/local/apache-tomcat-8.5.100/work/Catalina/localhost/ROOT/org/apac…

MyBatis-Plus核心功能详解:条件构造器、自定义SQL与Service接口

在Java的Web开发中&#xff0c;MyBatis-Plus作为MyBatis的增强工具&#xff0c;提供了许多实用的功能&#xff0c;极大地简化了数据库操作的开发过程。下面&#xff0c;我们将详细探讨MyBatis-Plus的三大核心功能&#xff1a;条件构造器、自定义SQL以及Service接口。 一、条件…

【Vue】更换vue-element-admin左侧 logo

准备&#xff1a;目标svg格式的 logo&#xff0c;并将目标logo命名为 vuejs-fill.svg替换路径&#xff1a;/icons 文件夹下&#xff0c;覆盖掉原本的 vuejs-fill.svg 原因&#xff1a;配置项的logo设置的是 vuejs-fill

摊还分析

一、摊还分析 概念&#xff1a;是求数据结构中一个操作序列执行所有操作的平均时间&#xff0c;与平均复杂度不同&#xff0c;它不涉及输入概率&#xff0c;能够保证在最坏情况下操作的平均性能。 适用场景&#xff1a;用含 n 个操作的序列&#xff08;o1&#xff0c;o2&#x…

【系统架构师】-选择题(十四)数据库基础

1、某企业开发信息管理系统平台进行 E-R 图设计&#xff0c;人力部门定义的是员工实体具有属性&#xff1a;员工号、姓名、性别、出生日期、联系方式和部门,培训部门定义的培训师实体具有属性:培训师号&#xff0c;姓名和职称&#xff0c;其中职称{初级培训师&#xff0c;中级培…

dnf手游攻略,新手入坑必备!

一、角色创建策略 在DNF手游中&#xff0c;角色创建是玩家初入游戏的首要步骤。为最大化游戏体验和收益&#xff0c;新手玩家通常建议创建三个角色&#xff1a;一个主账号和两个副账号。 主账号选择 主账号的选择应基于玩家个人的喜好和对职业的熟悉程度。无论选择哪个职业&a…

Ubuntu 安装chrome和chromedriver

1.安装包下载 百度网盘地址 2.更新软件包 sudo apt-get update 3.安装chrome sudo apt install ./google-chrome-stable_current_amd64.deb 4.检查是否安装成功 google-chrome --version 5.安装chrome-driver驱动&#xff0c;解压zip文件 unzip chromedriver_linux64.z…

哪款骨传导耳机最值得入手?精选5款顶尖配置的骨传导耳机,闭眼入也不踩雷!

作为一名有着多年工作经验的数码博主&#xff0c;我见证了无数因盲目追求新颖而引发的听力问题。在此&#xff0c;我必须郑重提醒大家&#xff0c;虽然市面上充斥着众多声称能提供卓越音质和佩戴舒适度的骨传导耳机品牌&#xff0c;但它们之间存在大量劣质产品&#xff0c;这类…

串联所有单词的子串 ---- 滑动窗口

题目链接 题目: 分析: 我们上次做的题目, 是找到所有字符的异位词, 和这道题有些类似, 使用记录有效字符的个数找到子字符, 此题无非是把字符变成了字符串题目回顾 有一下几方面不同, 我们以示例1为例: 1. 哈希表 上次我们使用的是哈希数组, 因为数组的下标可以是字符, 现…

生产透明化,交付无烦恼

生产进度总延误 质量把控总失守 计划赶不上变化 沟通不畅易误解 ...... 这些问题可能在一些工厂管理中几乎每天都在上演。 在如今快速变化的市场环境中&#xff0c;企业的生产效率和交付能力成为了衡量其竞争力的关键指标。而要实现高效、准确的生产和交付&#xff0c;透明化的…

JVM调优-调优原则和原理分析

1.写在前面 对于JVM调优这个话题&#xff0c;可能大部分程序员都听过这个名词。 但是绝大多数程序员&#xff0c;都没有真真实实去干过&#xff0c;都没有真实的实践过。也不懂得如何调优&#xff1f;不知道要调成怎么样&#xff1f; 那今天咋们就对这个话题来展开描述一下&…

“Linux”目录结构and配置网络

了解完命令格式和vi、vim编辑器后&#xff0c;我们来认识一下目录的结构&#xff1a; 一、目录 &#xff08;1&#xff09;目录的特点 windows特点&#xff1a; Windows中有C、D、E盘&#xff0c;每个都是一个根系统 Linux特点&#xff1a; linux中只有一个根&#xff08;单…

富在术数,不在劳身 财富的积累更多依赖于智慧和策略,而不是单纯的体力劳动 GPT-4o免费用

"富在术数&#xff0c;不在劳身"这句话的意思是财富的积累更多依赖于智慧和策略&#xff0c;而不是单纯的体力劳动。这句话强调了智慧和技巧在获取财富过程中的重要性&#xff0c;提示人们在追求财富时&#xff0c;应注重策略和方法的运用&#xff0c;而不仅仅依靠辛…

【正点原子Linux连载】第四十一章 Linux wifi驱动实验 摘自【正点原子】ATK-DLRK3568嵌入式Linux驱动开发指南

1&#xff09;实验平台&#xff1a;正点原子ATK-DLRK3568开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id731866264428 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/docs/boards/xiaoxitongban 第四十…

服务的war包已经丢在tomcat中但是还是没法访问,如何排查?

问题出现的现象是我已经将 XWiki 的 WAR 包放置在 Tomcat 的 webapps目录下但仍然无法访问&#xff0c;反思之后可以从下面以下几个方面来诊断和解决问题&#xff1a; 1. 确认 Tomcat 正在运行 首先&#xff0c;确保 Tomcat 服务正在正常运行。可以使用以下命令检查 Tomcat 的…