這是使用gomicro開發微服務系列的第二篇,在上一篇中我只是使用了user-srv和web-srv實現了一個demo,在這裏我將實用consul實現服務發現。若是想直接查閱源碼或者經過demo學習的,能夠訪問ricoder_demo。html
如何編寫一個微服務?這裏用的是go的微服務框架go micro,具體的狀況能夠查閱:http://btfak.com/%E5%BE%AE%E6%9C%8D%E5%8A%A1/2016/03/28/go-micro/node
1、如何安裝consul
個人開發系統採用的是ubunt16.04,這裏就給出ubuntu下安裝consul的步驟:linux
$ wget https://releases.hashicorp.com/consul/0.7.2/consul_0.7.2_linux_amd64.zip $ sudo apt-get install unzip $ ls $ unzip consul_0.7.2_linux_amd64.zip $ sudo mv consul /usr/local/bin/consul $ wget https://releases.hashicorp.com/consul/0.7.2/consul_0.7.2_web_ui.zip $ unzip consul_0.7.2_web_ui.zip $ mkdir -p /usr/share/consul $ mv dist /usr/share/consul/ui
Consul 壓縮包地址:https://www.consul.io/downloads.htmlgit
驗證安裝是否成功的方法:github
$ consul Usage: consul [--version] [--help] <command> [<args>] Available commands are: agent Runs a Consul agent catalog Interact with the catalog event Fire a new event exec Executes a command on Consul nodes force-leave Forces a member of the cluster to enter the "left" state info Provides debugging information for operators. join Tell Consul agent to join cluster keygen Generates a new encryption key keyring Manages gossip layer encryption keys kv Interact with the key-value store leave Gracefully leaves the Consul cluster and shuts down lock Execute a command holding a lock maint Controls node or service maintenance mode members Lists the members of a Consul cluster monitor Stream logs from a Consul agent operator Provides cluster-level tools for Consul operators reload Triggers the agent to reload configuration files rtt Estimates network round trip time between nodes snapshot Saves, restores and inspects snapshots of Consul server state validate Validate config files/directories version Prints the Consul version watch Watch for changes in Consul
啓動consul服務的方法:web
$ consul agent -dev ==> Starting Consul agent... ==> Consul agent running! Version: 'v0.9.3' Node ID: '199ee0e9-db61-f789-b22a-b6b472f63fbe' Node name: 'ricoder' Datacenter: 'dc1' (Segment: '<all>') Server: true (Bootstrap: false) Client Addr: 127.0.0.1 (HTTP: 8500, HTTPS: -1, DNS: 8600) Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302)
優雅的中止服務的方法:json
命令:CTRL+Cubuntu
其餘命令:後端
- consul members:查看集羣成員
- consul info:查看當前服務器的情況
- consul leave:退出當前服務集羣
成功開啓consul服務後能夠登陸後臺訪問地址:http://localhost:8500,以下:api
2、api-srv的開發,實現動態路由
根據官方對api-srv的介紹:The micro api is an API gateway for microservices. Use the API gateway pattern to provide a single entry point for your services. The micro api serves HTTP and dynamically routes to the appropriate backend service. 粗略翻譯的意思就是:api-srv是微服務的網關,使用API網關模式能夠爲咱們的服務提供一個入口,api-srv提供HTTP服務,並動態路由到相應的後端服務。
步驟1:監聽8082端口,並綁定handler處理http請求
mux := http.NewServeMux() mux.HandleFunc("/", handleRPC) log.Println("Listen on :8082") http.ListenAndServe(":8082", mux)
步驟2:實現handler,並實現跨域處理
if r.URL.Path == "/" { w.Write([]byte("ok,this is the server ...")) return } // 跨域處理 if origin := r.Header.Get("Origin"); cors[origin] { w.Header().Set("Access-Control-Allow-Origin", origin) } else if len(origin) > 0 && cors["*"] { w.Header().Set("Access-Control-Allow-Origin", origin) } w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-Token, X-Client") w.Header().Set("Access-Control-Allow-Credentials", "true") if r.Method == "OPTIONS" { return } if r.Method != "POST" { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return }
步驟3:實現將url轉換爲service和method,這裏我採用了pathToReceiver這個函數來處理
p = path.Clean(p) p = strings.TrimPrefix(p, "/") parts := strings.Split(p, "/") // If we've got two or less parts // Use first part as service // Use all parts as method if len(parts) <= 2 { service := ns + strings.Join(parts[:len(parts)-1], ".") method := strings.Title(strings.Join(parts, ".")) return service, method } // Treat /v[0-9]+ as versioning where we have 3 parts // /v1/foo/bar => service: v1.foo method: Foo.bar if len(parts) == 3 && versionRe.Match([]byte(parts[0])) { service := ns + strings.Join(parts[:len(parts)-1], ".") method := strings.Title(strings.Join(parts[len(parts)-2:], ".")) return service, method } // Service is everything minus last two parts // Method is the last two parts service := ns + strings.Join(parts[:len(parts)-2], ".") method := strings.Title(strings.Join(parts[len(parts)-2:], ".")) return service, method
http傳進來的url是http://127.0.0.1:8082/user/userService/SelectUser,我在handler中經過如下方式調用後:
service, method := apid.PathToReceiver(config.Namespace, r.URL.Path)
service和method分別是:
2017/10/14 13:56:12 service:com.class.cinema.user 2017/10/14 13:56:12 method:UserService.SelectUser
注意:var config.Namespace = "com.class.cinema"
步驟4:封裝request,調用服務
br, _ := ioutil.ReadAll(r.Body) request := json.RawMessage(br) var response json.RawMessage req := (*cmd.DefaultOptions().Client).NewJsonRequest(service, method, &request) ctx := apid.RequestToContext(r) err := (*cmd.DefaultOptions().Client).Call(ctx, req, &response)
在這裏Call就是調用相應服務的關鍵。
步驟5:對err進行相應的處理和返回調用結果
// make the call if err != nil { ce := microErrors.Parse(err.Error()) switch ce.Code { case 0: // assuming it's totally screwed ce.Code = 500 ce.Id = service ce.Status = http.StatusText(500) // ce.Detail = "error during request: " + ce.Detail w.WriteHeader(500) default: w.WriteHeader(int(ce.Code)) } w.Write([]byte(ce.Error())) return } b, _ := response.MarshalJSON() w.Header().Set("Content-Length", strconv.Itoa(len(b))) w.Write(b)
經過對err的處理,在請求的method或者service不存在時,如:
會有相應的錯誤信息提示返回到客戶端。
3、跑起服務,查看效果
步驟1:首先要先跑起consul服務發現機制,這樣後期加入的服務才能夠被檢測到,如:
步驟2:跑起user-srv服務,如:
登陸consul後臺,查看服務是否被發現:
能夠從中看到多了一個com.class.cinema.user這個服務
步驟3:經過postman訪問user-srv服務
能夠看到在Body處有數據顯示出來了,再看看服務後臺的日誌輸出
由上面兩個圖能夠看出來,客戶端的請求到達了api-srv,再經過api-srv到達了user-srv。
注意:此處的url的書寫曾經碰見過一個bug,那就是我第一次書寫成了 http://127.0.0.1:8082/user/SelectUser,致使出現這種異常:
有興趣的能夠關注個人我的公衆號 ~