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 }