首页 > 技术文章 > redis sentinel哨兵的使用

lanyangsh 2018-07-22 12:25 原文

哨兵模式是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

推荐阅读