github.com/hdt3213/godis@v1.2.9/cluster/cluster.go (about)

     1  // Package cluster provides a server side cluster which is transparent to client. You can connect to any node in the cluster to access all data in the cluster
     2  package cluster
     3  
     4  import (
     5  	"fmt"
     6  	"runtime/debug"
     7  	"strings"
     8  
     9  	"github.com/hdt3213/rdb/core"
    10  
    11  	"github.com/hdt3213/godis/config"
    12  	database2 "github.com/hdt3213/godis/database"
    13  	"github.com/hdt3213/godis/datastruct/dict"
    14  	"github.com/hdt3213/godis/interface/database"
    15  	"github.com/hdt3213/godis/interface/redis"
    16  	"github.com/hdt3213/godis/lib/consistenthash"
    17  	"github.com/hdt3213/godis/lib/idgenerator"
    18  	"github.com/hdt3213/godis/lib/logger"
    19  	"github.com/hdt3213/godis/lib/pool"
    20  	"github.com/hdt3213/godis/lib/utils"
    21  	"github.com/hdt3213/godis/redis/client"
    22  	"github.com/hdt3213/godis/redis/protocol"
    23  )
    24  
    25  type PeerPicker interface {
    26  	AddNode(keys ...string)
    27  	PickNode(key string) string
    28  }
    29  
    30  // Cluster represents a node of godis cluster
    31  // it holds part of data and coordinates other nodes to finish transactions
    32  type Cluster struct {
    33  	self string
    34  
    35  	nodes           []string
    36  	peerPicker      PeerPicker
    37  	nodeConnections map[string]*pool.Pool
    38  
    39  	db           database.DBEngine
    40  	transactions *dict.SimpleDict // id -> Transaction
    41  
    42  	idGenerator *idgenerator.IDGenerator
    43  	// use a variable to allow injecting stub for testing
    44  	relayImpl func(cluster *Cluster, node string, c redis.Connection, cmdLine CmdLine) redis.Reply
    45  }
    46  
    47  const (
    48  	replicas = 4
    49  )
    50  
    51  // if only one node involved in a transaction, just execute the command don't apply tcc procedure
    52  var allowFastTransaction = true
    53  
    54  // MakeCluster creates and starts a node of cluster
    55  func MakeCluster() *Cluster {
    56  	cluster := &Cluster{
    57  		self:            config.Properties.Self,
    58  		db:              database2.NewStandaloneServer(),
    59  		transactions:    dict.MakeSimple(),
    60  		peerPicker:      consistenthash.New(replicas, nil),
    61  		nodeConnections: make(map[string]*pool.Pool),
    62  
    63  		idGenerator: idgenerator.MakeGenerator(config.Properties.Self),
    64  		relayImpl:   defaultRelayImpl,
    65  	}
    66  
    67  	contains := make(map[string]struct{})
    68  	nodes := make([]string, 0, len(config.Properties.Peers)+1)
    69  	for _, peer := range config.Properties.Peers {
    70  		if _, ok := contains[peer]; ok {
    71  			continue
    72  		}
    73  		contains[peer] = struct{}{}
    74  		nodes = append(nodes, peer)
    75  	}
    76  	nodes = append(nodes, config.Properties.Self)
    77  	cluster.peerPicker.AddNode(nodes...)
    78  	connectionPoolConfig := pool.Config{
    79  		MaxIdle:   1,
    80  		MaxActive: 16,
    81  	}
    82  	for _, p := range config.Properties.Peers {
    83  		peer := p
    84  		factory := func() (interface{}, error) {
    85  			c, err := client.MakeClient(peer)
    86  			if err != nil {
    87  				return nil, err
    88  			}
    89  			c.Start()
    90  			// all peers of cluster should use the same password
    91  			if config.Properties.RequirePass != "" {
    92  				c.Send(utils.ToCmdLine("AUTH", config.Properties.RequirePass))
    93  			}
    94  			return c, nil
    95  		}
    96  		finalizer := func(x interface{}) {
    97  			cli, ok := x.(client.Client)
    98  			if !ok {
    99  				return
   100  			}
   101  			cli.Close()
   102  		}
   103  		cluster.nodeConnections[peer] = pool.New(factory, finalizer, connectionPoolConfig)
   104  	}
   105  	cluster.nodes = nodes
   106  	return cluster
   107  }
   108  
   109  // CmdFunc represents the handler of a redis command
   110  type CmdFunc func(cluster *Cluster, c redis.Connection, cmdLine CmdLine) redis.Reply
   111  
   112  // Close stops current node of cluster
   113  func (cluster *Cluster) Close() {
   114  	cluster.db.Close()
   115  	for _, pool := range cluster.nodeConnections {
   116  		pool.Close()
   117  	}
   118  }
   119  
   120  var router = makeRouter()
   121  
   122  func isAuthenticated(c redis.Connection) bool {
   123  	if config.Properties.RequirePass == "" {
   124  		return true
   125  	}
   126  	return c.GetPassword() == config.Properties.RequirePass
   127  }
   128  
   129  // Exec executes command on cluster
   130  func (cluster *Cluster) Exec(c redis.Connection, cmdLine [][]byte) (result redis.Reply) {
   131  	defer func() {
   132  		if err := recover(); err != nil {
   133  			logger.Warn(fmt.Sprintf("error occurs: %v\n%s", err, string(debug.Stack())))
   134  			result = &protocol.UnknownErrReply{}
   135  		}
   136  	}()
   137  	cmdName := strings.ToLower(string(cmdLine[0]))
   138  	if cmdName == "info" {
   139  		if ser, ok := cluster.db.(*database2.Server); ok {
   140  			return database2.Info(ser, cmdLine[1:])
   141  		}
   142  	}
   143  	if cmdName == "auth" {
   144  		return database2.Auth(c, cmdLine[1:])
   145  	}
   146  	if !isAuthenticated(c) {
   147  		return protocol.MakeErrReply("NOAUTH Authentication required")
   148  	}
   149  
   150  	if cmdName == "multi" {
   151  		if len(cmdLine) != 1 {
   152  			return protocol.MakeArgNumErrReply(cmdName)
   153  		}
   154  		return database2.StartMulti(c)
   155  	} else if cmdName == "discard" {
   156  		if len(cmdLine) != 1 {
   157  			return protocol.MakeArgNumErrReply(cmdName)
   158  		}
   159  		return database2.DiscardMulti(c)
   160  	} else if cmdName == "exec" {
   161  		if len(cmdLine) != 1 {
   162  			return protocol.MakeArgNumErrReply(cmdName)
   163  		}
   164  		return execMulti(cluster, c, nil)
   165  	} else if cmdName == "select" {
   166  		if len(cmdLine) != 2 {
   167  			return protocol.MakeArgNumErrReply(cmdName)
   168  		}
   169  		return execSelect(c, cmdLine)
   170  	}
   171  	if c != nil && c.InMultiState() {
   172  		return database2.EnqueueCmd(c, cmdLine)
   173  	}
   174  	cmdFunc, ok := router[cmdName]
   175  	if !ok {
   176  		return protocol.MakeErrReply("ERR unknown command '" + cmdName + "', or not supported in cluster mode")
   177  	}
   178  	result = cmdFunc(cluster, c, cmdLine)
   179  	return
   180  }
   181  
   182  // AfterClientClose does some clean after client close connection
   183  func (cluster *Cluster) AfterClientClose(c redis.Connection) {
   184  	cluster.db.AfterClientClose(c)
   185  }
   186  
   187  func (cluster *Cluster) LoadRDB(dec *core.Decoder) error {
   188  	return cluster.db.LoadRDB(dec)
   189  }