這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。這是使用gomicro開發微服務系列的第二篇,在上一篇中我只是使用了user-srv和web-srv實現了一個demo,在這裡我將實用consul實現服務發現。如果想直接查閱源碼或者通過demo學習的,可以訪問[ricoder_demo](https://github.com/wiatingpub/demo)。如何編寫一個微服務?這裡用的是go的微服務架構go micro,具體的情況可以查閱:http://btfak.com/%E5%BE%AE%E6%9C%8D%E5%8A%A1/2016/03/28/go-micro/##### 一、如何安裝consul我的開發系統採用的是ubunt16.04,這裡就給出ubuntu下安裝consul的步驟:```bash$ 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.html>驗證安裝是否成功的方法:```basic$ consulUsage: 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服務的方法:```bash$ 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)```優雅的停止服務的方法:命令:CTRL+C其他命令:- consul members:查看叢集成員- consul info:查看當前伺服器的狀況- consul leave:退出當前服務叢集成功開啟consul服務後可以登入後台訪問地址:[http://localhost:8500](http://localhost:8500/),如下:#### 二、api-srv的開發,實現動態路由根據官方對api-srv的介紹:The **micro api** is an API gateway for microservices. Use the API gateway [pattern](http://microservices.io/patterns/apigateway.html) 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 Gateway模式可以為我們的服務提供一個入口,api-srv提供HTTP服務,並動態路由到相應的後端服務。步驟1:監聽8082連接埠,並綁定handler處理http請求```gomux := http.NewServeMux()mux.HandleFunc("/", handleRPC)log.Println("Listen on :8082")http.ListenAndServe(":8082", mux)```步驟2:實現handler,並實現跨域處理```goif 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這個函數來處理```gop = 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 methodif 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.barif 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 partsservice := 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分別是:```bash2017/10/14 13:56:12 service:com.class.cinema.user2017/10/14 13:56:12 method:UserService.SelectUser```注意:var config.Namespace = "com.class.cinema"步驟4:封裝request,調用服務```gobr, _ := ioutil.ReadAll(r.Body)request := json.RawMessage(br)var response json.RawMessagereq := (*cmd.DefaultOptions().Client).NewJsonRequest(service, method, &request)ctx := apid.RequestToContext(r)err := (*cmd.DefaultOptions().Client).Call(ctx, req, &response)```在這裡Call就是調用相應服務的關鍵。步驟5:對err進行相應的處理和返回調用結果```go// make the callif err != nil {ce := microErrors.Parse(err.Error())switch ce.Code {case 0:// assuming it's totally screwedce.Code = 500ce.Id = servicece.Status = http.StatusText(500)// ce.Detail = "error during request: " + ce.Detailw.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不存在時,如:會有相應的錯誤資訊提示返回到用戶端。#### 三、跑起服務,查看效果步驟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,導致出現這種異常:**有興趣的可以關注我的個人公眾號 ~**474 次點擊