redis sentinel哨兵的使用

哨兵模式是Redis集群管理的一种方式。
下面以Go语言为例介绍其使用方式。

使用举例

package main
import (
    "fmt"
    "strings"
    "github.com/garyburd/redigo/redis"

    "github.com/FZambia/sentinel"
)

var RedisConnPool *redis.Pool

func InitRedisSentinelConnPool() {
    redisAddr := "192.168.1.11:26378,192.168.1.22:26378"
    redisAddrs := strings.Split(redisAddr, ",")
    masterName := "master1" // 根据redis集群具体配置设置

    sntnl := &sentinel.Sentinel{
        Addrs:      redisAddrs,
        MasterName: masterName,
        Dial: func(addr string) (redis.Conn, error) {
            timeout := 500 * time.Millisecond
            c, err := redis.DialTimeout("tcp", addr, timeout, timeout, timeout)
            if err != nil {
                return nil, err
            }
            return c, nil
        },
    }

    RedisConnPool = &redis.Pool{
        MaxIdle:     redisConfig.MaxIdle,
        IdleTimeout: 240 * time.Second,
        Dial: func() (redis.Conn, error) {
            masterAddr, err := sntnl.MasterAddr()
            if err != nil {
                return nil, err
            }
            c, err := redis.Dial("tcp", masterAddr)
            if err != nil {
                return nil, err
            }
            return c, nil
        },
        TestOnBorrow: CheckRedisRole,
    }
}

func CheckRedisRole(c redis.Conn, t time.Time) error {
    if !sentinel.TestRole(c, "master") {
        return fmt.Errorf("Role check failed")
    } else {
        return nil
    }
}



func main(){
    rc := RedisConnPool.Get()
    defer rc.Close()

    for {
        reply, err := redis.String(rc.Do("RPOP", "/queue/cmd"))
        if err != nil {
            if err != redis.ErrNil {
                log.Println("Redis RPOP failed:", err)
            }

            fmt.Println("reply:", reply)
            break
        }// if

    }// for
}

哨兵方式client端的实现原理

client查询集群中的master节点。

client查询Master
其代码如下:

// MasterAddr returns an address of current Redis master instance.
func (s *Sentinel) MasterAddr() (string, error) {
    res, err := s.doUntilSuccess(func(c redis.Conn) (interface{}, error) {
        return queryForMaster(c, s.MasterName)
    })
    if err != nil {
        return "", err
    }
    return res.(string), nil
}

基本过程是:使用redis 服务器地址,创建连接,发送请求,返回Redis Master地址。

连接redis集群使用的是轮询方式(见doUntilSuccess函数)。
doUntilSuccess函数接收查询master的函数queryForMaster作为参数,queryForMaster的代码如下。

查询master节点

func queryForMaster(conn redis.Conn, masterName string) (string, error) {
    res, err := redis.Strings(conn.Do("SENTINEL", "get-master-addr-by-name", masterName))
    if err != nil {
        return "", err
    }
    if len(res) < 2 {
        return "", errors.New("redigo: malformed get-master-addr-by-name reply")
    }
    masterAddr := net.JoinHostPort(res[0], res[1])
    return masterAddr, nil
}

轮询方式连接服务器doUntilSuccess

基本过程如下:

从Redis服务器地址中选择一台机器,尝试连接,并执行查询操作。如果成功,则直接返回结果。并将这台机器地址活跃性权重提升。

如果第一个地址失败,把这个地址从连接池中去掉,并降低活跃性权重。接着,尝试下一个地址。
如果所有地址都失败,则返回错误。

具体代码如下:

func (s *Sentinel) doUntilSuccess(f func(redis.Conn) (interface{}, error)) (interface{}, error) {
    s.mu.RLock()
    addrs := s.Addrs
    s.mu.RUnlock()

    var lastErr error

    for _, addr := range addrs {
        conn := s.get(addr)
        reply, err := f(conn)
        conn.Close()
        if err != nil {
            lastErr = err
            s.mu.Lock()
            pool, ok := s.pools[addr]
            if ok {
                pool.Close()
                delete(s.pools, addr)
            }
            s.putToBottom(addr)
            s.mu.Unlock()
            continue
        }
        s.putToTop(addr)
        return reply, nil
    }

    return nil, NoSentinelsAvailable{lastError: lastErr}
}

参考

https://github.com/garyburd/redigo
https://github.com/FZambia/go-sentinel

https://godoc.org/github.com/FZambia/go-sentinel

https://redis.io/topics/sentinel-clients

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页