草稿阶段,持续更新 ...
单独使用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() 时才做具体的连接 注册等工作