草稿階段,持續更新 ...
單獨使用broker
broker 可以單獨使用,默認是基於 http的broker。 broker 自帶了 register(默認是mdns)。 broker.Connect() 後會自動通過resiger找到當前運行的broker。
藉助 broker ,我們可以很方便的實現 發佈,訂閱。我們不用關心 broker 服務器 偵聽是什麼端口,有多少個broker服務器。我們只管通過 broker 發佈消息。
可以通過broker.Queue("some name") 來指定 broker.Version。如果沒指定或指定的字符串爲空,broker.Version會被設置爲broadcastVersion, 此時消息會被投遞到所有的broker服務器;否則,客戶端publish時,broker會隨機選一個 broker服務器 進行消息投遞。
如果對默認的 register(msdn)不滿意,也可以通過 broker.Registry(r registry.Registry) 換成你想要的。
broker.Secure(b bool) 可以指定是否使用 https。 默認爲 http, broker.Secure(true) 則使用 https
更詳細的選項 請參考 github.com/micro/go-micro/broker/options.go
以下示例的客戶端 藉助 broker發佈了簡單的 字符串, 或 proto 結構體 給broker服務器。
客戶端
package main
import (
"fmt"
"github.com/micro/go-micro/broker"
"github.com/golang/protobuf/proto"
"learn-micro/pubsub"
)
func main() {
broker.Connect()
defer broker.Disconnect()
ev := &pubsub.Event{
Id: "meme",
Message: "hello broker",
}
b,_ := proto.Marshal(ev)
msg := broker.Message{
Body:b,
}
broker.Publish("proto_message", &msg)
broker.Publish("string_message",
&broker.Message{
Body:[]byte("發送字符串"),
})
fmt.Println("send done")
select {}
}
服務器
package main
import(
"fmt"
"github.com/golang/protobuf/proto"
"github.com/micro/go-micro/broker"
"learn-micro/pubsub"
)
func protoHandler(e broker.Event) error {
var msg pubsub.Event
proto.Unmarshal(e.Message().Body, &msg)
fmt.Println(e.Topic())
fmt.Printf("%+v\n", msg)
return nil
}
func stringHandler(e broker.Event) error {
fmt.Println(string(e.Message().Body))
return nil
}
func main() {
bk := broker.NewBroker(
broker.Addrs(":7788"),
)
bk.Connect()
defer bk.Disconnect()
sub, err := bk.Subscribe("proto_message", protoHandler)
if err != nil {
fmt.Println(err)
return
}
defer sub.Unsubscribe()
sub2, _ := bk.Subscribe("string_message", stringHandler)
defer sub2.Unsubscribe()
fmt.Println("broker server start ... ")
for {
}
}
broker 相關 reflect 基礎
reflect.TypeOf
查看string變量的類型
var s string
t = reflect.TypeOf(s)
fmt.Println("kind", t.Kind()) // string
看看函數的類型
func Add(n1, n2 int) int {
return n1 + n2
}
t := reflect.TypeOf(Add)
fmt.Println("name", t.Name()) // Add() 函數沒有定義類型 所以它是空的
fmt.Println("kind", t.Kind()) // func 它是一個函數
結構體變量的類型
type Animal struct {
Name string
}
func (a *Animal) Show() string {
s := fmt.Sprintf("i am %s", a.Name)
fmt.Println(s)
return s
}
a := Animal{Name:"tiger"}
t = reflect.TypeOf(a)
fmt.Println("name", t.Name()) // Animal
fmt.Println("kind", t.Kind()) // struct
通過 reflect 來調用函數
// 相當於調用了 add(1, 2)
var v reflect.Value
v = reflect.ValueOf(Add)
vals := []reflect.Value{reflect.ValueOf(1), reflect.ValueOf(2)}
ret := v.Call(vals)
ret0 := ret[0].Interface()
fmt.Println(ret0.(int)) // 3
調用成員函數
成員函數第一個參數是對象, 和c++一樣呢, a.Show() ==> Show(&a)
雖然 Animal 的 Show() 函數是沒有參數的。 但是reflect 看來,它是有一個輸入參數的, Call之前記得填上
tiger := Animal{Name:"tiger"}
t = reflect.TypeOf(&tiger)
for n := 0; n < t.NumMethod(); n++ {
m := t.Method(n)
fmt.Println("func Name", m.Name)
if m.Name == "Show" {
// 記得填上第一個參數 &tiger
vals := []reflect.Value{reflect.ValueOf(&tiger)}
ret := m.Func.Call(vals)
fmt.Println(ret)
}
}
有了上面的基礎後,我們看看 go-micro 裏 broker的用法
broker 是消息發佈和訂閱的接口。很簡單的一個例子,因爲服務的節點是不固定的,如果有需要修改所有服務行爲的需求,可以使服務訂閱某個主題,當有信息發佈時,所有的監聽服務都會收到信息,根據你的需要做相應的行爲。
我們來看一個具體的例子。 以下是服務器代碼。啓動後訂閱 “example.topic.pubsub.1” 主題,一旦有消息來了就交給 Sub的Process() 處理。
package main
import (
proto "github.com/micro/examples/pubsub/srv/proto"
"github.com/micro/go-micro"
"github.com/micro/go-micro/metadata"
"github.com/micro/go-micro/server"
"github.com/micro/go-micro/util/log"
"context"
)
// All methods of Sub will be executed when
// a message is received
type Sub struct{}
// Method can be of any name
func (s *Sub) Process(ctx context.Context, event *proto.Event) error {
md, _ := metadata.FromContext(ctx)
log.Logf("[pubsub.1] Received event %+v with metadata %+v\n", event, md)
// do something with event
return nil
}
// Alternatively a function can be used
func subEv(ctx context.Context, event *proto.Event) error {
md, _ := metadata.FromContext(ctx)
log.Logf("[pubsub.2] Received event %+v with metadata %+v\n", event, md)
// do something with event
return nil
}
func main() {
// create a service
service := micro.NewService(
micro.Name("go.micro.srv.pubsub"),
)
// parse command line
service.Init()
// register subscriber
micro.RegisterSubscriber("example.topic.pubsub.1", service.Server(), new(Sub))
// register subscriber with queue, each message is delivered to a unique subscriber
micro.RegisterSubscriber("example.topic.pubsub.2", service.Server(), subEv, server.SubscriberQueue("queue.pubsub"))
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
Subscriber(主題的回調)有下面幾個方便值得關注:
1. 如果是函數,函數原型必須是 func (ctx context.Context, event *proto.Event) error
2. 如果是結構體, 結構體所有的成員函數 原型都必須是 func (ctx context.Context, event *proto.Event) error
3. 如果是結構體,訂閱的主題有消息來到時,結構體所有的成員函數都會被調用,調用的順序取決於 reflect 返回的method方法順序
validateSubscriber 會對回調進行合法性檢查,代碼如下
func validateSubscriber(sub Subscriber) error {
typ := reflect.TypeOf(sub.Subscriber())
var argType reflect.Type
if typ.Kind() == reflect.Func {
name := "Func"
switch typ.NumIn() {
case 2:
argType = typ.In(1)
default:
return fmt.Errorf("subscriber %v takes wrong number of args: %v required signature %s", name, typ.NumIn(), subSig)
}
if !isExportedOrBuiltinType(argType) {
return fmt.Errorf("subscriber %v argument type not exported: %v", name, argType)
}
if typ.NumOut() != 1 {
return fmt.Errorf("subscriber %v has wrong number of outs: %v require signature %s",
name, typ.NumOut(), subSig)
}
if returnType := typ.Out(0); returnType != typeOfError {
return fmt.Errorf("subscriber %v returns %v not error", name, returnType.String())
}
} else {
hdlr := reflect.ValueOf(sub.Subscriber())
name := reflect.Indirect(hdlr).Type().Name()
for m := 0; m < typ.NumMethod(); m++ {
method := typ.Method(m)
switch method.Type.NumIn() {
case 3:
argType = method.Type.In(2)
default:
return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s",
name, method.Name, method.Type.NumIn(), subSig)
}
if !isExportedOrBuiltinType(argType) {
return fmt.Errorf("%v argument type not exported: %v", name, argType)
}
if method.Type.NumOut() != 1 {
return fmt.Errorf(
"subscriber %v.%v has wrong number of outs: %v require signature %s",
name, method.Name, method.Type.NumOut(), subSig)
}
if returnType := method.Type.Out(0); returnType != typeOfError {
return fmt.Errorf("subscriber %v.%v returns %v not error", name, method.Name, returnType.String())
}
}
}
return nil
}
如果是函數,必須有2個參數,第二個參數必須是內置類型或者導出的類型(首字母大寫) 且返回類型必須是 error
這個函數的檢查不夠嚴格。我的 Process2 可以矇混過關
func (s *Sub) Process2(event *pubsub.Event, n int) error {
return nil
}
但是運行的時候會panic
因爲subscriber.go 處理的時候就是認爲 回調 只有以下2種情況
func (ctx context.Context, event *proto.Event) error
func (event *proto.Event) error
Process2 有2個輸入參數,就被當成了 func(ctx, event) error 於是消息到來的時候,就組織了 ctx,event兩個參數來 Call()
go 發現傳入的參數是 ctx, event 而 process2實際接收的卻是 *pubsub.Event 和 int 於是 panic
其實就內部實現而言 go-micro 是支持 一個參數的回調的。
validateSubscriber 做如下修改後
func validateSubscriber(sub Subscriber) error {
...
switch typ.NumIn() {
case 1:
argType = typ.In(0)
case 2:
argType = typ.In(1)
...
} else {
...
switch method.Type.NumIn() {
case 2:
argType = method.Type.In(1)
case 3:
argType = method.Type.In(2)
...
}
就可以使用
func (s *Sub) Process2(event *pubsub.Event) error {
log.Logf("[pubsub.1.2] Received event %+v\n", event)
return nil
}
訂閱過程詳解
micro.RegisterSubscriber("example.topic.pubsub.1", service.Server(), new(Sub))
相當於
ser := service.Server()
sb := ser.NewSubscriber("hello", new(Sub)) // 創建 Subscriber 對象
err := ser.Subscribe(sb) // 把 Subscriber 註冊到 server 中去
type Subscriber interface {
Topic() string
Subscriber() interface{}
Endpoints() []*registry.Endpoint
Options() SubscriberOptions
}
ser.NewSubscriber("hello", new(Sub))
默認是 rpc_server,因此
ser.NewSubscriber("hello", new(Sub))
實際上返回的 Subscriber
Topic() == > "hello"
Subscriber() ==> new(Sub) 就指向是這個 Sub 對象
Endpoints() ==> {Name:Sub.Process Request: 比較複雜晚點解釋 Response:<nil> Metadata:map[subscriber:true topic:hello]}
我的Sub 就一個函數 Process 因此 endpoint 的Name就是 Sub.Process
如果Sub還有一個成員函數 Process2 那麼它的 endpoint 的Name就是 Sub.Process2 Endpoints()就會有2個endpoint
endpoint的 Request 保存的是下面的結構體,它取自於最後一個參數。 比如 Sub.Process的 event *proto.Event
type Value struct {
Name string `json:"name"`
Type string `json:"type"`
Values []*Value `json:"values"`
}
由於proto.Event 是下面這樣的,因此最終的Request 是 {Name:"Event", type:"Event", Values:[ {Name:"id" type:"string"}, {Name:"message" type:"string"}] }
其實也就是一一對應 Event 結構體的成員名字和類型, 它是樹形結構的,如果 Event 下嵌套一個結構體, Request 也會有相應嵌套
syntax = "proto3";
// Example message
message Event {
// unique id
string id = 1;
// message
string message = 2;
}
SubscriberOptions 有4個屬性,以後詳細解釋各屬性的用途
type SubscriberOptions struct {
// AutoAck defaults to true. When a handler returns
// with a nil error the message is acked.
AutoAck bool
Queue string
Internal bool
Context context.Context
}
ser.Subscribe(sb)
只是把 剛剛創建好的 Subscriber 保存到 server中去 ,一直等到 service.Run() 時才做具體的連接 註冊等工作