概述
- 本文使用最簡單和快速的方式基于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
- 1 ) jaeger 鏈路追蹤
- 源碼
- 服務端 cart 服務:https://gitee.com/go-micro-services/cart
- 客戶端 網關api 服務: https://gitee.com/go-micro-services/api