trpc.group/trpc-go/trpc-go@v1.0.3/naming/loadbalance/consistenthash/consistenthash_test.go (about)

     1  //
     2  //
     3  // Tencent is pleased to support the open source community by making tRPC available.
     4  //
     5  // Copyright (C) 2023 THL A29 Limited, a Tencent company.
     6  // All rights reserved.
     7  //
     8  // If you have downloaded a copy of the tRPC source code from Tencent,
     9  // please note that tRPC source code is licensed under the  Apache 2.0 License,
    10  // A copy of the Apache 2.0 License is included in this file.
    11  //
    12  //
    13  
    14  package consistenthash
    15  
    16  import (
    17  	"fmt"
    18  	"strings"
    19  	"sync"
    20  	"testing"
    21  
    22  	"github.com/spaolacci/murmur3"
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/require"
    25  
    26  	"trpc.group/trpc-go/trpc-go/log"
    27  	"trpc.group/trpc-go/trpc-go/naming/loadbalance"
    28  	"trpc.group/trpc-go/trpc-go/naming/registry"
    29  )
    30  
    31  // Test whether key takes effect.
    32  // The returned node should not change for the same key in the same node list.
    33  func TestConsistentHashGetOne(t *testing.T) {
    34  	ch := NewConsistentHash()
    35  
    36  	// test list 1
    37  	n, err := ch.Select("test", list1, loadbalance.WithKey("123"))
    38  	assert.Nil(t, err)
    39  	expectAddr := n.Address
    40  	n, err = ch.Select("test", list1, loadbalance.WithKey("123"))
    41  	assert.Nil(t, err)
    42  	assert.Equal(t, expectAddr, n.Address)
    43  
    44  	n, err = ch.Select("test", list1, loadbalance.WithKey("123456"))
    45  	assert.Nil(t, err)
    46  	expectAddr = n.Address
    47  	n, err = ch.Select("test", list1, loadbalance.WithKey("123456"))
    48  	assert.Nil(t, err)
    49  	assert.Equal(t, expectAddr, n.Address)
    50  
    51  	n, err = ch.Select("test", list1, loadbalance.WithKey("12315"))
    52  	assert.Nil(t, err)
    53  	expectAddr = n.Address
    54  	n, err = ch.Select("test", list1, loadbalance.WithKey("12315"))
    55  	assert.Nil(t, err)
    56  	assert.Equal(t, expectAddr, n.Address)
    57  
    58  	// test list 4
    59  	n, err = ch.Select("test", list4, loadbalance.WithKey("Pony"))
    60  	assert.Nil(t, err)
    61  	expectAddr = n.Address
    62  	n, err = ch.Select("test", list4, loadbalance.WithKey("Pony"))
    63  	assert.Nil(t, err)
    64  	assert.Equal(t, expectAddr, n.Address)
    65  
    66  	n, err = ch.Select("test", list4, loadbalance.WithKey("John"))
    67  	assert.Nil(t, err)
    68  	expectAddr = n.Address
    69  	n, err = ch.Select("test", list4, loadbalance.WithKey("John"))
    70  	assert.Nil(t, err)
    71  	assert.Equal(t, expectAddr, n.Address)
    72  
    73  	n, err = ch.Select("test", list4, loadbalance.WithKey("Jack"))
    74  	assert.Nil(t, err)
    75  	expectAddr = n.Address
    76  	n, err = ch.Select("test", list4, loadbalance.WithKey("Jack"))
    77  	assert.Nil(t, err)
    78  	assert.Equal(t, expectAddr, n.Address)
    79  }
    80  
    81  // Test whether key takes effect using custom.
    82  // The returned node should not change for the same key in the same node list.
    83  func TestCustomConsistentHashGetOne(t *testing.T) {
    84  	ch := NewCustomConsistentHash(murmur3.Sum64)
    85  
    86  	// test list 1
    87  	n, err := ch.Select("test", list1, loadbalance.WithKey("123"))
    88  	assert.Nil(t, err)
    89  	expectAddr := n.Address
    90  	n, err = ch.Select("test", list1, loadbalance.WithKey("123"))
    91  	assert.Nil(t, err)
    92  	assert.Equal(t, expectAddr, n.Address)
    93  
    94  	n, err = ch.Select("test", list1, loadbalance.WithKey("123456"))
    95  	assert.Nil(t, err)
    96  	expectAddr = n.Address
    97  	n, err = ch.Select("test", list1, loadbalance.WithKey("123456"))
    98  	assert.Nil(t, err)
    99  	assert.Equal(t, expectAddr, n.Address)
   100  
   101  	n, err = ch.Select("test", list1, loadbalance.WithKey("12315"))
   102  	assert.Nil(t, err)
   103  	expectAddr = n.Address
   104  	n, err = ch.Select("test", list1, loadbalance.WithKey("12315"))
   105  	assert.Nil(t, err)
   106  	assert.Equal(t, expectAddr, n.Address)
   107  
   108  	// test list 4
   109  	n, err = ch.Select("test", list4, loadbalance.WithKey("Pony"))
   110  	assert.Nil(t, err)
   111  	expectAddr = n.Address
   112  	n, err = ch.Select("test", list4, loadbalance.WithKey("Pony"))
   113  	assert.Nil(t, err)
   114  	assert.Equal(t, expectAddr, n.Address)
   115  
   116  	n, err = ch.Select("test", list4, loadbalance.WithKey("John"))
   117  	assert.Nil(t, err)
   118  	expectAddr = n.Address
   119  	n, err = ch.Select("test", list4, loadbalance.WithKey("John"))
   120  	assert.Nil(t, err)
   121  	assert.Equal(t, expectAddr, n.Address)
   122  
   123  	n, err = ch.Select("test", list4, loadbalance.WithKey("Jack"))
   124  	assert.Nil(t, err)
   125  	expectAddr = n.Address
   126  	n, err = ch.Select("test", list4, loadbalance.WithKey("Jack"))
   127  	assert.Nil(t, err)
   128  	assert.Equal(t, expectAddr, n.Address)
   129  }
   130  
   131  // Test hash-collision.
   132  // The returned node should not equal for the different key.
   133  func TestHashCollision(t *testing.T) {
   134  	const magicKey = "magic_key"
   135  	ch := NewCustomConsistentHash(func(data []byte) uint64 {
   136  		if id := strings.Index(string(data), magicKey); id != -1 {
   137  			// the hash value is determined by the byte after magic key
   138  			return uint64(data[id+len(magicKey)])
   139  		}
   140  		// hash must collide if missing magic key.
   141  		return 0
   142  	})
   143  
   144  	nodes := []*registry.Node{
   145  		{Address: "a"},
   146  		{Address: "b"},
   147  		{Address: "c"},
   148  	}
   149  
   150  	// consistent hash has two replicas, select three different keys(three different hashes), there's no possible that
   151  	// all of them shares the same address.
   152  	addresses := make(map[string]struct{})
   153  
   154  	n, err := ch.Select("test", nodes, loadbalance.WithKey(magicKey+"a"), loadbalance.WithReplicas(2))
   155  	require.Nil(t, err)
   156  	log.Debug(n.Address)
   157  	addresses[n.Address] = struct{}{}
   158  	n, err = ch.Select("test", nodes, loadbalance.WithKey(magicKey+"b"), loadbalance.WithReplicas(2))
   159  	require.Nil(t, err)
   160  	log.Debug(n.Address)
   161  	addresses[n.Address] = struct{}{}
   162  	n, err = ch.Select("test", nodes, loadbalance.WithKey(magicKey+"c"), loadbalance.WithReplicas(2))
   163  	require.Nil(t, err)
   164  	log.Debug(n.Address)
   165  	addresses[n.Address] = struct{}{}
   166  	require.Less(t, 1, len(addresses))
   167  }
   168  
   169  // Test empty node list.
   170  // Should return an expected error.
   171  func TestNilList(t *testing.T) {
   172  	ch := NewConsistentHash()
   173  	n, err := ch.Select("test", nil, loadbalance.WithKey("123"))
   174  	assert.Nil(t, n)
   175  	assert.Equal(t, loadbalance.ErrNoServerAvailable, err)
   176  }
   177  
   178  // Test empty opt.
   179  // WithKey of opt must be provided.
   180  // Should return an expected error.
   181  func TestNilOpts(t *testing.T) {
   182  	ch := NewConsistentHash()
   183  
   184  	n, err := ch.Select("test", list1)
   185  	assert.Nil(t, n)
   186  	assert.NotNil(t, err)
   187  	assert.Equal(t, err.Error(), "missing key")
   188  
   189  	n, err = ch.Select("test", list1, loadbalance.WithKey("whatever"))
   190  	assert.Nil(t, err)
   191  	assert.NotNil(t, n)
   192  }
   193  
   194  // Test node list with only one node.
   195  // Should return the same result each time.
   196  func TestSingleNode(t *testing.T) {
   197  	ch := NewConsistentHash()
   198  	n, err := ch.Select("test", list2, loadbalance.WithKey("123"))
   199  	assert.Nil(t, err)
   200  	assert.Equal(t, list2[0].Address, n.Address)
   201  
   202  	n, err = ch.Select("test", list2, loadbalance.WithKey("456"))
   203  	assert.Nil(t, err)
   204  	assert.Equal(t, list2[0].Address, n.Address)
   205  
   206  	n, err = ch.Select("test", list2, loadbalance.WithKey("12306"))
   207  	assert.Nil(t, err)
   208  	assert.Equal(t, list2[0].Address, n.Address)
   209  
   210  	n, err = ch.Select("test", list2, loadbalance.WithKey("JackChen"))
   211  	assert.Nil(t, err)
   212  	assert.Equal(t, list2[0].Address, n.Address)
   213  }
   214  
   215  // Hash ring should be updated once length of node list changed.
   216  func TestInterval(t *testing.T) {
   217  	ch := NewConsistentHash()
   218  
   219  	// On list length changed, recalculate hash ring immediately.
   220  	n, err := ch.Select("test", list2, loadbalance.WithKey("123"))
   221  	assert.Nil(t, err)
   222  	assert.Equal(t, list2[0].Address, n.Address)
   223  
   224  	n, err = ch.Select("test", list4, loadbalance.WithKey("123"))
   225  	assert.Nil(t, err)
   226  	assert.Equal(t, false, isInList(n.Address, list2))
   227  	assert.Equal(t, true, isInList(n.Address, list4))
   228  }
   229  
   230  // Test the influence to object mapping position if node is deleted.
   231  func TestSubNode(t *testing.T) {
   232  	ch := NewConsistentHash()
   233  
   234  	var address1, address2, address3 string
   235  	n, err := ch.Select("test", list1, loadbalance.WithKey("123"))
   236  	assert.Nil(t, err)
   237  	address1 = n.Address
   238  
   239  	n, err = ch.Select("test", list1, loadbalance.WithKey("123456"))
   240  	assert.Nil(t, err)
   241  	address2 = n.Address
   242  
   243  	n, err = ch.Select("test", list1, loadbalance.WithKey("12315"))
   244  	assert.Nil(t, err)
   245  	address3 = n.Address
   246  
   247  	deletedAddress := address1
   248  
   249  	// Delete deletedAddress of list1.
   250  	// No key is effected except the key influenced by deletedAddress.
   251  	listTmp := deleteNode(deletedAddress, list1)
   252  
   253  	n, err = ch.Select("test", listTmp, loadbalance.WithKey("123"))
   254  	assert.Nil(t, err)
   255  	if address1 != deletedAddress {
   256  		assert.Equal(t, address1, n.Address)
   257  	} else {
   258  		assert.NotEqual(t, address1, n.Address)
   259  	}
   260  
   261  	n, err = ch.Select("test", listTmp, loadbalance.WithKey("123456"))
   262  	assert.Nil(t, err)
   263  	if address2 != deletedAddress {
   264  		assert.Equal(t, address2, n.Address)
   265  	} else {
   266  		assert.NotEqual(t, address2, n.Address)
   267  	}
   268  
   269  	n, err = ch.Select("test", listTmp, loadbalance.WithKey("12315"))
   270  	assert.Nil(t, err)
   271  	if address3 != deletedAddress {
   272  		assert.Equal(t, address3, n.Address)
   273  	} else {
   274  		assert.NotEqual(t, address3, n.Address)
   275  	}
   276  }
   277  
   278  // Test balance.
   279  func TestBalance(t *testing.T) {
   280  	ch := NewConsistentHash()
   281  	counter := make(map[string]int)
   282  	for i := 0; i < 200; i++ {
   283  		n, err := ch.Select("test", list1, loadbalance.WithKey(fmt.Sprintf("%d", i)), loadbalance.WithReplicas(100))
   284  		assert.Nil(t, err)
   285  		if _, ok := counter[n.Address]; !ok {
   286  			counter[n.Address] = 0
   287  		} else {
   288  			counter[n.Address]++
   289  		}
   290  	}
   291  	for _, v := range counter {
   292  		assert.NotEqual(t, 0, v)
   293  		fmt.Println(v)
   294  	}
   295  }
   296  
   297  func TestIsNodeSliceEqualBCE(t *testing.T) {
   298  	isEqual := isNodeSliceEqualBCE(list1, list2)
   299  	assert.Equal(t, false, isEqual)
   300  	isEqual = isNodeSliceEqualBCE(list1, list1)
   301  	assert.Equal(t, true, isEqual)
   302  	isEqual = isNodeSliceEqualBCE(list1, nil)
   303  	assert.Equal(t, false, isEqual)
   304  }
   305  
   306  // Test concurrency safety. List changes every visit.
   307  // This is an extreme situation. In fact, in most cases, node list of a service does not change
   308  // frequently, but will only change on service scaling.
   309  func TestParallel(t *testing.T) {
   310  	var wg sync.WaitGroup
   311  	ch := NewConsistentHash()
   312  	var lists [][]*registry.Node
   313  	var keys []string
   314  	var results []string
   315  
   316  	n, err := ch.Select("test", list1, loadbalance.WithKey("1"))
   317  	assert.Nil(t, err)
   318  	results = append(results, n.Address)
   319  	lists = append(lists, list1)
   320  	keys = append(keys, "1")
   321  
   322  	n, err = ch.Select("test", list2, loadbalance.WithKey("2"))
   323  	assert.Nil(t, err)
   324  	results = append(results, n.Address)
   325  	lists = append(lists, list2)
   326  	keys = append(keys, "2")
   327  
   328  	n, err = ch.Select("test", list3, loadbalance.WithKey("3"))
   329  	assert.Nil(t, err)
   330  	results = append(results, n.Address)
   331  	lists = append(lists, list3)
   332  	keys = append(keys, "3")
   333  
   334  	n, err = ch.Select("test", list4, loadbalance.WithKey("4"))
   335  	assert.Nil(t, err)
   336  	results = append(results, n.Address)
   337  	lists = append(lists, list4)
   338  	keys = append(keys, "4")
   339  
   340  	n, err = ch.Select("test", list5, loadbalance.WithKey("5"))
   341  	assert.Nil(t, err)
   342  	results = append(results, n.Address)
   343  	lists = append(lists, list5)
   344  	keys = append(keys, "5")
   345  
   346  	// To simulate a large concurrent goroutine.
   347  
   348  	// This is an extreme situation. In fact, in most cases, node list of a service does not change
   349  	// frequently, but will only change on service scaling.
   350  	for i := 0; i < 50; i++ {
   351  		wg.Add(1)
   352  		go func(i int) {
   353  			defer wg.Done()
   354  			n, err := ch.Select("test0", lists[i%5], loadbalance.WithKey(keys[i%5]))
   355  			assert.Nil(t, err)
   356  			assert.Equal(t, results[i%5], n.Address)
   357  		}(i)
   358  	}
   359  
   360  	for i := 0; i < 50; i++ {
   361  		wg.Add(1)
   362  		go func(i int) {
   363  			defer wg.Done()
   364  			n, err := ch.Select("test1", lists[0], loadbalance.WithKey(keys[0]))
   365  			assert.Nil(t, err)
   366  			assert.Equal(t, results[0], n.Address)
   367  		}(i)
   368  	}
   369  	wg.Wait()
   370  }
   371  
   372  // Test performance on current visit.
   373  func BenchmarkParallel(b *testing.B) {
   374  	ch := NewConsistentHash()
   375  	b.SetParallelism(10) // 10 concurrency
   376  	b.RunParallel(func(pb *testing.PB) {
   377  		for pb.Next() {
   378  			_, _ = ch.Select("test", list1, loadbalance.WithKey("HelloWorld"))
   379  		}
   380  	})
   381  }
   382  
   383  var list1 = []*registry.Node{
   384  	{
   385  		Address: "list1.ip.1:8080",
   386  	},
   387  	{
   388  		Address: "list1.ip.2:8080",
   389  	},
   390  	{
   391  		Address: "list1.ip.3:8080",
   392  	},
   393  	{
   394  		Address: "list1.ip.4:8080",
   395  	},
   396  }
   397  
   398  var list2 = []*registry.Node{
   399  	{
   400  		Address: "list2.ip.1:8080",
   401  	},
   402  }
   403  
   404  var list3 = []*registry.Node{
   405  	{
   406  		Address: "list3.ip.2:8080",
   407  	},
   408  	{
   409  		Address: "list3.ip.4:8080",
   410  	},
   411  	{
   412  		Address: "list3.ip.1:8080",
   413  	},
   414  }
   415  
   416  var list4 = []*registry.Node{
   417  	{
   418  		Address: "list4.ip.168:8080",
   419  	},
   420  	{
   421  		Address: "list4.ip.167:8080",
   422  	},
   423  	{
   424  		Address: "list4.ip.15:8080",
   425  	},
   426  	{
   427  		Address: "list4.ip.15:8081",
   428  	},
   429  }
   430  
   431  var list5 = []*registry.Node{
   432  	{
   433  		Address: "list5.ip.2:8080",
   434  	},
   435  }
   436  
   437  func deleteNode(address string, list []*registry.Node) []*registry.Node {
   438  	ret := make([]*registry.Node, 0, len(list))
   439  	for _, n := range list {
   440  		if n.Address != address {
   441  			ret = append(ret, n)
   442  		}
   443  	}
   444  	return ret
   445  }
   446  
   447  func isInList(address string, list []*registry.Node) bool {
   448  	for _, n := range list {
   449  		if n.Address == address {
   450  			return true
   451  		}
   452  	}
   453  	return false
   454  }