第一部分
首先,转自https://studygolang.com/articles/3314对断言的基本介绍
golang的语言中提供了断言的功能。golang中的所有程序都实现了interface{}的接口,这意味着,所有的类型如string,int,int64甚至是自定义的struct类型都就此拥有了interface{}的接口,这种做法和java中的Object类型比较类似。那么在一个数据通过func funcName(interface{})的方式传进来的时候,也就意味着这个参数被自动的转为interface{}的类型。
如以下的代码:
func funcName(a interface{}) string {
return string(a)
}
编译器将会返回:
cannot convert a (type interface{}) to type string: need type assertion
此时,意味着整个转化的过程需要类型断言。类型断言有以下几种形式:
1)直接断言使用
var a interface{}
fmt.Println("Where are you,Jonny?", a.(string))
但是如果断言失败一般会导致panic的发生。所以为了防止panic的发生,我们需要在断言前进行一定的判断
value, ok := a.(string)
如果断言失败,那么ok的值将会是false,但是如果断言成功ok的值将会是true,同时value将会得到所期待的正确的值。示例:
value, ok := a.(string)
if !ok {
fmt.Println("It's not ok for type string")
return
}
fmt.Println("The value is ", value)
另外也可以配合switch语句进行判断:
var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
fmt.Printf("unexpected type %T", t) // %T prints whatever type t has
break
case bool:
fmt.Printf("boolean %t\n", t) // t has type bool
break
case int:
fmt.Printf("integer %d\n", t) // t has type int
break
case *bool:
fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
break
case *int:
fmt.Printf("pointer to integer %d\n", *t) // t has type *int
break
}
第二部分
net/jsonrpc增加get_client_ip功能
问题描述:falcon-agent无法监测到宿主机ip的变动,导致初始化安装时写在配置里的IP地址与当前真实IP地址不符,产生错误的上报数据(将数据以旧IP上报,新IP查不到任何监控项)。
解决思路:由于agent已大规模部署在服务器上,更新比较麻烦,考虑修改transfer端,从rpc连接中直接获取到agent的IP。
来自open-falcon/falcon-plus/transfer组件中的rpc相关代码:
初始建立RPC server:open-falcon\falcon-plus\modules\transfer\receiver\rpc\rpc.go
package rpc import ( "github.com/open-falcon/falcon-plus/modules/transfer/g" "log" "net" "net/rpc" "net/rpc/jsonrpc" ) func StartRpc() { if !g.Config().Rpc.Enabled { return } addr := g.Config().Rpc.Listen tcpAddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil { log.Fatalf("net.ResolveTCPAddr fail: %s", err) } listener, err := net.ListenTCP("tcp", tcpAddr) if err != nil { log.Fatalf("listen %s fail: %s", addr, err) } else { log.Println("rpc listening", addr) } server := rpc.NewServer() server.Register(new(Transfer)) for { conn, err := listener.Accept() if err != nil { log.Println("listener.Accept occur error:", err) continue } go server.ServeCodec(jsonrpc.NewServerCodec(conn)) } }
这里使用的是jsonrpc的编码解码器,其中会对conn中的数据使用json.Unmarshal解码。
重要的步骤位于jsonrpc/server.go的下列函数中:
1 type ArgsContext interface { 2 Value(key string) interface{} 3 SetValue(key string, value interface{}) 4 } 5 6 func (c *serverCodec) ReadRequestBody(x interface{}) error { 7 if x == nil { 8 return nil 9 } 10 if c.req.Params == nil { 11 return errMissingParams 12 } 13 if args, ok := x.(ArgsContext); ok { 14 args.SetValue("conn", c.c) 15 } 16 // JSON params is array value. 17 // RPC params is struct. 18 // Unmarshal into array containing struct for now. 19 // Should think about making RPC more general. 20 var params [1]interface{} 21 params[0] = x 22 return json.Unmarshal(*c.req.Params, ¶ms) 23 }
1-4行和13-15行是新增的一个断言判断,目的是为了给解析出来的args参数增加一些上下文信息,比如最重要的:将conn对象存入其中。
如此,便可以从rpc的callback函数中访问到conn对象,从而拿到client IP【要求args的类型在定义时实现ArgsContext的接口】。
该思路源自https://github.com/club-codoon/rpcx/blob/630e53bff09759ba2a21644f318907504cfdd98a/_examples/context/server.go,应用方式如下:
1 package main 2 3 import ( 4 "fmt" 5 "net" 6 7 "github.com/smallnest/rpcx" 8 ) 9 10 type Args struct { 11 A int `msg:"a"` 12 B int `msg:"b"` 13 ctx map[string]interface{} 14 } 15 16 type Reply struct { 17 C int `msg:"c"` 18 } 19 20 func (a *Args) Value(key string) interface{} { 21 if a.ctx != nil { 22 return a.ctx[key] 23 } 24 return nil 25 } 26 27 func (a *Args) SetValue(key string, value interface{}) { 28 if a.ctx == nil { 29 a.ctx = make(map[string]interface{}) 30 } 31 a.ctx[key] = value 32 } 33 34 type Arith int 35 36 func (t *Arith) Mul(args *Args, reply *Reply) error { 37 reply.C = args.A * args.B 38 conn := args.Value("conn").(net.Conn) 39 fmt.Printf("Client IP: %s \n", conn.RemoteAddr().String()) 40 return nil 41 } 42 43 func main() { 44 server := rpcx.NewServer() 45 server.RegisterName("Arith", new(Arith)) 46 server.Serve("tcp", "127.0.0.1:8972") 47 }
但是该方法有一个局限,如下的callback场景中,args参数为[]*sometype,是一个slice。
如果是一个struct,则可以方便的按照上述方法添加一个私有的ctx即可存放相关数据,但如果是一个slice,是没办法放一个ctx解决的,那样的话会把slice改为一个struct,从而在json.Unmarshal时失败。
RPC server的callback函数定义:open-falcon\falcon-plus\modules\transfer\receiver\rpc\rpc_transfer.go
import ( "fmt" cmodel "github.com/open-falcon/falcon-plus/common/model" cutils "github.com/open-falcon/falcon-plus/common/utils" "github.com/open-falcon/falcon-plus/modules/transfer/g" "github.com/open-falcon/falcon-plus/modules/transfer/proc" "github.com/open-falcon/falcon-plus/modules/transfer/sender" "strconv" "time" "path/filepath" "crypto/md5" "io" "encoding/hex" ) type Transfer int type TransferResp struct { Msg string Total int ErrInvalid int Latency int64 } func (t *TransferResp) String() string { s := fmt.Sprintf("TransferResp total=%d, err_invalid=%d, latency=%dms", t.Total, t.ErrInvalid, t.Latency) if t.Msg != "" { s = fmt.Sprintf("%s, msg=%s", s, t.Msg) } return s } func (t *Transfer) Update(args []*cmodel.MetricValue, reply *cmodel.TransferResponse) error { return RecvMetricValues(args, reply, "rpc") }
一个workaround思路是,将jsonrpc单拿出来作为一个私有依赖包,更改其中的逻辑,直接将args断言为slice指针类型,并遍历其数据,将client IP放入Endpoint字段中。
【由于transfer的rpc机制只有这里用到了jsonrpc包,所以该workaround可以不影响其他rpc逻辑】:
1 func (c *serverCodec) ReadRequestBody(x interface{}) error { 2 if x == nil { 3 return nil 4 } 5 if c.req.Params == nil { 6 return errMissingParams 7 } 8 // JSON params is array value. 9 // RPC params is struct. 10 // Unmarshal into array containing struct for now. 11 // Should think about making RPC more general. 12 var params [1]interface{} 13 params[0] = x 14 if err := json.Unmarshal(*c.req.Params, ¶ms); err != nil { 15 return err 16 } 17 // fmt.Printf("[jsonrpc]x type is %T \n", x) 18 if args, ok := x.(*[]*cmodel.MetricValue); ok { 19 remote_addr := strings.Split(c.c.(net.Conn).RemoteAddr().String(), ":")[0] 20 if remote_addr != "" { 21 for _, v := range *args { 22 v.Endpoint = remote_addr 23 } 24 } 25 } 26 return nil 27 }