go-micro broker 用法 解析

2 篇文章 0 订阅

草稿阶段,持续更新  ...

 

单独使用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()

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值