dubbo.apache.org/dubbo-go/v3@v3.1.1/registry/nacos/listener.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 "bytes" 22 "net/url" 23 "reflect" 24 "strconv" 25 "sync" 26 ) 27 28 import ( 29 gxchan "github.com/dubbogo/gost/container/chan" 30 nacosClient "github.com/dubbogo/gost/database/kv/nacos" 31 "github.com/dubbogo/gost/log/logger" 32 33 "github.com/nacos-group/nacos-sdk-go/v2/model" 34 "github.com/nacos-group/nacos-sdk-go/v2/vo" 35 36 perrors "github.com/pkg/errors" 37 ) 38 39 import ( 40 "dubbo.apache.org/dubbo-go/v3/common" 41 "dubbo.apache.org/dubbo-go/v3/common/constant" 42 "dubbo.apache.org/dubbo-go/v3/config_center" 43 "dubbo.apache.org/dubbo-go/v3/registry" 44 "dubbo.apache.org/dubbo-go/v3/remoting" 45 ) 46 47 var ( 48 listenerCache sync.Map 49 ) 50 51 type callback func(services []model.Instance, err error) 52 53 type nacosListener struct { 54 namingClient *nacosClient.NacosNamingClient 55 listenURL *common.URL 56 regURL *common.URL 57 events *gxchan.UnboundedChan 58 instanceMap map[string]model.Instance 59 cacheLock sync.Mutex 60 done chan struct{} 61 subscribeParam *vo.SubscribeParam 62 } 63 64 // NewNacosListener creates a data listener for nacos 65 func NewNacosListener(url, regURL *common.URL, namingClient *nacosClient.NacosNamingClient) (*nacosListener, error) { 66 listener := &nacosListener{ 67 namingClient: namingClient, 68 listenURL: url, 69 regURL: regURL, 70 events: gxchan.NewUnboundedChan(32), 71 instanceMap: map[string]model.Instance{}, 72 done: make(chan struct{}), 73 } 74 err := listener.startListen() 75 return listener, err 76 } 77 78 // NewNacosListener creates a data listener for nacos 79 func NewNacosListenerWithServiceName(serviceName string, url, regURL *common.URL, namingClient *nacosClient.NacosNamingClient) (*nacosListener, error) { 80 listener := &nacosListener{ 81 namingClient: namingClient, 82 listenURL: url, 83 regURL: regURL, 84 events: gxchan.NewUnboundedChan(32), 85 instanceMap: map[string]model.Instance{}, 86 done: make(chan struct{}), 87 } 88 err := listener.startListenWithServiceName(serviceName) 89 return listener, err 90 } 91 92 func generateUrl(instance model.Instance) *common.URL { 93 if instance.Metadata == nil { 94 logger.Errorf("nacos instance metadata is empty,instance:%+v", instance) 95 return nil 96 } 97 path := instance.Metadata["path"] 98 myInterface := instance.Metadata["interface"] 99 if len(path) == 0 && len(myInterface) == 0 { 100 logger.Errorf("nacos instance metadata does not have both path key and interface key,instance:%+v", instance) 101 return nil 102 } 103 if len(path) == 0 && len(myInterface) != 0 { 104 path = "/" + myInterface 105 } 106 protocol := instance.Metadata["protocol"] 107 if len(protocol) == 0 { 108 logger.Errorf("nacos instance metadata does not have protocol key,instance:%+v", instance) 109 return nil 110 } 111 urlMap := url.Values{} 112 for k, v := range instance.Metadata { 113 urlMap.Set(k, v) 114 } 115 return common.NewURLWithOptions( 116 common.WithIp(instance.Ip), 117 common.WithPort(strconv.Itoa(int(instance.Port))), 118 common.WithProtocol(protocol), 119 common.WithParams(urlMap), 120 common.WithPath(path), 121 ) 122 } 123 124 // Callback will be invoked when got subscribed events. 125 func (nl *nacosListener) Callback(services []model.Instance, err error) { 126 if err != nil { 127 logger.Errorf("nacos subscribe callback error:%s , subscribe:%+v ", err.Error(), nl.subscribeParam) 128 return 129 } 130 131 addInstances := make([]model.Instance, 0, len(services)) 132 delInstances := make([]model.Instance, 0, len(services)) 133 updateInstances := make([]model.Instance, 0, len(services)) 134 newInstanceMap := make(map[string]model.Instance, len(services)) 135 136 nl.cacheLock.Lock() 137 defer nl.cacheLock.Unlock() 138 for i := range services { 139 if !services[i].Enable { 140 // instance is not available,so ignore it 141 continue 142 } 143 host := services[i].Ip + ":" + strconv.Itoa(int(services[i].Port)) 144 instance := services[i] 145 newInstanceMap[host] = instance 146 if old, ok := nl.instanceMap[host]; !ok && instance.Healthy { 147 // instance does not exist in cache, add it to cache 148 addInstances = append(addInstances, instance) 149 } else if !reflect.DeepEqual(old, instance) && instance.Healthy { 150 // instance is not different from cache, update it to cache 151 updateInstances = append(updateInstances, instance) 152 } 153 } 154 155 for host, inst := range nl.instanceMap { 156 if newInstance, ok := newInstanceMap[host]; !ok || !newInstance.Healthy { 157 // cache instance does not exist in new instance list, remove it from cache 158 delInstances = append(delInstances, inst) 159 } 160 } 161 162 nl.instanceMap = newInstanceMap 163 for i := range addInstances { 164 if newUrl := generateUrl(addInstances[i]); newUrl != nil { 165 nl.process(&config_center.ConfigChangeEvent{Value: newUrl, ConfigType: remoting.EventTypeAdd}) 166 } 167 } 168 for i := range delInstances { 169 if newUrl := generateUrl(delInstances[i]); newUrl != nil { 170 nl.process(&config_center.ConfigChangeEvent{Value: newUrl, ConfigType: remoting.EventTypeDel}) 171 } 172 } 173 174 for i := range updateInstances { 175 if newUrl := generateUrl(updateInstances[i]); newUrl != nil { 176 nl.process(&config_center.ConfigChangeEvent{Value: newUrl, ConfigType: remoting.EventTypeUpdate}) 177 } 178 } 179 } 180 181 func getSubscribeName(url *common.URL) string { 182 var buffer bytes.Buffer 183 184 buffer.Write([]byte(common.DubboNodes[common.PROVIDER])) 185 appendParam(&buffer, url, constant.InterfaceKey) 186 appendParam(&buffer, url, constant.VersionKey) 187 appendParam(&buffer, url, constant.GroupKey) 188 return buffer.String() 189 } 190 191 func (nl *nacosListener) startListen() error { 192 if nl.namingClient == nil { 193 return perrors.New("nacos naming namingClient stopped") 194 } 195 nl.subscribeParam = createSubscribeParam(nl.listenURL, nl.regURL, nl.Callback) 196 if nl.subscribeParam == nil { 197 return perrors.New("create nacos subscribeParam failed") 198 } 199 go func() { 200 err := nl.namingClient.Client().Subscribe(nl.subscribeParam) 201 if err == nil { 202 listenerCache.Store(nl.subscribeParam.ServiceName+nl.subscribeParam.GroupName, nl) 203 } 204 }() 205 return nil 206 } 207 208 func (nl *nacosListener) startListenWithServiceName(serviceName string) error { 209 if nl.namingClient == nil { 210 return perrors.New("nacos naming namingClient stopped") 211 } 212 nl.subscribeParam = createSubscribeParamWithServiceName(serviceName, nl.regURL, nl.Callback) 213 if nl.subscribeParam == nil { 214 return perrors.New("create nacos subscribeParam failed") 215 } 216 go func() { 217 err := nl.namingClient.Client().Subscribe(nl.subscribeParam) 218 if err == nil { 219 listenerCache.Store(nl.subscribeParam.ServiceName+nl.subscribeParam.GroupName, nl) 220 } 221 }() 222 return nil 223 } 224 225 func (nl *nacosListener) stopListen() error { 226 return nl.namingClient.Client().Unsubscribe(nl.subscribeParam) 227 } 228 229 func (nl *nacosListener) process(configType *config_center.ConfigChangeEvent) { 230 nl.events.In() <- configType 231 } 232 233 // Next returns the service event from nacos. 234 func (nl *nacosListener) Next() (*registry.ServiceEvent, error) { 235 for { 236 select { 237 case <-nl.done: 238 logger.Warnf("nacos listener is close!listenUrl:%+v", nl.listenURL) 239 return nil, perrors.New("listener stopped") 240 241 case val := <-nl.events.Out(): 242 e, _ := val.(*config_center.ConfigChangeEvent) 243 logger.Debugf("got nacos event %s", e) 244 return ®istry.ServiceEvent{Action: e.ConfigType, Service: e.Value.(*common.URL)}, nil 245 } 246 } 247 } 248 249 // nolint 250 func (nl *nacosListener) Close() { 251 _ = nl.stopListen() 252 close(nl.done) 253 }