github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/pingcap/tidb/server/server.go (about)

     1  // The MIT License (MIT)
     2  //
     3  // Copyright (c) 2014 wandoulabs
     4  // Copyright (c) 2014 siddontang
     5  //
     6  // Permission is hereby granted, free of charge, to any person obtaining a copy of
     7  // this software and associated documentation files (the "Software"), to deal in
     8  // the Software without restriction, including without limitation the rights to
     9  // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
    10  // the Software, and to permit persons to whom the Software is furnished to do so,
    11  // subject to the following conditions:
    12  //
    13  // The above copyright notice and this permission notice shall be included in all
    14  // copies or substantial portions of the Software.
    15  
    16  // Copyright 2015 PingCAP, Inc.
    17  //
    18  // Licensed under the Apache License, Version 2.0 (the "License");
    19  // you may not use this file except in compliance with the License.
    20  // You may obtain a copy of the License at
    21  //
    22  //     http://www.apache.org/licenses/LICENSE-2.0
    23  //
    24  // Unless required by applicable law or agreed to in writing, software
    25  // distributed under the License is distributed on an "AS IS" BASIS,
    26  // See the License for the specific language governing permissions and
    27  // limitations under the License.
    28  
    29  package server
    30  
    31  import (
    32  	"encoding/json"
    33  	"math/rand"
    34  	"net"
    35  	"net/http"
    36  	"sync"
    37  	"sync/atomic"
    38  	"time"
    39  
    40  	"github.com/insionng/yougam/libraries/juju/errors"
    41  	"github.com/insionng/yougam/libraries/ngaut/log"
    42  	"github.com/insionng/yougam/libraries/pingcap/tidb"
    43  	"github.com/insionng/yougam/libraries/pingcap/tidb/mysql"
    44  	"github.com/insionng/yougam/libraries/pingcap/tidb/util/arena"
    45  )
    46  
    47  var (
    48  	baseConnID uint32 = 10000
    49  )
    50  
    51  // Server is the MySQL protocol server
    52  type Server struct {
    53  	cfg               *Config
    54  	driver            IDriver
    55  	listener          net.Listener
    56  	rwlock            *sync.RWMutex
    57  	concurrentLimiter *TokenLimiter
    58  	clients           map[uint32]*clientConn
    59  }
    60  
    61  // ConnectionCount gets current connection count.
    62  func (s *Server) ConnectionCount() int {
    63  	return len(s.clients)
    64  }
    65  
    66  func (s *Server) getToken() *Token {
    67  	return s.concurrentLimiter.Get()
    68  }
    69  
    70  func (s *Server) releaseToken(token *Token) {
    71  	s.concurrentLimiter.Put(token)
    72  }
    73  
    74  // Generate a random string using ASCII characters but avoid separator character.
    75  // See: https://yougam/libraries/mysql/mysql-server/blob/5.7/mysys_ssl/crypt_genhash_impl.cc#L435
    76  func randomBuf(size int) []byte {
    77  	buf := make([]byte, size)
    78  	for i := 0; i < size; i++ {
    79  		buf[i] = byte(rand.Intn(127))
    80  		if buf[i] == 0 || buf[i] == byte('$') {
    81  			buf[i]++
    82  		}
    83  	}
    84  	return buf
    85  }
    86  
    87  func (s *Server) newConn(conn net.Conn) (cc *clientConn, err error) {
    88  	log.Info("newConn", conn.RemoteAddr().String())
    89  	cc = &clientConn{
    90  		conn:         conn,
    91  		pkg:          newPacketIO(conn),
    92  		server:       s,
    93  		connectionID: atomic.AddUint32(&baseConnID, 1),
    94  		collation:    mysql.DefaultCollationID,
    95  		charset:      mysql.DefaultCharset,
    96  		alloc:        arena.NewAllocator(32 * 1024),
    97  	}
    98  	cc.salt = randomBuf(20)
    99  	return
   100  }
   101  
   102  func (s *Server) skipAuth() bool {
   103  	return s.cfg.SkipAuth
   104  }
   105  
   106  // NewServer creates a new Server.
   107  func NewServer(cfg *Config, driver IDriver) (*Server, error) {
   108  	s := &Server{
   109  		cfg:               cfg,
   110  		driver:            driver,
   111  		concurrentLimiter: NewTokenLimiter(100),
   112  		rwlock:            &sync.RWMutex{},
   113  		clients:           make(map[uint32]*clientConn),
   114  	}
   115  
   116  	var err error
   117  	if cfg.Socket != "" {
   118  		cfg.SkipAuth = true
   119  		s.listener, err = net.Listen("unix", cfg.Socket)
   120  	} else {
   121  		s.listener, err = net.Listen("tcp", s.cfg.Addr)
   122  	}
   123  	if err != nil {
   124  		return nil, errors.Trace(err)
   125  	}
   126  
   127  	// Init rand seed for randomBuf()
   128  	rand.Seed(time.Now().UTC().UnixNano())
   129  	log.Infof("Server run MySql Protocol Listen at [%s]", s.cfg.Addr)
   130  	return s, nil
   131  }
   132  
   133  // Run runs the server.
   134  func (s *Server) Run() error {
   135  
   136  	// Start http api to report tidb info such as tps.
   137  	s.startStatusHTTP()
   138  
   139  	for {
   140  		conn, err := s.listener.Accept()
   141  		if err != nil {
   142  			if opErr, ok := err.(*net.OpError); ok {
   143  				if opErr.Err.Error() == "use of closed network connection" {
   144  					return nil
   145  				}
   146  			}
   147  			log.Errorf("accept error %s", err.Error())
   148  			return errors.Trace(err)
   149  		}
   150  
   151  		go s.onConn(conn)
   152  	}
   153  }
   154  
   155  // Close closes the server.
   156  func (s *Server) Close() {
   157  	s.rwlock.Lock()
   158  	defer s.rwlock.Unlock()
   159  
   160  	if s.listener != nil {
   161  		s.listener.Close()
   162  		s.listener = nil
   163  	}
   164  }
   165  
   166  func (s *Server) onConn(c net.Conn) {
   167  	conn, err := s.newConn(c)
   168  	if err != nil {
   169  		log.Errorf("newConn error %s", errors.ErrorStack(err))
   170  		return
   171  	}
   172  	if err := conn.handshake(); err != nil {
   173  		log.Errorf("handshake error %s", errors.ErrorStack(err))
   174  		c.Close()
   175  		return
   176  	}
   177  	defer func() {
   178  		log.Infof("close %s", conn)
   179  	}()
   180  
   181  	s.rwlock.Lock()
   182  	s.clients[conn.connectionID] = conn
   183  	s.rwlock.Unlock()
   184  
   185  	conn.Run()
   186  }
   187  
   188  var once sync.Once
   189  
   190  const defaultStatusAddr = ":10080"
   191  
   192  func (s *Server) startStatusHTTP() {
   193  	once.Do(func() {
   194  		go func() {
   195  			http.HandleFunc("/status", func(w http.ResponseWriter, req *http.Request) {
   196  				w.Header().Set("Content-Type", "application/json")
   197  				s := status{TPS: tidb.GetTPS(), Connections: s.ConnectionCount(), Version: mysql.ServerVersion}
   198  				js, err := json.Marshal(s)
   199  				if err != nil {
   200  					w.WriteHeader(http.StatusInternalServerError)
   201  					log.Error("Encode json error", err)
   202  				} else {
   203  					w.Write(js)
   204  				}
   205  
   206  			})
   207  			addr := s.cfg.StatusAddr
   208  			if len(addr) == 0 {
   209  				addr = defaultStatusAddr
   210  			}
   211  			log.Fatal(http.ListenAndServe(addr, nil))
   212  		}()
   213  	})
   214  }
   215  
   216  // TiDB status
   217  type status struct {
   218  	TPS         int64  `json:"tps"`
   219  	Connections int    `json:"connections"`
   220  	Version     string `json:"version"`
   221  }