博客
关于我
Golang 实现 Redis(6): 实现 pipeline 模式的 redis 客户端
阅读量:403 次
发布时间:2019-03-05

本文共 3746 字,大约阅读时间需要 12 分钟。

本文将介绍如何实现一个 Redis 客户端,采用 Pipeline 模式来提升吞吐量。Pipeline 模式允许客户端在等待服务端响应之前继续发送下一个请求,从而减少等待网络传输时间。

TCP 作为全双工协议,能够同时进行上行和下行通信。客户端和服务端可以同时发送和接收数据,不会出现冲突。为了确保请求和响应的正确对应,我们为每个 TCP 连接分配了一个 goroutine。服务端接收到的请求会按照先进先出的顺序处理,并返回响应。客户端则通过 channel 向后台协程发送请求,并通过 waitgroup 等待异步处理完成。

Client 结构

type Client struct {    conn        net.Conn    sendingReqs chan *Request    waitingReqs chan *Request    ticker      *time.Ticker    addr        string    ctx         context.Context    cancelFunc  context.CancelFunc    writing     *sync.WaitGroup}type Request struct {    id        uint64    args      [][]byte    reply     redis.Reply    heartbeat bool    waiting   *wait.Wait    err       error}

发送请求

调用者将请求发送给后台协程,并通过 waitgroup 等待异步处理完成:

func (client *Client) Send(args [][]byte) redis.Reply {    request := &Request{        args:      args,        heartbeat: false,        waiting:   &wait.Wait{},    }    request.waiting.Add(1)    client.sendingReqs <- request    timeout := request.waiting.WaitWithTimeout(maxWait)    if timeout {        return reply.MakeErrReply("server time out")    }    if request.err != nil {        return reply.MakeErrReply("request failed: " + err.Error())    }    return request.reply}

核心逻辑

写协程

// 写协程入口func (client *Client) handleWrite() {loop:    for {        select {        case req := <-client.sendingReqs:            client.writing.Add(1)            client.doRequest(req)        case <-client.ctx.Done():            break loop        }    }}

发送请求

func (client *Client) doRequest(req *Request) {    bytes := reply.MakeMultiBulkReply(req.args).ToBytes()    _, err := client.conn.Write(bytes)    i := 0    for err != nil && i < 3 {        err = client.handleConnectionError(err)        if err == nil {            _, err = client.conn.Write(bytes)        }        i++    }    if err == nil {        client.waitingReqs <- req    } else {        req.err = err        req.waiting.Done()        client.writing.Done()    }}

读协程

// 收到服务端的响应func (client *Client) finishRequest(reply redis.Reply) {    request := <-client.waitingReqs    request.reply = reply    if request.waiting != nil {        request.waiting.Done()    }    client.writing.Done()}

启动 client

func MakeClient(addr string) (*Client, error) {    conn, err := net.Dial("tcp", addr)    if err != nil {        return nil, err    }    ctx, cancel := context.WithCancel(context.Background())    return &Client{        addr:        addr,        conn:        conn,        sendingReqs: make(chan *Request, chanSize),        waitingReqs: make(chan *Request, chanSize),        ctx:         ctx,        cancelFunc:  cancel,        writing:     &sync.WaitGroup{},    }, nil}func (client *Client) Start() {    client.ticker = time.NewTicker(10 * time.Second)    go client.handleWrite()    go func() {        err := client.handleRead()        logger.Warn(err)    }()    go client.heartbeat()}

关闭 client

func (client *Client) Close() {    close(client.sendingReqs)    client.writing.Wait()    _ = client.conn.Close()    client.cancelFunc()    close(client.waitingReqs)}

测试

func TestClient(t *testing.T) {    client, err := MakeClient("localhost:6379")    if err != nil {        t.Error(err)    }    client.Start()    result := client.Send([][]byte{        []byte("SET"),        []byte("a"),        []byte("a"),    })    if statusRet, ok := result.(*reply.StatusReply); ok {        if statusRet.Status != "OK" {            t.Error("`set` failed, result: " + statusRet.Status)        }    }    result := client.Send([][]byte{        []byte("GET"),        []byte("a"),    })    if bulkRet, ok := result.(*reply.BulkReply); ok {        if string(bulkRet.Arg) != "a" {            t.Error("`get` failed, result: " + string(bulkRet.Arg))        }    }}

通过这种 Pipeline 模式,客户端可以在等待响应之前继续发送更多请求,大幅提高吞吐量。

转载地址:http://nxqzz.baihongyu.com/

你可能感兴趣的文章
Python“文件存在“制作目录时出错
查看>>
Python、IMAP 和 GMail.将消息标记为已查看
查看>>
python一个一个获取迭代器的元素
查看>>
python一维数组 提取_python – 如何从numpy中的1维数组中提取零维切片
查看>>
python三大库之--numpy(一)
查看>>
python三大库之--numpy(二)
查看>>
python上的字无效字符_标识中的无效字符
查看>>
python下用decode和encode 中文转码
查看>>
Python下调用Linux的Shell命令
查看>>
Python接口自动化测试实战系列
查看>>
python下载_安装_配置_以及第一行python程序---python工作笔记009
查看>>
Python不协调机器人消息.内容
查看>>
Python与C#之间内存共享互传信息
查看>>
python与c语言混合编程_你试过C语言和Python一起混合编程吗?两者相加不是已经无敌了!...
查看>>
python与excel
查看>>
Python与Excel的完美邂逅:深入浅出,手把手教你轻松玩转Excel数据操作
查看>>
python与flask_Python编程中flask的简介与简单使用
查看>>
python与图灵机器人交互(WXPY版本)
查看>>
python与运算符_python运算符
查看>>
python两个列表获取交集,并集,差集
查看>>