initial import
This commit is contained in:
179
rq/update/incrementalupdater.go
Normal file
179
rq/update/incrementalupdater.go
Normal file
@@ -0,0 +1,179 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"zero/core/hash"
|
||||
"zero/core/stringx"
|
||||
)
|
||||
|
||||
const (
|
||||
incrementalStep = 5
|
||||
stepDuration = time.Second * 3
|
||||
)
|
||||
|
||||
type (
|
||||
updateEvent struct {
|
||||
keys []string
|
||||
newKey string
|
||||
servers []string
|
||||
}
|
||||
|
||||
UpdateFunc func(change ServerChange)
|
||||
|
||||
IncrementalUpdater struct {
|
||||
lock sync.Mutex
|
||||
started bool
|
||||
taskChan chan updateEvent
|
||||
updates ServerChange
|
||||
updateFn UpdateFunc
|
||||
pendingEvents []updateEvent
|
||||
}
|
||||
)
|
||||
|
||||
func NewIncrementalUpdater(updateFn UpdateFunc) *IncrementalUpdater {
|
||||
return &IncrementalUpdater{
|
||||
taskChan: make(chan updateEvent),
|
||||
updates: ServerChange{
|
||||
Current: Snapshot{
|
||||
Keys: make([]string, 0),
|
||||
WeightedKeys: make([]weightedKey, 0),
|
||||
},
|
||||
Servers: make([]string, 0),
|
||||
},
|
||||
updateFn: updateFn,
|
||||
}
|
||||
}
|
||||
|
||||
func (ru *IncrementalUpdater) Update(keys []string, servers []string, newKey string) {
|
||||
ru.lock.Lock()
|
||||
defer ru.lock.Unlock()
|
||||
|
||||
if !ru.started {
|
||||
go ru.run()
|
||||
ru.started = true
|
||||
}
|
||||
|
||||
ru.taskChan <- updateEvent{
|
||||
keys: keys,
|
||||
newKey: newKey,
|
||||
servers: servers,
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if incremental update is done
|
||||
func (ru *IncrementalUpdater) advance() bool {
|
||||
previous := ru.updates.Current
|
||||
keys := make([]string, 0)
|
||||
weightedKeys := make([]weightedKey, 0)
|
||||
servers := ru.updates.Servers
|
||||
for _, key := range ru.updates.Current.Keys {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
for _, wkey := range ru.updates.Current.WeightedKeys {
|
||||
weight := wkey.Weight + incrementalStep
|
||||
if weight >= hash.TopWeight {
|
||||
keys = append(keys, wkey.Key)
|
||||
} else {
|
||||
weightedKeys = append(weightedKeys, weightedKey{
|
||||
Key: wkey.Key,
|
||||
Weight: weight,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, event := range ru.pendingEvents {
|
||||
// ignore reload events
|
||||
if len(event.newKey) == 0 || len(event.servers) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// anyway, add the servers, just to avoid missing notify any server
|
||||
servers = stringx.Union(servers, event.servers)
|
||||
if keyExists(keys, weightedKeys, event.newKey) {
|
||||
continue
|
||||
}
|
||||
|
||||
weightedKeys = append(weightedKeys, weightedKey{
|
||||
Key: event.newKey,
|
||||
Weight: incrementalStep,
|
||||
})
|
||||
}
|
||||
|
||||
// clear pending events
|
||||
ru.pendingEvents = ru.pendingEvents[:0]
|
||||
|
||||
change := ServerChange{
|
||||
Previous: previous,
|
||||
Current: Snapshot{
|
||||
Keys: keys,
|
||||
WeightedKeys: weightedKeys,
|
||||
},
|
||||
Servers: servers,
|
||||
}
|
||||
ru.updates = change
|
||||
ru.updateFn(change)
|
||||
|
||||
return len(weightedKeys) == 0
|
||||
}
|
||||
|
||||
func (ru *IncrementalUpdater) run() {
|
||||
defer func() {
|
||||
ru.lock.Lock()
|
||||
ru.started = false
|
||||
ru.lock.Unlock()
|
||||
}()
|
||||
|
||||
ticker := time.NewTicker(stepDuration)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if ru.advance() {
|
||||
return
|
||||
}
|
||||
case event := <-ru.taskChan:
|
||||
ru.updateKeys(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ru *IncrementalUpdater) updateKeys(event updateEvent) {
|
||||
isWeightedKey := func(key string) bool {
|
||||
for _, wkey := range ru.updates.Current.WeightedKeys {
|
||||
if wkey.Key == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(event.keys))
|
||||
for _, key := range event.keys {
|
||||
if !isWeightedKey(key) {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
|
||||
ru.updates.Current.Keys = keys
|
||||
ru.pendingEvents = append(ru.pendingEvents, event)
|
||||
}
|
||||
|
||||
func keyExists(keys []string, weightedKeys []weightedKey, key string) bool {
|
||||
for _, each := range keys {
|
||||
if key == each {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, wkey := range weightedKeys {
|
||||
if wkey.Key == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
106
rq/update/serverchange.go
Normal file
106
rq/update/serverchange.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"zero/core/hash"
|
||||
"zero/core/jsonx"
|
||||
"zero/rq/constant"
|
||||
)
|
||||
|
||||
var ErrInvalidServerChange = errors.New("not a server change message")
|
||||
|
||||
type (
|
||||
weightedKey struct {
|
||||
Key string
|
||||
Weight int
|
||||
}
|
||||
|
||||
Snapshot struct {
|
||||
Keys []string
|
||||
WeightedKeys []weightedKey
|
||||
}
|
||||
|
||||
ServerChange struct {
|
||||
Previous Snapshot
|
||||
Current Snapshot
|
||||
Servers []string
|
||||
}
|
||||
)
|
||||
|
||||
func (s Snapshot) GetCode() string {
|
||||
keys := append([]string(nil), s.Keys...)
|
||||
sort.Strings(keys)
|
||||
weightedKeys := append([]weightedKey(nil), s.WeightedKeys...)
|
||||
sort.SliceStable(weightedKeys, func(i, j int) bool {
|
||||
return weightedKeys[i].Key < weightedKeys[j].Key
|
||||
})
|
||||
|
||||
digest := md5.New()
|
||||
for _, key := range keys {
|
||||
io.WriteString(digest, fmt.Sprintf("%s\n", key))
|
||||
}
|
||||
for _, wkey := range weightedKeys {
|
||||
io.WriteString(digest, fmt.Sprintf("%s:%d\n", wkey.Key, wkey.Weight))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%x", digest.Sum(nil))
|
||||
}
|
||||
|
||||
func (sc ServerChange) CreateCurrentHash() *hash.ConsistentHash {
|
||||
curHash := hash.NewConsistentHash()
|
||||
|
||||
for _, key := range sc.Current.Keys {
|
||||
curHash.Add(key)
|
||||
}
|
||||
for _, wkey := range sc.Current.WeightedKeys {
|
||||
curHash.AddWithWeight(wkey.Key, wkey.Weight)
|
||||
}
|
||||
|
||||
return curHash
|
||||
}
|
||||
|
||||
func (sc ServerChange) CreatePrevHash() *hash.ConsistentHash {
|
||||
prevHash := hash.NewConsistentHash()
|
||||
|
||||
for _, key := range sc.Previous.Keys {
|
||||
prevHash.Add(key)
|
||||
}
|
||||
for _, wkey := range sc.Previous.WeightedKeys {
|
||||
prevHash.AddWithWeight(wkey.Key, wkey.Weight)
|
||||
}
|
||||
|
||||
return prevHash
|
||||
}
|
||||
|
||||
func (sc ServerChange) GetCode() string {
|
||||
return sc.Current.GetCode()
|
||||
}
|
||||
|
||||
func IsServerChange(message string) bool {
|
||||
return len(message) > 0 && message[0] == constant.ServerSensitivePrefix
|
||||
}
|
||||
|
||||
func (sc ServerChange) Marshal() (string, error) {
|
||||
body, err := jsonx.Marshal(sc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(append([]byte{constant.ServerSensitivePrefix}, body...)), nil
|
||||
}
|
||||
|
||||
func UnmarshalServerChange(body string) (ServerChange, error) {
|
||||
if len(body) == 0 {
|
||||
return ServerChange{}, ErrInvalidServerChange
|
||||
}
|
||||
|
||||
var change ServerChange
|
||||
err := jsonx.UnmarshalFromString(body[1:], &change)
|
||||
|
||||
return change, err
|
||||
}
|
||||
Reference in New Issue
Block a user