dubbo.apache.org/dubbo-go/v3@v3.1.1/registry/etcdv3/service_discovery.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package etcdv3 19 20 import ( 21 "fmt" 22 "strings" 23 "sync" 24 ) 25 26 import ( 27 gxset "github.com/dubbogo/gost/container/set" 28 gxetcd "github.com/dubbogo/gost/database/kv/etcd/v3" 29 gxpage "github.com/dubbogo/gost/hash/page" 30 "github.com/dubbogo/gost/log/logger" 31 32 "github.com/hashicorp/vault/sdk/helper/jsonutil" 33 ) 34 35 import ( 36 "dubbo.apache.org/dubbo-go/v3/common" 37 "dubbo.apache.org/dubbo-go/v3/common/constant" 38 "dubbo.apache.org/dubbo-go/v3/common/extension" 39 "dubbo.apache.org/dubbo-go/v3/registry" 40 "dubbo.apache.org/dubbo-go/v3/remoting" 41 "dubbo.apache.org/dubbo-go/v3/remoting/etcdv3" 42 ) 43 44 const ( 45 ROOT = "/services" 46 ) 47 48 var initLock sync.Mutex 49 50 func init() { 51 extension.SetServiceDiscovery(constant.EtcdV3Key, newEtcdV3ServiceDiscovery) 52 } 53 54 // new etcd service discovery struct 55 type etcdV3ServiceDiscovery struct { 56 // descriptor is a short string about the basic information of this instance 57 descriptor string 58 // client is current Etcdv3 client 59 client *gxetcd.Client 60 // serviceInstance is current serviceInstance 61 serviceInstance *registry.ServiceInstance 62 // services is when register or update will add service name 63 services *gxset.HashSet 64 // child listener 65 childListenerMap map[string]*etcdv3.EventListener 66 instanceListenerMap map[string]*gxset.HashSet 67 } 68 69 // basic information of this instance 70 func (e *etcdV3ServiceDiscovery) String() string { 71 return e.descriptor 72 } 73 74 // Destroy service discovery 75 func (e *etcdV3ServiceDiscovery) Destroy() error { 76 if e.client != nil { 77 e.client.Close() 78 } 79 return nil 80 } 81 82 // Register will register an instance of ServiceInstance to registry 83 func (e *etcdV3ServiceDiscovery) Register(instance registry.ServiceInstance) error { 84 e.serviceInstance = &instance 85 86 path := toPath(instance) 87 88 if nil != e.client { 89 ins, err := jsonutil.EncodeJSON(instance) 90 if err == nil { 91 err = e.client.RegisterTemp(path, string(ins)) 92 if err != nil { 93 logger.Errorf("cannot register the instance: %s", string(ins), err) 94 } else { 95 e.services.Add(instance.GetServiceName()) 96 } 97 } 98 } 99 100 return nil 101 } 102 103 // Update will update the data of the instance in registry 104 func (e *etcdV3ServiceDiscovery) Update(instance registry.ServiceInstance) error { 105 path := toPath(instance) 106 107 if nil != e.client { 108 ins, err := jsonutil.EncodeJSON(instance) 109 if err == nil { 110 if err = e.client.RegisterTemp(path, string(ins)); err != nil { 111 logger.Warnf("etcdV3ServiceDiscovery.client.RegisterTemp(path:%v, instance:%v) = error:%v", 112 path, string(ins), err) 113 } 114 e.services.Add(instance.GetServiceName()) 115 } 116 } 117 118 return nil 119 } 120 121 // Unregister will unregister this instance from registry 122 func (e *etcdV3ServiceDiscovery) Unregister(instance registry.ServiceInstance) error { 123 path := toPath(instance) 124 125 if nil != e.client { 126 err := e.client.Delete(path) 127 e.services.Remove(instance.GetServiceName()) 128 e.serviceInstance = nil 129 return err 130 } 131 132 return nil 133 } 134 135 // GetDefaultPageSize will return the default page size 136 func (e *etcdV3ServiceDiscovery) GetDefaultPageSize() int { 137 return registry.DefaultPageSize 138 } 139 140 // GetServices will return the all service names. 141 func (e *etcdV3ServiceDiscovery) GetServices() *gxset.HashSet { 142 return e.services 143 } 144 145 // GetInstances will return all service instances with serviceName 146 func (e *etcdV3ServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance { 147 if nil != e.client { 148 // get keys and values 149 _, vList, err := e.client.GetChildrenKVList(toParentPath(serviceName)) 150 if nil == err { 151 serviceInstances := make([]registry.ServiceInstance, 0, len(vList)) 152 for _, v := range vList { 153 instance := ®istry.DefaultServiceInstance{} 154 err = jsonutil.DecodeJSON([]byte(v), &instance) 155 if nil == err { 156 serviceInstances = append(serviceInstances, instance) 157 } 158 } 159 return serviceInstances 160 } 161 logger.Infof("could not getChildrenKVList the err is:%v", err) 162 } 163 164 return make([]registry.ServiceInstance, 0) 165 } 166 167 // GetInstancesByPage will return a page containing instances of ServiceInstance with the serviceName 168 // the page will start at offset 169 func (e *etcdV3ServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager { 170 all := e.GetInstances(serviceName) 171 172 res := make([]interface{}, 0, pageSize) 173 174 for i := offset; i < len(all) && i < offset+pageSize; i++ { 175 res = append(res, all[i]) 176 } 177 178 return gxpage.NewPage(offset, pageSize, res, len(all)) 179 } 180 181 // GetHealthyInstancesByPage will return a page containing instances of ServiceInstance. 182 // The param healthy indices that the instance should be healthy or not. 183 // The page will start at offset 184 func (e *etcdV3ServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager { 185 all := e.GetInstances(serviceName) 186 res := make([]interface{}, 0, pageSize) 187 188 var ( 189 i = offset 190 count = 0 191 ) 192 for i < len(all) && count < pageSize { 193 ins := all[i] 194 if ins.IsHealthy() == healthy { 195 res = append(res, all[i]) 196 count++ 197 } 198 i++ 199 } 200 return gxpage.NewPage(offset, pageSize, res, len(all)) 201 } 202 203 // GetRequestInstances Batch get all instances by the specified service names 204 func (e *etcdV3ServiceDiscovery) GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager { 205 res := make(map[string]gxpage.Pager, len(serviceNames)) 206 for _, name := range serviceNames { 207 res[name] = e.GetInstancesByPage(name, offset, requestedSize) 208 } 209 return res 210 } 211 212 // AddListener adds a new ServiceInstancesChangedListenerImpl 213 // see addServiceInstancesChangedListener in Java 214 func (e *etcdV3ServiceDiscovery) AddListener(listener registry.ServiceInstancesChangedListener) error { 215 for _, t := range listener.GetServiceNames().Values() { 216 err := e.registerServiceInstanceListener(t.(string), listener) 217 if err != nil { 218 return err 219 } 220 221 err = e.registerServiceWatcher(t.(string)) 222 if err != nil { 223 return err 224 } 225 } 226 return nil 227 } 228 229 // Convert instance to dubbo path 230 func toPath(instance registry.ServiceInstance) string { 231 if instance == nil { 232 return "" 233 } 234 // like: /services/servicename1/host(127.0.0.1)/8080 235 return fmt.Sprintf("%s%d", ROOT+constant.PathSeparator+instance.GetServiceName()+constant.PathSeparator+instance.GetHost()+constant.KeySeparator, instance.GetPort()) 236 } 237 238 // to dubbo service path 239 func toParentPath(serviceName string) string { 240 return ROOT + constant.PathSeparator + serviceName 241 } 242 243 // register service instance listener, instance listener and watcher are matched through serviceName 244 func (e *etcdV3ServiceDiscovery) registerServiceInstanceListener(serviceName string, listener registry.ServiceInstancesChangedListener) error { 245 initLock.Lock() 246 defer initLock.Unlock() 247 248 set, found := e.instanceListenerMap[serviceName] 249 if !found { 250 set = gxset.NewSet(listener) 251 set.Add(listener) 252 e.instanceListenerMap[serviceName] = set 253 return nil 254 } 255 set.Add(listener) 256 return nil 257 } 258 259 // register service watcher 260 func (e *etcdV3ServiceDiscovery) registerServiceWatcher(serviceName string) error { 261 initLock.Lock() 262 defer initLock.Unlock() 263 264 path := toParentPath(serviceName) 265 266 listener, found := e.childListenerMap[serviceName] 267 268 if !found { 269 listener = etcdv3.NewEventListener(e.client) 270 e.childListenerMap[serviceName] = listener 271 } 272 273 listener.ListenServiceEvent(path, e) 274 275 return nil 276 } 277 278 // DataChange when child data change should DispatchEventByServiceName 279 func (e *etcdV3ServiceDiscovery) DataChange(eventType remoting.Event) bool { 280 if eventType.Action == remoting.EventTypeUpdate { 281 instance := ®istry.DefaultServiceInstance{} 282 err := jsonutil.DecodeJSON([]byte(eventType.Content), &instance) 283 if err != nil { 284 instance.ServiceName = "" 285 } 286 287 // notify instance listener instance change 288 name := instance.ServiceName 289 instances := e.GetInstances(name) 290 for _, lis := range e.instanceListenerMap[instance.ServiceName].Values() { 291 var instanceLis registry.ServiceInstancesChangedListener 292 instanceLis = lis.(registry.ServiceInstancesChangedListener) 293 err = instanceLis.OnEvent(registry.NewServiceInstancesChangedEvent(name, instances)) 294 } 295 if err != nil { 296 return false 297 } 298 } 299 300 return true 301 } 302 303 // newEtcdv3ServiceDiscovery 304 func newEtcdV3ServiceDiscovery(url *common.URL) (registry.ServiceDiscovery, error) { 305 initLock.Lock() 306 defer initLock.Unlock() 307 308 timeout := url.GetParamDuration(constant.RegistryTimeoutKey, constant.DefaultRegTimeout) 309 310 logger.Infof("etcd address is: %v,timeout is:%s", url.Location, timeout.String()) 311 312 client := etcdv3.NewServiceDiscoveryClient( 313 gxetcd.WithName(gxetcd.RegistryETCDV3Client), 314 gxetcd.WithTimeout(timeout), 315 gxetcd.WithEndpoints(strings.Split(url.Location, ",")...), 316 ) 317 318 descriptor := fmt.Sprintf("etcd-service-discovery[%s]", url.Location) 319 320 return &etcdV3ServiceDiscovery{ 321 descriptor: descriptor, 322 client: client, 323 serviceInstance: nil, 324 services: gxset.NewSet(), 325 childListenerMap: make(map[string]*etcdv3.EventListener), 326 instanceListenerMap: make(map[string]*gxset.HashSet)}, nil 327 }