Fork me on GitHub

版权声明 本站原创文章 由 萌叔 发表
转载请注明 萌叔 | https://vearne.cc

1. 前言

说到Golang的Redis库,用到最多的恐怕是
redigogo-redis。其中 redigo 不支持对集群的访问。
本文想聊聊go-redis 2个高级用法

2. 开启对Cluster中Slave Node的访问

在一个负载比较高的Redis Cluster中,如果允许对slave节点进行读操作将极大的提高集群的吞吐能力。

开启对Slave 节点的访问,受以下3个参数的影响

type ClusterOptions struct {
    // Enables read-only commands on slave nodes.
    ReadOnly bool
    // Allows routing read-only commands to the closest master or slave node.
    // It automatically enables ReadOnly.
    RouteByLatency bool
    // Allows routing read-only commands to the random master or slave node.
    // It automatically enables ReadOnly.
    RouteRandomly bool
    ... 
}

go-redis 选择节点的逻辑如下

func (c *ClusterClient) cmdSlotAndNode(cmd Cmder) (int, *clusterNode, error) {
    state, err := c.state.Get()
    if err != nil {
        return 0, nil, err
    }

    cmdInfo := c.cmdInfo(cmd.Name())
    slot := cmdSlot(cmd, cmdFirstKeyPos(cmd, cmdInfo))

    if c.opt.ReadOnly && cmdInfo != nil && cmdInfo.ReadOnly {
        if c.opt.RouteByLatency {
            node, err := state.slotClosestNode(slot)
            return slot, node, err
        }

        if c.opt.RouteRandomly {
            node := state.slotRandomNode(slot)
            return slot, node, nil
        }

        node, err := state.slotSlaveNode(slot)
        return slot, node, err
    }

    node, err := state.slotMasterNode(slot)
    return slot, node, err
}
  • 如果ReadOnly = true,只选择Slave Node
  • 如果ReadOnly = true 且 RouteByLatency = true 将从slot对应的Master NodeSlave Node选择,选择策略为: 选择PING 延迟最低的节点
  • 如果ReadOnly = true 且 RouteRandomly = true 将从slot对应的Master NodeSlave Node选择,选择策略为:随机选择

3. 在集群模式下使用pipeline功能

Redis的pipeline功能的原理是 Client通过一次性将多条redis命令发往Redis Server,减少了每条命令分别传输的IO开销。同时减少了系统调用的次数,因此提升了整体的吞吐能力。

我们在主-从模式的Redis中,pipeline功能应该用的很多,但是Cluster模式下,估计还没有几个人用过。
我们知道 redis cluster 默认分配了 16384 个slot,当我们set一个key 时,会用CRC16算法来取模得到所属的slot,然后将这个key 分到哈希槽区间的节点上,具体算法就是:CRC16(key) % 16384。如果我们使用pipeline功能,一个批次中包含的多条命令,每条命令涉及的key可能属于不同的slot

go-redis 为了解决这个问题, 分为3步
源码可以阅读 defaultProcessPipeline
1) 将计算command 所属的slot, 根据slot选择合适的Cluster Node
2)将同一个Cluster Node 的所有command,放在一个批次中发送(并发操作)
3)接收结果

注意:这里go-redis 为了处理简单,每个command 只能涉及一个key, 否则你可能会收到如下错误

err CROSSSLOT Keys in request don't hash to the same slot

也就是说go-redis不支持类似 MGET 命令的用法

一个简单的例子

package main

import (
    "github.com/go-redis/redis"
    "fmt"
)

func main() {
    client := redis.NewClusterClient(&redis.ClusterOptions{
        Addrs: []string{"192.168.120.110:6380", "192.168.120.111:6380"},
        ReadOnly: true,
        RouteRandomly: true,
    })

    pipe := client.Pipeline()
    pipe.HGetAll("1371648200")
    pipe.HGetAll("1371648300")
    pipe.HGetAll("1371648400")
    cmders, err := pipe.Exec()
    if err != nil {
        fmt.Println("err", err)
    }
    for _, cmder := range cmders {
        cmd := cmder.(*redis.StringStringMapCmd)
        strMap, err := cmd.Result()
        if err != nil {
            fmt.Println("err", err)
        }
        fmt.Println("strMap", strMap)
    }
}

后记:

2022年09月16日 需要注意的点
主从模式下,一组只读副本(多个slave实例)如果使用的是高可用域名,由于域名解析的结果在进程中会缓存,因此所有请求可能都被发往了同一个实例。

你需要使用Dialer参数,它会在每次创建连接时被调用。

    options := redis.Options{
        Addr:     "movie.vearne.redis:18429",
        Password: "xxxxxx",
        // Dialer creates new network connection and has priority over
        // Network and Addr options.
        Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
            r := &net.Resolver{}
            host, port, err := net.SplitHostPort(addr)
            if err != nil {
                return nil, err
            }
            ips, err := r.LookupHost(context.Background(), host)
            if err != nil {
                return nil, err
            }
            ip := ips[rand.Intn(len(ips))]
            netDialer := &net.Dialer{
                Timeout:   10 * time.Second,
                KeepAlive: 1 * time.Minute,
            }
            fmt.Println("addr", net.JoinHostPort(ip, port))
            return netDialer.DialContext(ctx, network, net.JoinHostPort(ip, port))
        },
    }
    client := redis.NewClient(&options)

微信公众号

2 对 “聊聊go-redis的一些高级用法”的想法;

  1. Свадебные и вечерние платья нынешнего года отличаются разнообразием.
    Популярны пышные модели до колен из полупрозрачных тканей.
    Детали из люрекса создают эффект жидкого металла.
    Асимметричные силуэты становятся хитами сезона.
    Минималистичные силуэты придают пикантности образу.
    Ищите вдохновение в новых коллекциях — стиль и качество оставят в памяти гостей!
    http://www.frontenginedragsters.org/forum/index.php/topic,205053.new.html#new

  2. Трендовые фасоны сезона нынешнего года вдохновляют дизайнеров.
    Актуальны кружевные рукава и корсеты из полупрозрачных тканей.
    Металлические оттенки придают образу роскоши.
    Асимметричные силуэты становятся хитами сезона.
    Минималистичные силуэты подчеркивают элегантность.
    Ищите вдохновение в новых коллекциях — оригинальность и комфорт оставят в памяти гостей!
    https://kudprathay.go.th/forum/suggestion-box/382180-r-nd-vi-sv-d-bni-f-s-ni-e-g-g-d-vibr-i

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注