日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
GO實(shí)現(xiàn)高并發(fā)高可用分布式系統(tǒng):Log微服務(wù)的實(shí)現(xiàn)

GO 實(shí)現(xiàn)高并發(fā)高可用分布式系統(tǒng):Log微服務(wù)的實(shí)現(xiàn)

作者:陳屹 2022-01-10 19:45:40

開發(fā)

架構(gòu)

分布式 在大數(shù)據(jù)時(shí)代,具備高并發(fā),高可用,理解微服務(wù)系統(tǒng)設(shè)計(jì)的人員需求很大,如果你想從事后臺(tái)開發(fā),在JD的描述中最常見的要求就是有所謂的“高并發(fā)”系統(tǒng)開發(fā)經(jīng)驗(yàn)。

創(chuàng)新互聯(lián)建站10多年企業(yè)網(wǎng)站制作服務(wù);為您提供網(wǎng)站建設(shè),網(wǎng)站制作,網(wǎng)頁(yè)設(shè)計(jì)及高端網(wǎng)站定制服務(wù),企業(yè)網(wǎng)站制作及推廣,對(duì)成都酒樓設(shè)計(jì)等多個(gè)方面擁有多年的網(wǎng)站營(yíng)銷經(jīng)驗(yàn)的網(wǎng)站建設(shè)公司。

本文轉(zhuǎn)載自微信公眾號(hào)「Coding迪斯尼」,作者陳屹。轉(zhuǎn)載本文請(qǐng)聯(lián)系Coding迪斯尼公眾號(hào)。

在大數(shù)據(jù)時(shí)代,具備高并發(fā),高可用,理解微服務(wù)系統(tǒng)設(shè)計(jì)的人員需求很大,如果你想從事后臺(tái)開發(fā),在JD的描述中最常見的要求就是有所謂的“高并發(fā)”系統(tǒng)開發(fā)經(jīng)驗(yàn)。但我發(fā)現(xiàn)在市面上并沒有直接針對(duì)“高并發(fā)”,“高可用”的教程,你搜到的資料往往都是只言片語(yǔ),要不就是闡述那些令人摸不著頭腦的理論。但是技術(shù)的掌握必須從實(shí)踐中來,我找了很久發(fā)現(xiàn)很少有指導(dǎo)人動(dòng)手實(shí)踐基于微服務(wù)的高并發(fā)系統(tǒng)開發(fā),因此我希望結(jié)合自己的學(xué)習(xí)和實(shí)踐經(jīng)驗(yàn)跟大家分享一下這方面的技術(shù),特別是要強(qiáng)調(diào)具體的動(dòng)手實(shí)踐來理解和掌握分布式系統(tǒng)設(shè)計(jì)的理論和技術(shù)。

所謂“微服務(wù)”其實(shí)沒什么神奇的地方,它只不過是把我們?cè)瓉砭酆显谝黄鸬哪K分解成多個(gè)獨(dú)立的,基于服務(wù)器程序存在的形式,假設(shè)我們開發(fā)的后臺(tái)系統(tǒng)分為日志,存儲(chǔ),業(yè)務(wù)邏輯,算法邏輯等模塊,以前這些模塊會(huì)聚合成一個(gè)整體形成一個(gè)復(fù)雜龐大的應(yīng)用程序:

這種方式存在很多問題,第一是過多模塊糅合在一起會(huì)使得系統(tǒng)設(shè)計(jì)過于復(fù)雜,因?yàn)槟K直接存在各種邏輯耦合,這使得隨著時(shí)間的推移,系統(tǒng)的開發(fā)和維護(hù)變得越來越困難。第二是系統(tǒng)越來越脆弱,只要其中一個(gè)模塊發(fā)送錯(cuò)誤或奔潰,整個(gè)系統(tǒng)可能就會(huì)垮塌。第三是可擴(kuò)展性不強(qiáng),系統(tǒng)很難通過硬件性能的增強(qiáng)而實(shí)現(xiàn)相應(yīng)擴(kuò)展。

要實(shí)現(xiàn)高并發(fā),高可用,其基本思路就是將模塊拆解,然后讓他們成為獨(dú)立運(yùn)行的服務(wù)器程序,各個(gè)模塊之間通過消息發(fā)送的方式完成配合:

這種模式的好處在于:1,模塊之間解耦合,一個(gè)模塊出問題對(duì)整個(gè)系統(tǒng)影響很小。2,可擴(kuò)展,高可用,我們可以將模塊部署到不同服務(wù)器上,當(dāng)流量增加,我們只要簡(jiǎn)單的增加服務(wù)器數(shù)量就能使得系統(tǒng)的響應(yīng)能力實(shí)現(xiàn)同等擴(kuò)展。3,魯棒性增強(qiáng),由于模塊能備份多個(gè),其中一個(gè)模塊出問題,請(qǐng)求可以重定向到其他同樣模塊,于是系統(tǒng)的可靠性能大大增強(qiáng)。

當(dāng)然任何收益都有對(duì)應(yīng)代價(jià),分布式系統(tǒng)的設(shè)計(jì)開發(fā)相比于原來的聚合性系統(tǒng)會(huì)多出很多難點(diǎn)。例如負(fù)載均衡,服務(wù)發(fā)現(xiàn),模塊協(xié)商,共識(shí)達(dá)成等,分布式算法強(qiáng)調(diào)的就是這些問題的解決,但是理論總是抽象難以理解,倘若不能動(dòng)手實(shí)現(xiàn)一個(gè)高可用高并發(fā)系統(tǒng),你看多少理論都是霧里看花,越看越糊涂,所以我們必須通過動(dòng)手實(shí)踐來理解和掌握理論,首先我們從最簡(jiǎn)單的服務(wù)入手,那就是日志服務(wù),我們將使用GO來實(shí)現(xiàn)。

首先創(chuàng)建根目錄,可以命名為go_distributed_system,后面所有服務(wù)模塊都實(shí)現(xiàn)在該目錄下,然后創(chuàng)建子目錄proglog,進(jìn)去后我們?cè)賱?chuàng)建子目錄internel/server/在這里我們實(shí)現(xiàn)日志服務(wù)的邏輯模塊,首先在internel/server下面執(zhí)行初始化命令:

  
 
 
 
  1. go mod init internal/server

這里開發(fā)的模塊會(huì)被其他模塊引用,所以我們需要?jiǎng)?chuàng)建mod文件。首先我們需要完成日志系統(tǒng)所需的底層數(shù)據(jù)結(jié)構(gòu),創(chuàng)建log.go文件,相應(yīng)代碼如下:

  
 
 
 
  1. package server
  2. import (
  3.     "fmt"
  4.     "sync"
  5. )
  6. type Log struct {
  7.     mu sync.Mutex
  8.     records [] Record 
  9. }
  10. func NewLog() *Log {
  11.     return &Log{ch : make(chan Record),} 
  12. }
  13. func(c *Log) Append(record Record) (uint64, error) {
  14.      c.mu.Lock()
  15.     defer c.mu.Unlock()
  16.     record.Offset = uint64(len(c.records))
  17.     c.records = append(c.records, record)
  18.     return record.Offset, nil 
  19. }
  20. func (c *Log) Read(offset uint64)(Record, error) {
  21.     c.mu.Lock()
  22.     defer c.mu.Unlock()
  23.     if offset >= uint64(len(c.records)) {
  24.         return Record{}, ErrOffsetNotFound 
  25.     }
  26.     return c.records[offset], nil 
  27. }
  28. type Record struct {
  29.     Value []byte `json:"value"`
  30.     Offset uint64 `json:"offset"`
  31. }
  32. var ErrOffsetNotFound = fmt.Errorf("offset not found")

由于我們的日志服務(wù)將以http服務(wù)器程序的方式接收日志讀寫請(qǐng)求,因此多個(gè)讀或?qū)懻?qǐng)求會(huì)同時(shí)執(zhí)行,所以我們需要對(duì)records數(shù)組進(jìn)行互斥操作,因此使用了互斥鎖,在每次讀取records數(shù)組前先獲得鎖,這樣能防止服務(wù)在同時(shí)接收多個(gè)讀寫請(qǐng)求時(shí)破壞掉數(shù)據(jù)的一致性。

所有的日志讀寫請(qǐng)求會(huì)以http POST 和 GET的方式發(fā)起,數(shù)據(jù)通過json來封裝,所以我們下面將創(chuàng)建一個(gè)http服務(wù)器對(duì)象,新建文件http.go,完成如下代碼:

  
 
 
 
  1. package server 
  2. import (
  3.     "encoding/json"
  4.     "net/http"
  5.     "github.com/gorilla/mux"
  6. )
  7. func NewHttpServer(addr string) *http.Server {
  8.     httpsrv := newHttpServer()
  9.     r := mux.NewRouter()
  10.     r.HandleFunc("/", httpsrv.handleLogWrite).Methods("POST")
  11.     r.HandleFunc("/", httpsrv.hadnleLogRead).Methods("GET")
  12.     return &http.Server{
  13.         Addr : addr,
  14.         Handler: r,
  15.     }
  16. }
  17. type httpServer struct{
  18.     Log *Log 
  19. }
  20. func newHttpServer() *httpServer {
  21.     return &httpServer {
  22.         Log: NewLog(),
  23.     }
  24. }
  25. type WriteRequest struct {
  26.     Record Record `json:"record"`
  27. }
  28. type WriteResponse struct {
  29.     Offset uint64 `json:"offset"`
  30. }
  31. type ReadRequest struct {
  32.     Offset uint64 `json:"offset"`
  33. }
  34. type ReadResponse struct {
  35.     Record Record `json:"record"`
  36. }
  37. func (s *httpServer) handleLogWrite(w http.ResponseWriter, r * http.Request) {
  38.     var req WriteRequest 
  39.     //服務(wù)以json格式接收請(qǐng)求
  40.     err := json.NewDecoder(r.Body).Decode(&req)
  41.     if err != nil {
  42.         http.Error(w, err.Error(), http.StatusBadRequest)
  43.         return 
  44.     }
  45.     off, err := s.Log.Append(req.Record)
  46.     if err != nil {
  47.         http.Error(w, err.Error(), http.StatusInternalServerError)
  48.         return 
  49.     }
  50.     res := WriteResponse{Offset: off}
  51.     //服務(wù)以json格式返回結(jié)果
  52.     err = json.NewEncoder(w).Encode(res)
  53.     if err != nil {
  54.         http.Error(w, err.Error(), http.StatusInternalServerError)
  55.         return 
  56.     }
  57. }
  58. func (s *httpServer) hadnleLogRead(w http.ResponseWriter, r *http.Request) {
  59.     var req ReadRequest 
  60.     err := json.NewDecoder(r.Body).Decode(&req)
  61.     if err != nil {
  62.         http.Error(w, err.Error(), http.StatusBadRequest)
  63.         return 
  64.     }
  65.     record, err := s.Log.Read(req.Offset)
  66.     if err == ErrOffsetNotFound {
  67.         http.Error(w, err.Error(), http.StatusNotFound)
  68.         return
  69.     }
  70.     if err != nil {
  71.         http.Error(w, err.Error(), http.StatusInternalServerError)
  72.         return 
  73.     }
  74.     res := ReadResponse{Record: record}
  75.     err = json.NewEncoder(w).Encode(res)
  76.     if err != nil {
  77.         http.Error(w, err.Error(), http.StatusInternalServerError)
  78.         return
  79.     }
  80. }

上面代碼顯示出“分布式”,“微服務(wù)”的特點(diǎn)。相應(yīng)的功能代碼以單獨(dú)服務(wù)器的形式運(yùn)行,通過網(wǎng)絡(luò)來接收服務(wù)請(qǐng)求,這對(duì)應(yīng)“分布式”,每個(gè)獨(dú)立模塊只完成一個(gè)特定任務(wù),這就對(duì)應(yīng)“微服務(wù)”,由于這種方式可以同時(shí)在不同的機(jī)器上運(yùn)行,于是展示了“可擴(kuò)展性”。

同時(shí)服務(wù)既然以http 服務(wù)器的形式存在,因此服務(wù)的請(qǐng)求和返回也要走Http形式,同時(shí)數(shù)據(jù)以Json方式進(jìn)行封裝。同時(shí)實(shí)現(xiàn)的邏輯很簡(jiǎn)單,但有日志寫請(qǐng)求時(shí),我們把請(qǐng)求解析成Record結(jié)構(gòu)體后加入到隊(duì)列末尾,當(dāng)有讀取日志的請(qǐng)求時(shí),我們獲得客戶端發(fā)來的讀取偏移,然后取出對(duì)應(yīng)的記錄,封裝成json格式后返回給客戶。

完成了服務(wù)器的代碼后,我們需要將服務(wù)器運(yùn)行起來,為了達(dá)到模塊化的目的,我們把服務(wù)器的啟動(dòng)放置在另一個(gè)地方,在proglog根目錄下創(chuàng)建cmd/server,在里面添加main.go:

  
 
 
 
  1. package main 
  2. import (
  3.     "log"
  4.     "internal/server"
  5. )
  6. func main() {
  7.     srv := server.NewHttpServer(":8080")
  8.     log.Fatal(srv.ListenAndServe())
  9. }

同時(shí)為了能夠引用internal/server下面的模塊,我們需要在cmd/server下先通過go mod init cmd/server進(jìn)行初始化,然后在go.mod文件中添加如下一行:

  
 
 
 
  1. replace internal/server => ../../internal/server

然后執(zhí)行命令 go mod tidy,這樣本地模塊就知道根據(jù)給定的目錄轉(zhuǎn)換去引用模塊,最后使用go run main.go啟動(dòng)日志服務(wù),現(xiàn)在我們要做的是測(cè)試服務(wù)器的可用性,我們同樣在目錄下創(chuàng)建server_test.go,然后編寫測(cè)試代碼,基本邏輯就是想服務(wù)器發(fā)送日志寫請(qǐng)求,然后再發(fā)送讀請(qǐng)求,并比較讀到的數(shù)據(jù)是否和我們寫入的數(shù)據(jù)一致,代碼如下:

  
 
 
 
  1. package main
  2. import(
  3.     "encoding/json"
  4.     "net/http"
  5.     "internal/server"
  6.     "bytes"
  7.     "testing"
  8.     "io/ioutil"
  9. )
  10. func TestServerLogWrite(t *testing.T) {
  11.     var tests = []struct {
  12.         request server.WriteRequest
  13.         want_response server.WriteResponse 
  14.     } {
  15.         {request: server.WriteRequest{server.Record{[]byte(`this is log request 1`), 0}}, 
  16.          want_response:  server.WriteResponse{Offset: 0, },},
  17.          {request: server.WriteRequest{server.Record{[]byte(`this is log request 2`), 0}}, 
  18.          want_response:  server.WriteResponse{Offset: 1, },},
  19.          {request: server.WriteRequest{server.Record{[]byte(`this is log request 3`), 0}}, 
  20.          want_response:  server.WriteResponse{Offset: 2, },},
  21.     }
  22.     for _, test := range tests {
  23.         //將請(qǐng)求轉(zhuǎn)換成json格式并post給日志服務(wù)
  24.         request := &test.request 
  25.         request_json, err := json.Marshal(request)
  26.         if err != nil {
  27.             t.Errorf("convert request to json fail")
  28.             return 
  29.         }
  30.         resp, err := http.Post("http://localhost:8080", "application/json",bytes.NewBuffer(request_json))
  31.         defer resp.Body.Close()
  32.         if err != nil {
  33.             t.Errorf("http post request fail: %v", err)
  34.             return
  35.         }
  36.         //解析日志服務(wù)返回結(jié)果
  37.         body, err := ioutil.ReadAll(resp.Body)
  38.         var response server.WriteResponse 
  39.         err = json.Unmarshal([]byte(body), &response)
  40.         if err != nil {
  41.             t.Errorf("Unmarshal write response fail: %v", err)
  42.         }
  43.         //檢測(cè)結(jié)果是否與預(yù)期一致
  44.         if response.Offset != test.want_response.Offset {
  45.             t.Errorf("got offset: %d, but want offset: %d", response.Offset, test.want_response.Offset)
  46.         }
  47.     }
  48.     var read_tests = []struct {
  49.         request server.ReadRequest 
  50.         want server.ReadResponse 
  51.     } {
  52.         {request: server.ReadRequest{Offset : 0,}, 
  53.         want: server.ReadResponse{server.Record{[]byte(`this is log request 1`), 0}} },
  54.         {request: server.ReadRequest{Offset : 1,}, 
  55.         want: server.ReadResponse{server.Record{[]byte(`this is log request 2`), 0}} },
  56.         {request: server.ReadRequest{Offset : 2,}, 
  57.         want: server.ReadResponse{server.Record{[]byte(`this is log request 3`), 0}} },
  58.     }
  59.     for _, test := range read_tests {
  60.         request := test.request 
  61.         request_json , err := json.Marshal(request)
  62.         if err != nil {
  63.             t.Errorf("convert read request to json fail")
  64.             return 
  65.         }
  66.         //將請(qǐng)求轉(zhuǎn)換為json并放入GET請(qǐng)求體
  67.         client := &http.Client{}
  68.         req, err := http.NewRequest(http.MethodGet, "http://localhost:8080", bytes.NewBuffer(request_json))
  69.         req.Header.Set("Content-Type", "application/json")
  70.         resp, err := client.Do(req)
  71.         if err != nil {
  72.             t.Errorf("read request fail: %v", err)
  73.             return 
  74.         }
  75.         //解析讀請(qǐng)求返回的結(jié)果
  76.         defer resp.Body.Close()
  77.         body, err := ioutil.ReadAll(resp.Body)
  78.         var response server.ReadResponse
  79.         err = json.Unmarshal([]byte(body), &response)
  80.         if err != nil {
  81.             t.Errorf("Unmarshal read response fail: %v", err)
  82.             return 
  83.         }
  84.         res := bytes.Compare(response.Record.Value, test.want.Record.Value)
  85.         if res != 0 {
  86.             t.Errorf("got value: %q, but want value : %q", response.Record.Value, test.want.Record.Value)
  87.         }
  88.     }
  89. }

完成上面代碼后,使用go test運(yùn)行,結(jié)果如下圖所示:

從結(jié)果看到,我們的測(cè)試能通過,也就是無論是向日志服務(wù)提交寫入請(qǐng)求還是讀取請(qǐng)求,所得的結(jié)果跟我們預(yù)想的一致??偨Y(jié)一下,本節(jié)我們?cè)O(shè)計(jì)了一個(gè)簡(jiǎn)單的JSON/HTTP 日志服務(wù),它能夠接收基于JSON的http寫請(qǐng)求和讀請(qǐng)求,后面我們還會(huì)研究基于gPRC技術(shù)的微服務(wù)開發(fā)技術(shù).

代碼獲取

https://github.com/wycl16514/golang_distribute_system_log_service.git


網(wǎng)站題目:GO實(shí)現(xiàn)高并發(fā)高可用分布式系統(tǒng):Log微服務(wù)的實(shí)現(xiàn)
網(wǎng)頁(yè)網(wǎng)址:http://www.5511xx.com/article/dpdgjsi.html