github.com/matrixorigin/matrixone@v1.2.0/pkg/proxy/router.go (about) 1 // Copyright 2021 - 2023 Matrix Origin 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package proxy 16 17 import ( 18 "context" 19 "time" 20 21 "github.com/fagongzi/goetty/v2" 22 "github.com/matrixorigin/matrixone/pkg/clusterservice" 23 "github.com/matrixorigin/matrixone/pkg/common/moerr" 24 "github.com/matrixorigin/matrixone/pkg/frontend" 25 "github.com/matrixorigin/matrixone/pkg/logutil" 26 "github.com/matrixorigin/matrixone/pkg/pb/metadata" 27 pb "github.com/matrixorigin/matrixone/pkg/pb/proxy" 28 v2 "github.com/matrixorigin/matrixone/pkg/util/metric/v2" 29 "github.com/matrixorigin/matrixone/pkg/vm/engine/disttae/route" 30 "go.uber.org/zap" 31 ) 32 33 const ( 34 tenantLabelKey = "account" 35 ) 36 37 var ( 38 // noCNServerErr indicates that there are no available CN servers. 39 noCNServerErr = moerr.NewInternalErrorNoCtx("no available CN server") 40 ) 41 42 // Router is an interface to select CN server and connects to it. 43 type Router interface { 44 // Route selects the best CN server according to the clientInfo. 45 // This is the only method that allocate *CNServer, and other 46 // SelectXXX method in this interface select CNServer from the 47 // ones it allocated. 48 // filter is a function which is used to do more checks whether the 49 // CN server is a proper one. If it returns true, means that CN 50 // server is not a valid one. 51 Route(ctx context.Context, client clientInfo, filter func(string) bool) (*CNServer, error) 52 53 // SelectByConnID selects the CN server which has the connection ID. 54 SelectByConnID(connID uint32) (*CNServer, error) 55 56 // Connect connects to the CN server and returns the connection. 57 // It should take a handshakeResp as a parameter, which is the auth 58 // request from client including tenant, username, database and others. 59 Connect(c *CNServer, handshakeResp *frontend.Packet, t *tunnel) (ServerConn, []byte, error) 60 } 61 62 // RefreshableRouter is a router that can be refreshed to get latest route strategy 63 type RefreshableRouter interface { 64 Router 65 66 Refresh(sync bool) 67 } 68 69 // CNServer represents the backend CN server, including salt, tenant, uuid and address. 70 // When there is a new client connection, a new CNServer will be created. 71 type CNServer struct { 72 // connID is the backend CN server's connection ID, which is global unique 73 // and is tracked in connManager. 74 connID uint32 75 // salt is generated in proxy module and will be sent to backend 76 // server when build connection. 77 salt []byte 78 // reqLabel is the client requests, but not the label which CN server really has. 79 reqLabel labelInfo 80 // cnLabel is the labels that CN server has. 81 cnLabel map[string]metadata.LabelList 82 // hash keep the hash in it. 83 hash LabelHash 84 // uuid of the CN server. 85 uuid string 86 // addr is the net address of CN server. 87 addr string 88 // internalConn indicates the connection is from internal network. Default is false, 89 internalConn bool 90 91 // clientAddr is the real client address. 92 clientAddr string 93 } 94 95 // Connect connects to backend server and returns IOSession. 96 func (s *CNServer) Connect(logger *zap.Logger, timeout time.Duration) (goetty.IOSession, error) { 97 c := goetty.NewIOSession( 98 goetty.WithSessionCodec(frontend.NewSqlCodec()), 99 goetty.WithSessionLogger(logger), 100 ) 101 err := c.Connect(s.addr, timeout) 102 if err != nil { 103 logutil.Errorf("failed to connect to cn server, timeout: %v, conn ID: %d, cn: %s, error: %v", 104 timeout, s.connID, s.addr, err) 105 return nil, newConnectErr(err) 106 } 107 if len(s.salt) != 20 { 108 return nil, moerr.NewInternalErrorNoCtx("salt is empty") 109 } 110 info := pb.ExtraInfo{ 111 Salt: s.salt, 112 InternalConn: s.internalConn, 113 ConnectionID: s.connID, 114 Label: s.reqLabel.allLabels(), 115 ClientAddr: s.clientAddr, 116 } 117 data, err := info.Encode() 118 if err != nil { 119 return nil, err 120 } 121 // When build connection with backend server, proxy send its salt, request 122 // labels and other information to the backend server. 123 if err := c.Write(data, goetty.WriteOptions{Flush: true}); err != nil { 124 return nil, err 125 } 126 return c, nil 127 } 128 129 // router can route the client connections to backend CN servers. 130 // Also, it can balance the load between CN servers so there is a 131 // rebalancer in it. 132 type router struct { 133 rebalancer *rebalancer 134 moCluster clusterservice.MOCluster 135 test bool 136 137 // timeout configs. 138 connectTimeout time.Duration 139 authTimeout time.Duration 140 } 141 142 type routeOption func(*router) 143 144 var _ Router = (*router)(nil) 145 146 func withConnectTimeout(t time.Duration) routeOption { 147 return func(r *router) { 148 r.connectTimeout = t 149 } 150 } 151 152 func withAuthTimeout(t time.Duration) routeOption { 153 return func(r *router) { 154 r.authTimeout = t 155 } 156 } 157 158 // newCNConnector creates a Router. 159 func newRouter( 160 mc clusterservice.MOCluster, 161 r *rebalancer, 162 test bool, 163 opts ...routeOption, 164 ) Router { 165 rt := &router{ 166 rebalancer: r, 167 moCluster: mc, 168 test: test, 169 } 170 for _, opt := range opts { 171 opt(rt) 172 } 173 if rt.authTimeout == 0 { // for test 174 rt.authTimeout = defaultAuthTimeout / 3 175 } 176 return rt 177 } 178 179 // SelectByConnID implements the Router interface. 180 func (r *router) SelectByConnID(connID uint32) (*CNServer, error) { 181 cn := r.rebalancer.connManager.getCNServerByConnID(connID) 182 if cn == nil { 183 return nil, noCNServerErr 184 } 185 // Return a new CNServer instance for temporary connection. 186 return &CNServer{ 187 connID: cn.connID, 188 salt: cn.salt, 189 uuid: cn.uuid, 190 addr: cn.addr, 191 }, nil 192 } 193 194 // selectForSuperTenant is used to select CN servers for sys tenant. 195 // For more detail, see route.RouteForSuperTenant. 196 func (r *router) selectForSuperTenant(c clientInfo, filter func(string) bool) []*CNServer { 197 var cns []*CNServer 198 route.RouteForSuperTenant(c.labelInfo.genSelector(clusterservice.EQ_Globbing), c.username, filter, 199 func(s *metadata.CNService) { 200 cns = append(cns, &CNServer{ 201 reqLabel: c.labelInfo, 202 cnLabel: s.Labels, 203 uuid: s.ServiceID, 204 addr: s.SQLAddress, 205 }) 206 }) 207 return cns 208 } 209 210 // selectForCommonTenant is used to select CN servers for common tenant. 211 // For more detail, see route.RouteForCommonTenant. 212 func (r *router) selectForCommonTenant(c clientInfo, filter func(string) bool) []*CNServer { 213 var cns []*CNServer 214 route.RouteForCommonTenant(c.labelInfo.genSelector(clusterservice.EQ_Globbing), filter, func(s *metadata.CNService) { 215 cns = append(cns, &CNServer{ 216 reqLabel: c.labelInfo, 217 cnLabel: s.Labels, 218 uuid: s.ServiceID, 219 addr: s.SQLAddress, 220 }) 221 }) 222 return cns 223 } 224 225 // Route implements the Router interface. 226 func (r *router) Route(ctx context.Context, c clientInfo, filter func(string) bool) (*CNServer, error) { 227 var cns []*CNServer 228 if c.isSuperTenant() { 229 cns = r.selectForSuperTenant(c, filter) 230 } else { 231 cns = r.selectForCommonTenant(c, filter) 232 } 233 cnCount := len(cns) 234 v2.ProxyAvailableBackendServerNumGauge. 235 WithLabelValues(string(c.Tenant)).Set(float64(cnCount)) 236 237 // getHash returns same hash for same labels. 238 hash, err := c.labelInfo.getHash() 239 if err != nil { 240 return nil, err 241 } 242 243 if cnCount == 0 { 244 return nil, noCNServerErr 245 } else if cnCount == 1 { 246 cns[0].hash = hash 247 return cns[0], nil 248 } 249 250 s := r.rebalancer.connManager.selectOne(hash, cns) 251 if s == nil { 252 return nil, ErrNoAvailableCNServers 253 } 254 // Set the label hash for the select one. 255 s.hash = hash 256 return s, nil 257 } 258 259 // Connect implements the CNConnector interface. 260 func (r *router) Connect( 261 cn *CNServer, handshakeResp *frontend.Packet, t *tunnel, 262 ) (_ ServerConn, _ []byte, e error) { 263 // Creates a server connection. 264 sc, err := newServerConn(cn, t, r.rebalancer, r.connectTimeout) 265 if err != nil { 266 return nil, nil, err 267 } 268 defer func() { 269 if e != nil { 270 _ = sc.Close() 271 } 272 }() 273 274 // For test, ignore handshake phase. 275 if r.test { 276 r.rebalancer.connManager.connect(cn, t) 277 // The second value should be recognized OK packet. 278 return sc, makeOKPacket(8), nil 279 } 280 281 // Use the handshakeResp, which is auth request from client, to communicate 282 // with CN server. 283 resp, err := sc.HandleHandshake(handshakeResp, r.authTimeout) 284 if err != nil { 285 r.rebalancer.connManager.disconnect(cn, t) 286 return nil, nil, err 287 } 288 // After handshake with backend CN server, set the connID of serverConn. 289 cn.connID = sc.ConnID() 290 291 // After connect succeed, track the connection. 292 r.rebalancer.connManager.connect(cn, t) 293 294 return sc, packetToBytes(resp), nil 295 } 296 297 // Refresh refreshes the router 298 func (r *router) Refresh(sync bool) { 299 r.moCluster.ForceRefresh(sync) 300 }