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 }