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

     1  // Copyright 2015 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 hbasekv
    15  
    16  import (
    17  	"fmt"
    18  	"math/rand"
    19  	"net/url"
    20  	"path/filepath"
    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/go-hbase"
    28  	"github.com/insionng/yougam/libraries/pingcap/go-themis"
    29  	"github.com/insionng/yougam/libraries/pingcap/go-themis/oracle"
    30  	"github.com/insionng/yougam/libraries/pingcap/go-themis/oracle/oracles"
    31  	"github.com/insionng/yougam/libraries/pingcap/tidb/kv"
    32  )
    33  
    34  const (
    35  	// hbaseColFamily is the hbase column family name.
    36  	hbaseColFamily = "f"
    37  	// hbaseQualifier is the hbase column name.
    38  	hbaseQualifier = "q"
    39  	// hbaseFmlAndQual is a shortcut.
    40  	hbaseFmlAndQual = hbaseColFamily + ":" + hbaseQualifier
    41  	// fix length conn pool
    42  	hbaseConnPoolSize = 10
    43  )
    44  
    45  var (
    46  	hbaseColFamilyBytes = []byte(hbaseColFamily)
    47  	hbaseQualifierBytes = []byte(hbaseQualifier)
    48  )
    49  
    50  var (
    51  	_ kv.Storage = (*hbaseStore)(nil)
    52  )
    53  
    54  var (
    55  	// ErrInvalidDSN is returned when store dsn is invalid.
    56  	ErrInvalidDSN = errors.New("invalid dsn")
    57  )
    58  
    59  type storeCache struct {
    60  	mu    sync.Mutex
    61  	cache map[string]*hbaseStore
    62  }
    63  
    64  var mc storeCache
    65  
    66  func init() {
    67  	mc.cache = make(map[string]*hbaseStore)
    68  	rand.Seed(time.Now().UnixNano())
    69  }
    70  
    71  type hbaseStore struct {
    72  	mu        sync.Mutex
    73  	uuid      string
    74  	storeName string
    75  	oracle    oracle.Oracle
    76  	conns     []hbase.HBaseClient
    77  }
    78  
    79  func (s *hbaseStore) getHBaseClient() hbase.HBaseClient {
    80  	// return hbase connection randomly
    81  	return s.conns[rand.Intn(hbaseConnPoolSize)]
    82  }
    83  
    84  func (s *hbaseStore) Begin() (kv.Transaction, error) {
    85  	s.mu.Lock()
    86  	defer s.mu.Unlock()
    87  	hbaseCli := s.getHBaseClient()
    88  	t, err := themis.NewTxn(hbaseCli, s.oracle)
    89  	if err != nil {
    90  		return nil, errors.Trace(err)
    91  	}
    92  	txn := newHbaseTxn(t, s.storeName)
    93  	return txn, nil
    94  }
    95  
    96  func (s *hbaseStore) GetSnapshot(ver kv.Version) (kv.Snapshot, error) {
    97  	hbaseCli := s.getHBaseClient()
    98  	t, err := themis.NewTxn(hbaseCli, s.oracle)
    99  	if err != nil {
   100  		return nil, errors.Trace(err)
   101  	}
   102  	return newHbaseSnapshot(t, s.storeName), nil
   103  }
   104  
   105  func (s *hbaseStore) Close() error {
   106  	mc.mu.Lock()
   107  	defer mc.mu.Unlock()
   108  
   109  	delete(mc.cache, s.uuid)
   110  
   111  	var err error
   112  	for _, conn := range s.conns {
   113  		err = conn.Close()
   114  		if err != nil {
   115  			log.Error(err)
   116  		}
   117  	}
   118  	// return last error
   119  	return err
   120  }
   121  
   122  func (s *hbaseStore) UUID() string {
   123  	return s.uuid
   124  }
   125  
   126  func (s *hbaseStore) CurrentVersion() (kv.Version, error) {
   127  	hbaseCli := s.getHBaseClient()
   128  	t, err := themis.NewTxn(hbaseCli, s.oracle)
   129  	if err != nil {
   130  		return kv.Version{Ver: 0}, errors.Trace(err)
   131  	}
   132  	defer t.Release()
   133  
   134  	return kv.Version{Ver: t.GetStartTS()}, nil
   135  }
   136  
   137  // Driver implements engine Driver.
   138  type Driver struct {
   139  }
   140  
   141  const (
   142  	tsoTypeLocal = "local"
   143  	tsoTypeZK    = "zk"
   144  
   145  	tsoZKPath = "/zk/tso"
   146  )
   147  
   148  // Open opens or creates an HBase storage with given path.
   149  //
   150  // The format of path should be 'hbase://zk1,zk2,zk3/table[?tso=local|zk]'.
   151  // If tso is not provided, it will use a local oracle instead. (for test only)
   152  func (d Driver) Open(path string) (kv.Storage, error) {
   153  	mc.mu.Lock()
   154  	defer mc.mu.Unlock()
   155  
   156  	zks, tso, tableName, err := parsePath(path)
   157  	if err != nil {
   158  		return nil, errors.Trace(err)
   159  	}
   160  	if tso != tsoTypeLocal && tso != tsoTypeZK {
   161  		return nil, errors.Trace(ErrInvalidDSN)
   162  	}
   163  
   164  	uuid := fmt.Sprintf("hbase-%v-%v", zks, tableName)
   165  	if tso == tsoTypeLocal {
   166  		log.Warnf("hbase: store(%s) is using local oracle(for test only)", uuid)
   167  	}
   168  	if store, ok := mc.cache[uuid]; ok {
   169  		return store, nil
   170  	}
   171  
   172  	// create buffered HBase connections, HBaseClient is goroutine-safe, so
   173  	// it's OK to redistribute to transactions.
   174  	conns := make([]hbase.HBaseClient, 0, hbaseConnPoolSize)
   175  	for i := 0; i < hbaseConnPoolSize; i++ {
   176  		var c hbase.HBaseClient
   177  		c, err = hbase.NewClient(strings.Split(zks, ","), "/hbase")
   178  		if err != nil {
   179  			return nil, errors.Trace(err)
   180  		}
   181  		conns = append(conns, c)
   182  	}
   183  
   184  	c := conns[0]
   185  	var b bool
   186  	b, err = c.TableExists(tableName)
   187  	if err != nil {
   188  		return nil, errors.Trace(err)
   189  	}
   190  	if !b {
   191  		// Create new hbase table for store.
   192  		t := hbase.NewTableDesciptor(tableName)
   193  		cf := hbase.NewColumnFamilyDescriptor(hbaseColFamily)
   194  		cf.AddAttr("THEMIS_ENABLE", "true")
   195  		t.AddColumnDesc(cf)
   196  		//TODO: specify split?
   197  		if err := c.CreateTable(t, nil); err != nil {
   198  			return nil, errors.Trace(err)
   199  		}
   200  	}
   201  
   202  	var ora oracle.Oracle
   203  	switch tso {
   204  	case tsoTypeLocal:
   205  		ora = oracles.NewLocalOracle()
   206  	case tsoTypeZK:
   207  		ora = oracles.NewRemoteOracle(zks, tsoZKPath)
   208  	}
   209  
   210  	s := &hbaseStore{
   211  		uuid:      uuid,
   212  		storeName: tableName,
   213  		oracle:    ora,
   214  		conns:     conns,
   215  	}
   216  	mc.cache[uuid] = s
   217  	return s, nil
   218  }
   219  
   220  func parsePath(path string) (zks, tso, tableName string, err error) {
   221  	u, err := url.Parse(path)
   222  	if err != nil {
   223  		return "", "", "", errors.Trace(err)
   224  	}
   225  	if strings.ToLower(u.Scheme) != "hbase" {
   226  		return "", "", "", errors.Trace(ErrInvalidDSN)
   227  	}
   228  	p, tableName := filepath.Split(u.Path)
   229  	if p != "/" {
   230  		return "", "", "", errors.Trace(ErrInvalidDSN)
   231  	}
   232  	zks = u.Host
   233  	tso = u.Query().Get("tso")
   234  	if tso == "" {
   235  		tso = tsoTypeLocal
   236  	}
   237  	return zks, tso, tableName, nil
   238  }