go-micro微服務(wù)實(shí)踐(一)


目錄

一、go-micro框架

二、服務(wù)注冊(cè)發(fā)現(xiàn)(etcd)

三、服務(wù)網(wǎng)關(guān)

四、鏈路追蹤(jaeger)

五、protobuf協(xié)議

六、部署(docker-compose)

七、其他

八、錯(cuò)誤總結(jié)


github完整代碼示例:https://github.com/catwrench/go-micro
內(nèi)容比較多,多看代碼示例,有空再來(lái)拆分成多篇文章細(xì)說(shuō)


參考資料

  • go-micro中文文檔

安裝 |《Go Micro 中文文檔 2.x》| Go 技術(shù)論壇

  • micro組件

micro/micro

  • 官方示例

microhq/examples

  • go-plugins支持示例

microhq/go-plugins

  • go-micro實(shí)踐

Golang微服務(wù)開(kāi)發(fā)實(shí)踐

go-micro V2 從零開(kāi)始_hotcoffie的博客-CSDN博客


一、go-micro框架

micro 是一套微服務(wù)構(gòu)建工具庫(kù)。對(duì)于微服務(wù)架構(gòu)的應(yīng)用,micro 提供平臺(tái)層面、高度彈性的工具組件,讓服務(wù)開(kāi)發(fā)者們可以把復(fù)雜的分布式系統(tǒng)以簡(jiǎn)單的方式構(gòu)建起來(lái),并且盡可能讓開(kāi)發(fā)者使用最少的時(shí)間完成基礎(chǔ)架構(gòu)的構(gòu)建。

go-micro 是獨(dú)立的 rpc 框架,它是 micro 工具集的核心

micro的服務(wù)核心組件:

  • Client:發(fā)送RPC請(qǐng)求與廣播消息

  • Server:接收RPC請(qǐng)求與消費(fèi)消息

  • Broker:異步通信組件

  • Codec:數(shù)據(jù)編碼組件

  • Registry:服務(wù)注冊(cè)組件

  • Selector:客戶端均衡器

  • Transport:同步通信組件

其中,BrokerTransport都是通訊組件,區(qū)別就是一個(gè)是異步,另一個(gè)是同步。所以我們?cè)跇I(yè)務(wù)中使用Transport組件會(huì)比較頻繁,因?yàn)闃I(yè)務(wù)需要關(guān)心調(diào)用結(jié)果。

Codec是數(shù)據(jù)編碼組件,它可以自動(dòng)將請(qǐng)求及其參數(shù),轉(zhuǎn)換為需要的格式。例如,當(dāng)我們需要調(diào)用其他服務(wù)時(shí),Transport默認(rèn)的是使用grpc2,grpc2使用的通訊格式是Protobuf,所以Codec會(huì)幫我們將數(shù)據(jù)轉(zhuǎn)為Protobuf格式進(jìn)行發(fā)送。

Registry是服務(wù)注冊(cè)組件,它既可以幫我們把我們的服務(wù)注冊(cè)到服務(wù)中心,又可以在服務(wù)中心中獲取已經(jīng)注冊(cè)的服務(wù)列表,供我們進(jìn)行調(diào)用。

Selector客戶端均衡器配合服務(wù)注冊(cè)組件。當(dāng)從服務(wù)中心中獲取已經(jīng)注冊(cè)的服務(wù)列表時(shí),由于相同的一個(gè)服務(wù)可能是高可用的架構(gòu),所以需要一個(gè)均衡調(diào)度器,根據(jù)不同的均衡權(quán)重算法,來(lái)幫我們選擇一個(gè)合適的節(jié)點(diǎn)進(jìn)行調(diào)用。


二、服務(wù)注冊(cè)發(fā)現(xiàn)(etcd)

go-micro框架為服務(wù)注冊(cè)發(fā)現(xiàn)提供了標(biāo)準(zhǔn)的接口Registry。只要實(shí)現(xiàn)這個(gè)接口就可以定制自己的服務(wù)注冊(cè)和發(fā)現(xiàn)。不過(guò)官方已經(jīng)為主流注冊(cè)中心提供了官方的接口實(shí)現(xiàn)

目前最新版的go-micro默認(rèn)使用mDNS 提供零配置的發(fā)現(xiàn)系統(tǒng),他內(nèi)置于大多數(shù)系統(tǒng)。所以之前我們的程序完全不用做任何配置,也不用搭建任何環(huán)境,就具備服務(wù)注冊(cè)和發(fā)現(xiàn)能力。

而在生產(chǎn)環(huán)境,官方則推薦使用etcd組成更具彈性的集群方案,在v2版中,官方已經(jīng)不推薦使用consul。

  • 配置默認(rèn)使用etcd

go run main --registry=etcd

通過(guò)設(shè)置 GoLand 的 Go Modules 環(huán)境變量 MICRO_REGISTRY=etcd 來(lái)統(tǒng)一設(shè)置,這樣,就不需要在啟動(dòng)服務(wù)時(shí)額外傳入--registry=etcd這個(gè)選項(xiàng)了(打開(kāi) GoLand 的 Preferences 界面即可完成設(shè)置)

動(dòng)手實(shí)踐

  • 注冊(cè)(示例是etcd的,替換成consul也是一樣的)
func init()  {
    //注冊(cè)地址為 ip:端口
    etcdRegister := etcd.NewRegistry(
        registry.Addrs("192.168.110.195:2379"),
    )
}
  • 發(fā)現(xiàn)
//從注冊(cè)中心獲取服務(wù)節(jié)點(diǎn)
func getSrvNode(reg registry.Registry, srvName string) (*registry.Node, error) {
    //獲取服務(wù)列表
    services, err := reg.GetService(srvName)
    if err != nil {
        log.Info("未獲取到服務(wù) " + srvName + ",請(qǐng)確認(rèn)服務(wù)是否存在")
        return nil, err
    }
    //獲取隨機(jī)服務(wù),也可以使用 RoundRobin 之類的算法
    next := selector.Random(services)
    node, err := next()
    if err != nil {
        log.Info("隨機(jī)獲取服務(wù) " + srvName + " 實(shí)例失敗")
        return nil, err
    }
    return node, nil
}

三、服務(wù)網(wǎng)關(guān)

Micro的api就是api網(wǎng)關(guān),API參考了API網(wǎng)關(guān)模式為服務(wù)提供了一個(gè)單一的公共入口?;诜?wù)發(fā)現(xiàn),使得micro api可以提供具備http及動(dòng)態(tài)路由的服務(wù)。

micro 自帶的api網(wǎng)關(guān)功能比較單一,而且http和rpc請(qǐng)求需要起不同的服務(wù)來(lái)處理,服務(wù)網(wǎng)關(guān)通常會(huì)整合很多系統(tǒng)相關(guān)邏輯,所以是使用的gin框架自行實(shí)現(xiàn)的網(wǎng)關(guān)。

動(dòng)手實(shí)踐

  • main.go
    核心是初始化一個(gè)服務(wù)注冊(cè)到etcd,然后啟動(dòng)一個(gè)web服務(wù)對(duì)外提供api訪問(wèn),同時(shí)將gin.router作為處理器綁定到go-micro,讓gin框架來(lái)接管路由
func main() {
    ......
    //etcd注冊(cè)實(shí)例
    etcdRegister := etcd.NewRegistry(
        registry.Addrs(etcdAddr),
    )
    //----------------注冊(cè)網(wǎng)關(guān)-----------------------------------

    //注冊(cè)網(wǎng)關(guān)服務(wù)
    //這個(gè)服務(wù)實(shí)際不會(huì)調(diào)用.run方法,實(shí)際會(huì)啟動(dòng)的是下面的webService
    gwtService := micro.NewService(
        micro.Name(gatewayName),
        micro.Version("latest"),
        // 配置etcd為注冊(cè)中心,配置etcd路徑,默認(rèn)端口是2379
        micro.Registry(etcdRegister),
    )

    //---------------注冊(cè)web服務(wù)-------------------------------
    //會(huì)議室預(yù)訂服務(wù)的restful api映射
    webService := web.NewService(
        web.Name(gatewayWeb),
        web.Address(webServiceAddr),
        web.Registry(etcdRegister),
    )
    //注冊(cè)路由處理器
    webService.Handle("/", router.NewRouter())

    //啟動(dòng)服務(wù)
    if err := webService.Run(); err != nil {
        fmt.Println("webService.Run error:", err)
    }
    ......
}
  • route.go
//返回gin router
func NewRouter() *gin.Engine {
    route := gin.Default()

    //跨域處理
    route.Use(middleware.Cors())

    //通配路由,以 meeting 為前綴的都轉(zhuǎn)發(fā)到會(huì)議室預(yù)訂服務(wù)去
    uriMeeting := serviceclient.MeetingApiNode.Address
    route.Any("/api/meeting/*any", ReverseProxy(uriMeeting, ""))
    route.Any("/api/meetingApplet/*any", ReverseProxy(uriMeeting, ""))

    return route
}

//反向代理
func ReverseProxy(host string, scheme string) gin.HandlerFunc {
    return func(context *gin.Context) {
        director := func(req *http.Request) {
            if scheme == "" {
                scheme = "http"
            }
            req.URL.Scheme = scheme
            req.URL.Host = host
            req.Host = host //一個(gè)ip對(duì)應(yīng)多個(gè)域名的情況需要設(shè)置這項(xiàng)
        }
        proxy := &httputil.ReverseProxy{Director: director}
        proxy.ServeHTTP(context.Writer, context.Request)
    }
}

四、鏈路追蹤(jaeger)

中文文檔 介紹

工作原理:

動(dòng)手實(shí)踐

  • 改造一下網(wǎng)關(guān)的main.go
    ......
    //----------------注冊(cè)網(wǎng)關(guān)-----------------------------------
    // 配置jaeger連接
    jaegerTracer, closer, err := tracer.NewJaegerTracer(gatewayName, jaegerAddr)
    if err != nil {
        log.Fatal(err)
    }
    defer closer.Close()
    wrapperTrace.SetGlobalTracer(jaegerTracer)

    //注冊(cè)網(wǎng)關(guān)服務(wù)
    //這個(gè)服務(wù)實(shí)際不會(huì)調(diào)用.run方法,實(shí)際會(huì)啟動(dòng)的是下面的webService
    gwtService := micro.NewService(
        ......
        // 配置鏈路追蹤為 jaeger
        micro.WrapHandler(opentracing.NewHandlerWrapper(wrapperTrace.GlobalTracer())),
    )
  • 改造一下路由router.go,加入鏈路追蹤中間件
func NewRouter() *gin.Engine {
    ......
    route.Use(
        lib.JaegerMiddleware(),               //jaeger中間件
    )
    ......
}
  • JaegerMiddleware鏈路追蹤中間件
//jaeger中間件,記錄token和span信息
func JaegerMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        sp := opentracing.GlobalTracer().StartSpan(c.Request.URL.Path)
        tracer := opentracing.GlobalTracer()
        //元數(shù)據(jù)metadata
        md := make(map[string]string)
        //獲取請(qǐng)求投的 spanCtx
        spanCtx, sErr := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(c.Request.Header))
        if sErr != nil {
            sp = opentracing.GlobalTracer().StartSpan(c.Request.URL.Path, opentracing.ChildOf(spanCtx))
            tracer = sp.Tracer()
        }
        //基于提取的 spanCtx 創(chuàng)建新的子span
        sp = opentracing.GlobalTracer().StartSpan(c.Request.URL.Path, opentracing.ChildOf(spanCtx))
        sp.SetTag("Authorization", c.GetHeader("Authorization"))
        defer sp.Finish()

        //注入span到tracer.text
        if err := tracer.Inject(
            sp.Context(),
            opentracing.TextMap,
            opentracing.TextMapCarrier(md),
        ); err != nil {
            log.Log(err)
        }
        //注入span到tracer.text
        if err := tracer.Inject(
            sp.Context(),
            opentracing.HTTPHeaders,
            opentracing.HTTPHeadersCarrier(c.Request.Header),
        ); err != nil {
            log.Log(err)
        }

        ctx := context.TODO()
        ctx = opentracing.ContextWithSpan(ctx, sp)
        ctx = metadata.NewContext(ctx, md)
        c.Set(contextTracerKey, ctx)

        c.Next()

        //通過(guò)ext可以為追蹤設(shè)置額外的一些信息
        statusCode := c.Writer.Status()
        ext.HTTPStatusCode.Set(sp, uint16(statusCode))
        ext.HTTPMethod.Set(sp, c.Request.Method)
        ext.HTTPUrl.Set(sp, c.Request.URL.EscapedPath())
        if statusCode >= http.StatusInternalServerError {
            ext.Error.Set(sp, true)
        } else if rand.Intn(100) > sf {
            ext.SamplingPriority.Set(sp, 0)
        }
    }
}

// ContextWithSpan 返回context
func ContextWithSpan(c *gin.Context) (ctx context.Context, ok bool) {
    v, exist := c.Get(contextTracerKey)
    if exist == false {
        ok = false
        ctx = context.TODO()
        return
    }
    ctx, ok = v.(context.Context)
    return
}

五、protobuf協(xié)議

工作流程:

  • Proto 語(yǔ)言文件的規(guī)范
proto 文件遵循只增不減的原則
proto 文件中的接口遵循只增不減的原則
proto 文件中的 message 字段遵循只增不減的原則
proto 文件中的 message 字段類型和序號(hào)不得修改
  • 官方文檔

golang/protobuf

  • 服務(wù)間調(diào)用依賴的proto文件如何進(jìn)行管理

微服務(wù)架構(gòu)下RPC IDL及代碼如何統(tǒng)一管理?_韓亞軍的博客-CSDN博客

動(dòng)手實(shí)踐

這里需要?jiǎng)?chuàng)建兩個(gè)子服務(wù),meeting-apimeeting-srv,meeting-srv實(shí)現(xiàn)會(huì)議室預(yù)訂相關(guān)的CRUD,meeting-api通過(guò)rpc遠(yuǎn)程調(diào)用meeting-srv完成業(yè)務(wù)組裝,并提供對(duì)外部的api訪問(wèn)。(需要兩個(gè)項(xiàng)目持有同一份proto文件,才能通過(guò)protobuf協(xié)議完成 rpc調(diào)用)

  • 定義響應(yīng)的公共文件response.proto
syntax = "proto3";
package response;
import "google/protobuf/any.proto";

option go_package = ".;proto";

message Response  {
  int64 Code = 1;
  string Message = 2;
  google.protobuf.Any Data = 3;
}
  • 定義會(huì)議室room.proto文件
syntax = "proto3";
package meeting;
import "proto/response/response.proto";//標(biāo)紅無(wú)所謂,一樣能導(dǎo)入
option go_package = ".;proto";

service RoomService{
  //查詢會(huì)議室列表
  rpc GetRooms(ReqGetRooms) returns(response.Response){}
  //查詢會(huì)議室詳情
  rpc GetRoom(ReqGetRoom) returns(response.Response){}
  //新增會(huì)議室
  rpc CreateRoom(ReqCreateRoom) returns(response.Response){}
  //編輯會(huì)議室
  rpc UpdateRoom(ReqUpdateRoom) returns(response.Response){}
  //刪除會(huì)議室
  rpc DeleteRoom(ReqDeleteRoom) returns(response.Response){}
}

message Room{
  int64 id = 1;
  int64 space_id = 2;//所屬地點(diǎn)ID
  string name = 3;//會(huì)議室名稱
  oneof one_status{
    string status = 4;// 啟用狀態(tài):0禁用、1啟用
  };
  string image_url = 5;//會(huì)議室圖片
  int64 capacity_min = 6;//建議使用人數(shù)(最小)
  int64 capacity_max = 7;//建議使用人數(shù)(最大)
  string created_at = 8;
}

message ReqGetRooms{
  int64 page = 1;
  int64 pageSize = 2;
  string sortBy = 3;
  string order = 4;
  int64 space_id = 5;
  string name = 6;
  oneof one_status{
    string status = 7;
  };
}

message ReqGetRoom{
  int64 id = 1;
}

message ReqCreateRoom{
  int64 space_id = 2;
  string name = 3;
  oneof one_status{
    string status = 4;
  };
  string image_url = 5;
  int64 capacity_min = 6;
  int64 capacity_max = 7;
  string DeviceIds = 8;
}

message ReqUpdateRoom{
  int64 id = 1;
  int64 space_id = 2;
  string name = 3;
  oneof one_status{
    string status = 4;
  };
  string image_url = 5;
  int64 capacity_min = 6;
  int64 capacity_max = 7;
  string DeviceIds = 8;
}

message ReqDeleteRoom{
  int64 id = 1;
}
  • 在common根目錄下生成所有proto文件命令:(注意執(zhí)行路徑和輸入輸出路徑)
for x in **/*.proto; do protoc --go_out=protob --micro_out=protob $x; done

六、部署(docker-compose)

直接參考github代碼吧:https://github.com/catwrench/go-micro/tree/main/deploy

ps:開(kāi)始前請(qǐng)確認(rèn)
1、將common服務(wù)復(fù)制到每個(gè)項(xiàng)目的submodules路徑下(使用git submodules引入公共模塊,配置其實(shí)可以使用etcd存儲(chǔ))
2、是否將`submodules/common/config/config.dev.yml`重命名為`config.yml`,并填寫(xiě)正確參數(shù)
3、確認(rèn)`deploy/.env`是否配置正確

# 先構(gòu)建golang基礎(chǔ)鏡像,打上標(biāo)簽
cd golang
docker build -t golang:base .
docker tag golang:base golang-base:1.14.4

# 通過(guò)docker-compose 構(gòu)建微服務(wù)鏡像并啟動(dòng)電容器
# 在deploy根目錄執(zhí)行
docker-compose up --build -d  
  • 啟動(dòng)后的效果


  • deploy/.env

# docker-compose 環(huán)境配置

# go-micro公共配置
WORKSPACE="../../go-micro"
MICRO_REGISTRY="etcd"
MICRO_SERVER_ADDRESS=":2379"

# MYSQL配置
MYSQL_DATABASE=micro_dev
MYSQL_PORT=3306
MYSQL_ROOT_USER=root
MYSQL_ROOT_PASSWORD=root
MYSQL_TEST_USER=test
MYSQL_TEST_PASSWORD=test
MYSQL_DATA_DIR=./db_data
MYSQL_LOG=./log/mysql

# REDIS配置
REDIS_PORT=6379
REDIS_PASSWORD=null

#-------------------------------------
# 注冊(cè)中心
ALLOW_NONE_AUTHENTICATION="yes"
ETCD_ADVERTISE_CLIENT_URLS="http://etcd:2379"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://0.0.0.0:2380"
ETCD_INITIAL_CLUSTER="http://0.0.0.0:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_NAME="node1"

# 網(wǎng)關(guān)
GATEWAY_PORT=8091

# 會(huì)議室預(yù)訂服務(wù)【api】
MEETING_API_PORT=56201

# 會(huì)議室預(yù)訂服務(wù)【srv】
MEETING_SRV_PORT=56302

# 用戶服務(wù)【srv】
USER_SRV_PORT=56301

# 消息通知服務(wù)【srv】
NOTICE_SRV_PORT=56303
  • deploy/docker-compose.yml
version: '3.1'

services:
  #mysql服務(wù)
  db:
    build: ./mysql
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - ${MYSQL_DATA_DIR}:/var/lib/mysql
      - ${MYSQL_LOG}:/var/log/mysql
    ports:
      - "${MYSQL_PORT}:3306"
    environment:
      #mysql的root密碼
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      #容器會(huì)創(chuàng)建的數(shù)據(jù)庫(kù)
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      #test用戶
      MYSQL_USER: ${MYSQL_TEST_USER}
      #test用戶的密碼
      MYSQL_PASS: ${MYSQL_TEST_PASSWORD}
    networks:
      - gomicro

  #redis服務(wù)
  redis:
    build: ./redis
    ports:
      - "${REDIS_PORT}:6379"
      #指定創(chuàng)建redis容器后,設(shè)置的密碼
    #command:
    #  - "--requirepass Admin@${REDIS_PASSWORD}"
    networks:
      - gomicro

  # ------------------------------
  # 注冊(cè)中心 etcd
  etcd:
    image: bitnami/etcd:3
    ports:
      - 2379:2379
      - 2380:2380
    environment:
      ALLOW_NONE_AUTHENTICATION: ${ALLOW_NONE_AUTHENTICATION}
      ETCD_ADVERTISE_CLIENT_URLS: ${ETCD_ADVERTISE_CLIENT_URLS}
      ETCD_LISTEN_CLIENT_URLS: ${ETCD_LISTEN_CLIENT_URLS}
      ETCD_LISTEN_PEER_URLS: ${ETCD_LISTEN_PEER_URLS}
      ETCD_INITIAL_ADVERTISE_PEER_URLS: ${ETCD_INITIAL_ADVERTISE_PEER_URLS}
      ETCD_INITIAL_CLUSTER: "${ETCD_NAME}=${ETCD_INITIAL_CLUSTER}"
      ETCD_INITIAL_CLUSTER_TOKEN: ${ETCD_INITIAL_CLUSTER_TOKEN}
      ETCD_INITIAL_CLUSTER_STATE: ${ETCD_INITIAL_CLUSTER_STATE}
      ETCD_NAME: ${ETCD_NAME}
    networks:
      - gomicro

  # 鏈路追蹤 jaeger
  jaeger:
    image: jaegertracing/all-in-one:latest
    ports:
      - 16686:16686
    networks:
      - gomicro

  # 網(wǎng)關(guān)
  gateway:
    build:
      # 設(shè)置context為上級(jí)相對(duì)路徑,避免dockerfile構(gòu)建時(shí)add命令不能添加上級(jí)目錄
      context: ../
      dockerfile: gateway/deploy/Dockerfile
      args:
        - EXPOSE_PORT=${GATEWAY_PORT}
    ports:
      - "${GATEWAY_PORT}:${GATEWAY_PORT}"
    environment:
      MICRO_REGISTRY: ${MICRO_REGISTRY}
      MICRO_SERVER_ADDRESS: ":${GATEWAY_PORT}"
    volumes:
      - "${WORKSPACE}:/go/src/go-micro"
    depends_on:
      - etcd
      - jaeger
      - meeting-api
    networks:
      - gomicro

  # ------服務(wù)列表------
  # 會(huì)議室預(yù)訂服務(wù)【api】
  meeting-api:
    build:
      context: ../
      dockerfile: meeting-api/deploy/Dockerfile
      args:
        - EXPOSE_PORT=${MEETING_API_PORT}
    ports:
      - "${MEETING_API_PORT}:${MEETING_API_PORT}"
    environment:
      MICRO_REGISTRY: ${MICRO_REGISTRY}
      MICRO_SERVER_ADDRESS: ":${MEETING_API_PORT}"
    volumes:
      - "${WORKSPACE}:/go/src/go-micro"
    depends_on:
      - etcd
      - jaeger
      - meeting-srv
    networks:
      - gomicro

  # 會(huì)議室預(yù)訂服務(wù)【srv】
  meeting-srv:
    build:
      context: ../
      dockerfile: meeting-srv/deploy/Dockerfile
      args:
        - EXPOSE_PORT=${MEETING_SRV_PORT}
    ports:
      - "${MEETING_SRV_PORT}:${MEETING_SRV_PORT}"
    environment:
      MICRO_REGISTRY: ${MICRO_REGISTRY}
      MICRO_SERVER_ADDRESS: ":${MEETING_SRV_PORT}"
    volumes:
      - "${WORKSPACE}:/go/src/go-micro"
    depends_on:
      - etcd
      - db
    networks:
      - gomicro

  # 用戶服務(wù)【srv】
  user-srv:
    build:
      context: ../
      dockerfile: user-srv/deploy/Dockerfile
      args:
        - EXPOSE_PORT=${USER_SRV_PORT}
    ports:
      - "${USER_SRV_PORT}:${USER_SRV_PORT}"
    environment:
      MICRO_REGISTRY: ${MICRO_REGISTRY}
      MICRO_SERVER_ADDRESS: ":${USER_SRV_PORT}"
    volumes:
      - "${WORKSPACE}:/go/src/go-micro"
    depends_on:
      - etcd
    networks:
      - gomicro

  # 消息通知服務(wù)【srv】
  notice-srv:
    build:
      context: ../
      dockerfile: notice-srv/deploy/Dockerfile
      args:
        - EXPOSE_PORT=${NOTICE_SRV_PORT}
    ports:
      - "${NOTICE_SRV_PORT}:${NOTICE_SRV_PORT}"
    environment:
      MICRO_REGISTRY: ${MICRO_REGISTRY}
      MICRO_SERVER_ADDRESS: ":${NOTICE_SRV_PORT}"
    depends_on:
      - etcd
      - db
      - redis
    volumes:
      - "${WORKSPACE}:/go/src/go-micro"
    networks:
      - gomicro

networks:
  gomicro:
    driver: bridge


七、其他組件

具體使用參考github倉(cāng)庫(kù)代碼,限于篇幅這里就不寫(xiě)了

  • 參數(shù)驗(yàn)證(validator/v10, gin框架默認(rèn)組件)

golang常用庫(kù):字段參數(shù)驗(yàn)證庫(kù)-validator使用 - 九卷 - 博客園

  • gorm

GORM 指南

  • 如何使用gorm編寫(xiě)良好可復(fù)用代碼

利用go+grpc+gorm+proto、通過(guò)設(shè)計(jì)好的數(shù)據(jù)表快速生成curd增刪改查代碼_陳福華的博客-CSDN博客

  • 讀取配置文件(viper)

spf13/viper

  • 時(shí)間格式轉(zhuǎn)換(golang-module/carbon,不推薦用,因?yàn)殄e(cuò)誤全部拋出panic)

golang-module/carbon


八、錯(cuò)誤總結(jié)

  • 總結(jié):
  1. web服務(wù)(消費(fèi)者)和api服務(wù)(提供者)一定是分開(kāi)的兩個(gè)服務(wù),一個(gè)以web.newService聲明,一個(gè)以micro.newService聲明

  2. web服務(wù)無(wú)法注冊(cè)鏈路追蹤,解決辦法為:micro.newService創(chuàng)建一個(gè)普通服務(wù)并注冊(cè)到注冊(cè)中心,但是不啟動(dòng),實(shí)際啟動(dòng)的是web服務(wù)

  3. rpc遠(yuǎn)程調(diào)用通常為 res := client.call(service.func, &req ,&parms)

  4. 在Micro api功能中,支持多種處理請(qǐng)求路由的方式,我們稱之為Handler。包括:API Handler、RPC Handler、反向代理、Event Handler,RPC等五種方式

  5. 網(wǎng)關(guān)將請(qǐng)求根據(jù)路由前綴批量轉(zhuǎn)發(fā)到api服務(wù)(如:mcms、uims),然后由api服務(wù)(消費(fèi)者)進(jìn)行匹配,調(diào)用業(yè)務(wù)服務(wù)(提供者)

  6. git代碼倉(cāng)庫(kù)每個(gè)服務(wù)分開(kāi),可以考慮使用git submodule來(lái)進(jìn)行管理公共服務(wù)

  7. web.NewService和micro.NewService的區(qū)別

  • 可能的錯(cuò)誤
1、protobuf缺省值問(wèn)題
原因:proto3傳輸時(shí),0、""、null會(huì)被忽略
解決:可以使用one_of,并且將字段類型設(shè)置為string,避免int類型默認(rèn)值被設(shè)置為0 
https://developers.google.com/protocol-buffers/docs/proto3#oneof

2、gorm使用update進(jìn)行更新struct時(shí)忽略了0值
原因:update存儲(chǔ)時(shí)會(huì)先將struct轉(zhuǎn)map,轉(zhuǎn)換過(guò)程中0值會(huì)被忽略
解決一:使用save方法進(jìn)行保存
解決二:使用update方法進(jìn)行保存,但傳入?yún)?shù)手動(dòng)轉(zhuǎn)換為map類型

3、引入viper組件,讀取配置文件后,開(kāi)啟調(diào)試模式報(bào)錯(cuò)
原因:ide配置錯(cuò)誤
解決:debug配置->go build->Run kind(package)->working directory(debug服務(wù)的根目錄)

4、讀取ctx.request.body時(shí)報(bào)錯(cuò)"unexpected EOF"
原因:將數(shù)據(jù)重新寫(xiě)入body,因?yàn)閞eadall讀取一次后就不在了,會(huì)導(dǎo)致后面的api服務(wù)讀取body是報(bào)錯(cuò)"unexpected EOF"
解決一:ctx.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
解決二:在gin 1.4 之前,重復(fù)使用ShouldBind綁定會(huì)報(bào)錯(cuò)EOF。
gin 1.4 之后官方提供了一個(gè) ShouldBindBodyWith 的方法,可以支持重復(fù)綁定,
原理就是將body的數(shù)據(jù)緩存了下來(lái),但是二次取數(shù)據(jù)的時(shí)候還是得用 ShouldBindBodyWith 
才行,直接用 ShouldBind 還是會(huì)報(bào)錯(cuò)的。

5、啟動(dòng)服務(wù)報(bào)錯(cuò):panic: qtls.ConnectionState not compatible with tls.ConnectionState
原因:go-micro的部分組件還未對(duì)go1.15做適配
解決:使用低于1.15版本的go

6、報(bào)錯(cuò):command not found: micro
原因:在gopath/bin目錄下沒(méi)有micro二進(jìn)制文件 或 未配置系統(tǒng)環(huán)境變量
解決:https://blog.csdn.net/m0_38025165/article/details/106865383

7、報(bào)錯(cuò):undefined: balancer.PickOptions 
原因:依賴沖突
解決:replace google.golang.org/grpc => google.golang.org/grpc v1.26.0

8、生成proto文件報(bào)錯(cuò):handler/hello.go:8:2: package hello/proto/hello is not in GOROOT (/usr/local/go/src/hello/proto/hello)
解決:其實(shí)不是GOROOT的問(wèn)題,是對(duì)應(yīng)的proto文件沒(méi)有生成,進(jìn)入到服務(wù)根目錄下,執(zhí)行make proto

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容