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 }