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
先寫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是一個工具包toolkit合集,能幫助咱們快速開發、調試微服務 本文使用的micro版本是v0.22.0,其餘版本可能會有不一樣之處
執行go get -u github.com/micro/micro
便可安裝,micro依賴的東西比較多,建議"fanqiang"
測試安裝 micro --version
瀏覽器
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
複製代碼
micro web [arguments...]
啓動web管理界面,在瀏覽器中輸入localhost:8082
便可打開
管理界面提供了許多相似於cli的功能,更加方便、直觀, micro web自己也是個微服務
查看web資源
調用服務
查看服務詳細信息
交互式命令行
micro api [arguments...]
詳見下文, micro 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: 內網的後臺服務
複製代碼
micro api
便可啓動api一個網關,默認的端口是8080
能夠經過--address=0.0.0.0:8080
flag或者設置環境MICRO_API_ADDRESS=0.0.0.0:8080
來修改
ACME( Automatic Certificate Management Environment)是由Let’s Encrypt制定的安全協議 經過--enable_acme=true
或者設置環境MICRO_ENABLE_ACME=true
能夠選擇是否配置白名單
--acme_hosts=example.com
或MICRO_ACME_HOSTS=example.com,api.example.com
micro --enable_tls=true --tls_cert_file=/path/to/cert --tls_key_file=/path/to/key api
或MICRO_ENABLE_TLS=true MICRO_TLS_CERT_FILE=/path/to/cert MICRO_TLS_KEY_FILE=/path/to/key micro api
micro --api_namespace=namespace api
或MICRO_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是默認的處理器,接收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"
與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")
裏的
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等知識的時間較短,若是有理解錯誤的地方,歡迎批評指正,能夠加我微信一塊兒探討學習