go-micro broker 用法 解析

草稿階段,持續更新  ...

 

單獨使用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() 時才做具體的連接 註冊等工作

 

service.Run()

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章