1. 前言
说到Golang的Redis库,用到最多的恐怕是
redigo 和 go-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) cmdNode(ctx context.Context,cmdInfo *CommandInfo,slot int,
) (*clusterNode, error) {state, err := c.state.Get(ctx)if err != nil {return nil, err}if c.opt.ReadOnly && cmdInfo != nil && cmdInfo.ReadOnly {return c.slotReadOnlyNode(state, slot)}return state.slotMasterNode(slot)
}
func (c *clusterClient) slotReadOnlyNode(state *clusterState, slot int) (*clusterNode, error) {if c.opt.RouteByLatency {return state.slotClosestNode(slot)}if c.opt.RouteRandomly {return state.slotRandomNode(slot)}return state.slotSlaveNode(slot)
}
- 如果ReadOnly = true,只选择Slave Node
- 如果ReadOnly = true 且 RouteByLatency = true 将从slot对应的Master Node 和 Slave Node选择,选择策略为: 选择PING 延迟最低的节点
- 如果ReadOnly = true 且 RouteRandomly = true 将从slot对应的Master Node 和 Slave 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步。
- 将计算command 所属的slot, 根据slot选择合适的Cluster Node
- 将同一个Cluster Node 的所有command,放在一个批次中发送(并发操作)
- 接收结果
注意:这里go-redis 为了处理简单,每个command 只能涉及一个key, 否则你可能会收到如下错误
err CROSSSLOT Keys in request don't hash to the same slot
也就是说go-redis不支持类似 MGET 命令的用法
一个简单的例子:
package mainimport ("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)}
}