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  }