github.com/XiaoMi/Gaea@v1.2.5/proxy/router/shard_mycat.go (about) 1 // Copyright 2019 The Gaea Authors. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package router 16 17 import ( 18 "bytes" 19 "errors" 20 "fmt" 21 "strconv" 22 "strings" 23 24 "github.com/emirpasic/gods/maps/treemap" 25 "github.com/XiaoMi/Gaea/util" 26 "github.com/XiaoMi/Gaea/util/hack" 27 ) 28 29 // MycatPartitionModShard mycat route algorithm: PartitionByMod 30 // take care: in Mycat, the key is parsed to a BigInteger, not int64. 31 type MycatPartitionModShard struct { 32 ShardNum int 33 } 34 35 // NewMycatPartitionModShard constructor of MycatPartitionModShard 36 func NewMycatPartitionModShard(shardNum int) *MycatPartitionModShard { 37 return &MycatPartitionModShard{ 38 ShardNum: shardNum, 39 } 40 } 41 42 // FindForKey return result of calculated key 43 func (m *MycatPartitionModShard) FindForKey(key interface{}) (int, error) { 44 h := hack.Abs(NumValue(key)) 45 return int(h % int64(m.ShardNum)), nil 46 } 47 48 const ( 49 // PartitionLength length of partition 50 PartitionLength = 1024 51 andValue = PartitionLength - 1 52 ) 53 54 // MycatPartitionLongShard mycat route algorithm: PartitionByLong 55 type MycatPartitionLongShard struct { 56 shardNum int 57 countStr string 58 lengthStr string 59 count []int 60 length []int 61 segment []int 62 } 63 64 // NewMycatPartitionLongShard constructor of MycatPartitionLongShard 65 func NewMycatPartitionLongShard(shardNum int, partitionCount, partitionLength string) *MycatPartitionLongShard { 66 return &MycatPartitionLongShard{ 67 shardNum: shardNum, 68 countStr: partitionCount, 69 lengthStr: partitionLength, 70 count: make([]int, 0, 10), 71 length: make([]int, 0, 10), 72 segment: make([]int, 0, PartitionLength), 73 } 74 } 75 76 // Init init MycatPartitionLongShard 77 func (m *MycatPartitionLongShard) Init() error { 78 countList, err := toIntArray(m.countStr) 79 if err != nil { 80 return err 81 } 82 83 lengthList, err := toIntArray(m.lengthStr) 84 if err != nil { 85 return err 86 } 87 88 countSize := len(countList) 89 lengthSize := len(lengthList) 90 if countSize != lengthSize { 91 return errors.New("error, check your scope & scopeLength definition") 92 } 93 94 segmentLength := 0 95 for i := 0; i < countSize; i++ { 96 segmentLength += countList[i] 97 } 98 99 if segmentLength != m.shardNum { 100 return errors.New("segmentLength is not equal to shardNum") 101 } 102 103 ai := make([]int, segmentLength+1) 104 105 index := 0 106 for i := 0; i < countSize; i++ { 107 for j := 0; j < countList[i]; j++ { 108 ai[index+1] = ai[index] + lengthList[i] 109 index++ 110 } 111 } 112 if ai[len(ai)-1] != PartitionLength { 113 return errors.New("error, check your partitionScope definition") 114 } 115 116 segment := make([]int, PartitionLength) 117 for i := 1; i < len(ai); i++ { 118 for j := ai[i-1]; j < ai[i]; j++ { 119 segment[j] = i - 1 120 } 121 } 122 123 m.count = countList 124 m.length = lengthList 125 m.segment = segment 126 return nil 127 } 128 129 func toIntArray(str string) ([]int, error) { 130 str = strings.Replace(str, " ", "", -1) 131 strList := strings.Split(str, ",") 132 ret := make([]int, 0, len(strList)) 133 for _, s := range strList { 134 num, err := strconv.Atoi(s) 135 if err != nil { 136 return ret, err 137 } 138 ret = append(ret, num) 139 } 140 return ret, nil 141 } 142 143 // FindForKey return MycatPartitionLongShard calculated result 144 func (m *MycatPartitionLongShard) FindForKey(key interface{}) (int, error) { 145 h := NumValue(key) 146 return m.segment[int(h)&andValue], nil 147 } 148 149 // MycatPartitionStringShard mycat route algorithm: PartitionByString 150 type MycatPartitionStringShard struct { 151 *MycatPartitionLongShard 152 hashSliceStr string 153 hashSliceStart int 154 hashSliceEnd int 155 } 156 157 // NewMycatPartitionStringShard constructor of MycatPartitionStringShard 158 func NewMycatPartitionStringShard(shardNum int, partitionCount, partitionLength string, hashSliceStr string) *MycatPartitionStringShard { 159 longShard := NewMycatPartitionLongShard(shardNum, partitionCount, partitionLength) 160 stringShard := &MycatPartitionStringShard{ 161 MycatPartitionLongShard: longShard, 162 hashSliceStr: hashSliceStr, 163 } 164 return stringShard 165 } 166 167 // Init init NewMycatPartitionStringShard 168 func (m *MycatPartitionStringShard) Init() error { 169 if err := m.MycatPartitionLongShard.Init(); err != nil { 170 return err 171 } 172 173 start, end, err := parseHashSliceStartEnd(m.hashSliceStr) 174 if err != nil { 175 return err 176 } 177 m.hashSliceStart = start 178 m.hashSliceEnd = end 179 return nil 180 } 181 182 /** 183 * "2" -> (0,2)<br/> 184 * "1:2" -> (1,2)<br/> 185 * "1:" -> (1,0)<br/> 186 * "-1:" -> (-1,0)<br/> 187 * ":-1" -> (0,-1)<br/> 188 * ":" -> (0,0)<br/> 189 */ 190 // copied from mycat 191 func parseHashSliceStartEnd(hashSliceStr string) (int, int, error) { 192 hashSliceStr = strings.TrimSpace(hashSliceStr) 193 strs := strings.Split(hashSliceStr, ":") 194 195 if len(strs) == 1 { 196 v, err := strconv.Atoi(strs[0]) 197 if err != nil { 198 return -1, -1, err 199 } 200 if v >= 0 { 201 return 0, v, nil 202 } 203 return v, 0, nil 204 } 205 206 if len(strs) == 2 { 207 start, err := parseHashSliceValue(strs[0]) 208 if err != nil { 209 return -1, -1, fmt.Errorf("parse hash slice start error: %v", err) 210 } 211 end, err := parseHashSliceValue(strs[1]) 212 if err != nil { 213 return -1, -1, fmt.Errorf("parse hash slice end error: %v", err) 214 } 215 return start, end, nil 216 } 217 218 return -1, -1, fmt.Errorf("invalid hash slice str") 219 } 220 221 // 如果是空字符串, 则返回0 222 // 如果是非数字, 则返回err 223 // 如果是数字, 则正常返回 224 func parseHashSliceValue(str string) (int, error) { 225 if str == "" { 226 return 0, nil 227 } 228 v, err := strconv.Atoi(str) 229 return v, err 230 } 231 232 // FindForKey return MycatPartitionStringShard calculated result 233 func (m *MycatPartitionStringShard) FindForKey(key interface{}) (int, error) { 234 keyStr := GetString(key) 235 var start int 236 if m.hashSliceStart >= 0 { 237 start = m.hashSliceStart 238 } else { 239 start = len(keyStr) + m.hashSliceStart 240 } 241 242 var end int 243 if m.hashSliceEnd > 0 { 244 end = m.hashSliceEnd 245 } else { 246 end = len(keyStr) + m.hashSliceEnd 247 } 248 h := stringHash(keyStr, start, end) 249 return m.segment[int(h)&andValue], nil 250 } 251 252 // copied from mycat 253 func stringHash(s string, start, end int) int64 { 254 input := []rune(s) 255 if start < 0 { 256 start = 0 257 } 258 259 if end > len(input) { 260 end = len(input) 261 } 262 263 var h int64 264 for i := start; i < end; i++ { 265 h = (h << 5) - h + int64(input[i]) 266 } 267 268 return h 269 } 270 271 const ( 272 defaultWeight = 1 273 ) 274 275 // MycatPartitionMurmurHashShard mycat route algorithm: PartitionByMurmurHash 276 type MycatPartitionMurmurHashShard struct { 277 seed int 278 count int 279 virtualBucketTimes int 280 weightMap map[int]int 281 bucketMap *treemap.Map 282 hashFunction *util.MurmurHash 283 } 284 285 // NewMycatPartitionMurmurHashShard constructor of MycatPartitionMurmurHashShard 286 func NewMycatPartitionMurmurHashShard(seedStr, virtualBucketTimesStr string, count int) (*MycatPartitionMurmurHashShard, error) { 287 seed, err := strconv.Atoi(seedStr) 288 if err != nil { 289 return nil, err 290 } 291 if virtualBucketTimesStr == "" { 292 virtualBucketTimesStr = "160" 293 } 294 virtualBucketTimes, err := strconv.Atoi(virtualBucketTimesStr) 295 if err != nil { 296 return nil, err 297 } 298 299 return &MycatPartitionMurmurHashShard{ 300 seed: seed, 301 count: count, 302 virtualBucketTimes: virtualBucketTimes, 303 }, nil 304 } 305 306 // Init init MycatPartitionMurmurHashShard 307 func (m *MycatPartitionMurmurHashShard) Init() error { 308 m.bucketMap = treemap.NewWithIntComparator() 309 310 m.hashFunction = util.NewMurmurHash(m.seed) //计算一致性哈希的对象 311 if err := m.generateBucketMap(); err != nil { 312 return fmt.Errorf("murmur hash error, %v", err) 313 } 314 315 m.weightMap = make(map[int]int) 316 return nil 317 } 318 319 // FindForKey return MycatPartitionMurmurHashShard calculated result 320 func (m *MycatPartitionMurmurHashShard) FindForKey(key interface{}) (int, error) { 321 keyStr := GetString(key) 322 hashKey := m.hashFunction.HashUnencodedChars(keyStr) 323 _, ret := m.bucketMap.Ceiling(int(hashKey)) 324 if ret != nil { 325 return ret.(int), nil 326 } 327 k, v := m.bucketMap.Min() 328 if k == nil { 329 return 0, errors.New("bucket map is empty") 330 } 331 return v.(int), nil 332 } 333 334 // SetWeightMapFromFile init weight 335 func (m *MycatPartitionMurmurHashShard) SetWeightMapFromFile(weightMapPath string) error { 336 weightMap, err := parseWeightMapFile(weightMapPath) 337 if err != nil { 338 return err 339 } 340 m.weightMap = weightMap 341 return nil 342 } 343 344 /** 345 * 节点的权重,没有指定权重的节点默认是1。以properties文件的格式填写,以从0开始到count-1的整数值也就是节点索引为key,以节点权重值为值。 346 * 所有权重值必须是正整数,否则以1代替 347 */ 348 // TODO: not implemented 349 func parseWeightMapFile(fileName string) (map[int]int, error) { 350 return nil, errors.New("not implemented") 351 } 352 353 /** 354 * 得到桶的权重,桶就是实际存储数据的DB实例 355 * 从0开始的桶编号为key,权重为值,权重默认为1。 356 * 键值必须都是整数 357 * 358 * @param bucket 359 * @return 360 */ 361 func (m *MycatPartitionMurmurHashShard) getWeight(bucket int) int { 362 w, ok := m.weightMap[bucket] 363 if !ok { 364 return defaultWeight 365 } 366 return w 367 } 368 369 func (m *MycatPartitionMurmurHashShard) generateBucketMap() error { 370 hash := m.hashFunction 371 for i := 0; i < m.count; i++ { //构造一致性哈希环,用TreeMap表示 372 buf := bytes.NewBufferString("SHARD-" + strconv.Itoa(i)) 373 shard := m.virtualBucketTimes * m.getWeight(i) 374 for n := 0; n < shard; n++ { 375 // the hashKey is same with mycat, but maybe ugly, like: 376 // SHARD-0-NODE-0, SHARD-0-NODE-0-NODE-1, SHARD-0-NODE-0-NODE-1-NODE-2, 377 // not SHARD-0-NODE-0, SHARD-0-NODE-1, SHARD-0-NODE-2, 378 // take care! 379 buf.WriteString("-NODE-" + strconv.Itoa(n)) 380 hashKey := hash.HashUnencodedChars(buf.String()) 381 m.bucketMap.Put(hashKey, i) 382 } 383 } 384 return nil 385 } 386 387 // mod padding 388 const ( 389 PaddingModLeftEnd = 0 390 PaddingModRightEnd = 1 391 392 PaddingModDefaultPadFrom = PaddingModRightEnd 393 PaddingModDefaultPadLength = 18 394 PaddingModDefaultModBegin = 10 395 PaddingModDefaultModEnd = 16 396 PaddingModDefaultMod = 2 397 ) 398 399 // MycatPartitionPaddingModShard mcyat partition padding mod 400 type MycatPartitionPaddingModShard struct { 401 padFrom int 402 padLength int 403 modBegin int 404 modEnd int 405 mod int 406 } 407 408 func newDefaultMycatPartitionPaddingModShard() *MycatPartitionPaddingModShard { 409 return &MycatPartitionPaddingModShard{ 410 padFrom: PaddingModDefaultPadFrom, 411 padLength: PaddingModDefaultPadLength, 412 modBegin: PaddingModDefaultModBegin, 413 modEnd: PaddingModDefaultModEnd, 414 mod: PaddingModDefaultMod, 415 } 416 } 417 418 // GetMycatPartitionPaddingModShard wrapper newDefaultMycatPartitionPaddingModShard 419 func GetMycatPartitionPaddingModShard(padFromStr, padLengthStr, modBeginStr, modEndStr string, mod int) (shard *MycatPartitionPaddingModShard, err error) { 420 padFrom, err := strconv.Atoi(padFromStr) 421 if err != nil { 422 return nil, err 423 } 424 425 padLength, err := strconv.Atoi(padLengthStr) 426 if err != nil { 427 return nil, err 428 } 429 430 modBegin, err := strconv.Atoi(modBeginStr) 431 if err != nil { 432 return nil, err 433 } 434 435 modEnd, err := strconv.Atoi(modEndStr) 436 if err != nil { 437 return nil, err 438 } 439 440 return newMycatPartitionPaddingModShard(padFrom, padLength, modBegin, modEnd, mod), nil 441 } 442 443 func newMycatPartitionPaddingModShard(padFrom, padLength, modBegin, modEnd, mod int) *MycatPartitionPaddingModShard { 444 return &MycatPartitionPaddingModShard{ 445 padFrom: padFrom, 446 padLength: padLength, 447 modBegin: modBegin, 448 modEnd: modEnd, 449 mod: mod, 450 } 451 } 452 453 // Init init MycatPartitionPaddingModShard and check params 454 func (m *MycatPartitionPaddingModShard) Init() error { 455 return m.checkParam() 456 } 457 458 func (m *MycatPartitionPaddingModShard) checkParam() error { 459 if m.padFrom != PaddingModLeftEnd && m.padFrom != PaddingModRightEnd { 460 return fmt.Errorf("invalid padding mod mode: %d", m.padFrom) 461 } 462 463 if m.mod < PaddingModDefaultMod { 464 return fmt.Errorf("invalid padding mod number: %d", m.mod) 465 } 466 467 if m.modBegin < 0 || m.modBegin >= m.modEnd { 468 return fmt.Errorf("invalid padding modBegin or modEnd: %d, %d", m.modBegin, m.modEnd) 469 } 470 471 if m.padLength <= 0 { 472 return fmt.Errorf("invalid padding mod padLength: %d", m.padLength) 473 } 474 475 if m.padLength < (m.modEnd - m.modBegin) { 476 return fmt.Errorf("invalid padding mod, padLength is less than modBegin - modEnd: %d, %d, %d", m.padLength, m.modBegin, m.modEnd) 477 } 478 479 return nil 480 } 481 482 // FindForKey return MycatPartitionPaddingModShard calculated result 483 func (m *MycatPartitionPaddingModShard) FindForKey(key interface{}) (int, error) { 484 h := NumValue(key) // assert the key is number 485 keyStr := strconv.FormatInt(h, 10) 486 487 var paddingKey string 488 if len(keyStr) > m.padLength { 489 paddingKey = keyStr[0:m.padLength] 490 } else { 491 if m.padFrom == PaddingModLeftEnd { 492 if h < 0 { // Compatible with xiaomi mycat version 493 return -1, errors.New("padding left to a negative is not allowed") 494 } 495 paddingKey = util.Left(keyStr, m.padLength, "0") 496 } else { 497 paddingKey = util.Right(keyStr, m.padLength, "0") 498 } 499 } 500 501 modSegment := paddingKey[m.modBegin:m.modEnd] 502 bigNum, err := strconv.ParseInt(modSegment, 10, 64) // in mycat, this is a BigInteger, but here is int64 503 if err != nil { 504 return -1, err 505 } 506 bigNumAbs := hack.Abs(bigNum) 507 return int(bigNumAbs % int64(m.mod)), nil 508 }