github.com/uber/kraken@v0.1.4/tracker/peerstore/redis.go (about)

     1  // Copyright (c) 2016-2019 Uber Technologies, 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  // 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  package peerstore
    15  
    16  import (
    17  	"errors"
    18  	"fmt"
    19  	"strconv"
    20  	"strings"
    21  
    22  	"github.com/uber/kraken/core"
    23  	"github.com/uber/kraken/utils/log"
    24  	"github.com/uber/kraken/utils/randutil"
    25  
    26  	"github.com/andres-erbsen/clock"
    27  	"github.com/garyburd/redigo/redis"
    28  )
    29  
    30  func peerSetKey(h core.InfoHash, window int64) string {
    31  	return fmt.Sprintf("peerset:%s:%d", h.String(), window)
    32  }
    33  
    34  func serializePeer(p *core.PeerInfo) string {
    35  	var completeBit int
    36  	if p.Complete {
    37  		completeBit = 1
    38  	}
    39  	return fmt.Sprintf("%s:%s:%d:%d", p.PeerID.String(), p.IP, p.Port, completeBit)
    40  }
    41  
    42  type peerIdentity struct {
    43  	peerID core.PeerID
    44  	ip     string
    45  	port   int
    46  }
    47  
    48  func deserializePeer(s string) (id peerIdentity, complete bool, err error) {
    49  	parts := strings.Split(s, ":")
    50  	if len(parts) != 4 {
    51  		return id, false, fmt.Errorf("invalid peer encoding: expected 'pid:ip:port:complete'")
    52  	}
    53  	peerID, err := core.NewPeerID(parts[0])
    54  	if err != nil {
    55  		return id, false, fmt.Errorf("parse peer id: %s", err)
    56  	}
    57  	ip := parts[1]
    58  	port, err := strconv.Atoi(parts[2])
    59  	if err != nil {
    60  		return id, false, fmt.Errorf("parse port: %s", err)
    61  	}
    62  	id = peerIdentity{peerID, ip, port}
    63  	complete = parts[3] == "1"
    64  	return id, complete, nil
    65  }
    66  
    67  // RedisStore is a Store backed by Redis.
    68  type RedisStore struct {
    69  	config RedisConfig
    70  	pool   *redis.Pool
    71  	clk    clock.Clock
    72  }
    73  
    74  // NewRedisStore creates a new RedisStore.
    75  func NewRedisStore(config RedisConfig, clk clock.Clock) (*RedisStore, error) {
    76  	config.applyDefaults()
    77  
    78  	if config.Addr == "" {
    79  		return nil, errors.New("invalid config: missing addr")
    80  	}
    81  
    82  	s := &RedisStore{
    83  		config: config,
    84  		pool: &redis.Pool{
    85  			Dial: func() (redis.Conn, error) {
    86  				// TODO Add options
    87  				return redis.Dial(
    88  					"tcp",
    89  					config.Addr,
    90  					redis.DialConnectTimeout(config.DialTimeout),
    91  					redis.DialReadTimeout(config.ReadTimeout),
    92  					redis.DialWriteTimeout(config.WriteTimeout))
    93  			},
    94  			MaxIdle:     config.MaxIdleConns,
    95  			MaxActive:   config.MaxActiveConns,
    96  			IdleTimeout: config.IdleConnTimeout,
    97  			Wait:        true,
    98  		},
    99  		clk: clk,
   100  	}
   101  
   102  	// Ensure we can connect to Redis.
   103  	c, err := s.pool.Dial()
   104  	if err != nil {
   105  		return nil, fmt.Errorf("dial redis: %s", err)
   106  	}
   107  	c.Close()
   108  
   109  	return s, nil
   110  }
   111  
   112  func (s *RedisStore) curPeerSetWindow() int64 {
   113  	t := s.clk.Now().Unix()
   114  	return t - (t % int64(s.config.PeerSetWindowSize.Seconds()))
   115  }
   116  
   117  func (s *RedisStore) peerSetWindows() []int64 {
   118  	cur := s.curPeerSetWindow()
   119  	ws := make([]int64, s.config.MaxPeerSetWindows)
   120  	for i := range ws {
   121  		ws[i] = cur - int64(i)*int64(s.config.PeerSetWindowSize.Seconds())
   122  	}
   123  	return ws
   124  }
   125  
   126  // UpdatePeer writes p to Redis with a TTL.
   127  func (s *RedisStore) UpdatePeer(h core.InfoHash, p *core.PeerInfo) error {
   128  	c := s.pool.Get()
   129  	defer c.Close()
   130  
   131  	w := s.curPeerSetWindow()
   132  	expireAt := w + int64(s.config.PeerSetWindowSize.Seconds())*int64(s.config.MaxPeerSetWindows)
   133  
   134  	// Add p to the current window.
   135  	k := peerSetKey(h, w)
   136  
   137  	if err := c.Send("SADD", k, serializePeer(p)); err != nil {
   138  		return fmt.Errorf("send SADD: %s", err)
   139  	}
   140  	if err := c.Send("EXPIREAT", k, expireAt); err != nil {
   141  		return fmt.Errorf("send EXPIREAT: %s", err)
   142  	}
   143  	if err := c.Flush(); err != nil {
   144  		return fmt.Errorf("flush: %s", err)
   145  	}
   146  	if _, err := c.Receive(); err != nil {
   147  		return fmt.Errorf("SADD: %s", err)
   148  	}
   149  	if _, err := c.Receive(); err != nil {
   150  		return fmt.Errorf("EXPIREAT: %s", err)
   151  	}
   152  	return nil
   153  }
   154  
   155  // GetPeers returns at most n PeerInfos associated with h.
   156  func (s *RedisStore) GetPeers(h core.InfoHash, n int) ([]*core.PeerInfo, error) {
   157  	c := s.pool.Get()
   158  	defer c.Close()
   159  
   160  	// Try to sample n peers from each window in randomized order until we have
   161  	// collected n distinct peers. This achieves random sampling across multiple
   162  	// windows.
   163  	// TODO(codyg): One limitation of random window sampling is we're no longer
   164  	// guaranteed to include the latest completion bits. A simple way to mitigate
   165  	// this is to decrease the number of windows.
   166  	windows := s.peerSetWindows()
   167  	randutil.ShuffleInt64s(windows)
   168  
   169  	// Eliminate duplicates from other windows and collapses complete bits.
   170  	selected := make(map[peerIdentity]bool)
   171  
   172  	for i := 0; len(selected) < n && i < len(windows); i++ {
   173  		k := peerSetKey(h, windows[i])
   174  		result, err := redis.Strings(c.Do("SRANDMEMBER", k, n-len(selected)))
   175  		if err == redis.ErrNil {
   176  			continue
   177  		} else if err != nil {
   178  			return nil, err
   179  		}
   180  		for _, s := range result {
   181  			id, complete, err := deserializePeer(s)
   182  			if err != nil {
   183  				log.Errorf("Error deserializing peer %q: %s", s, err)
   184  				continue
   185  			}
   186  			selected[id] = selected[id] || complete
   187  		}
   188  	}
   189  
   190  	var peers []*core.PeerInfo
   191  	for id, complete := range selected {
   192  		p := core.NewPeerInfo(id.peerID, id.ip, id.port, false, complete)
   193  		peers = append(peers, p)
   194  	}
   195  	return peers, nil
   196  }