第一部分java
首先,轉自https://studygolang.com/articles/3314對斷言的基本介紹git
golang的語言中提供了斷言的功能。golang中的全部程序都實現了interface{}的接口,這意味着,全部的類型如string,int,int64甚至是自定義的struct類型都就此擁有了interface{}的接口,這種作法和java中的Object類型比較相似。那麼在一個數據經過func funcName(interface{})的方式傳進來的時候,也就意味着這個參數被自動的轉爲interface{}的類型。github
如如下的代碼:golang
func funcName(a interface{}) string { return string(a) }
編譯器將會返回:json
cannot convert a (type interface{}) to type string: need type assertionruby
此時,意味着整個轉化的過程須要類型斷言。類型斷言有如下幾種形式:服務器
1)直接斷言使用tcp
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 }