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

     1  // Copyright 2016 PingCAP, Inc.
     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  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package tikv
    15  
    16  import (
    17  	"fmt"
    18  	"math/rand"
    19  	"net/url"
    20  	"strconv"
    21  	"strings"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/insionng/yougam/libraries/juju/errors"
    26  	"github.com/insionng/yougam/libraries/ngaut/log"
    27  	"github.com/insionng/yougam/libraries/pingcap/kvproto/pkg/errorpb"
    28  	pb "github.com/insionng/yougam/libraries/pingcap/kvproto/pkg/kvrpcpb"
    29  	"github.com/insionng/yougam/libraries/pingcap/pd/pd-client"
    30  	"github.com/insionng/yougam/libraries/pingcap/tidb/kv"
    31  	"github.com/insionng/yougam/libraries/pingcap/tidb/store/tikv/mock-tikv"
    32  	"github.com/insionng/yougam/libraries/pingcap/tidb/store/tikv/oracle"
    33  	"github.com/insionng/yougam/libraries/pingcap/tidb/store/tikv/oracle/oracles"
    34  )
    35  
    36  type storeCache struct {
    37  	mu    sync.Mutex
    38  	cache map[string]*tikvStore
    39  }
    40  
    41  var mc storeCache
    42  
    43  // Driver implements engine Driver.
    44  type Driver struct {
    45  }
    46  
    47  // Open opens or creates an TiKV storage with given path.
    48  // Path example: tikv://etcd-node1:port,etcd-node2:port/pd-path?cluster=1
    49  func (d Driver) Open(path string) (kv.Storage, error) {
    50  	mc.mu.Lock()
    51  	defer mc.mu.Unlock()
    52  
    53  	etcdAddrs, pdPath, clusterID, err := parsePath(path)
    54  	if err != nil {
    55  		return nil, errors.Trace(err)
    56  	}
    57  	// FIXME: uuid will be a very long and ugly string, simplify it.
    58  	uuid := fmt.Sprintf("tikv-%v-%v-%v", etcdAddrs, pdPath, clusterID)
    59  	if store, ok := mc.cache[uuid]; ok {
    60  		return store, nil
    61  	}
    62  
    63  	pdCli, err := pd.NewClient(etcdAddrs, pdPath, clusterID)
    64  	if err != nil {
    65  		return nil, errors.Trace(err)
    66  	}
    67  	s := newTikvStore(uuid, &codecPDClient{pdCli}, NewRPCClient)
    68  	mc.cache[uuid] = s
    69  	return s, nil
    70  }
    71  
    72  type tikvStore struct {
    73  	mu     sync.Mutex
    74  	uuid   string
    75  	oracle oracle.Oracle
    76  	// addr => *Client
    77  	clients       map[string]Client
    78  	clientFactory ClientFactory
    79  	clientLock    sync.RWMutex
    80  	regionCache   *RegionCache
    81  }
    82  
    83  func newTikvStore(uuid string, pdClient pd.Client, factory ClientFactory) *tikvStore {
    84  	return &tikvStore{
    85  		uuid:          uuid,
    86  		oracle:        oracles.NewPdOracle(pdClient),
    87  		clients:       make(map[string]Client),
    88  		clientFactory: factory,
    89  		regionCache:   NewRegionCache(pdClient),
    90  	}
    91  }
    92  
    93  // NewMockTikvStore creates a mocked tikv store.
    94  func NewMockTikvStore() kv.Storage {
    95  	cluster := mocktikv.NewCluster()
    96  	mocktikv.BootstrapWithSingleStore(cluster)
    97  	mvccStore := mocktikv.NewMvccStore()
    98  	clientFactory := mockClientFactory(cluster, mvccStore)
    99  	uuid := fmt.Sprintf("mock-tikv-store-:%v", time.Now().Unix())
   100  	return newTikvStore(uuid, mocktikv.NewPDClient(cluster), clientFactory)
   101  }
   102  
   103  func mockClientFactory(cluster *mocktikv.Cluster, mvccStore *mocktikv.MvccStore) ClientFactory {
   104  	return func(addr string) (Client, error) {
   105  		return mocktikv.NewRPCClient(cluster, mvccStore, addr), nil
   106  	}
   107  }
   108  
   109  func (s *tikvStore) getClient(addr string) (Client, error) {
   110  	s.clientLock.RLock()
   111  	client, ok := s.clients[addr]
   112  	s.clientLock.RUnlock()
   113  	if ok {
   114  		return client, nil
   115  	}
   116  
   117  	s.clientLock.Lock()
   118  	defer s.clientLock.Unlock()
   119  
   120  	client, err := s.clientFactory(addr)
   121  	if err != nil {
   122  		return nil, errors.Trace(err)
   123  	}
   124  
   125  	s.clients[addr] = client
   126  	return client, nil
   127  }
   128  
   129  func (s *tikvStore) Begin() (kv.Transaction, error) {
   130  	s.mu.Lock()
   131  	defer s.mu.Unlock()
   132  
   133  	txn, err := newTiKVTxn(s)
   134  	if err != nil {
   135  		return nil, errors.Trace(err)
   136  	}
   137  	return txn, nil
   138  }
   139  
   140  func (s *tikvStore) GetSnapshot(ver kv.Version) (kv.Snapshot, error) {
   141  	snapshot := newTiKVSnapshot(s, ver)
   142  	return snapshot, nil
   143  }
   144  
   145  func (s *tikvStore) Close() error {
   146  	mc.mu.Lock()
   147  	defer mc.mu.Unlock()
   148  
   149  	delete(mc.cache, s.uuid)
   150  
   151  	var lastErr error
   152  	for addr, client := range s.clients {
   153  		err := client.Close()
   154  		if err != nil {
   155  			log.Errorf("Close client error[%s]", err)
   156  			lastErr = err
   157  		}
   158  		delete(s.clients, addr)
   159  	}
   160  	return lastErr
   161  }
   162  
   163  func (s *tikvStore) UUID() string {
   164  	return s.uuid
   165  }
   166  
   167  func (s *tikvStore) CurrentVersion() (kv.Version, error) {
   168  	startTS, err := s.oracle.GetTimestamp()
   169  	if err != nil {
   170  		return kv.NewVersion(0), errors.Trace(err)
   171  	}
   172  
   173  	return kv.NewVersion(startTS), nil
   174  }
   175  
   176  // sendKVReq sends req to tikv server. It will retry internally to find the right
   177  // region leader if i) fails to establish a connection to server or ii) server
   178  // returns `NotLeader`.
   179  func (s *tikvStore) SendKVReq(req *pb.Request, regionID RegionVerID) (*pb.Response, error) {
   180  	var backoffErr error
   181  	for backoff := rpcBackoff(); backoffErr == nil; backoffErr = backoff() {
   182  		region := s.regionCache.GetRegionByVerID(regionID)
   183  		if region == nil {
   184  			// If the region is not found in cache, it must be out
   185  			// of date and already be cleaned up. We can skip the
   186  			// RPC by returning RegionError directly.
   187  			return &pb.Response{
   188  				Type:        req.GetType().Enum(),
   189  				RegionError: &errorpb.Error{StaleEpoch: &errorpb.StaleEpoch{}},
   190  			}, nil
   191  		}
   192  		client, err := s.getClient(region.GetAddress())
   193  		if err != nil {
   194  			return nil, errors.Trace(err)
   195  		}
   196  		req.Context = region.GetContext()
   197  		resp, err := client.SendKVReq(req)
   198  		if err != nil {
   199  			log.Warnf("send tikv request error: %v, try next peer later", err)
   200  			s.regionCache.NextPeer(region.VerID())
   201  			continue
   202  		}
   203  		if regionErr := resp.GetRegionError(); regionErr != nil {
   204  			// Retry if error is `NotLeader`.
   205  			if notLeader := regionErr.GetNotLeader(); notLeader != nil {
   206  				log.Warnf("tikv reports `NotLeader`: %s, retry later", notLeader.String())
   207  				s.regionCache.UpdateLeader(region.VerID(), notLeader.GetLeader().GetId())
   208  				continue
   209  			}
   210  			// For other errors, we only drop cache here.
   211  			// Because caller may need to re-split the request.
   212  			log.Warnf("tikv reports region error: %v", resp.GetRegionError())
   213  			s.regionCache.DropRegion(region.VerID())
   214  			return resp, nil
   215  		}
   216  		if resp.GetType() != req.GetType() {
   217  			return nil, errors.Trace(errMismatch(resp, req))
   218  		}
   219  		return resp, nil
   220  	}
   221  	return nil, errors.Trace(backoffErr)
   222  }
   223  
   224  func parsePath(path string) (etcdAddrs []string, pdPath string, clusterID uint64, err error) {
   225  	var u *url.URL
   226  	u, err = url.Parse(path)
   227  	if err != nil {
   228  		err = errors.Trace(err)
   229  		return
   230  	}
   231  	if strings.ToLower(u.Scheme) != "tikv" {
   232  		log.Errorf("Uri scheme expected[tikv] but found [%s]", u.Scheme)
   233  		err = errors.Trace(err)
   234  		return
   235  	}
   236  	clusterID, err = strconv.ParseUint(u.Query().Get("cluster"), 10, 64)
   237  	if err != nil {
   238  		log.Errorf("Parse clusterID error [%s]", err)
   239  		err = errors.Trace(err)
   240  		return
   241  	}
   242  	etcdAddrs = strings.Split(u.Host, ",")
   243  	pdPath = u.Path
   244  	return
   245  }
   246  
   247  func init() {
   248  	mc.cache = make(map[string]*tikvStore)
   249  	rand.Seed(time.Now().UnixNano())
   250  }