dubbo.apache.org/dubbo-go/v3@v3.1.1/registry/nacos/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 nacos 19 20 import ( 21 "fmt" 22 "sync" 23 ) 24 25 import ( 26 gxset "github.com/dubbogo/gost/container/set" 27 nacosClient "github.com/dubbogo/gost/database/kv/nacos" 28 gxpage "github.com/dubbogo/gost/hash/page" 29 "github.com/dubbogo/gost/log/logger" 30 31 "github.com/nacos-group/nacos-sdk-go/v2/model" 32 "github.com/nacos-group/nacos-sdk-go/v2/vo" 33 34 perrors "github.com/pkg/errors" 35 ) 36 37 import ( 38 "dubbo.apache.org/dubbo-go/v3/common" 39 "dubbo.apache.org/dubbo-go/v3/common/constant" 40 "dubbo.apache.org/dubbo-go/v3/common/extension" 41 "dubbo.apache.org/dubbo-go/v3/registry" 42 "dubbo.apache.org/dubbo-go/v3/remoting/nacos" 43 ) 44 45 const ( 46 defaultGroup = constant.ServiceDiscoveryDefaultGroup 47 idKey = "id" 48 ) 49 50 func init() { 51 extension.SetServiceDiscovery(constant.NacosKey, newNacosServiceDiscovery) 52 } 53 54 // nacosServiceDiscovery is the implementation of service discovery based on nacos. 55 // There is a problem, the go namingClient for nacos does not support the id field. 56 // we will use the metadata to store the id of ServiceInstance 57 type nacosServiceDiscovery struct { 58 group string 59 // descriptor is a short string about the basic information of this instance 60 descriptor string 61 62 // namingClient is the Nacos' namingClient 63 namingClient *nacosClient.NacosNamingClient 64 // cache registry instances 65 registryInstances []registry.ServiceInstance 66 67 instanceListenerMap map[string]*gxset.HashSet 68 listenerLock sync.Mutex 69 } 70 71 // Destroy will close the service discovery. 72 // Actually, it only marks the naming namingClient as null and then return 73 func (n *nacosServiceDiscovery) Destroy() error { 74 for _, inst := range n.registryInstances { 75 err := n.Unregister(inst) 76 logger.Infof("Unregister nacos instance:%+v", inst) 77 if err != nil { 78 logger.Errorf("Unregister nacos instance:%+v, err:%+v", inst, err) 79 } 80 } 81 n.namingClient.Close() 82 return nil 83 } 84 85 // Register will register the service to nacos 86 func (n *nacosServiceDiscovery) Register(instance registry.ServiceInstance) error { 87 ins := n.toRegisterInstance(instance) 88 ok, err := n.namingClient.Client().RegisterInstance(ins) 89 if err != nil || !ok { 90 return perrors.WithMessage(err, "Could not register the instance. "+instance.GetServiceName()) 91 } 92 n.registryInstances = append(n.registryInstances, instance) 93 return nil 94 } 95 96 // Update will update the information 97 // However, because nacos client doesn't support the update API, 98 // so we should unregister the instance and then register it again. 99 // the error handling is hard to implement 100 func (n *nacosServiceDiscovery) Update(instance registry.ServiceInstance) error { 101 // TODO(wait for nacos support) 102 err := n.Unregister(instance) 103 if err != nil { 104 return perrors.WithStack(err) 105 } 106 return n.Register(instance) 107 } 108 109 // Unregister will unregister the instance 110 func (n *nacosServiceDiscovery) Unregister(instance registry.ServiceInstance) error { 111 ok, err := n.namingClient.Client().DeregisterInstance(n.toDeregisterInstance(instance)) 112 if err != nil || !ok { 113 return perrors.WithMessage(err, "Could not unregister the instance. "+instance.GetServiceName()) 114 } 115 return nil 116 } 117 118 // GetDefaultPageSize will return the constant registry.DefaultPageSize 119 func (n *nacosServiceDiscovery) GetDefaultPageSize() int { 120 return registry.DefaultPageSize 121 } 122 123 // GetServices will return the all services 124 func (n *nacosServiceDiscovery) GetServices() *gxset.HashSet { 125 services, err := n.namingClient.Client().GetAllServicesInfo(vo.GetAllServiceInfoParam{ 126 GroupName: n.group, 127 }) 128 129 res := gxset.NewSet() 130 if err != nil { 131 logger.Errorf("Could not query the services: %v", err) 132 return res 133 } 134 135 for _, e := range services.Doms { 136 res.Add(e) 137 } 138 return res 139 } 140 141 // GetInstances will return the instances of serviceName and the group 142 func (n *nacosServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance { 143 instances, err := n.namingClient.Client().SelectAllInstances(vo.SelectAllInstancesParam{ 144 ServiceName: serviceName, 145 GroupName: n.group, 146 }) 147 if err != nil { 148 logger.Errorf("Could not query the instances for service: %+v, group: %+v . It happened err %+v", 149 serviceName, n.group, err) 150 return make([]registry.ServiceInstance, 0) 151 } 152 res := make([]registry.ServiceInstance, 0, len(instances)) 153 for _, ins := range instances { 154 metadata := ins.Metadata 155 id := metadata[idKey] 156 157 delete(metadata, idKey) 158 159 res = append(res, ®istry.DefaultServiceInstance{ 160 ID: id, 161 // ins.ServiceName is nacos service name like 'DEFAULT_GROUP@@MyAppName", 162 // which is not the service name we wanted, so we use serviceName directly. 163 ServiceName: serviceName, 164 Host: ins.Ip, 165 Port: int(ins.Port), 166 Enable: ins.Enable, 167 Healthy: ins.Healthy, 168 Metadata: metadata, 169 GroupName: n.group, 170 }) 171 } 172 return res 173 } 174 175 // GetInstancesByPage will return the instances 176 // Due to nacos namingClient does not support pagination, so we have to query all instances and then return part of them 177 func (n *nacosServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager { 178 all := n.GetInstances(serviceName) 179 res := make([]interface{}, 0, pageSize) 180 // could not use res = all[a:b] here because the res should be []interface{}, not []ServiceInstance 181 for i := offset; i < len(all) && i < offset+pageSize; i++ { 182 res = append(res, all[i]) 183 } 184 return gxpage.NewPage(offset, pageSize, res, len(all)) 185 } 186 187 // GetHealthyInstancesByPage will return the instance 188 // The nacos namingClient has an API SelectInstances, which has a parameter call HealthyOnly. 189 // However, the healthy parameter in this method maybe false. So we can not use that API. 190 // Thus, we must query all instances and then do filter 191 func (n *nacosServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager { 192 all := n.GetInstances(serviceName) 193 res := make([]interface{}, 0, pageSize) 194 // could not use res = all[a:b] here because the res should be []interface{}, not []ServiceInstance 195 var ( 196 i = offset 197 count = 0 198 ) 199 for i < len(all) && count < pageSize { 200 ins := all[i] 201 if ins.IsHealthy() == healthy { 202 res = append(res, all[i]) 203 count++ 204 } 205 i++ 206 } 207 return gxpage.NewPage(offset, pageSize, res, len(all)) 208 } 209 210 // GetRequestInstances will return the instances 211 // The nacos namingClient doesn't have batch API, so we should query those serviceNames one by one. 212 func (n *nacosServiceDiscovery) GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager { 213 res := make(map[string]gxpage.Pager, len(serviceNames)) 214 for _, name := range serviceNames { 215 res[name] = n.GetInstancesByPage(name, offset, requestedSize) 216 } 217 return res 218 } 219 220 func (n *nacosServiceDiscovery) registerInstanceListener(listener registry.ServiceInstancesChangedListener) { 221 n.listenerLock.Lock() 222 defer n.listenerLock.Unlock() 223 224 for _, t := range listener.GetServiceNames().Values() { 225 serviceName, ok := t.(string) 226 if !ok { 227 logger.Errorf("service name error %s", t) 228 continue 229 } 230 listenerSet, found := n.instanceListenerMap[serviceName] 231 if !found { 232 listenerSet = gxset.NewSet(listener) 233 listenerSet.Add(listener) 234 n.instanceListenerMap[serviceName] = listenerSet 235 } else { 236 listenerSet.Add(listener) 237 } 238 } 239 } 240 241 // AddListener will add a listener 242 func (n *nacosServiceDiscovery) AddListener(listener registry.ServiceInstancesChangedListener) error { 243 n.registerInstanceListener(listener) 244 for _, t := range listener.GetServiceNames().Values() { 245 serviceName := t.(string) 246 err := n.namingClient.Client().Subscribe(&vo.SubscribeParam{ 247 ServiceName: serviceName, 248 GroupName: n.group, 249 SubscribeCallback: func(services []model.Instance, err error) { 250 if err != nil { 251 logger.Errorf("Could not handle the subscribe notification because the err is not nil."+ 252 " service name: %s, err: %v", serviceName, err) 253 } 254 instances := make([]registry.ServiceInstance, 0, len(services)) 255 for _, service := range services { 256 // we won't use the nacos instance id here but use our instance id 257 metadata := service.Metadata 258 id := metadata[idKey] 259 260 delete(metadata, idKey) 261 262 instances = append(instances, ®istry.DefaultServiceInstance{ 263 ID: id, 264 ServiceName: service.ServiceName, 265 Host: service.Ip, 266 Port: int(service.Port), 267 Enable: service.Enable, 268 Healthy: true, 269 Metadata: metadata, 270 GroupName: n.group, 271 }) 272 } 273 274 var e error 275 for _, lis := range n.instanceListenerMap[serviceName].Values() { 276 var instanceListener registry.ServiceInstancesChangedListener 277 instanceListener = lis.(registry.ServiceInstancesChangedListener) 278 e = instanceListener.OnEvent(registry.NewServiceInstancesChangedEvent(serviceName, instances)) 279 } 280 281 if e != nil { 282 logger.Errorf("Dispatching event got exception, service name: %s, err: %v", serviceName, err) 283 } 284 }, 285 }) 286 if err != nil { 287 return err 288 } 289 } 290 return nil 291 } 292 293 // toRegisterInstance convert the ServiceInstance to RegisterInstanceParam 294 // the Ephemeral will be true 295 func (n *nacosServiceDiscovery) toRegisterInstance(instance registry.ServiceInstance) vo.RegisterInstanceParam { 296 metadata := instance.GetMetadata() 297 if metadata == nil { 298 metadata = make(map[string]string, 1) 299 } 300 metadata[idKey] = instance.GetID() 301 return vo.RegisterInstanceParam{ 302 ServiceName: instance.GetServiceName(), 303 Ip: instance.GetHost(), 304 Port: uint64(instance.GetPort()), 305 Metadata: metadata, 306 // We must specify the weight since Java nacos namingClient will ignore the instance whose weight is 0 307 Weight: 1, 308 Enable: instance.IsEnable(), 309 Healthy: instance.IsHealthy(), 310 GroupName: n.group, 311 Ephemeral: true, 312 } 313 } 314 315 // toDeregisterInstance will convert the ServiceInstance to DeregisterInstanceParam 316 func (n *nacosServiceDiscovery) toDeregisterInstance(instance registry.ServiceInstance) vo.DeregisterInstanceParam { 317 return vo.DeregisterInstanceParam{ 318 ServiceName: instance.GetServiceName(), 319 Ip: instance.GetHost(), 320 Port: uint64(instance.GetPort()), 321 GroupName: n.group, 322 } 323 } 324 325 func (n *nacosServiceDiscovery) String() string { 326 return n.descriptor 327 } 328 329 // newNacosServiceDiscovery will create new service discovery instance 330 func newNacosServiceDiscovery(url *common.URL) (registry.ServiceDiscovery, error) { 331 discoveryURL := common.NewURLWithOptions( 332 common.WithParams(url.GetParams()), 333 common.WithParamsValue(constant.TimeoutKey, url.GetParam(constant.RegistryTimeoutKey, constant.DefaultRegTimeout)), 334 common.WithParamsValue(constant.NacosGroupKey, url.GetParam(constant.RegistryGroupKey, defaultGroup)), 335 common.WithParamsValue(constant.NacosUsername, url.Username), 336 common.WithParamsValue(constant.NacosPassword, url.Password), 337 common.WithParamsValue(constant.NacosNamespaceID, url.GetParam(constant.RegistryNamespaceKey, ""))) 338 discoveryURL.Location = url.Location 339 discoveryURL.Username = url.Username 340 discoveryURL.Password = url.Password 341 client, err := nacos.NewNacosClientByURL(discoveryURL) 342 if err != nil { 343 return nil, perrors.WithMessage(err, "create nacos namingClient failed.") 344 } 345 346 descriptor := fmt.Sprintf("nacos-service-discovery[%s]", discoveryURL.Location) 347 348 group := url.GetParam(constant.RegistryGroupKey, defaultGroup) 349 newInstance := &nacosServiceDiscovery{ 350 group: group, 351 namingClient: client, 352 descriptor: descriptor, 353 registryInstances: []registry.ServiceInstance{}, 354 instanceListenerMap: make(map[string]*gxset.HashSet), 355 } 356 return newInstance, nil 357 }