微服務架構的演進和go的初步實踐

零、背景

近一段時間在學習和實踐用go來實現微服務架構的開發,本文來記錄下什麼狀況下要使用微服務架構,分析下利弊。而且用grpc初步實現微服務的模型。java

1、服務端架構的演進

一、單體架構

在 Web 應用程序發展的早期,大部分工程是將全部的服務端功能模塊打包成單個巨石型應用,最終會造成以下圖所示的架構。golang

圖片14.png

優勢:算法

  • 開發簡單
  • 技術單一
  • 部署方便

缺點:數據庫

  • 隨着業務的發展,應用會愈來愈龐大
  • 技術棧單一,不易擴展
  • 牽一髮而動全身

二、垂直分層架構

隨着單體應用愈來愈龐大,單體架構中不一樣業務模塊的差別就會顯現,將大應用拆分紅一個個單體結構的應用。垂直分層是一個典型的對複雜系統進行結構化思考和抽象聚合的通用性方法。編程

圖片15.png

優勢:windows

  • 分擔部分流量
  • 服務間相互獨立,能夠針對單個服務模塊進行優化
  • 易於水平擴展

缺點:網絡

  • 集羣搭建變得複雜
  • 可能存在大量耦合代碼,調用關係錯綜複雜
  • 難以維護

三、微服務架構

微服務是一種小型的SOA架構(面向服務的架構),其理念是將業務系統完全地組件化和服務化,造成多個能夠獨立開發、部署和維護的服務或者應用的集合,以應對更快的需求變動和更短的開發迭代週期。架構

圖片17.png

優勢:併發

  • 服務模塊解耦
  • 團隊分工更容易,更明確
  • 獨立部署,可針對獨立模塊進行發佈
  • 擴展能力強

缺點:負載均衡

  • 服務劃分標準多樣
  • 增長系統複雜度
  • 部署更復雜
  • 對整個團隊的要求更高

微服務特色:

  • 在分佈式環境中,將單體應用拆分爲一系列服務,共同組成整個系統。
  • 每一個服務都是輕量級,單獨部署。
  • 每一個微服務注重本身的核心能力的開發,微服務組件之間採用RPC輕量級通訊方式進行通訊。
  • 按照業務邊界進行劃分。
  • 微服務是一種編程架構思想,有不一樣的語言實現。

2、微服務實踐

一、語言選擇

近幾年,隨着微服務架構的火熱,也誕生了不少微服務框架,如 Java 語言的 Spring Cloud、Go 語言的 Go Kit 和 Go Micro 以及 Node.js 的 Seneca。充分說明了微服務架構的火熱態勢。雖然微服務架構的實踐落地獨立於編程語言,可是 Go 語言在微服務架構的落地中仍有其獨特的優點。所以,Go 語言的微服務框架也相繼涌現,各方面都較爲優秀的有 Go Kit 和 Go Micro 等。

Go 語言十分輕量,運行效率極高,原生支持併發編程,相較其餘語言主要有如下優點:

  • 語法簡單,上手快。Go 提供的語法十分簡潔,關鍵字只有 25 個,幾乎支持大多數現代編程語言常見的特性。
  • 原生支持併發,協程模型是很是優秀的服務端模型。
  • 豐富的標準庫。Go 目前內置了大量的庫,開箱即用,很是方便。
  • 部署方便,編譯包小,可直接編譯成機器碼,不依賴其餘庫。
  • 簡言之,用 Go 語言實現微服務,在性能、易用性和生態等方面都擁有優點。

二、服務間的通訊

1)RPC

遠程過程調用(Remote Procedure Call)是一個計算機通訊協議。該協議容許運行於一臺計算機的程序調用另外一臺計算機的子程序。簡單地說就是能使應用像調用本地方法同樣的調用遠程的過程或服務。

2)Protobuf

Protobuf 是由 Google 開源的消息傳輸協議,用於將結構化的數據序列化、反序列化經過網絡進行傳輸。Protobuf 首先解決的是如何在不一樣語言之間傳遞數據的問題,目前支持的編程語言有C++、Java、Python、Objective-C、C#、JavaScript、Ruby、Go、PHP 等。

對比XML、JSON

優勢:XML、JSON 也能夠用來存儲此類結構化數據,可是使用ProtoBuf表示的數據能更加高效,而且將數據壓縮得更小。(比XML小3-10倍,快20-100倍)

缺點:因爲是二進制,沒法直接閱讀

3)grpc

gRPC是由Google公司開源的一款高性能的遠程過程調用(RPC)框架。

grpc結合protobuf實現遠程服務調用的使用步驟展現

0一、定義.proto文件
syntax = "proto3";  
package services;  
​  
//訂單請求參數  
message OrderRequest {  
 string orderId = 1;  
 int64 timeStamp = 2;  
}  
​  
//訂單信息  
message OrderInfo {  
 string OrderId = 1;  
 string OrderName = 2;  
 string OrderStatus = 3;  
}  
​  
//訂單服務service定義  
service OrderService{  
 rpc GetOrderInfo(OrderRequest) returns (OrderInfo);  
}
0二、工具生成go代碼

protoc --go_out=plugins=grpc:. *.proto

0三、編寫方法體的業務實現
func (os \*OrderServiceImpl) GetOrderInfo(ctx context.Context, request \*OrderRequest) (\*OrderInfo, error) {  
 fmt.Println("收到請求訂單號:", request.OrderId)  
​  
 return &OrderInfo{OrderId: request.OrderId, OrderName: "訂單名稱", OrderStatus: "已經支付"}, nil  
}
0四、service端啓動服務
addr :\= "127.0.0.1:8972"  
 rpcServer :\= grpc.NewServer()  
 services.RegisterOrderServiceServer(rpcServer, new(services.OrderServiceImpl))  
 listen, err :\= net.Listen("tcp", addr)  
 if err != nil {  
 log.Fatalf("啓動網絡監聽失敗 %v\\n", err)  
 }  
​  
 //啓動服務  
 if err :\= rpcServer.Serve(listen); err != nil {  
 fmt.Println("啓動錯誤", err)  
 } else {  
 fmt.Println("服務開啓")  
 }
0五、client調用
addr :\= "127.0.0.1:8972"  
 conn, err :\= grpc.Dial(addr, grpc.WithInsecure())  
 if err != nil {  
 log.Fatalf("鏈接GRPC服務端失敗 %v\\n", err)  
 }  
 defer conn.Close()  
​  
 //實例客戶端  
 orderServiceClient :\= sOrder.NewOrderServiceClient(conn)  
 //模擬訂單號  
 orderId :\= "order\_" + strconv.Itoa(time.Now().Second())  
 //組織請求體  
 orderRequest :\= &sOrder.OrderRequest{OrderId: orderId, TimeStamp: time.Now().Unix()}  
 //rpc調用GetOrderInfo  
 orderInfo, err :\= orderServiceClient.GetOrderInfo(context.Background(), orderRequest)  
 if orderInfo != nil {  
 fmt.Println(orderInfo.GetOrderId(), orderInfo.GetOrderName(), orderInfo.GetOrderStatus())  
 return orderInfo.GetOrderStatus()  
 } else {  
 return "訂單服務讀取失敗"  
 }

三、服務註冊中心

在微服務架構中,通常每個服務都是有多個拷貝,來保證高可用。一個服務隨時可能下線,也可能應對臨時訪問壓力增長新的服務節點。這就出現了新的問題:

  • 服務之間如何相互感知?例若有新的服務實例上線,已上線的實例如何知道並與之通訊。
  • 服務如何管理?服務實例數量多了,也面臨着如何管理的問題。

image-20200718205040141

服務註冊

service啓動時向註冊中心註冊

健康檢查

註冊中心和service之間會保持心跳檢查,來維護註冊表裏的service是否存活

服務發現

client向註冊中心獲取可用的service

  • register 在每一個服務啓動時會向服務註冊中心上報本身的網絡位置。這樣,在服務發現中心內部會造成一個服務註冊表服務註冊表是服務發現的核心部分,是包含全部服務實例的網絡地址的數據庫。
  • healthy check 註冊中心與各微服務節點間保持心跳檢測,來保證服務註冊表的服務都是可用狀態。
  • discover 當須要對某服務進行請求時,服務實例經過該註冊表,定位目標服務網絡地址。若目標服務存在多個網絡地址,則使用負載均衡算法從多個服務實例中選擇出一個,而後發出請求。

註冊中心中間件的比較

Consul

Consul 是 HashiCorp 公司推出的開源工具,用於實現分佈式系統的服務發現與配置。Consul 使用 Go 語言編寫,所以具備自然可移植性(支持Linux、windows和Mac OS X)。Consul 內置了服務註冊與發現框架、分佈一致性協議實現、健康檢查、Key/Value 存儲、多數據中心方案,再也不須要依賴其餘工具,使用起來也較爲簡單。

Etcd

etcd 是用 go 開發的,出現的時間並不長,不像 zookeeper 那麼悠久和有名,可是前景很是好。etcd 是由於 kubernetes 而被人熟知的,kubernetes 的 kube master 使用 etcd 做爲分佈式存儲獲取分佈式鎖,這爲 etcd 的強大作了背書。etcd 使用 RAFT 算法實現的一致性,比 zookeeper 的 ZAB 算法更簡單

Zookeeper

zookeeper 起源於 Hadoop,後來進化爲 Apache 的頂級項目。如今已經被普遍使用在 Apache 的項目中,例如 Hadoop,kafka,solr 等等。是用java 開發的,部署的時候要裝JAVA環境。歷史悠久,功能豐富,因此比較重,易用性不如以上2個。

image-20200718214015685

代碼展現

我暫時選用了etcd來作註冊中心,如下展現加入註冊中心後的grpc調用代碼。

service端
package main

import (
    "fmt"
    "google.golang.org/grpc"
    "goshare/etcd"
    "grpc-service/services"
    "log"
    "net"
)

func main() {
    addr := "127.0.0.1:8972"
    rpcServer := grpc.NewServer()
    services.RegisterOrderServiceServer(rpcServer, new(services.OrderServiceImpl))
    listen, err := net.Listen("tcp", addr)
    if err != nil {
        log.Fatalf("啓動網絡監聽失敗 %v\n", err)
    }

    //etcd服務註冊
    reg, err := etcd.NewService(etcd.ServiceInfo{
        Name: "mirco.service.order",
        IP:   addr, //grpc服務節點ip
    }, []string{"172.24.132.232:2379"}) // etcd的節點ip
    if err != nil {
        log.Fatal(err)
    }
    go reg.Start()

    //啓動服務
    if err := rpcServer.Serve(listen); err != nil {
        fmt.Println("啓動錯誤", err)
    } else {
        fmt.Println("服務開啓")
    }
}
client端
r := etcd.NewResolver([]string{"172.24.132.232:2379"}, "mirco.service.order")
    resolver.Register(r)

    addr := fmt.Sprintf("%s:///%s", r.Scheme(), "")
    //addr := "127.0.0.1:8972"
    fmt.Println("addr", addr)

    conn, err := grpc.Dial(addr, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("鏈接GRPC服務端失敗 %v\n", err)
    }
    defer conn.Close()

    orderServiceClient := sOrder.NewOrderServiceClient(conn)

    orderId := "order_" + strconv.Itoa(time.Now().Second())
    orderRequest := &sOrder.OrderRequest{OrderId: orderId, TimeStamp: time.Now().Unix()}
    orderInfo, err := orderServiceClient.GetOrderInfo(context.Background(), orderRequest)
    if orderInfo != nil {
        fmt.Println(orderInfo.GetOrderId(), orderInfo.GetOrderName(), orderInfo.GetOrderStatus())
        return orderInfo.GetOrderStatus()
    } else {
        return "訂單服務讀取失敗"
    }

總結

以上就實現了註冊中心來作服務註冊和發現,咱們測試效果的時候能夠啓用2個service,一個監聽8972,一個監聽8973。client經過註冊中心(172.24.132.232:2379)來作服務發現,當2個service其中任何一個關閉,咱們的服務依然能正常提供服務,實現了高可用。
image.png

tips:另外推薦你們本人蔘與的而且以爲不錯的學習渠道
一個是拉鉤教育上的《Go微服務實戰38講》98元。
image.png
另外一個是58沈劍的《架構師訓練營》399元。微服務對架構知識體系由必定要求,這個課程是沈老師10多年的架構經驗成體系的整理,能夠幫助咱們從簡到深的理解架構層面的知識。另外若是不花錢報課,每週基本都有免費的互聯網架構直播課,通常1個多小時,都是乾貨知識。
image.png

相關文章
相關標籤/搜索