etcd套路(八)實現服務註冊中心

huxiaobai_001發表於2020-08-25

不喜勿噴!大神請繞道!
這一篇我們來講服務註冊中心的實現,什麼是服務註冊中心?首先你得明白etcd的作用是什麼?它的作用在部落格:etcd套路(一)什麼是etcd 這篇博文當中有詳細的描述,服務註冊發現是它的特色之一,那就看看是如何實現的,篇幅較長內容較多,很多東西我都直接寫到程式碼註釋裡面供大家檢視,因為還不完善沒有實現RoundRobin服務發現以及etcd實現負載均衡(我們利用etcd要實現的目標是服務註冊與發現以及負載均衡功能) 所以撒 先提供部分程式碼供參考,比這程式碼一步步進行起碼這篇博文能讓你知道如何試下etcd服務註冊中心,這也是核心!
上程式碼:

A:demo.proto內容:

syntax = "proto3";
package demo;
service DemoService {
    rpc DemoHandler(DemoRequest) returns (DemoResponse){};
}
message DemoRequest{
    string name = 1;
}
message DemoResponse{
    string name = 1;
}

利用這個proto檔案如何生成demo.pd.go這裡就不說了,前邊的博文都講過了,在grpc的大章節中好幾篇是在講它!

B:有了demo.pd.go介面檔案我們得要去實現啊 家裡grpc服務端
demoserverimp.go實現類檔案(其實沒有類你就把它理解為類檔案就行了)

package rpcserverimpl
import (
"context"
"grpclb/rpcfile"
)
type DemoServiceServerImpl struct {}
func (s *DemoServiceServerImpl) DemoHandler(_ context.Context,request *demo.DemoRequest) (*demo.DemoResponse,error) {
    return &demo.DemoResponse{
        Name: request.Name,
    },nil
}

c.現在我們來看最為核心的etcd當中服務註冊中心的程式碼,也沒啥男的!

package register

import (
    "context"
    "fmt"
    "go.etcd.io/etcd/clientv3"
    "go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
    "log"
    "time"
)

type (
    Register struct {
        key           string          //字首
        client3       *clientv3.Client //etcd的連結
        serverAddress string          //服務地址
        stop          chan bool       //假設一個rpc服務當機了我們需要從etcd當中刪除 利用channel管道技術傳遞bool值
        interval      time.Duration   //心跳週期  我們需要跟etcd保持聯絡 隔一段時間就去聯絡一下證明自己還活著
        leaseTime     int64           //租賃的時間 都是有時間限制的
    }
)

func NewRegister(key string,client3 *clientv3.Client,serverAddress string) *Register{
    return &Register{
        key:           key,
        client3:      client3,
        serverAddress: serverAddress,
        //心跳的週期一定要小於租賃的週期  不然會存在真空期
        interval:    3 * time.Second,
        leaseTime: 15,
        stop:          make(chan bool,1),
    }
}

//註冊
func (r *Register) Reg() {
    k := r.makeKye()
    //心跳
    go func() {
        //每次心跳週期設定的時間都會往通道里面塞入值
        t := time.NewTicker(r.interval)
        //這裡起一個死迴圈 不停的迴圈
        for {
            //租賃 生成租賃合同哦
            lgs, err := r.client3.Grant(context.TODO(), r.leaseTime)
            if nil != err {
                panic(err)
            }
            //判斷key是否存在 存在則更新 不存在則寫入
            if _, err := r.client3.Get(context.TODO(), k);err != nil {
                //如果沒有發現key值的存在
                if err == rpctypes.ErrKeyNotFound {
                    //沒有發現key那就往裡面新增嘍  k就是key+伺服器地址   value就是伺服器地址 然後再來個租賃週期(得有個過期時間啊)
                    if _, err := r.client3.Put(context.TODO(), k, r.serverAddress,clientv3.WithLease(lgs.ID));err != nil {
                        //如果有錯誤那麼我們就直接退出了
                        panic(err)
                    }
                }else {
                        //既然沒發現次key的存在 err還不為空 那就是未知錯誤來處理
                        panic(err)
                }
            }else{
                //有key的存在那麼我們就去更新這個key
                if _, err := r.client3.Put(context.TODO(), k, r.serverAddress, clientv3.WithLease(lgs.ID));err != nil {
                    panic(err)
                }
            }
            //這裡需要對select有深入的理解https://studygolang.com/articles/7203 <-t.C 以及 <-r.stop會一直等待
            select {
            //通道里面沒有值 程式會阻塞在這裡
            case ttl := <-t.C:
                log.Println(ttl)
            //如果收到了停止訊號 則整個協程結束 即心跳結束
            case <-r.stop:
                return
            }

        }
    }()
}

//取消註冊
//比如我的服務端掛掉了 我需要取消key即刪除掉key
func (r *Register) UnReg() {
    //1.首先要停止心跳
    r.stop <- true
    //為了防止多執行緒下出現死鎖的問題  channel管道就是為了協程和協程之間的通訊 上邊設定了true那麼註冊中心裡面的心跳程式就死掉了 初始化一下r.stop
    r.stop = make(chan bool,1)
    k := r.makeKye()
    if _, e := r.client3.Delete(context.TODO(), k);e != nil {
        panic(e)
    }else {
        //列印哥日誌看看嘍
        log.Printf("%s unreg success",k)
    }

    return
}

//生成key策略
func (r *Register) makeKye() string {
    return fmt.Sprintf("%s_%s",r.key,r.serverAddress)
}

套路我們再golang的基礎博文當中就講過了,你就把他理解為一個類(雖然golang當中沒有類的概念),建立一個newxxxx的函式暴露給外界呼叫,在這個newxxxx函式當中調起struct結構體就完事!多麼簡單的套路撒!
裡面註釋已經寫得很清楚了,核心我說一下,其實就一個心跳,有些新朋友會問什麼是心跳啊?就是你的心臟在不停的跳!程式也是一樣的,比如這裡弄了個定時器3s一請求,當然這裡用到了channel管道 協程間通訊用的 不到時間 <-t.C就會阻塞等著 其實跟sleep(3)一樣一樣的效果!這不為了裝逼才這樣寫的嗎!心跳去請求etcd判斷當前的具有一定策略的key是否存在,這個key不是亂寫的哈 自己去看裡面的策略!如果存在則更新租賃週期 如果不存在則建立並且設定租賃週期,當然一個細節需要注意的就是心跳的週期一定是要小於租賃的週期的,因為如果是大於就會出現真空期!那不就扯犢子了!要說的就這些,核心也就這麼多叮叮東西!

d.再來看我們的server.go服務 喲了註冊中心你得連結啊 你的起來rpc服務啊你得去etcd服務啊 草,好麻煩 看程式碼吧:

package main

import (
    "flag"
    "fmt"
    "go.etcd.io/etcd/clientv3"
    "google.golang.org/grpc"
    "grpclb/register"
    demo1 "grpclb/rpcfile"
    "grpclb/rpcserverimpl"
    "log"
    "net"
    "os"
    "os/signal"
    "syscall"
)
var (
    port  = flag.Int("p",50001,"server port")
)
const (
    key string = "vector_rpc_server"
)
func main(){
    //解析標籤
    flag.Parse()
    //形成服務地址端著埠
    serverAddress := fmt.Sprintf("127.0.0.1:%d",*port)

    //=================================================================================================
    //A.監聽rpc服務
    listen, err := net.Listen("tcp", serverAddress)
    if err != nil {
        panic(err)
    }
    //A1.服務監聽成功列印日誌
    log.Printf("start demo rpc listen on %d",*port)
    //A2.老樣子 grpc必須的一步
    server := grpc.NewServer()
    //A3.老樣子 RegisterDemoServiceServer是形成的 pb.go檔案裡面的哦  註冊服務 第二個引數是你實現介面的struct結構體
    demo1.RegisterDemoServiceServer(server,&rpcserverimpl.DemoServiceServerImpl{})

    //=================================================================================================

    //B.註冊etcd
    //B1.服務列表 切片型別
    endpoints := []string{
        "127.0.0.1:2379",
    }
    //B2.例項化etcd服務
    client3,err := clientv3.New(
        clientv3.Config{
            Endpoints:            endpoints,
            //你還可以設定更過的引數 如果開啟了auth驗證 也可以配置username和password
        },
    )
    // conn fail
    if err != nil {
        panic(err)
    }
    //B3.註冊rpc服務到etcd中心
    reg := register.NewRegister(key, client3, serverAddress)
    reg.Reg()

    //=================================================================================================

    //C.善後工作
    //我們的rpc服務有沒有掛掉 伺服器有沒有當機......
    //所以我們需要建立一個通道 告訴etcd我的服務掛掉了 伺服器當機了...... 趕緊把我的合同解約刪掉key 好讓接下里的請求不再調取我的rpc服務
    //所以 我們要接收來自系統的訊號
    ch := make(chan os.Signal,1)
    //接收系統訊號寫到ch管道當中去
    signal.Notify(ch,syscall.SIGTERM,syscall.SIGINT,syscall.SIGKILL,syscall.SIGHUP,syscall.SIGQUIT)
    go func() {
        s := <-ch
        log.Printf("signal.notify %v",s)
    }()

    //=================================================================================================

    //A4.啟動rpc服務   這個地方我們不需要for死迴圈 grpc當中會自動幫助我們實時監聽 所以主程式也不會死掉的!!! 一直在監聽在跑!
    server.Serve(listen)

    //因為我們呼叫的reg()註冊中心裡面使用的是協程 所以這裡我們需要防止主程式死亡導致協程結束 所以讓它睡眠20s
    //time.Sleep(20 * time.Second)
    //fmt.Println("ok")

}

說兩句核心,正如上邊程式碼裡面註釋講的很明白瞭如果還是看不懂只能說明基礎不牢地動山搖!
核心無法就是一個監聽rpc服務 註冊etcd服務 以及 善後工作對系統服務的監控比如當機、rpc服務掛掉、程式被無情的殺死……
最後的server.Serve(listen)還是比較有講究的,因為服務註冊中心當中用到了協程,主程式一死所有的協程統統得死!所以這句話那會一直監聽著rpc埠 也就保證了主程式不死 那麼也就保證了服務註冊中心的子協程不死!你不是我不死大家都不死!就萬事大吉了!

就說到這裡吧!下邊給大家提供一下我的檔案目錄,自己寫的時候可以參考一下:

etcd套路(八)實現服務註冊中心

本作品採用《CC 協議》,轉載必須註明作者和本文連結
胡軍

相關文章