github.com/hdt3213/godis@v1.2.9/redis/client/client.go (about)

     1  package client
     2  
     3  import (
     4  	"errors"
     5  	"github.com/hdt3213/godis/interface/redis"
     6  	"github.com/hdt3213/godis/lib/logger"
     7  	"github.com/hdt3213/godis/lib/sync/wait"
     8  	"github.com/hdt3213/godis/redis/parser"
     9  	"github.com/hdt3213/godis/redis/protocol"
    10  	"net"
    11  	"runtime/debug"
    12  	"strings"
    13  	"sync"
    14  	"sync/atomic"
    15  	"time"
    16  )
    17  
    18  const (
    19  	created = iota
    20  	running
    21  	closed
    22  )
    23  
    24  // Client is a pipeline mode redis client
    25  type Client struct {
    26  	conn        net.Conn
    27  	pendingReqs chan *request // wait to send
    28  	waitingReqs chan *request // waiting response
    29  	ticker      *time.Ticker
    30  	addr        string
    31  
    32  	status  int32
    33  	working *sync.WaitGroup // its counter presents unfinished requests(pending and waiting)
    34  }
    35  
    36  // request is a message sends to redis server
    37  type request struct {
    38  	id        uint64
    39  	args      [][]byte
    40  	reply     redis.Reply
    41  	heartbeat bool
    42  	waiting   *wait.Wait
    43  	err       error
    44  }
    45  
    46  const (
    47  	chanSize = 256
    48  	maxWait  = 3 * time.Second
    49  )
    50  
    51  // MakeClient creates a new client
    52  func MakeClient(addr string) (*Client, error) {
    53  	conn, err := net.Dial("tcp", addr)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	return &Client{
    58  		addr:        addr,
    59  		conn:        conn,
    60  		pendingReqs: make(chan *request, chanSize),
    61  		waitingReqs: make(chan *request, chanSize),
    62  		working:     &sync.WaitGroup{},
    63  	}, nil
    64  }
    65  
    66  // Start starts asynchronous goroutines
    67  func (client *Client) Start() {
    68  	client.ticker = time.NewTicker(10 * time.Second)
    69  	go client.handleWrite()
    70  	go client.handleRead()
    71  	go client.heartbeat()
    72  	atomic.StoreInt32(&client.status, running)
    73  }
    74  
    75  // Close stops asynchronous goroutines and close connection
    76  func (client *Client) Close() {
    77  	atomic.StoreInt32(&client.status, closed)
    78  	client.ticker.Stop()
    79  	// stop new request
    80  	close(client.pendingReqs)
    81  
    82  	// wait stop process
    83  	client.working.Wait()
    84  
    85  	// clean
    86  	_ = client.conn.Close()
    87  	close(client.waitingReqs)
    88  }
    89  
    90  func (client *Client) reconnect() {
    91  	logger.Info("reconnect with: " + client.addr)
    92  	_ = client.conn.Close() // ignore possible errors from repeated closes
    93  
    94  	var conn net.Conn
    95  	for i := 0; i < 3; i++ {
    96  		var err error
    97  		conn, err = net.Dial("tcp", client.addr)
    98  		if err != nil {
    99  			logger.Error("reconnect error: " + err.Error())
   100  			time.Sleep(time.Second)
   101  			continue
   102  		} else {
   103  			break
   104  		}
   105  	}
   106  	if conn == nil { // reach max retry, abort
   107  		client.Close()
   108  		return
   109  	}
   110  	client.conn = conn
   111  
   112  	close(client.waitingReqs)
   113  	for req := range client.waitingReqs {
   114  		req.err = errors.New("connection closed")
   115  		req.waiting.Done()
   116  	}
   117  	client.waitingReqs = make(chan *request, chanSize)
   118  	// restart handle read
   119  	go client.handleRead()
   120  }
   121  
   122  func (client *Client) heartbeat() {
   123  	for range client.ticker.C {
   124  		client.doHeartbeat()
   125  	}
   126  }
   127  
   128  func (client *Client) handleWrite() {
   129  	for req := range client.pendingReqs {
   130  		client.doRequest(req)
   131  	}
   132  }
   133  
   134  // Send sends a request to redis server
   135  func (client *Client) Send(args [][]byte) redis.Reply {
   136  	if atomic.LoadInt32(&client.status) != running {
   137  		return protocol.MakeErrReply("client closed")
   138  	}
   139  	request := &request{
   140  		args:      args,
   141  		heartbeat: false,
   142  		waiting:   &wait.Wait{},
   143  	}
   144  	request.waiting.Add(1)
   145  	client.working.Add(1)
   146  	defer client.working.Done()
   147  	client.pendingReqs <- request
   148  	timeout := request.waiting.WaitWithTimeout(maxWait)
   149  	if timeout {
   150  		return protocol.MakeErrReply("server time out")
   151  	}
   152  	if request.err != nil {
   153  		return protocol.MakeErrReply("request failed")
   154  	}
   155  	return request.reply
   156  }
   157  
   158  func (client *Client) doHeartbeat() {
   159  	request := &request{
   160  		args:      [][]byte{[]byte("PING")},
   161  		heartbeat: true,
   162  		waiting:   &wait.Wait{},
   163  	}
   164  	request.waiting.Add(1)
   165  	client.working.Add(1)
   166  	defer client.working.Done()
   167  	client.pendingReqs <- request
   168  	request.waiting.WaitWithTimeout(maxWait)
   169  }
   170  
   171  func (client *Client) doRequest(req *request) {
   172  	if req == nil || len(req.args) == 0 {
   173  		return
   174  	}
   175  	re := protocol.MakeMultiBulkReply(req.args)
   176  	bytes := re.ToBytes()
   177  	var err error
   178  	for i := 0; i < 3; i++ { // only retry, waiting for handleRead
   179  		_, err = client.conn.Write(bytes)
   180  		if err == nil ||
   181  			(!strings.Contains(err.Error(), "timeout") && // only retry timeout
   182  				!strings.Contains(err.Error(), "deadline exceeded")) {
   183  			break
   184  		}
   185  	}
   186  	if err == nil {
   187  		client.waitingReqs <- req
   188  	} else {
   189  		req.err = err
   190  		req.waiting.Done()
   191  	}
   192  }
   193  
   194  func (client *Client) finishRequest(reply redis.Reply) {
   195  	defer func() {
   196  		if err := recover(); err != nil {
   197  			debug.PrintStack()
   198  			logger.Error(err)
   199  		}
   200  	}()
   201  	request := <-client.waitingReqs
   202  	if request == nil {
   203  		return
   204  	}
   205  	request.reply = reply
   206  	if request.waiting != nil {
   207  		request.waiting.Done()
   208  	}
   209  }
   210  
   211  func (client *Client) handleRead() {
   212  	ch := parser.ParseStream(client.conn)
   213  	for payload := range ch {
   214  		if payload.Err != nil {
   215  			status := atomic.LoadInt32(&client.status)
   216  			if status == closed {
   217  				return
   218  			}
   219  			client.reconnect()
   220  			return
   221  		}
   222  		client.finishRequest(payload.Data)
   223  	}
   224  }