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 }