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  }