You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by al...@apache.org on 2025/07/13 07:14:35 UTC

(dubbo-go) branch develop updated: feat: Refactor weight handling in loadbalance and service discovery (#2937)

This is an automated email from the ASF dual-hosted git repository.

alexstocks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git


The following commit(s) were added to refs/heads/develop by this push:
     new f39795d37 feat: Refactor weight handling in loadbalance and service discovery (#2937)
f39795d37 is described below

commit f39795d3735617e114e9c92427ff0b2851379729
Author: john <32...@users.noreply.github.com>
AuthorDate: Sun Jul 13 15:14:29 2025 +0800

    feat: Refactor weight handling in loadbalance and service discovery (#2937)
    
    - Convert Nacos weight from float64 to int64 (with rounding)
    - Refactor weight injection as Option to eliminate ToURLs duplication
    - Optimize loadbalance weight processing for better performance
    - Format code layout and clean up comments for better readability
    - Improve test cases with better naming conventions
    - Fix formatting issues in service configuration and URL handling
    
    Co-authored-by: john <>
---
 cluster/loadbalance/p2c/loadbalance_test.go | 11 +---
 cluster/loadbalance/util.go                 | 50 +++++++++--------
 common/url.go                               | 18 +++---
 global/service_config.go                    |  2 +
 registry/nacos/service_discovery.go         | 44 +++++++++------
 registry/nacos/service_discovery_test.go    | 87 ++++++++++++++++++++++++++---
 registry/service_instance.go                | 23 +++++---
 server/action.go                            |  2 +
 8 files changed, 166 insertions(+), 71 deletions(-)

diff --git a/cluster/loadbalance/p2c/loadbalance_test.go b/cluster/loadbalance/p2c/loadbalance_test.go
index 489e0a66e..1f4fa034d 100644
--- a/cluster/loadbalance/p2c/loadbalance_test.go
+++ b/cluster/loadbalance/p2c/loadbalance_test.go
@@ -20,18 +20,13 @@ package p2c
 import (
 	"math/rand"
 	"testing"
-)
-
-import (
-	"github.com/golang/mock/gomock"
 
-	"github.com/stretchr/testify/assert"
-)
-
-import (
 	"dubbo.apache.org/dubbo-go/v3/cluster/metrics"
 	"dubbo.apache.org/dubbo-go/v3/common"
 	"dubbo.apache.org/dubbo-go/v3/protocol/base"
+	"github.com/golang/mock/gomock"
+	"github.com/stretchr/testify/assert"
+
 	protoinvoc "dubbo.apache.org/dubbo-go/v3/protocol/invocation"
 )
 
diff --git a/cluster/loadbalance/util.go b/cluster/loadbalance/util.go
index 5de8db5ae..2eb76fe47 100644
--- a/cluster/loadbalance/util.go
+++ b/cluster/loadbalance/util.go
@@ -19,36 +19,41 @@ package loadbalance
 
 import (
 	"time"
-)
 
-import (
 	"dubbo.apache.org/dubbo-go/v3/common/constant"
 	"dubbo.apache.org/dubbo-go/v3/protocol/base"
 )
 
-// GetWeight gets weight for load balance strategy
+// GetWeight returns the weight for the load‑balancing strategy.
 func GetWeight(invoker base.Invoker, invocation base.Invocation) int64 {
-	var weight int64
+
 	url := invoker.GetURL()
-	// Multiple registry scenario, load balance among multiple registries.
-	isRegIvk := url.GetParamBool(constant.RegistryKey+"."+constant.RegistryLabelKey, false)
-	if isRegIvk {
+
+	//  Method‑level or registry‑level weight taken from URL parameters — highest priority.
+	var weight = url.GetMethodParamInt64(
+		invocation.MethodName(), constant.WeightKey, -1)
+
+	if weight < 0 && url.GetParamBool(constant.RegistryKey+"."+constant.RegistryLabelKey, false) {
 		weight = url.GetParamInt(constant.RegistryKey+"."+constant.WeightKey, constant.DefaultWeight)
-	} else {
-		weight = url.GetMethodParamInt64(invocation.MethodName(), constant.WeightKey, constant.DefaultWeight)
-
-		if weight > 0 {
-			// get service register time an do warm up time
-			now := time.Now().Unix()
-			timestamp := url.GetParamInt(constant.RemoteTimestampKey, now)
-			if uptime := now - timestamp; uptime > 0 {
-				warmup := url.GetParamInt(constant.WarmupKey, constant.DefaultWarmup)
-				if uptime < warmup {
-					if ww := float64(uptime) / float64(warmup) / float64(weight); ww < 1 {
-						weight = 1
-					} else if int64(ww) <= weight {
-						weight = int64(ww)
-					}
+	}
+
+	// If the URL does not specify a weight, fall back to the default value (100).
+	if weight < 0 {
+		weight = constant.DefaultWeight
+	}
+
+	// Warm‑up adjustment (same logic as before).
+	if weight > 0 {
+		now := time.Now().Unix()
+		ts := url.GetParamInt(constant.RemoteTimestampKey, now)
+		if uptime := now - ts; uptime > 0 {
+			warm := url.GetParamInt(constant.WarmupKey, constant.DefaultWarmup)
+			if uptime < warm {
+				calc := float64(uptime) * float64(weight) / float64(warm)
+				if calc < 1 {
+					weight = 1
+				} else if int64(calc) <= weight {
+					weight = int64(calc)
 				}
 			}
 		}
@@ -57,6 +62,5 @@ func GetWeight(invoker base.Invoker, invocation base.Invocation) int64 {
 	if weight < 0 {
 		weight = 0
 	}
-
 	return weight
 }
diff --git a/common/url.go b/common/url.go
index 8307f74c5..34a55c6cc 100644
--- a/common/url.go
+++ b/common/url.go
@@ -28,22 +28,15 @@ import (
 	"strings"
 	"sync"
 	"time"
-)
 
-import (
 	cm "github.com/Workiva/go-datastructures/common"
 
 	gxset "github.com/dubbogo/gost/container/set"
-
 	"github.com/google/uuid"
-
 	"github.com/jinzhu/copier"
 
-	perrors "github.com/pkg/errors"
-)
-
-import (
 	"dubbo.apache.org/dubbo-go/v3/common/constant"
+	perrors "github.com/pkg/errors"
 )
 
 // dubbo role type constant
@@ -228,6 +221,15 @@ func WithAttribute(key string, attribute any) Option {
 	}
 }
 
+// WithWeight sets weight for URL
+func WithWeight(weight int64) Option {
+	return func(url *URL) {
+		if weight > 0 {
+			url.SetParam(constant.WeightKey, strconv.FormatInt(weight, 10))
+		}
+	}
+}
+
 // NewURLWithOptions will create a new URL with options
 func NewURLWithOptions(opts ...Option) *URL {
 	newURL := &URL{}
diff --git a/global/service_config.go b/global/service_config.go
index b1c097af0..69fd41f7c 100644
--- a/global/service_config.go
+++ b/global/service_config.go
@@ -46,6 +46,7 @@ type ServiceConfig struct {
 	ParamSign                   string            `yaml:"param.sign" json:"param.sign,omitempty" property:"param.sign"`
 	Tag                         string            `yaml:"tag" json:"tag,omitempty" property:"tag"`
 	TracingKey                  string            `yaml:"tracing-key" json:"tracing-key,omitempty" propertiy:"tracing-key"`
+	Weight                      int64             `yaml:"weight" json:"weight,omitempty" property:"weight"`
 
 	RCProtocolsMap  map[string]*ProtocolConfig
 	RCRegistriesMap map[string]*RegistryConfig
@@ -126,5 +127,6 @@ func (c *ServiceConfig) Clone() *ServiceConfig {
 		ParamSign:                   c.ParamSign,
 		Tag:                         c.Tag,
 		TracingKey:                  c.TracingKey,
+		Weight:                      c.Weight,
 	}
 }
diff --git a/registry/nacos/service_discovery.go b/registry/nacos/service_discovery.go
index 5d255bfd1..d00c1f5cf 100644
--- a/registry/nacos/service_discovery.go
+++ b/registry/nacos/service_discovery.go
@@ -19,29 +19,26 @@ package nacos
 
 import (
 	"fmt"
+	"math"
 	"regexp"
 	"strconv"
 	"sync"
-)
 
-import (
 	gxset "github.com/dubbogo/gost/container/set"
+
 	nacosClient "github.com/dubbogo/gost/database/kv/nacos"
+
 	gxpage "github.com/dubbogo/gost/hash/page"
 	"github.com/dubbogo/gost/log/logger"
-
 	"github.com/nacos-group/nacos-sdk-go/v2/model"
 	"github.com/nacos-group/nacos-sdk-go/v2/vo"
 
-	perrors "github.com/pkg/errors"
-)
-
-import (
 	"dubbo.apache.org/dubbo-go/v3/common"
 	"dubbo.apache.org/dubbo-go/v3/common/constant"
 	"dubbo.apache.org/dubbo-go/v3/common/extension"
 	"dubbo.apache.org/dubbo-go/v3/registry"
 	"dubbo.apache.org/dubbo-go/v3/remoting/nacos"
+	perrors "github.com/pkg/errors"
 )
 
 const (
@@ -187,6 +184,7 @@ func (n *nacosServiceDiscovery) GetInstances(serviceName string) []registry.Serv
 			ServiceName: serviceName,
 			Host:        ins.Ip,
 			Port:        int(ins.Port),
+			Weight:      int64(math.Round(ins.Weight)),
 			Enable:      ins.Enable,
 			Healthy:     ins.Healthy,
 			Metadata:    metadata,
@@ -288,6 +286,7 @@ func (n *nacosServiceDiscovery) AddListener(listener registry.ServiceInstancesCh
 						ServiceName: serviceName,
 						Host:        service.Ip,
 						Port:        int(service.Port),
+						Weight:      int64(math.Round(service.Weight)),
 						Enable:      service.Enable,
 						Healthy:     true,
 						Metadata:    metadata,
@@ -321,14 +320,27 @@ func (n *nacosServiceDiscovery) toRegisterInstance(instance registry.ServiceInst
 		metadata = make(map[string]string, 1)
 	}
 
-	weightStr := n.registryURL.GetParam(constant.RegistryKey+"."+constant.WeightKey, "1.0")
-	weight, err := strconv.ParseFloat(weightStr, 64)
-	if err != nil || weight <= constant.MinNacosWeight {
-		logger.Warnf("Invalid weight value %q, using default 1.0. err: %v", weightStr, err)
-		weight = constant.DefaultNacosWeight
-	} else if weight > constant.MaxNacosWeight {
-		logger.Warnf("Weight %f exceeds Nacos maximum 10000, setting to 10000", weight)
-		weight = constant.MaxNacosWeight
+	//Retrieve weight (Provider takes precedence; URL may override)
+	w := instance.GetWeight()
+	// Set by Provider via WithServerWeight / WithWeight
+
+	// If the registry URL explicitly sets registry.weight, override the value
+	if urlW := n.registryURL.GetParam(constant.RegistryKey+"."+constant.WeightKey, ""); urlW != "" {
+		if f, err := strconv.ParseFloat(urlW, 64); err == nil {
+			w = int64(f)
+		} else {
+			logger.Warnf("Invalid weight override value '%s': %v. Using default weight.", urlW, err)
+		}
+	}
+
+	//Validity check
+	switch {
+	case w <= 0:
+		w = int64(constant.DefaultNacosWeight)
+		// Nacos default is 1
+	case w > constant.MaxNacosWeight:
+		w = constant.MaxNacosWeight
+		// Nacos upper limit is 10000
 	}
 
 	metadata[idKey] = instance.GetID()
@@ -338,7 +350,7 @@ func (n *nacosServiceDiscovery) toRegisterInstance(instance registry.ServiceInst
 		Port:        uint64(instance.GetPort()),
 		Metadata:    metadata,
 		// We must specify the weight since Java nacos namingClient will ignore the instance whose weight is 0
-		Weight:    weight,
+		Weight:    float64(w),
 		Enable:    instance.IsEnable(),
 		Healthy:   instance.IsHealthy(),
 		GroupName: n.group,
diff --git a/registry/nacos/service_discovery_test.go b/registry/nacos/service_discovery_test.go
index d6b3f78f5..a9fc833dd 100644
--- a/registry/nacos/service_discovery_test.go
+++ b/registry/nacos/service_discovery_test.go
@@ -21,23 +21,17 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"math"
 	"strconv"
 	"sync"
 	"testing"
-)
 
-import (
-	gxset "github.com/dubbogo/gost/container/set"
+	"dubbo.apache.org/dubbo-go/v3/metadata/info"
 
+	gxset "github.com/dubbogo/gost/container/set"
 	"github.com/nacos-group/nacos-sdk-go/v2/model"
 	"github.com/nacos-group/nacos-sdk-go/v2/vo"
 
-	perrors "github.com/pkg/errors"
-
-	"github.com/stretchr/testify/assert"
-)
-
-import (
 	"dubbo.apache.org/dubbo-go/v3/common"
 	"dubbo.apache.org/dubbo-go/v3/common/constant"
 	"dubbo.apache.org/dubbo-go/v3/common/extension"
@@ -46,6 +40,8 @@ import (
 	"dubbo.apache.org/dubbo-go/v3/registry"
 	"dubbo.apache.org/dubbo-go/v3/registry/servicediscovery"
 	"dubbo.apache.org/dubbo-go/v3/remoting/nacos"
+	perrors "github.com/pkg/errors"
+	"github.com/stretchr/testify/assert"
 )
 
 const testName = "test"
@@ -59,6 +55,55 @@ func TestNewNacosServiceDiscovery(t *testing.T) {
 	assert.Nil(t, err)
 }
 
+// Registration side: toRegisterInstance writes Weight into param
+func TestWeight_RegisterParam(t *testing.T) {
+	// Prepare a fake registryURL to avoid a nil pointer
+	u, _ := common.NewURL("nacos://127.0.0.1:8848")
+	sd := &nacosServiceDiscovery{registryURL: u}
+
+	si := &registry.DefaultServiceInstance{
+		ServiceName: "Demo",
+		Host:        "127.0.0.1",
+		Port:        20000,
+		Weight:      77,
+	}
+	param := sd.toRegisterInstance(si)
+	assert.Equal(t, 77.0, param.Weight)
+}
+
+//Subscription side: convertInstances writes Weight back
+//to the instance and injects it into the URL
+
+func fakeInstance(w float64) model.Instance {
+	return model.Instance{
+		Ip:     "192.181.10.1", //NOSONAR
+		Port:   20000,
+		Weight: w,
+		Metadata: map[string]string{
+			idKey: "inst-1",
+		},
+	}
+}
+
+func TestWeight_ConvertInstances(t *testing.T) {
+	sd := &nacosServiceDiscovery{}
+	out := sd.convertInstances([]model.Instance{fakeInstance(55)})
+
+	assert.Len(t, out, 1)
+	inst := out[0].(*registry.DefaultServiceInstance)
+	assert.Equal(t, int64(55), inst.Weight)
+
+	// Add valid endpoints data to avoid ERROR logs
+	inst.Metadata[constant.ServiceInstanceEndpoints] = `[{"port":20000,"protocol":"tri"}]`
+
+	// Verify that the weight is correctly injected into the URL
+	urls := inst.ToURLs(&info.ServiceInfo{Protocol: "tri"})
+	if len(urls) > 0 {
+		got := urls[0].GetParam(constant.WeightKey, "")
+		assert.Equal(t, "55", got, "Weight not injected into URL; load balancer cannot read it")
+	}
+}
+
 func TestNacosServiceDiscoveryGetDefaultPageSize(t *testing.T) {
 	serviceDiscovery := &nacosServiceDiscovery{}
 	assert.Equal(t, registry.DefaultPageSize, serviceDiscovery.GetDefaultPageSize())
@@ -320,3 +365,27 @@ func (m *mockInvoker) Invoke(context.Context, base.Invocation) result.Result {
 
 type mockResult struct {
 }
+
+// convertInstances converts nacos model.Instance to registry.ServiceInstance
+// This method is used for testing weight conversion
+func (n *nacosServiceDiscovery) convertInstances(instances []model.Instance) []registry.ServiceInstance {
+	res := make([]registry.ServiceInstance, 0, len(instances))
+	for _, ins := range instances {
+		metadata := ins.Metadata
+		id := metadata[idKey]
+		delete(metadata, idKey)
+
+		res = append(res, &registry.DefaultServiceInstance{
+			ID:          id,
+			ServiceName: "test-service", // service name for test
+			Host:        ins.Ip,
+			Port:        int(ins.Port),
+			Weight:      int64(math.Round(ins.Weight)), // Ensure weight is correctly passed (for test)
+			Enable:      ins.Enable,
+			Healthy:     ins.Healthy,
+			Metadata:    metadata,
+			GroupName:   n.group,
+		})
+	}
+	return res
+}
diff --git a/registry/service_instance.go b/registry/service_instance.go
index 8863b8e96..2ce2e2c4d 100644
--- a/registry/service_instance.go
+++ b/registry/service_instance.go
@@ -21,17 +21,13 @@ import (
 	"encoding/json"
 	url2 "net/url"
 	"strconv"
-)
 
-import (
 	"github.com/dubbogo/gost/log/logger"
-	gxsort "github.com/dubbogo/gost/sort"
-)
 
-import (
 	"dubbo.apache.org/dubbo-go/v3/common"
 	"dubbo.apache.org/dubbo-go/v3/common/constant"
 	"dubbo.apache.org/dubbo-go/v3/metadata/info"
+	gxsort "github.com/dubbogo/gost/sort"
 )
 
 // ServiceInstance is the interface  which is used for service registration and discovery.
@@ -76,6 +72,9 @@ type ServiceInstance interface {
 
 	// GetTag will return the tag of the instance
 	GetTag() string
+
+	// GetWeight will return the weight of the instance; if ≤0, the caller should use DefaultWeight as fallback
+	GetWeight() int64
 }
 
 // nolint
@@ -91,6 +90,7 @@ type DefaultServiceInstance struct {
 	ServiceName     string
 	Host            string
 	Port            int
+	Weight          int64
 	Enable          bool
 	Healthy         bool
 	Metadata        map[string]string
@@ -175,7 +175,8 @@ func (d *DefaultServiceInstance) ToURLs(service *info.ServiceInfo) []*common.URL
 					common.WithIp(d.Host), common.WithPort(strconv.Itoa(endpoint.Port)),
 					common.WithPath(service.Name), common.WithInterface(service.Name),
 					common.WithMethods(service.GetMethods()), common.WithParams(service.GetParams()),
-					common.WithParams(url2.Values{constant.Tagkey: {d.Tag}}))
+					common.WithParams(url2.Values{constant.Tagkey: {d.Tag}}),
+					common.WithWeight(d.GetWeight()))
 				urls = append(urls, url)
 			}
 		}
@@ -184,7 +185,8 @@ func (d *DefaultServiceInstance) ToURLs(service *info.ServiceInfo) []*common.URL
 			common.WithIp(d.Host), common.WithPort(strconv.Itoa(d.Port)),
 			common.WithPath(service.Name), common.WithInterface(service.Name),
 			common.WithMethods(service.GetMethods()), common.WithParams(service.GetParams()),
-			common.WithParams(url2.Values{constant.Tagkey: {d.Tag}}))
+			common.WithParams(url2.Values{constant.Tagkey: {d.Tag}}),
+			common.WithWeight(d.GetWeight()))
 		urls = append(urls, url)
 	}
 	return urls
@@ -238,3 +240,10 @@ type ServiceInstanceCustomizer interface {
 
 	Customize(instance ServiceInstance)
 }
+
+func (d *DefaultServiceInstance) GetWeight() int64 {
+	if d.Weight <= 0 {
+		return constant.DefaultWeight
+	}
+	return d.Weight
+}
diff --git a/server/action.go b/server/action.go
index 82a6043db..b85edcee5 100644
--- a/server/action.go
+++ b/server/action.go
@@ -364,6 +364,8 @@ func (svcOpts *ServiceOptions) getUrlMap() url.Values {
 	urlMap.Set(constant.AppVersionKey, app.Version)
 	urlMap.Set(constant.OwnerKey, app.Owner)
 	urlMap.Set(constant.EnvironmentKey, app.Environment)
+	//issue #2864  nacos client add weight
+	urlMap.Set(constant.WeightKey, strconv.FormatInt(svcOpts.Provider.Weight, 10))
 
 	//filter
 	var filters string