牌類遊戲使用微服務重構筆記(二): micro框架簡介:micro toolkit

做者簡介

Asim Aslam, 前谷歌工程師,領英地址。Technology is rapidly evolving. Cloud computing now gives us almost unlimited scale, however leveraging that scale with existing tools is still difficult. Micro is solving this problem with a developer first focus.node

Asim在宣傳視頻中說由於在谷歌工做6年的經歷,他對proto、grpc、golang是很是熟悉的,micro這個框架也作的比較完善,許多企業都在用,因此我的比較放心,並且看這個髮量,應該是也是很靠譜的。。。git

我從學習到使用的時間也不長,有一些關於做者的客觀評價github

  • Asim目前在全職作micro這個項目,註冊了一家公司也叫micro,有商業版本(官網), 認真的在賺錢,開源版和商業版的區別
  • 不管是micro這個項目仍是公司,貌似都只是Asim一我的
  • github上的issue解決的很是快,給個人感受他是24小時在線的,可是他特別喜歡關閉issue,open狀態的issue幾乎看不到
  • 版本變化比較快
  • 文檔比較少且不細緻,坑很多(可能也是筆者懂的知識太少了)

helloWorld

先寫proto, 命名爲greeter.protogolang

syntax = "proto3";

service Greeter {
	rpc Hello(HelloRequest) returns (HelloResponse) {}
}

message HelloRequest {
	string name = 1;
}

message HelloResponse {
	string greeting = 2;
}

複製代碼

編譯proto
protoc -I . --go_out=. --micro_out=. proto/greeter.proto
會生成兩個文件,greeter.pb.go 原生proto go文件,greeter.micro.go 微服務proto go文件web

微服務代碼
json

package main

import (
	"context"
	"log"

	"github.com/micro/go-micro"
	// 引用上面生成的proto文件
	proto "micro-blog/helloworld/proto"
)

type Greeter struct{}

func (g *Greeter) Hello(ctx context.Context, req *proto.HelloRequest, rsp *proto.HelloResponse) error {
	rsp.Greeting = "Hello " + req.Name
	return nil
}

func main() {
	// new一個微服務出來
	service := micro.NewService(
		micro.Name("greeter"),
		micro.Version("latest"),
	)

	// 可選 解析命令行
	service.Init()

	// 註冊 handler
	proto.RegisterGreeterHandler(service.Server(), new(Greeter))

	// 啓動服務
	if err := service.Run(); err != nil {
		log.Fatal(err)
	}
}

複製代碼

啓動微服務 go run main.go
這樣一個最簡單的微服務就ok了,是否是很是簡單,咱們再嘗試一下調用後端

package main

import (
	"context"
	"fmt"

	// 引用上面生成的proto文件
	"github.com/micro/go-micro"
	proto "micro-blog/helloworld/proto"
)

func main() {
	// new一個服務
	service := micro.NewService()

	// 解析命令行flag
	service.Init()

	// 使用proto建立一個客戶端
	cl := proto.NewGreeterService("greeter", service.Client())

	// 發出請求
	rsp, err := cl.Hello(context.Background(), &proto.HelloRequest{
		Name: "John",
	})
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(rsp.Greeting)
}

複製代碼

輸出 Hello John
能夠看出,調用微服務也是很是簡單的,這是由於micro把許多東西都幫咱們作好了,後面咱們逐漸去了解api

安裝micro

micro是一個工具包toolkit合集,能幫助咱們快速開發、調試微服務 本文使用的micro版本是v0.22.0,其餘版本可能會有不一樣之處
執行go get -u github.com/micro/micro便可安裝,micro依賴的東西比較多,建議"fanqiang"
測試安裝 micro --version瀏覽器

micro工具包介紹

  • micro cli 交互模式

micro cli
進入交互模式,交互模式下的命令與下文中的命令相似但可能不一樣,具體查看help
若是要鏈接遠程環境,可使用代理,在遠程機器上執行 micro proxy
在本機上執行MICRO_PROXY_ADDRESS=staging.micro.mu:8081 micro cli安全

例如筆者在公司開發時會把micro運行在臺式機上,而後在本身的筆記本上敲代碼

  • 列出服務

micro list services
若是你還運行着上文的helloworld服務,會輸出greeter

  • 獲取服務

micro get service [servicename]獲取某個服務的詳細信息

例如micro get service greeter,會輸出:

service  greeter # 服務名上文填寫的

version latest # 服務的版本號

# 詳細信息 服務id、服務所在機器的ip、端口號、協議、註冊方式、transport、broker等等,若是你再啓動一個相同的服務進程,下面會出現兩條記錄
ID	Address	Port	Metadata
greeter-8f9ea45b-d8ce-43f6-97b5-a41d8514cd79	192.168.1.107	65217	protocol=mucp,registry=mdns,server=rpc,transport=http,broker=http

# 能夠理解爲一個rpc接口
Endpoint: Greeter.Hello
Metadata: stream=false

# Greeter.Hello請求結構
Request: {
	name string
	- 
	- []uint8 {
		uint8 uint8
	}
	- int32
}

# Greeter.Hello響應結構
Response: {
	greeting string
	- 
	- []uint8 {
		uint8 uint8
	}
	- int32
}
複製代碼
  • 調用服務

micro call [servicename] [endpoint] [data]當即調用某個服務的某個方法

例如micro call greeter Greeter.Hello '{"name": "John"}',會輸出

{
	"greeting": "Hello John"
}
複製代碼

與代碼裏調用的結果一致,其實不管是micro cli裏調用仍是代碼裏面調用,過程是同樣的,後面會有詳細解釋

  • 服務健康檢測

micro health [servicename]獲取某個服務的健康狀況
例如micro health greeter, 會輸出

service  greeter

version latest

node		address:port		status
greeter-8f9ea45b-d8ce-43f6-97b5-a41d8514cd79		192.168.1.107:65217		ok
複製代碼
  • 快速生成服務模板

micro new [servicename] [arguments...]新寫一個服務時可使用這個命令快速生成模板,默認會生成在$GOPATH的相對目錄

例如micro new test --gopath=false,會輸出

Creating service go.micro.srv.test in test

.
├── main.go
├── plugin.go
├── handler
│   └── example.go
├── subscriber
│   └── example.go
├── proto/example
│   └── example.proto
├── Dockerfile
├── Makefile
└── README.md


download protobuf for micro:

brew install protobuf
go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
go get -u github.com/micro/protoc-gen-micro

compile the proto file example.proto:

cd test
protoc --proto_path=. --go_out=. --micro_out=. proto/example/example.proto

複製代碼
  • Web管理控制檯

micro web [arguments...] 啓動web管理界面,在瀏覽器中輸入localhost:8082便可打開
管理界面提供了許多相似於cli的功能,更加方便、直觀, micro web自己也是個微服務
查看web資源

調用服務

查看服務詳細信息

交互式命令行

  • API網關

micro api [arguments...] 詳見下文, micro api自己也是個微服務

API網關

  • 一切皆服務

在micro的系統中,有許多資源類型,筆者理解的是服務的一種抽象歸類,好比經常使用的有:api、fnc(函數)、srv、web。這些資源在被定義時都是以服務的方式進行的,上文helloworld中的服務"greeter" 只是最簡單的服務,如何進行資源歸類呢?答案就是增長前綴,好比"greeter"屬於一個後端服務,咱們把它定義爲"srv.greeter",這樣micro就知道了這個服務是srv分類,修改代碼

// new一個微服務出來
	service := micro.NewService(
		micro.Name("srv.greeter"),
		micro.Version("latest"),
	)
複製代碼
  • 命名空間

在服務的類型歸類好以後,有時候可能須要根據項目、服務特色等再次進行歸類,這時候就須要命名空間了,例如修改"srv.greeter"爲"proj1.srv.greeter",表示這個資源屬於"proj1"這個命名空間下,默認的命名空間是"go.micro"

  • 三層架構

在micro中,推薦以三層架構方式組織衆多的微服務

micro api: http訪問入口

some api: 對外暴露的API服務

some srv: 內網的後臺服務
複製代碼

  • 啓動api網關

micro api 便可啓動api一個網關,默認的端口是8080
能夠經過--address=0.0.0.0:8080flag或者設置環境MICRO_API_ADDRESS=0.0.0.0:8080來修改

  • 使用ACME協議

ACME( Automatic Certificate Management Environment)是由Let’s Encrypt制定的安全協議 經過--enable_acme=true或者設置環境MICRO_ENABLE_ACME=true

能夠選擇是否配置白名單
--acme_hosts=example.comMICRO_ACME_HOSTS=example.com,api.example.com

  • 設置TLS證書

micro --enable_tls=true --tls_cert_file=/path/to/cert --tls_key_file=/path/to/key apiMICRO_ENABLE_TLS=true MICRO_TLS_CERT_FILE=/path/to/cert MICRO_TLS_KEY_FILE=/path/to/key micro api

  • 設置命名空間

micro --api_namespace=namespace apiMICRO_API_NAMESPACE=namespace micro api
注意啓動api時設置的namespace必須與要訪問的資源的namespace一致否則沒法訪問,Web管理控制檯相似

  • 直接訪問服務

經過/rpc這個固定url能夠繞過rpc處理器直接對服務進行訪問,例如

curl -d 'service=go.micro.srv.greeter' \
     -d 'endpoint=Greeter.Hello' \
     -d 'request={"name": "Bob"}' \
     http://localhost:8080/rpc
複製代碼

會輸出

{"greeting":"Hello Bob"}
複製代碼

可是不推薦這麼使用,由於使用micro通常都是三層架構,能夠在開發調試階段這麼使用。若是要禁用rpc調用方式,須要使用go-plugin插件,後文會有介紹

  • 使用處理器訪問服務

所謂處理器,筆者理解的就是經過api網關訪問service時處理http請求的方式。micro提供了幾種處理器,上文經過/rpc這個固定路由就是繞過處理器直接使用發送的json序列化請求進而訪問服務。使用處理器通常用於上文提過的三層架構,處理器提供一層api服務,進而再訪問其餘後端微服務

- /[service]/[method]	# HTTP paths are dynamically mapped to services
    - /rpc			# Explicitly call a backend service by name and method
複製代碼
  • api處理器

API是默認的處理器,接收http請求,把http請求/響應信息序列化成api.Request/api.Response格式

Content-Type: Any
Body: Any
Forward Format: api.Request/api.Response
Path: /[service]/[method]
Resolver: 請求解析器,路徑會被解析成服務與方法
Configure: 配置,在啓動時指定--handler=api或在啓動命令前指定環境變量MICRO_API_HANDLER=api

例如,將上文中的helloworld服務,補一個api結構

package main

import (
	"encoding/json"
	"log"
	"strings"

	// 引用上面生成的proto文件
	"github.com/micro/go-micro"
	"github.com/micro/go-micro/errors"
	api "github.com/micro/go-api/proto"
	proto "micro-blog/helloworld/proto"

	"context"
)

type Say struct {
	Client proto.GreeterService
}

func (s *Say) Hello(ctx context.Context, req *api.Request, rsp *api.Response) error {
	log.Print("Received Say.Hello API request")

	name, ok := req.Get["name"]
	if !ok || len(name.Values) == 0 {
		return errors.BadRequest("go.micro.api.greeter", "Name cannot be blank")
	}

	response, err := s.Client.Hello(ctx, &proto.HelloRequest{
		Name: strings.Join(name.Values, " "),
	})
	if err != nil {
		return err
	}

	rsp.StatusCode = 200
	b, _ := json.Marshal(map[string]string{
		"message": response.Greeting,
	})
	rsp.Body = string(b)

	return nil
}

func main() {
	// new一個微服務出來 資源類型設置爲api
	service := micro.NewService(
		micro.Name("go.micro.api.greeter"),
	)

	// 可選 解析命令行
	service.Init()

	// 註冊handler
	service.Server().Handle(
		service.Server().NewHandler(
			&Say{Client: proto.NewGreeterService("go.micro.srv.greeter", service.Client())},
		),
	)

	if err := service.Run(); err != nil {
		log.Fatal(err)
	}
}

複製代碼

經過api網關進行調用

curl -H 'Content-Type: application/json' \
    -s "http://localhost:8080/greeter/say/hello?name=Pengju"
複製代碼

會輸出:

{
    "message": "Hello Pengju"
}
複製代碼

greeter/say/hello被解析成發送到service=go.micro.srv.greeter endpoint=Greeter.Hello的請求, 請求結構被解析成api.Request, 使用proto提供的方法就能夠獲取本次http請求的信息,例如name, ok := req.Get["name"]獲取查詢參數"name"

  • rpc 處理器

與api處理器相似, 不一樣的是序列化數據的結構能夠自定義指定 例如使用下面的proto

Content-Type: application/json or application/protobuf
Body: JSON 或者 Protobuf
Forward Format: json-rpc或者proto-rpc,與Content-Type有關
Path: /[service]/[method]
Resolver: 請求解析器,路徑會被解析成服務與方法
Configure: 配置,在啓動時指定--handler=rpc或在啓動命令前指定環境變量MICRO_API_HANDLER=rpc

syntax = "proto3";

service Example {
	rpc Call(CallRequest) returns(CallResponse) {};
}

service Foo {
	rpc Bar(EmptyRequest) returns(EmptyResponse) {};
}

message CallRequest {
	string name = 1;
}

message CallResponse {
	string message = 2;
}

message EmptyRequest {
}

message EmptyResponse {
}

複製代碼

更改helloworld api的代碼

package main

import (
	"log"

	"github.com/micro/go-micro"
	proto "micro-blog/helloworld/api/rpc/proto"
	greeter "micro-blog/helloworld/proto"

	"context"
)

type Greeter struct {
	Client greeter.GreeterService
}

func (g *Greeter) Hello(ctx context.Context, req *proto.Request, rsp *proto.Response) error {
	log.Print("Received Greeter.Hello API request")

	// make the request
	response, err := g.Client.Hello(ctx, &greeter.HelloRequest{Name: req.Name})
	if err != nil {
		return err
	}

	// set api response
	rsp.Msg = response.Greeting
	return nil
}

func main() {
	// Create service
	service := micro.NewService(
		micro.Name("go.micro.api.greeter"),
	)

	// Init to parse flags
	service.Init()

	// Register Handlers
	proto.RegisterGreeterHandler(service.Server(), &Greeter{
		// Create Service Client
		Client: greeter.NewGreeterService("go.micro.srv.greeter", service.Client()),
	})

	// for handler use

	// Run server
	if err := service.Run(); err != nil {
		log.Fatal(err)
	}
}

複製代碼

發起調用

curl -H 'Content-Type: application/json' \
         -X POST \
         -d "{\"name\": \"PengJu\"}" \
         http://localhost:8080/greeter/greeter/hello

複製代碼

輸出

{"msg":"Hello PengJu"}
複製代碼

能夠看到,請求已按照自定義的proto進行序列化。有個文檔沒提的細節是,api類型的服務使用handler時,服務的endpoint是類名首字母小寫 + "." + 方法名首字母小寫, 如上文中的 Greeter的Hello方法,那麼整個路由就是http://localhost:8080/greeter/greeter/hello,第一個greeter是服務名,micro.Name("go.micro.api.greeter") 裏的

  • web處理器

http反向代理,支持web socket,只會匹配Path: /[service]這一層,剩下的就都交給開發者本身了,你可使用本身喜歡的web框架,自定義中間件等等好處, 比較自由,也是筆者最終選擇的處理器

Content-Type: 支持任何類型
Body: 支持任何格式
Forward Format: HTTP反向代理,包括web socket
Path: /[service]
Resolver: 請求解析器,路徑會被解析成服務名
Configure: 配置,在啓動時指定--handler=web或在啓動命令前指定環境變量MICRO_API_HANDLER=web

好比使用gin

package main

import (
	"log"

	"github.com/gin-gonic/gin"

	"context"
	"github.com/micro/go-micro/client"
	"github.com/micro/go-web"
	proto "micro-blog/helloworld/proto"
)

type Say struct{}

var (
	cl proto.GreeterService
)

func (s *Say) Anything(c *gin.Context) {
	log.Print("Received Say.Anything API request")
	c.JSON(200, map[string]string{
		"message": "Hi, this is the Greeter API",
	})
}

func (s *Say) Hello(c *gin.Context) {
	log.Print("Received Say.Hello API request")

	name := c.Param("name")

	response, err := cl.Hello(context.TODO(), &proto.HelloRequest{
		Name: name,
	})

	if err != nil {
		c.JSON(500, err)
	}

	c.JSON(200, response)
}

func main() {
	// Create service 這裏須要注意使用的web.NewService 而不是micro.NewService 後文會有解釋
	service := web.NewService(
		web.Name("go.micro.api.greeter"),
	)

	service.Init()

	// setup Greeter Server Client
	cl = proto.NewGreeterService("go.micro.srv.greeter", client.DefaultClient)

	// Create RESTful handler (using Gin)
	say := new(Say)
	router := gin.Default()
	router.GET("/greeter", say.Anything)
	router.GET("/greeter/:name", say.Hello)

	// Register Handler
	service.Handle("/", router)

	// Run server
	if err := service.Run(); err != nil {
		log.Fatal(err)
	}
}

複製代碼

發起調用

curl -H 'Content-Type: application/json' \
    -s "http://localhost:8080/greeter/Pengju"
複製代碼

輸出

{"greeting":"Hello Pengju"}
複製代碼

本人學習golang、micro、k8s、grpc、protobuf等知識的時間較短,若是有理解錯誤的地方,歡迎批評指正,能夠加我微信一塊兒探討學習

相關文章
相關標籤/搜索