成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

用Go實(shí)現(xiàn)Redis之四實(shí)現(xiàn)Redis的協(xié)議交互

legendmohe / 3543人閱讀

摘要:在本文,將替換文本協(xié)議為版本后的統(tǒng)一協(xié)議。協(xié)議格式在發(fā)送命令和返回結(jié)果中均使用同一套標(biāo)準(zhǔn)協(xié)議。實(shí)現(xiàn)通信協(xié)議版本協(xié)議實(shí)現(xiàn)初探很多相關(guān)的組件模塊工具都有協(xié)議的生成和解析實(shí)現(xiàn),并歷經(jīng)生產(chǎn)環(huán)境的考驗(yàn)。

寫(xiě)在前面

本文實(shí)現(xiàn)的Godis代碼版本為:v0.0.3

在前三篇文章中,實(shí)現(xiàn)了客戶(hù)端/服務(wù)端的交互(基于textprotoco)、服務(wù)端初始化和get/set命令。如果閱讀過(guò)或者調(diào)試過(guò)粗略的代碼實(shí)現(xiàn),會(huì)發(fā)現(xiàn)使用文本協(xié)議進(jìn)行交互,除了容易閱讀之外,解析效率是比較低下的。
因?yàn)槲覀兊氖纠?set alpha 123n",工整的單個(gè)空格和n分割,可能在分割上效率還好;既要分割,不免低效。

在本文,將替換文本協(xié)議為Redis1.2版本后的統(tǒng)一協(xié)議。

Redis通信協(xié)議

Redis通信協(xié)議解析高效、二進(jìn)制安全,同時(shí)也對(duì)人類(lèi)友好(可直接閱讀解析)。

協(xié)議格式

Redis在發(fā)送命令和返回結(jié)果中均使用同一套標(biāo)準(zhǔn)協(xié)議。Reids協(xié)議“肉眼可辨”,在發(fā)送命令是使用類(lèi)型為"multi bulk reply"的協(xié)議類(lèi)型,回復(fù)時(shí)根據(jù)結(jié)果的不同使用不同類(lèi)型協(xié)議。

通過(guò)檢查服務(wù)器發(fā)回?cái)?shù)據(jù)的第一個(gè)字節(jié), 可以確定這個(gè)回復(fù)是什么類(lèi)型:

狀態(tài)回復(fù)(status reply)的第一個(gè)字節(jié)是 "+"

錯(cuò)誤回復(fù)(error reply)的第一個(gè)字節(jié)是 "-"

整數(shù)回復(fù)(integer reply)的第一個(gè)字節(jié)是 ":"

批量回復(fù)(bulk reply)的第一個(gè)字節(jié)是 "$"

多條批量回復(fù)(multi bulk reply)的第一個(gè)字節(jié)是 "*"

舉兩個(gè)例子:

1.客戶(hù)端執(zhí)行命令"set alpha 123", 服務(wù)器返回 "OK"
該類(lèi)型即為狀態(tài)恢復(fù),服務(wù)器返回的結(jié)果封裝為標(biāo)準(zhǔn)協(xié)議是"+OKrn",客戶(hù)端解釋協(xié)議結(jié)果,將之反饋給使用者。

2.還是客戶(hù)端執(zhí)行命令"set alpha 123",在發(fā)送給服務(wù)端時(shí)也是以協(xié)議格式交互的。前文提到發(fā)送命令使用的是”多條批量回復(fù)“類(lèi)型協(xié)議,封裝好的命令就是*3 $3 set $5 alpha $3 123
對(duì)應(yīng)的ASCII碼如下:

符號(hào)"*"標(biāo)識(shí)協(xié)議類(lèi)型是多條批量回復(fù),"rn"為元素分割標(biāo)記;

"$"標(biāo)識(shí)接下來(lái)的是批量回復(fù)協(xié)議,要按照批量回復(fù)格式解析;

"3"代表該批量回復(fù)長(zhǎng)度為3字節(jié);

"set"為批量回復(fù)協(xié)議內(nèi)容;

重復(fù)2-4直到協(xié)議解析完成。

可以看出,協(xié)議的生成和解析可以簡(jiǎn)化理解為兩段文本處理程序。

Godis實(shí)現(xiàn)Redis通信協(xié)議 GO版本協(xié)議實(shí)現(xiàn)初探

很多Redis相關(guān)的GO組件、模塊、工具都有協(xié)議的生成和解析實(shí)現(xiàn),并歷經(jīng)生產(chǎn)環(huán)境的考驗(yàn)。如go-redis、codis等知名項(xiàng)目。
不提性能和擴(kuò)展性,協(xié)議生成的GO代碼可以實(shí)現(xiàn)如下:

//將命令行轉(zhuǎn)換為協(xié)議
func Cmd2Protocol(cmd string) (pro string) {
    //cmd := "set alpha 123"
    ret := strings.Split(cmd, " ")
    //todo validate cmd and params
    for k, v := range ret {
        if k == 0 {
            pro = fmt.Sprintf("*%d
", len(ret))
        }
        pro += fmt.Sprintf("$%d
%s
", len(v), v)
    }
    return
}

以上代碼便可以將命令"set alpha 123"轉(zhuǎn)換為Redis的標(biāo)準(zhǔn)協(xié)議格式。

而協(xié)議的解析,可以拆解為如下流程:

以前文示例,拆解過(guò)程如下:

最終的操作只是多帶帶的數(shù)據(jù)類(lèi)型解析,數(shù)字解析將數(shù)字轉(zhuǎn)成文字、文本解析讀取對(duì)應(yīng)字節(jié)數(shù)量的字符即可。

//將協(xié)議轉(zhuǎn)成argc、argv
func Protocol2Args(protocol string) (argv []string, argc int) {
    parts := strings.Split(strings.Trim(protocol, " "), "
")
    if len(parts) == 0 {
        return nil, 0
    }
    argc, err := strconv.Atoi(parts[0][1:])
    if err != nil {
        return nil, 0
    }
    j := 0
    var vlen []int
    for _, v := range parts[1:] {
        if len(v) == 0 {
            continue
        }
        if v[0] == "$" {
            tmpl, err := strconv.Atoi(v[1:])
            if err == nil {
                vlen = append(vlen, tmpl)
            }
        } else {
            if j < len(vlen) && vlen[j] == len(v) {
                j++
                argv = append(argv, v)
            }
        }
    }
    return argv, argc
}
協(xié)議最終實(shí)現(xiàn)

在實(shí)現(xiàn)協(xié)議的編碼過(guò)程中,一直希望編碼能盡可能簡(jiǎn)單、又有值得思考和改進(jìn)的地方,無(wú)奈能力有限,遠(yuǎn)不如codis的實(shí)現(xiàn)優(yōu)雅。還是覺(jué)得使用codis的實(shí)現(xiàn)方案,才是值得一看的代碼。對(duì)codis的代碼做了部分修改,如果想直接看codis的實(shí)現(xiàn),可以點(diǎn)這里直達(dá)。
在Godis的協(xié)議實(shí)現(xiàn)中,去掉了codis的錯(cuò)誤處理和一部分I/O優(yōu)化,希望盡量讓其看起來(lái)簡(jiǎn)單,希望不會(huì)生硬:)。
主要增加了兩個(gè)包:
其一為共用的帶緩沖I/O包,封裝了ByteReader的一些byte級(jí)操作;
其二為proto包,分別可實(shí)例化為proto.Encoder和proto.Decoder來(lái)處理協(xié)議編解碼。

協(xié)議編碼

將release v0.0.2中的純文本協(xié)議交互改為編碼后的協(xié)議交互:

func send2Server(msg string, conn net.Conn) (n int, err error) {
    p, e := proto.EncodeCmd(msg)
    if e != nil {
        return 0, e
    }
    //fmt.Println("proto encode", p, string(p))
    n, err = conn.Write(p)
    return n, err
}

前文說(shuō)過(guò),編碼使用的協(xié)議類(lèi)型是多條批量回復(fù)。這里仍然以"set alpha 123"命令為例。
首先,拆解字符串為[set alpha 123]三部分(請(qǐng)暫時(shí)忽略異常格式)。三部分分別是一條批量回復(fù),每一部分按照一個(gè)批量回復(fù)格式編碼處理即可。
在proto包,使用如下結(jié)構(gòu)體保存協(xié)議格式和數(shù)據(jù)信息:

type Resp struct {
    Type byte

    Value []byte
    Array []*Resp
}

以上文例子,單條批量回復(fù)"set",填充進(jìn)Resp結(jié)構(gòu)的方法是:

// NewBulkBytes 批量回復(fù)類(lèi)型
func NewBulkBytes(value []byte) *Resp {
    r := &Resp{}
    r.Type = TypeBulkBytes//批量回復(fù)類(lèi)型
    r.Value = value
    return r
}

"set","alpha","123"三條批量回復(fù)構(gòu)成多條批量回復(fù)類(lèi)型的方法如下:

// NewArray 多條批量回復(fù)類(lèi)型
func NewArray(array []*Resp) *Resp {
    r := &Resp{}
    r.Type = TypeArray//多條批量回復(fù)
    r.Array = array
    return r
}

這樣就將[set alpha 123]構(gòu)成了多條批量回復(fù)類(lèi)型的協(xié)議。而在將該多條批量回復(fù)類(lèi)型的協(xié)議編碼的操作偽代碼如下:

// encodeResp 編碼
func (e *Encoder) encodeResp(r *Resp) error {
    if err := e.bw.WriteByte(byte(r.Type)); err != nil {
        return errorsTrace(err)
    }
    switch r.Type {
    case TypeString, TypeError, TypeInt:
        return e.encodeTextBytes(r.Value)
    case TypeBulkBytes:
        return e.encodeBulkBytes(r.Value)
    case TypeArray:
        return e.encodeArray(r.Array)
    default:
        return errorsTrace(e.Err)
    }
}
// encodeArray encode 多條批量回復(fù)
func (e *Encoder) encodeArray(array []*Resp) error {
    if array == nil {
        return e.encodeInt(-1)
    } else {
        if err := e.encodeInt(int64(len(array))); err != nil {
            return err
        }
        for _, r := range array {
            if err := e.encodeResp(r); err != nil {
                return err
            }
        }
        return nil
    }
}

——編碼多條批量回復(fù)的操作是先逐條編碼Resp.Array數(shù)組的元素,比如"set",真正的編碼操作為將"set"長(zhǎng)度、分隔符"rn"和"set"本身分別追加到協(xié)議,
結(jié)果就是$3 set

協(xié)議解碼

協(xié)議生成的過(guò)程只依賴(lài)多條批量回復(fù)類(lèi)型,而客戶(hù)端在解讀服務(wù)端的返回時(shí),會(huì)面臨不同的回復(fù)類(lèi)型:

// decodeResp 根據(jù)返回類(lèi)型調(diào)用不同解析實(shí)現(xiàn)
func (d *Decoder) decodeResp() (*Resp, error) {
    b, err := d.br.ReadByte()
    if err != nil {
        return nil, errorsTrace(err)
    }
    r := &Resp{}
    r.Type = byte(b)
    switch r.Type {
    default:
        return nil, errorsTrace(err)
    case TypeString, TypeError, TypeInt:
        r.Value, err = d.decodeTextBytes()
    case TypeBulkBytes:
        r.Value, err = d.decodeBulkBytes()
    case TypeArray:
        r.Array, err = d.decodeArray()
    }
    return r, err
}

該過(guò)程與編碼過(guò)程操作類(lèi)似,不再贅述。下面的代碼是為服務(wù)端增加協(xié)議解析:

// ProcessInputBuffer 處理客戶(hù)端請(qǐng)求信息
func (c *Client) ProcessInputBuffer() error {
    //r := regexp.MustCompile("[^s]+")
    decoder := proto.NewDecoder(bytes.NewReader([]byte(c.QueryBuf)))
    //decoder := proto.NewDecoder(bytes.NewReader([]byte("*2
$3
get
")))
    if resp, err := decoder.DecodeMultiBulk(); err == nil {
        c.Argc = len(resp)
        c.Argv = make([]*GodisObject, c.Argc)
        for k, s := range resp {
            c.Argv[k] = CreateObject(ObjectTypeString, string(s.Value))
        }
        return nil
    }
    return errors.New("ProcessInputBuffer failed")
}

這里是一些調(diào)試信息:

最后請(qǐng)看添加了協(xié)議實(shí)現(xiàn)之后的演示:

因?yàn)槎际墙?jīng)過(guò)客戶(hù)端/服務(wù)端的編解碼之后的結(jié)果,并不能看出協(xié)議本身的內(nèi)容。感興趣的讀者可以直接編譯本篇的release版本v0.0.3,打開(kāi)調(diào)試日志查看交互過(guò)程的協(xié)議實(shí)現(xiàn)。

本篇問(wèn)題

bufio包的實(shí)現(xiàn)中,涉及到一些GO版本和讀寫(xiě)操作的問(wèn)題,細(xì)節(jié)不容易講清楚;

多帶帶編寫(xiě)的Encoder和Decoder在實(shí)現(xiàn)上有一些效率和擴(kuò)展性問(wèn)題,歡迎討論。

下集預(yù)告

AOF持久化——數(shù)據(jù)保存;

AOF持久化——啟動(dòng)加載。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/28852.html

相關(guān)文章

  • Go 來(lái)了解一下 Redis 通訊協(xié)議

    摘要:用來(lái)了解一下通訊協(xié)議原文地址用來(lái)了解一下通訊協(xié)議都有那么多包來(lái)支撐你使用,那你是否有想過(guò)有了服務(wù)端,有了客戶(hù)端,他們倆是怎樣通訊,又是基于什么通訊協(xié)議做出交互的呢介紹基于我們的目的,本文主要講解和實(shí)踐的通訊協(xié)議的客戶(hù)端和服務(wù)端是通過(guò)連接來(lái)進(jìn) 用 Go 來(lái)了解一下 Redis 通訊協(xié)議 原文地址:用 Go 來(lái)了解一下 Redis 通訊協(xié)議 Go、PHP、Java... 都有那么多包來(lái)支...

    cnsworder 評(píng)論0 收藏0
  • Go實(shí)現(xiàn)Redis之一準(zhǔn)備工作

    摘要:命令實(shí)現(xiàn)命令是最常用的命令之一,也是最能反映緩存發(fā)展歷史的操作。命令在客戶(hù)端接收之后,經(jīng)由協(xié)議轉(zhuǎn)換傳遞給服務(wù)端執(zhí)行。服務(wù)端執(zhí)行命令前先查詢(xún)是否支持該命令,以決定是否執(zhí)行。,是的簡(jiǎn)稱(chēng),代表的是只存增量的持久化方式。 緣起 最近公司的第一個(gè)PHP轉(zhuǎn)GO項(xiàng)目已經(jīng)在生產(chǎn)環(huán)境穩(wěn)定運(yùn)行數(shù)周,又逢需求小年兒,最近可以得空分享下去年學(xué)GO過(guò)程中的練手項(xiàng)目Godis——用Golang實(shí)現(xiàn)的Redis. ...

    zhangke3016 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

legendmohe

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<