github.com/matrixorigin/matrixone@v1.2.0/pkg/common/morpc/client.go (about) 1 // Copyright 2021 - 2022 Matrix Origin 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 15 package morpc 16 17 import ( 18 "context" 19 "sync" 20 "sync/atomic" 21 "time" 22 23 "github.com/matrixorigin/matrixone/pkg/common/moerr" 24 "github.com/matrixorigin/matrixone/pkg/common/stopper" 25 "github.com/matrixorigin/matrixone/pkg/logutil" 26 v2 "github.com/matrixorigin/matrixone/pkg/util/metric/v2" 27 "go.uber.org/zap" 28 ) 29 30 // WithClientMaxBackendPerHost maximum number of connections per host 31 func WithClientMaxBackendPerHost(maxBackendsPerHost int) ClientOption { 32 return func(c *client) { 33 c.options.maxBackendsPerHost = maxBackendsPerHost 34 } 35 } 36 37 // WithClientLogger set client logger 38 func WithClientLogger(logger *zap.Logger) ClientOption { 39 return func(c *client) { 40 c.logger = logger 41 } 42 } 43 44 // WithClientInitBackends set the number of connections for the initialized backends. 45 func WithClientInitBackends(backends []string, counts []int) ClientOption { 46 return func(c *client) { 47 if len(backends) != len(counts) { 48 panic("backend and count mismatch") 49 } 50 51 c.options.initBackends = backends 52 c.options.initBackendCounts = counts 53 } 54 } 55 56 // WithClientCreateTaskChanSize set the buffer size of the chan that creates the Backend Task. 57 func WithClientCreateTaskChanSize(size int) ClientOption { 58 return func(c *client) { 59 c.createC = make(chan string, size) 60 } 61 } 62 63 // WithClientMaxBackendMaxIdleDuration set the maximum idle duration of the backend connection. 64 // Backend connection that exceed this time will be automatically closed. 0 means no idle time 65 // limit. 66 func WithClientMaxBackendMaxIdleDuration(value time.Duration) ClientOption { 67 return func(c *client) { 68 c.options.maxIdleDuration = value 69 } 70 } 71 72 // WithClientEnableAutoCreateBackend enable client to automatically create a backend 73 // in the background, when the links in the connection pool are used, if the pool has 74 // not reached the maximum number of links, it will automatically create them in the 75 // background to improve the latency of link creation. 76 func WithClientEnableAutoCreateBackend() ClientOption { 77 return func(c *client) { 78 c.options.enableAutoCreate = true 79 } 80 } 81 82 type client struct { 83 name string 84 metrics *metrics 85 logger *zap.Logger 86 stopper *stopper.Stopper 87 factory BackendFactory 88 createC chan string 89 gcInactiveC chan string 90 91 mu struct { 92 sync.Mutex 93 closed bool 94 backends map[string][]Backend 95 ops map[string]*op 96 } 97 98 options struct { 99 maxBackendsPerHost int 100 maxIdleDuration time.Duration 101 initBackends []string 102 initBackendCounts []int 103 enableAutoCreate bool 104 } 105 } 106 107 // NewClient create rpc client with options 108 func NewClient( 109 name string, 110 factory BackendFactory, 111 options ...ClientOption) (RPCClient, error) { 112 v2.RPCClientCreateCounter.WithLabelValues(name).Inc() 113 c := &client{ 114 name: name, 115 metrics: newMetrics(name), 116 factory: factory, 117 gcInactiveC: make(chan string), 118 } 119 c.mu.backends = make(map[string][]Backend) 120 c.mu.ops = make(map[string]*op) 121 122 for _, opt := range options { 123 opt(c) 124 } 125 c.adjust() 126 c.stopper = stopper.NewStopper(c.name, stopper.WithLogger(c.logger)) 127 128 if err := c.maybeInitBackends(); err != nil { 129 c.Close() 130 return nil, err 131 } 132 133 if err := c.stopper.RunTask(c.createTask); err != nil { 134 return nil, err 135 } 136 if c.options.maxIdleDuration > 0 { 137 if err := c.stopper.RunTask(c.gcIdleTask); err != nil { 138 return nil, err 139 } 140 } 141 if err := c.stopper.RunTask(c.gcInactiveTask); err != nil { 142 return nil, err 143 } 144 return c, nil 145 } 146 147 func (c *client) adjust() { 148 c.logger = logutil.Adjust(c.logger).Named(c.name) 149 if c.createC == nil { 150 c.createC = make(chan string, 16) 151 } 152 if c.options.maxBackendsPerHost == 0 { 153 c.options.maxBackendsPerHost = 1 154 } 155 if len(c.options.initBackendCounts) > 0 { 156 for _, cnt := range c.options.initBackendCounts { 157 if cnt > c.options.maxBackendsPerHost { 158 c.options.maxBackendsPerHost = cnt 159 } 160 } 161 } 162 if c.options.maxIdleDuration == 0 { 163 c.options.maxIdleDuration = defaultMaxIdleDuration 164 } 165 } 166 167 func (c *client) maybeInitBackends() error { 168 c.mu.Lock() 169 defer c.mu.Unlock() 170 if len(c.options.initBackends) > 0 { 171 for idx, backend := range c.options.initBackends { 172 for i := 0; i < c.options.initBackendCounts[idx]; i++ { 173 _, err := c.createBackendLocked(backend) 174 if err != nil { 175 return err 176 } 177 } 178 } 179 } 180 return nil 181 } 182 183 func (c *client) Send(ctx context.Context, backend string, request Message) (*Future, error) { 184 if backend == "" { 185 return nil, moerr.NewBackendCannotConnectNoCtx() 186 } 187 188 if ctx == nil { 189 panic("client Send nil context") 190 } 191 for { 192 b, err := c.getBackend(backend, false) 193 if err != nil { 194 return nil, err 195 } 196 197 f, err := b.Send(ctx, request) 198 if err != nil && err == backendClosed { 199 continue 200 } 201 return f, err 202 } 203 } 204 205 func (c *client) NewStream(backend string, lock bool) (Stream, error) { 206 for { 207 b, err := c.getBackend(backend, lock) 208 if err != nil { 209 return nil, err 210 } 211 212 st, err := b.NewStream(lock) 213 if err != nil && err == backendClosed { 214 continue 215 } 216 return st, err 217 } 218 } 219 220 func (c *client) Ping(ctx context.Context, backend string) error { 221 if ctx == nil { 222 panic("client Ping nil context") 223 } 224 for { 225 b, err := c.getBackend(backend, false) 226 if err != nil { 227 return err 228 } 229 230 f, err := b.SendInternal(ctx, &flagOnlyMessage{flag: flagPing}) 231 if err != nil { 232 if err == backendClosed { 233 continue 234 } 235 return err 236 } 237 _, err = f.Get() 238 f.Close() 239 return err 240 } 241 } 242 243 func (c *client) Close() error { 244 c.mu.Lock() 245 if c.mu.closed { 246 c.mu.Unlock() 247 return nil 248 } 249 c.mu.closed = true 250 251 for _, backends := range c.mu.backends { 252 for _, b := range backends { 253 b.Close() 254 } 255 } 256 c.mu.Unlock() 257 258 c.stopper.Stop() 259 close(c.createC) 260 return nil 261 } 262 263 func (c *client) CloseBackend() error { 264 c.mu.Lock() 265 defer c.mu.Unlock() 266 for _, backends := range c.mu.backends { 267 for _, b := range backends { 268 b.Close() 269 } 270 } 271 return nil 272 } 273 274 func (c *client) getBackend(backend string, lock bool) (Backend, error) { 275 c.mu.Lock() 276 b, err := c.getBackendLocked(backend, lock) 277 if err != nil { 278 c.mu.Unlock() 279 return nil, err 280 } 281 if b != nil { 282 c.mu.Unlock() 283 return b, nil 284 } 285 c.mu.Unlock() 286 287 return c.createBackend(backend, lock) 288 } 289 290 func (c *client) getBackendLocked(backend string, lock bool) (Backend, error) { 291 if c.mu.closed { 292 return nil, moerr.NewClientClosedNoCtx() 293 } 294 defer func() { 295 n := 0 296 for _, backends := range c.mu.backends { 297 n += len(backends) 298 } 299 c.metrics.poolSizeGauge.Set(float64(n)) 300 }() 301 302 lockedCnt := 0 303 inactiveCnt := 0 304 if backends, ok := c.mu.backends[backend]; ok { 305 n := uint64(len(backends)) 306 var b Backend 307 for i := uint64(0); i < n; i++ { 308 seq := c.mu.ops[backend].next() 309 b = backends[seq%n] 310 if !b.Locked() && b.LastActiveTime() != (time.Time{}) { 311 break 312 } 313 314 if b.Locked() { 315 lockedCnt++ 316 } 317 if b.LastActiveTime() == (time.Time{}) { 318 inactiveCnt++ 319 } 320 b = nil 321 } 322 323 // all backend inactived, trigger gc inactive. 324 if b == nil && n > 0 { 325 c.triggerGCInactive(backend) 326 c.logger.Debug("no available backends", 327 zap.String("backend", backend), 328 zap.Int("locked", lockedCnt), 329 zap.Int("inactive", inactiveCnt), 330 zap.Int("max", c.options.maxBackendsPerHost)) 331 if !c.canCreateLocked(backend) { 332 return nil, moerr.NewNoAvailableBackendNoCtx() 333 } 334 } 335 336 if lock && b != nil { 337 b.Lock() 338 } 339 c.maybeCreateLocked(backend) 340 return b, nil 341 } 342 return nil, nil 343 } 344 345 func (c *client) maybeCreateLocked(backend string) bool { 346 if len(c.mu.backends[backend]) == 0 { 347 return c.tryCreate(backend) 348 } 349 350 if !c.canCreateLocked(backend) { 351 return false 352 } 353 354 for _, b := range c.mu.backends[backend] { 355 if b.Busy() || b.Locked() { 356 return c.tryCreate(backend) 357 } 358 } 359 return false 360 } 361 362 func (c *client) tryCreate(backend string) bool { 363 if !c.options.enableAutoCreate { 364 return false 365 } 366 367 select { 368 case c.createC <- backend: 369 return true 370 default: 371 return false 372 } 373 } 374 375 func (c *client) gcIdleTask(ctx context.Context) { 376 c.logger.Info("gc idle backends task started") 377 defer c.logger.Error("gc idle backends task stopped") 378 379 ticker := time.NewTicker(c.options.maxIdleDuration) 380 defer ticker.Stop() 381 382 for { 383 select { 384 case <-ctx.Done(): 385 return 386 case <-ticker.C: 387 c.closeIdleBackends() 388 } 389 } 390 } 391 392 func (c *client) triggerGCInactive(remote string) { 393 select { 394 case c.gcInactiveC <- remote: 395 c.logger.Debug("try to remove all inactived backends", 396 zap.String("remote", remote)) 397 default: 398 } 399 } 400 401 func (c *client) gcInactiveTask(ctx context.Context) { 402 c.logger.Debug("gc inactive backends task started") 403 defer c.logger.Error("gc inactive backends task stopped") 404 405 for { 406 select { 407 case <-ctx.Done(): 408 return 409 case remote := <-c.gcInactiveC: 410 c.doRemoveInactive(remote) 411 } 412 } 413 } 414 415 func (c *client) doRemoveInactive(remote string) { 416 c.mu.Lock() 417 defer c.mu.Unlock() 418 backends, ok := c.mu.backends[remote] 419 if !ok { 420 return 421 } 422 423 newBackends := backends[:0] 424 for _, backend := range backends { 425 if backend.LastActiveTime() == (time.Time{}) { 426 backend.Close() 427 continue 428 } 429 newBackends = append(newBackends, backend) 430 } 431 c.mu.backends[remote] = newBackends 432 } 433 434 func (c *client) closeIdleBackends() { 435 var idleBackends []Backend 436 c.mu.Lock() 437 for k, backends := range c.mu.backends { 438 var newBackends []Backend 439 for _, b := range backends { 440 if !b.Locked() && 441 time.Since(b.LastActiveTime()) > c.options.maxIdleDuration { 442 idleBackends = append(idleBackends, b) 443 continue 444 } 445 newBackends = append(newBackends, b) 446 } 447 c.mu.backends[k] = newBackends 448 } 449 c.mu.Unlock() 450 451 for _, b := range idleBackends { 452 b.Close() 453 } 454 } 455 456 func (c *client) createTask(ctx context.Context) { 457 for { 458 select { 459 case <-ctx.Done(): 460 return 461 case backend, ok := <-c.createC: 462 if ok { 463 c.mu.Lock() 464 if _, err := c.createBackendLocked(backend); err != nil { 465 c.logger.Error("create backend failed", 466 zap.String("backend", backend), 467 zap.Error(err)) 468 } 469 c.mu.Unlock() 470 } 471 } 472 } 473 } 474 475 func (c *client) createBackend(backend string, lock bool) (Backend, error) { 476 c.mu.Lock() 477 defer c.mu.Unlock() 478 479 b, err := c.getBackendLocked(backend, lock) 480 if err != nil { 481 return nil, err 482 } 483 if b != nil { 484 return b, nil 485 } 486 487 b, err = c.createBackendLocked(backend) 488 if err != nil { 489 return nil, err 490 } 491 if lock { 492 b.Lock() 493 } 494 return b, nil 495 } 496 497 func (c *client) createBackendLocked(backend string) (Backend, error) { 498 if !c.canCreateLocked(backend) { 499 return nil, moerr.NewNoAvailableBackendNoCtx() 500 } 501 502 b, err := c.doCreate(backend) 503 if err != nil { 504 return nil, err 505 } 506 c.mu.backends[backend] = append(c.mu.backends[backend], b) 507 if _, ok := c.mu.ops[backend]; !ok { 508 c.mu.ops[backend] = &op{} 509 } 510 return b, nil 511 } 512 513 func (c *client) doCreate(backend string) (Backend, error) { 514 b, err := c.factory.Create(backend, WithBackendMetrics(c.metrics)) 515 if err != nil { 516 c.logger.Error("create backend failed", 517 zap.String("backend", backend), 518 zap.Error(err)) 519 return nil, err 520 } 521 return b, nil 522 } 523 524 func (c *client) canCreateLocked(backend string) bool { 525 return len(c.mu.backends[backend]) < c.options.maxBackendsPerHost 526 } 527 528 type op struct { 529 seq uint64 530 } 531 532 func (o *op) next() uint64 { 533 return atomic.AddUint64(&o.seq, 1) 534 }