-
Notifications
You must be signed in to change notification settings - Fork 773
feature: define the interface SupernodeLocator #1294
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
/* | ||
* Copyright The Dragonfly Authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package locator | ||
|
||
// SupernodeLocator defines the way how to get available supernodes. | ||
// Developers can implement their own locator more flexibly , not just get the | ||
// supernode list from configuration or CLI. | ||
type SupernodeLocator interface { | ||
// Get returns the current selected supernode, it should be idempotent. | ||
Get() *Supernode | ||
|
||
// Next chooses the next available supernode for retrying or other | ||
// purpose. The current supernode should be set as this result. | ||
Next() *Supernode | ||
|
||
// GetGroup returns the group with the giving name. | ||
GetGroup(name string) *SupernodeGroup | ||
|
||
// All returns all the supernodes. | ||
All() []*SupernodeGroup | ||
|
||
// Report records the metrics of the current supernode in order to choose a | ||
// more appropriate supernode for the next time if necessary. | ||
Report(node string, metrics *SupernodeMetrics) | ||
|
||
// Refresh refreshes all the supernodes. | ||
Refresh() bool | ||
} | ||
|
||
// SupernodeGroup groups supernodes which have same attributes. | ||
// For example, we can group supernodes by region, business, version and so on. | ||
// The implementation of SupernodeLocator can select a supernode based on the | ||
// group. | ||
type SupernodeGroup struct { | ||
Name string | ||
Nodes []*Supernode | ||
|
||
// Infos stores other information that user can customized. | ||
Infos map[string]string | ||
} | ||
|
||
// GetNode return the node with the giving index. | ||
func (sg *SupernodeGroup) GetNode(idx int) *Supernode { | ||
if idx < 0 || idx >= len(sg.Nodes) { | ||
return nil | ||
} | ||
return sg.Nodes[idx] | ||
} | ||
|
||
// Supernode holds the basic information of supernodes. | ||
type Supernode struct { | ||
Schema string | ||
IP string | ||
Port int | ||
Weight int | ||
GroupName string | ||
Metrics *SupernodeMetrics | ||
} | ||
|
||
// SupernodeMetrics holds metrics used for the locator to choose supernode. | ||
type SupernodeMetrics struct { | ||
Metrics map[string]interface{} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
/* | ||
* Copyright The Dragonfly Authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package locator | ||
|
||
import ( | ||
"math/rand" | ||
"sync/atomic" | ||
"time" | ||
|
||
"github.com/dragonflyoss/Dragonfly/dfget/config" | ||
"github.com/dragonflyoss/Dragonfly/pkg/algorithm" | ||
"github.com/dragonflyoss/Dragonfly/pkg/netutils" | ||
) | ||
|
||
func init() { | ||
rand.Seed(time.Now().UnixNano()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we can consider moving func main() {
rand.Seed(time.Now().UnixNano())
app.Execute()
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about move There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure. That's OK with me. Just don't like calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In order to ensure the independence of this pr, I open another pr #1318 to do this, please take a look. |
||
} | ||
|
||
const staticLocatorGroupName = "config" | ||
|
||
// StaticLocator uses the nodes passed from configuration or CLI. | ||
type StaticLocator struct { | ||
idx int32 | ||
Group *SupernodeGroup | ||
} | ||
|
||
// ---------------------------------------------------------------------------- | ||
// constructors | ||
|
||
// NewStaticLocator constructs StaticLocator which uses the nodes passed from | ||
// configuration or CLI. | ||
func NewStaticLocator(nodes []*config.NodeWeight) *StaticLocator { | ||
locator := &StaticLocator{} | ||
if len(nodes) == 0 { | ||
return locator | ||
} | ||
group := &SupernodeGroup{ | ||
Name: staticLocatorGroupName, | ||
} | ||
for _, node := range nodes { | ||
ip, port := netutils.GetIPAndPortFromNode(node.Node, config.DefaultSupernodePort) | ||
if ip == "" { | ||
continue | ||
} | ||
supernode := &Supernode{ | ||
Schema: config.DefaultSupernodeSchema, | ||
IP: ip, | ||
Port: port, | ||
Weight: node.Weight, | ||
GroupName: staticLocatorGroupName, | ||
} | ||
for i := 0; i < supernode.Weight; i++ { | ||
group.Nodes = append(group.Nodes, supernode) | ||
} | ||
} | ||
shuffleNodes(group.Nodes) | ||
locator.Group = group | ||
return locator | ||
} | ||
|
||
// NewStaticLocatorFromStr constructs StaticLocator from string list. | ||
// The format of nodes is: ip:port=weight | ||
func NewStaticLocatorFromStr(nodes []string) (*StaticLocator, error) { | ||
nodeWeight, err := config.ParseNodesSlice(nodes) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return NewStaticLocator(nodeWeight), nil | ||
} | ||
|
||
// ---------------------------------------------------------------------------- | ||
// implement api methods | ||
|
||
func (s *StaticLocator) Get() *Supernode { | ||
if s.Group == nil { | ||
return nil | ||
} | ||
return s.Group.GetNode(s.load()) | ||
} | ||
|
||
func (s *StaticLocator) Next() *Supernode { | ||
if s.Group == nil || s.load() >= len(s.Group.Nodes) { | ||
return nil | ||
} | ||
return s.Group.GetNode(s.inc()) | ||
} | ||
|
||
func (s *StaticLocator) GetGroup(name string) *SupernodeGroup { | ||
if s.Group == nil || s.Group.Name != name { | ||
return nil | ||
} | ||
return s.Group | ||
} | ||
|
||
func (s *StaticLocator) All() []*SupernodeGroup { | ||
if s.Group == nil { | ||
return nil | ||
} | ||
return []*SupernodeGroup{s.Group} | ||
} | ||
|
||
func (s *StaticLocator) Report(node string, metrics *SupernodeMetrics) { | ||
// unnecessary to implement this method | ||
return | ||
} | ||
|
||
func (s *StaticLocator) Refresh() bool { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks more like Reset 😅 How about rename as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This implementation looks more like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make sense! |
||
atomic.StoreInt32(&s.idx, 0) | ||
return true | ||
} | ||
|
||
// ---------------------------------------------------------------------------- | ||
// private methods of StaticLocator | ||
|
||
func (s *StaticLocator) load() int { | ||
return int(atomic.LoadInt32(&s.idx)) | ||
} | ||
|
||
func (s *StaticLocator) inc() int { | ||
return int(atomic.AddInt32(&s.idx, 1)) | ||
} | ||
|
||
// ---------------------------------------------------------------------------- | ||
// helper functions | ||
|
||
func shuffleNodes(nodes []*Supernode) []*Supernode { | ||
if length := len(nodes); length > 1 { | ||
algorithm.Shuffle(length, func(i, j int) { | ||
nodes[i], nodes[j] = nodes[j], nodes[i] | ||
}) | ||
} | ||
return nodes | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just out of curiosity, why consider add a supernode group concept here? And the
SupernodeGroup
can only be retrieved for viewing. Is it expected that I can choose a group to use?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example, we can group supernodes by region, business, version and so on. The implementation of SupernodeLocator can select a supernode based on the group. The caller does not need to care about the specific selection logic, but if it is really needed, the supernode list can also be obtained through this interface.