github.com/unionj-cloud/go-doudou@v1.3.8-0.20221011095552-0088008e5b31/framework/http/client.go (about) 1 package ddhttp 2 3 import ( 4 "fmt" 5 "github.com/go-resty/resty/v2" 6 "github.com/klauspost/compress/gzhttp" 7 "github.com/opentracing-contrib/go-stdlib/nethttp" 8 "github.com/unionj-cloud/go-doudou/framework/internal/config" 9 "github.com/unionj-cloud/go-doudou/framework/memberlist" 10 "github.com/unionj-cloud/go-doudou/framework/registry" 11 "github.com/unionj-cloud/go-doudou/framework/registry/nacos" 12 "github.com/unionj-cloud/go-doudou/toolkit/cast" 13 logger "github.com/unionj-cloud/go-doudou/toolkit/zlogger" 14 "github.com/wubin1989/nacos-sdk-go/clients/naming_client" 15 "github.com/wubin1989/nacos-sdk-go/model" 16 "github.com/wubin1989/nacos-sdk-go/vo" 17 "net" 18 "net/http" 19 "os" 20 "runtime" 21 "sort" 22 "sync" 23 "sync/atomic" 24 "time" 25 ) 26 27 // DdClient defines service client interface 28 type DdClient interface { 29 SetProvider(provider registry.IServiceProvider) 30 SetClient(client *resty.Client) 31 SetRootPath(rootPath string) 32 } 33 34 // DdClientOption defines configure function type 35 type DdClientOption func(DdClient) 36 37 // WithProvider sets service provider 38 func WithProvider(provider registry.IServiceProvider) DdClientOption { 39 return func(c DdClient) { 40 c.SetProvider(provider) 41 } 42 } 43 44 // WithClient sets http client 45 func WithClient(client *resty.Client) DdClientOption { 46 return func(c DdClient) { 47 c.SetClient(client) 48 } 49 } 50 51 // WithRootPath sets root path for sending http requests 52 func WithRootPath(rootPath string) DdClientOption { 53 return func(c DdClient) { 54 c.SetRootPath(rootPath) 55 } 56 } 57 58 // ServiceProvider defines an implementation for IServiceProvider 59 type ServiceProvider struct { 60 server string 61 } 62 63 // SelectServer return service address from environment variable 64 func (s *ServiceProvider) SelectServer() string { 65 return s.server 66 } 67 68 // NewServiceProvider creates new ServiceProvider instance 69 func NewServiceProvider(env string) *ServiceProvider { 70 return &ServiceProvider{ 71 server: os.Getenv(env), 72 } 73 } 74 75 // NewClient creates new resty Client instance 76 func NewClient() *resty.Client { 77 client := resty.New() 78 client.SetTimeout(1 * time.Minute) 79 dialer := &net.Dialer{ 80 Timeout: 30 * time.Second, 81 KeepAlive: 30 * time.Second, 82 DualStack: true, 83 } 84 client.SetTransport(gzhttp.Transport(&nethttp.Transport{ 85 RoundTripper: &http.Transport{ 86 Proxy: http.ProxyFromEnvironment, 87 DialContext: dialer.DialContext, 88 ForceAttemptHTTP2: true, 89 MaxIdleConns: 100, 90 IdleConnTimeout: 90 * time.Second, 91 TLSHandshakeTimeout: 10 * time.Second, 92 ExpectContinueTimeout: 1 * time.Second, 93 MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1, 94 MaxConnsPerHost: 10000, 95 }, 96 })) 97 retryCnt := config.DefaultGddRetryCount 98 if cnt, err := cast.ToIntE(config.GddRetryCount.Load()); err == nil { 99 retryCnt = cnt 100 } 101 client.SetRetryCount(retryCnt) 102 return client 103 } 104 105 type server struct { 106 service string 107 node string 108 baseUrl string 109 weight int 110 currentWeight int 111 } 112 113 func (s *server) Weight() int { 114 return s.weight 115 } 116 117 type base struct { 118 name string 119 nodes []*server 120 nodeMap map[string]*server 121 lock sync.RWMutex 122 } 123 124 // AddNode add or update node providing the service 125 func (m *base) AddNode(node *memberlist.Node) { 126 m.lock.Lock() 127 defer m.lock.Unlock() 128 svcName := registry.SvcName(node) 129 if svcName != m.name { 130 return 131 } 132 baseUrl, _ := registry.BaseUrl(node) 133 weight, _ := registry.MetaWeight(node) 134 if s, exists := m.nodeMap[node.Name]; !exists { 135 s = &server{ 136 service: m.name, 137 node: node.Name, 138 baseUrl: baseUrl, 139 weight: weight, 140 currentWeight: 0, 141 } 142 m.nodes = append(m.nodes, s) 143 m.nodeMap[node.Name] = s 144 logger.Info().Msgf("[go-doudou] add node %s to load balancer, supplying %s service", node.Name, svcName) 145 } else { 146 old := *s 147 s.baseUrl = baseUrl 148 s.weight = weight 149 logger.Info().Msgf("[go-doudou] node %s update, supplying %s service, old: %+v, new: %+v", node.Name, svcName, old, *s) 150 } 151 } 152 153 func (m *base) UpdateWeight(node *memberlist.Node) { 154 weight, _ := registry.MetaWeight(node) 155 if weight > 0 { 156 return 157 } 158 m.lock.Lock() 159 defer m.lock.Unlock() 160 svcName := registry.SvcName(node) 161 if svcName != m.name { 162 return 163 } 164 if s, exists := m.nodeMap[node.Name]; exists { 165 old := *s 166 s.weight = node.Weight 167 logger.Info().Msgf("[go-doudou] weight of node %s update, old: %d, new: %d", node.Name, old.weight, s.weight) 168 } 169 } 170 171 func (m *base) GetServer(nodeName string) *server { 172 return m.nodeMap[nodeName] 173 } 174 175 func (m *base) RemoveNode(node *memberlist.Node) { 176 m.lock.Lock() 177 defer m.lock.Unlock() 178 svcName := registry.SvcName(node) 179 if svcName != m.name { 180 return 181 } 182 if _, exists := m.nodeMap[node.Name]; exists { 183 var idx int 184 for i, n := range m.nodes { 185 if n.node == node.Name { 186 idx = i 187 } 188 } 189 m.nodes = append(m.nodes[:idx], m.nodes[idx+1:]...) 190 delete(m.nodeMap, node.Name) 191 logger.Info().Msgf("[go-doudou] remove node %s from load balancer, supplying %s service", node.Name, svcName) 192 } 193 } 194 195 // MemberlistServiceProvider defines an implementation for IServiceProvider 196 type MemberlistServiceProvider struct { 197 base 198 current uint64 199 } 200 201 // SelectServer selects a node which is supplying service specified by name property from cluster 202 func (m *MemberlistServiceProvider) SelectServer() string { 203 m.lock.RLock() 204 defer m.lock.RUnlock() 205 if len(m.nodes) == 0 { 206 return "" 207 } 208 next := int(atomic.AddUint64(&m.current, uint64(1)) % uint64(len(m.nodes))) 209 m.current = uint64(next) 210 selected := m.nodes[next] 211 return selected.baseUrl 212 } 213 214 // NewMemberlistServiceProvider create an NewMemberlistServiceProvider instance 215 func NewMemberlistServiceProvider(name string) *MemberlistServiceProvider { 216 sp := &MemberlistServiceProvider{ 217 base: base{ 218 name: name, 219 nodeMap: make(map[string]*server), 220 }, 221 } 222 registry.RegisterServiceProvider(sp) 223 return sp 224 } 225 226 // SmoothWeightedRoundRobinProvider is a smooth weighted round-robin algo implementation for IServiceProvider 227 // https://github.com/nginx/nginx/commit/52327e0627f49dbda1e8db695e63a4b0af4448b1 228 type SmoothWeightedRoundRobinProvider struct { 229 base 230 } 231 232 // SelectServer selects a node which is supplying service specified by name property from cluster 233 func (m *SmoothWeightedRoundRobinProvider) SelectServer() string { 234 m.lock.RLock() 235 defer m.lock.RUnlock() 236 if len(m.nodes) == 0 { 237 return "" 238 } 239 var selected *server 240 total := 0 241 for i := 0; i < len(m.nodes); i++ { 242 s := m.nodes[i] 243 s.currentWeight += s.weight 244 total += s.weight 245 if selected == nil || s.currentWeight > selected.currentWeight { 246 selected = s 247 } 248 } 249 selected.currentWeight -= total 250 return selected.baseUrl 251 } 252 253 // NewSmoothWeightedRoundRobinProvider create an SmoothWeightedRoundRobinProvider instance 254 func NewSmoothWeightedRoundRobinProvider(name string) *SmoothWeightedRoundRobinProvider { 255 sp := &SmoothWeightedRoundRobinProvider{ 256 base: base{ 257 name: name, 258 nodeMap: make(map[string]*server), 259 }, 260 } 261 registry.RegisterServiceProvider(sp) 262 return sp 263 } 264 265 type nacosBase struct { 266 clusters []string //optional,default:DEFAULT 267 serviceName string //required 268 groupName string //optional,default:DEFAULT_GROUP 269 270 lock sync.RWMutex 271 namingClient naming_client.INamingClient 272 } 273 274 func (b *nacosBase) SetClusters(clusters []string) { 275 b.clusters = clusters 276 } 277 278 func (b *nacosBase) SetGroupName(groupName string) { 279 b.groupName = groupName 280 } 281 282 func (b *nacosBase) SetNamingClient(namingClient naming_client.INamingClient) { 283 b.namingClient = namingClient 284 } 285 286 type NacosProviderOption func(registry.INacosServiceProvider) 287 288 func WithNacosClusters(clusters []string) NacosProviderOption { 289 return func(provider registry.INacosServiceProvider) { 290 provider.SetClusters(clusters) 291 } 292 } 293 294 func WithNacosGroupName(groupName string) NacosProviderOption { 295 return func(provider registry.INacosServiceProvider) { 296 provider.SetGroupName(groupName) 297 } 298 } 299 300 func WithNacosNamingClient(namingClient naming_client.INamingClient) NacosProviderOption { 301 return func(provider registry.INacosServiceProvider) { 302 provider.SetNamingClient(namingClient) 303 } 304 } 305 306 type instance []model.Instance 307 308 func (a instance) Len() int { 309 return len(a) 310 } 311 312 func (a instance) Swap(i, j int) { 313 a[i], a[j] = a[j], a[i] 314 } 315 316 func (a instance) Less(i, j int) bool { 317 return a[i].InstanceId < a[j].InstanceId 318 } 319 320 // NacosRRServiceProvider is a simple round-robin load balance implementation for IServiceProvider 321 type NacosRRServiceProvider struct { 322 nacosBase 323 current uint64 324 } 325 326 // SelectServer return service address from environment variable 327 func (n *NacosRRServiceProvider) SelectServer() string { 328 n.lock.RLock() 329 defer n.lock.RUnlock() 330 if n.namingClient == nil { 331 logger.Error().Msg("[go-doudou] nacos discovery client has not been initialized") 332 return "" 333 } 334 instances, err := n.namingClient.SelectInstances(vo.SelectInstancesParam{ 335 Clusters: n.clusters, 336 ServiceName: n.serviceName, 337 GroupName: n.groupName, 338 HealthyOnly: true, 339 }) 340 if err != nil { 341 logger.Error().Err(err).Msg("[go-doudou] error") 342 return "" 343 } 344 sort.Sort(instance(instances)) 345 next := int(atomic.AddUint64(&n.current, uint64(1)) % uint64(len(instances))) 346 n.current = uint64(next) 347 selected := instances[next] 348 return fmt.Sprintf("http://%s:%d%s", selected.Ip, selected.Port, selected.Metadata["rootPath"]) 349 } 350 351 // NewNacosRRServiceProvider creates new ServiceProvider instance 352 func NewNacosRRServiceProvider(serviceName string, opts ...NacosProviderOption) *NacosRRServiceProvider { 353 provider := &NacosRRServiceProvider{ 354 nacosBase: nacosBase{ 355 serviceName: serviceName, 356 namingClient: nacos.NamingClient, 357 }, 358 } 359 for _, opt := range opts { 360 opt(provider) 361 } 362 return provider 363 } 364 365 // NacosWRRServiceProvider is a WRR load balance implementation for IServiceProvider 366 type NacosWRRServiceProvider struct { 367 nacosBase 368 } 369 370 // SelectServer return service address from environment variable 371 func (n *NacosWRRServiceProvider) SelectServer() string { 372 n.lock.RLock() 373 defer n.lock.RUnlock() 374 if n.namingClient == nil { 375 logger.Error().Msg("[go-doudou] nacos discovery client has not been initialized") 376 return "" 377 } 378 instance, err := n.namingClient.SelectOneHealthyInstance(vo.SelectOneHealthInstanceParam{ 379 Clusters: n.clusters, 380 ServiceName: n.serviceName, 381 GroupName: n.groupName, 382 }) 383 if err != nil { 384 logger.Error().Err(err).Msg("[go-doudou] failed to select one healthy instance") 385 return "" 386 } 387 return fmt.Sprintf("http://%s:%d%s", instance.Ip, instance.Port, instance.Metadata["rootPath"]) 388 } 389 390 // NewNacosWRRServiceProvider creates new ServiceProvider instance 391 func NewNacosWRRServiceProvider(serviceName string, opts ...NacosProviderOption) *NacosWRRServiceProvider { 392 provider := &NacosWRRServiceProvider{ 393 nacosBase{ 394 serviceName: serviceName, 395 namingClient: nacos.NamingClient, 396 }, 397 } 398 for _, opt := range opts { 399 opt(provider) 400 } 401 return provider 402 }