github.com/polarismesh/polaris@v1.17.8/service/batch/client.go (about)

     1  /**
     2   * Tencent is pleased to support the open source community by making Polaris available.
     3   *
     4   * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
     5   *
     6   * Licensed under the BSD 3-Clause License (the "License");
     7   * you may not use this file except in compliance with the License.
     8   * You may obtain a copy of the License at
     9   *
    10   * https://opensource.org/licenses/BSD-3-Clause
    11   *
    12   * Unless required by applicable law or agreed to in writing, software distributed
    13   * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    14   * CONDITIONS OF ANY KIND, either express or implied. See the License for the
    15   * specific language governing permissions and limitations under the License.
    16   */
    17  
    18  package batch
    19  
    20  import (
    21  	"context"
    22  	"errors"
    23  	"time"
    24  
    25  	apimodel "github.com/polarismesh/specification/source/go/api/v1/model"
    26  
    27  	"github.com/polarismesh/polaris/common/model"
    28  	commonstore "github.com/polarismesh/polaris/common/store"
    29  	"github.com/polarismesh/polaris/store"
    30  )
    31  
    32  // InstanceCtrl 批量操作实例的类
    33  type ClientCtrl struct {
    34  	config          *CtrlConfig
    35  	storage         store.Store
    36  	storeThreadCh   []chan []*ClientFuture      // store协程,负责写操作
    37  	clientHandler   func([]*ClientFuture) error // store协程里面调用的instance处理函数,可以是注册和反注册
    38  	idleStoreThread chan int                    // 空闲的store协程,记录每一个空闲id
    39  	waitDuration    time.Duration
    40  	queue           chan *ClientFuture // 请求接受协程
    41  	label           string
    42  }
    43  
    44  // NewBatchRegisterClientCtrl 注册客户端批量操作对象
    45  func NewBatchRegisterClientCtrl(storage store.Store, config *CtrlConfig) (*ClientCtrl, error) {
    46  	register, err := newBatchClientCtrl(storage, config)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  	if register == nil {
    51  		return nil, nil
    52  	}
    53  
    54  	log.Infof("[Batch] open batch register client")
    55  	register.label = "register"
    56  	register.clientHandler = register.registerHandler
    57  	return register, nil
    58  }
    59  
    60  // NewBatchDeregisterClientCtrl 注册客户端批量操作对象
    61  func NewBatchDeregisterClientCtrl(storage store.Store, config *CtrlConfig) (*ClientCtrl, error) {
    62  	deregister, err := newBatchClientCtrl(storage, config)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	if deregister == nil {
    67  		return nil, nil
    68  	}
    69  
    70  	log.Infof("[Batch] open batch deregister client")
    71  	deregister.label = "deregister"
    72  	deregister.clientHandler = deregister.deregisterHandler
    73  	return deregister, nil
    74  }
    75  
    76  // Start 开始启动批量操作实例的相关协程
    77  func (ctrl *ClientCtrl) Start(ctx context.Context) {
    78  	log.Infof("[Batch][Client] Start batch instance, config: %+v", ctrl.config)
    79  
    80  	// 初始化并且启动多个store协程,并发对数据库写
    81  	for i := 0; i < ctrl.config.Concurrency; i++ {
    82  		ctrl.storeThreadCh = append(ctrl.storeThreadCh, make(chan []*ClientFuture))
    83  	}
    84  	for i := 0; i < ctrl.config.Concurrency; i++ {
    85  		go ctrl.storeWorker(ctx, i)
    86  	}
    87  
    88  	// 进入主循环
    89  	ctrl.mainLoop(ctx)
    90  }
    91  
    92  // newBatchInstanceCtrl 创建批量控制instance的对象
    93  func newBatchClientCtrl(storage store.Store, config *CtrlConfig) (*ClientCtrl, error) {
    94  	if config == nil || !config.Open {
    95  		return nil, nil
    96  	}
    97  	duration, err := time.ParseDuration(config.WaitTime)
    98  	if err != nil {
    99  		log.Errorf("[Batch] parse waitTime(%s) err: %s", config.WaitTime, err.Error())
   100  		return nil, err
   101  	}
   102  	if duration == 0 {
   103  		log.Errorf("[Batch] config waitTime is invalid")
   104  		return nil, errors.New("config waitTime is invalid")
   105  	}
   106  
   107  	instance := &ClientCtrl{
   108  		config:          config,
   109  		storage:         storage,
   110  		storeThreadCh:   make([]chan []*ClientFuture, 0, config.Concurrency),
   111  		idleStoreThread: make(chan int, config.Concurrency),
   112  		queue:           make(chan *ClientFuture, config.QueueSize),
   113  		waitDuration:    duration,
   114  	}
   115  	return instance, nil
   116  }
   117  
   118  // mainLoop 注册主协程
   119  // 从注册队列中获取注册请求,当达到b.config.MaxBatchCount,
   120  // 或当到了一个超时时间b.waitDuration,则发起一个写请求
   121  // 写请求发送到store协程,规则:从空闲的管道idleStoreThread中挑选一个
   122  func (ctrl *ClientCtrl) mainLoop(ctx context.Context) {
   123  	futures := make([]*ClientFuture, 0, ctrl.config.MaxBatchCount)
   124  	idx := 0
   125  	triggerConsume := func(data []*ClientFuture) {
   126  		if idx == 0 {
   127  			return
   128  		}
   129  		// 选择一个idle的store协程写数据 TODO 这里需要统计一下
   130  		idleIdx := <-ctrl.idleStoreThread
   131  		ctrl.storeThreadCh[idleIdx] <- data
   132  		futures = make([]*ClientFuture, 0, ctrl.config.MaxBatchCount)
   133  		idx = 0
   134  	}
   135  	// 启动接受注册请求的协程
   136  	go func() {
   137  		ticker := time.NewTicker(ctrl.waitDuration)
   138  		defer ticker.Stop()
   139  		for {
   140  			select {
   141  			case future := <-ctrl.queue:
   142  				futures = append(futures, future)
   143  				idx++
   144  				if idx == ctrl.config.MaxBatchCount {
   145  					triggerConsume(futures[0:idx])
   146  				}
   147  			case <-ticker.C:
   148  				triggerConsume(futures[0:idx])
   149  			case <-ctx.Done():
   150  				log.Infof("[Batch] %s main loop exited", ctrl.label)
   151  				return
   152  			}
   153  		}
   154  	}()
   155  }
   156  
   157  // storeWorker store写协程的主循环
   158  // 从chan中获取数据,直接写数据库
   159  // 每次写完,设置协程为空闲
   160  func (ctrl *ClientCtrl) storeWorker(ctx context.Context, index int) {
   161  	log.Infof("[Batch][Client] %s worker(%d) running in main loop", ctrl.label, index)
   162  	// store协程启动,先把自己注册到idle中
   163  	ctrl.idleStoreThread <- index
   164  	// 主循环
   165  	for {
   166  		select {
   167  		case futures := <-ctrl.storeThreadCh[index]:
   168  			if err := ctrl.clientHandler(futures); err != nil {
   169  				// 所有的错误都在instanceHandler函数里面进行答复和处理,这里只需记录一条日志
   170  				log.Errorf("[Batch][Client] %s clients err: %s", ctrl.label, err.Error())
   171  			}
   172  			ctrl.idleStoreThread <- index
   173  		case <-ctx.Done():
   174  			// idle is not ready
   175  			log.Infof("[Batch][Client] %s worker(%d) exited", ctrl.label, index)
   176  			return
   177  		}
   178  	}
   179  }
   180  
   181  // registerHandler 外部应该把鉴权完成
   182  // 判断实例是否存在,也可以提前判断,减少batch复杂度
   183  // 提前通过token判断,再进入batch操作
   184  // batch操作,只是写操作
   185  func (ctrl *ClientCtrl) registerHandler(futures []*ClientFuture) error {
   186  	if len(futures) == 0 {
   187  		return nil
   188  	}
   189  
   190  	log.Infof("[Batch] Start batch creating clients count: %d", len(futures))
   191  
   192  	// 调用batch接口,创建实例
   193  	clients := make([]*model.Client, 0, len(futures))
   194  	for _, entry := range futures {
   195  		clients = append(clients, model.NewClient(entry.request))
   196  	}
   197  	if err := ctrl.storage.BatchAddClients(clients); err != nil {
   198  		SendClientReply(futures, commonstore.StoreCode2APICode(err), err)
   199  		return err
   200  	}
   201  
   202  	SendClientReply(futures, apimodel.Code_ExecuteSuccess, nil)
   203  	return nil
   204  }
   205  
   206  // deregisterHandler 外部应该把鉴权完成
   207  // 判断实例是否存在,也可以提前判断,减少batch复杂度
   208  // 提前通过token判断,再进入batch操作
   209  // batch操作,只是写操作
   210  func (ctrl *ClientCtrl) deregisterHandler(futures []*ClientFuture) error {
   211  	if len(futures) == 0 {
   212  		return nil
   213  	}
   214  
   215  	log.Infof("[Batch] Start batch deleting clients count: %d", len(futures))
   216  
   217  	// 调用batch接口,创建实例
   218  	clients := make([]string, 0, len(futures))
   219  	for _, entry := range futures {
   220  		id := entry.request.GetId().GetValue()
   221  		clients = append(clients, id)
   222  	}
   223  	if err := ctrl.storage.BatchDeleteClients(clients); err != nil {
   224  		SendClientReply(futures, commonstore.StoreCode2APICode(err), err)
   225  		return err
   226  	}
   227  
   228  	SendClientReply(futures, apimodel.Code_ExecuteSuccess, nil)
   229  	return nil
   230  }