Documentation ¶
Overview ¶
Package fuddle implements an SDK client for the Fuddle service registry.
Clients use the SDK to register themselves with Fuddle and discover nodes and services in the cluster.
Register ¶
Nodes register by passing their registered node state and a list of Fuddle node seed addresses to Register:
registry, err := fuddle.Register( // List of Fuddle seed addresses. []string{"192.168.1.1:8220", "192.168.1.2:8220", "192.168.1.3:8220"}, fuddle.Node{ // Attributes describing the local node. ID: "orders-32eaba4e", Service: "orders", Locality: "aws.us-east-1-b", Created: time.Now().UnixMilli(), Revision: "v5.1.0-812ebbc", // Application defined metadata to share as part of service discovery. Metadata: map[string]string{ "status": "booting", "addr.rpc.ip": "192.168.2.1", "addr.rpc.port": "5562", "protocol.version": "3", }, }, )
This will register the local node so it can be discovered by other nodes in the cluster.
The node state includes a set of attributes that can be used for service discovery, such as looking up the nodes in the order service in us-east-1, and observability, such as checking the revision and start time of nodes that are unhealthy. The attributes are immutable so cannot be changed during the lifetime of the node.
Nodes also include a map of application defined metadata which is shared with other nodes in the cluster. Such as routing information and protocol version. This metadata may be updated using registry.UpdateLocalMetadata, and the update will be propagated to the other nodes.
Remember to unregister the node with registry.Unregister() when it is shutdown to gracefully leave the cluster. Otherwise Fuddle will consider the node as failed rather than unregistered.
Cluster Discovery ¶
Once a node has registered, it will receive the state of the other nodes in the cluster and stream updates about changes to the nodes in the registry. Therefore it maintains its own eventually-consistent view of the cluster.
This cluster state can be queried to receive the set of nodes matching some filter. Users can also subscribe to updates when the registry is updated, due to nodes joining, leaving or updating their metadata.
Lookup a set of nodes:
registry.Nodes(opts)
Subscribe to changes in a set of nodes:
registry.Subscribe(callback, opts)
Note when subscribing the callback will fire immediately with the matching cluster state, so theres no need to call Nodes first.
Filters ¶
Queries and subscriptions can filter the set of nodes they are interested in based on service name, locality and metadata.
The service, locality and metadata field formats are all user defined, however it is recommended to structure as a hierarchy with some delimiter like a dot to make it easy to filter using wildcards.
Such as using a format '<provider>.<availability zone>' for the locality lets you filter either availability zones ('aws.us-east-1-a'), regions ('aws.us-east-1-*') or location ('aws.eu-*').
Wildcards can be used for the service name, locality and metadata values (though not metadata keys). The locality and metadata filters also support multiple possible values.
For example to filter only order service nodes in us-east-1 whose status is 'active' and protocol version is either 2 or 3:
filter := fuddle.Filter{ "order": { Locality: []string{"aws.us-east-1-*"}, Metadata: fuddle.MetadataFilter{ "status": []string{"active"}, "protocol.version": []string{"2", "3"}, }, }, } // Lookup the set of nodes matching the filter. registry.Nodes(fuddle.WithFilter(filter)) // Subscribe to updates in the set of nodes matching the filter. registry.Subscribe(callback, fuddle.WithFilter(filter))
Example (LookupOrdersServiceNodes) ¶
Registers a 'frontend' service and queries the set of active order service nodes in us-east-1.
package main import ( "fmt" fuddle "github.com/fuddle-io/fuddle-go" ) func main() { registry, err := fuddle.Register( // Seed addresses of Fuddle servers. []string{"192.168.1.1:8220", "192.168.1.2:8220", "192.168.1.3:8220"}, fuddle.Node{ // ... }, ) if err != nil { // handle err ... } defer registry.Unregister() // Filter to only include order service nodes in us-east-1 whose status // is active and protocol version is either 2 or 3. orderNodes := registry.Nodes(fuddle.WithFilter(fuddle.Filter{ "order": { Locality: []string{"aws.us-east-1-*"}, Metadata: fuddle.MetadataFilter{ "status": []string{"active"}, "protocol.version": []string{"2", "3"}, }, }, })) addrs := []string{} for _, node := range orderNodes { addr := node.Metadata["addr.rpc.ip"] + ":" + node.Metadata["addr.rpc.port"] addrs = append(addrs, addr) } // ... fmt.Println("order service:", addrs) }
Output:
Example (RegisterOrdersServiceNode) ¶
Registers an 'orders' service node in 'us-east-1-b'.
package main import ( "time" fuddle "github.com/fuddle-io/fuddle-go" ) func main() { registry, err := fuddle.Register( // Seed addresses of Fuddle servers. []string{"192.168.1.1:8220", "192.168.1.2:8220", "192.168.1.3:8220"}, fuddle.Node{ ID: "orders-32eaba4e", Service: "orders", Locality: "aws.us-east-1-b", Created: time.Now().UnixMilli(), Revision: "v5.1.0-812ebbc", Metadata: map[string]string{ "status": "booting", "addr.rpc.ip": "192.168.2.1", "addr.rpc.port": "5562", "addr.admin.ip": "192.168.2.1", "addr.admin.port": "7723", "protocol.version": "3", "instance": "i-0bc636e38d6c537a7", }, }, ) if err != nil { // handle err ... } defer registry.Unregister() // ... // Once ready update the nodes status to 'active'. This update will be // propagated to the other nodes in the cluster. err = registry.UpdateLocalMetadata(map[string]string{ "status": "active", }) if err != nil { // handle err ... } }
Output:
Example (SubscribeToOrdersServiceNodes) ¶
Registers a 'frontend' service and subscribes to the set of active order service nodes in us-east-1.
package main import ( "fmt" fuddle "github.com/fuddle-io/fuddle-go" ) func main() { registry, err := fuddle.Register( // Seed addresses of Fuddle servers. []string{"192.168.1.1:8220", "192.168.1.2:8220", "192.168.1.3:8220"}, fuddle.Node{ // ... }, ) if err != nil { // handle err ... } defer registry.Unregister() filter := fuddle.Filter{ "order": { Locality: []string{"aws.us-east-1-*"}, Metadata: fuddle.MetadataFilter{ "status": []string{"active"}, "protocol.version": []string{"2", "3"}, }, }, } // Filter to only include order service nodes in us-east-1 whose status // is active and protocol version is either 2 or 3. var addrs []string registry.Subscribe(func(orderNodes []fuddle.Node) { addrs = nil for _, node := range orderNodes { addr := node.Metadata["addr.rpc.ip"] + ":" + node.Metadata["addr.rpc.port"] addrs = append(addrs, addr) } fmt.Println("order service:", addrs) }, fuddle.WithFilter(filter)) }
Output:
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Filter ¶
type Filter map[string]ServiceFilter
Filter specifies a node filter.
This maps a service name (which may include wildcards with '*') to a service filter.
Any nodes whose service don't match any of those listed are discarded.
type MetadataFilter ¶
MetadataFilter specifies a node filter that discards nodes whose metadata doesn't match the state listed.
To match, for each filter key, the node must include a value for that key and match at least on of the filters for that key.
The filter values may include wildcards, though the keys cannot.
func (*MetadataFilter) Match ¶
func (f *MetadataFilter) Match(node Node) bool
type Node ¶
type Node struct { // ID is a unique identifier for the node in the cluster. ID string // Service is the name of the service running on the node. Service string // Locality is the location of the node in the cluster. Locality string // Created is the time the node was created in UNIX milliseconds. Created int64 // Revision identifies the version of the service running on the node. Revision string // Metadata contains application defined key-value pairs. Metadata map[string]string }
Node represents the state of a node in the cluster.
It includes both fixed attributes of the node, and mutable application defined state.
type NodesOption ¶
type NodesOption interface {
// contains filtered or unexported methods
}
func WithFilter ¶
func WithFilter(f Filter) NodesOption
WithFilter filters the returned set of nodes.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry manages the nodes entry into the cluster registry.
func Register ¶
func Register(addrs []string, node Node, opts ...RegistryOption) (*Registry, error)
Register registers the given node with the cluster registry.
Once registered the nodes state will be propagated to the other nodes in the cluster. It will also stream the existing cluster state and any future updates to maintain a local eventually consistent view of the cluster.
The given addresses are a set of seed addresses for Fuddle nodes.
func (*Registry) Nodes ¶
func (r *Registry) Nodes(opts ...NodesOption) []Node
Nodes returns the set of nodes in the cluster.
func (*Registry) Subscribe ¶
func (r *Registry) Subscribe(cb func(nodes []Node), opts ...NodesOption) func()
Subscribe registers the given callback to fire when the registry state changes.
The callback will be called immediately after registering with the current node state.
Note the callback is called synchronously with the registry mutex held, therefore it must NOT block or callback to the registry (or it will deadlock).
func (*Registry) Unregister ¶
Unregister unregisters the node from the cluster registry.
Note nodes must unregister themselves before shutting down. Otherwise Fuddle will think the node failed rather than left.
type RegistryOption ¶
type RegistryOption interface {
// contains filtered or unexported methods
}
func WithConnectTimeout ¶
func WithConnectTimeout(timeout time.Duration) RegistryOption
WithConnectTimeout defines the time to wait for each connection attempt before timing out. Default to 1 second.
type ServiceFilter ¶
type ServiceFilter struct { // Locality is a list of localities (which may include wildcards with '*'), // where the nodes locality must match at least on of the listed localities. Locality []string // Metadata contains the state filter. Metadata MetadataFilter }
ServiceFilter specifies a node filter that applies to all nodes in a service.
func (*ServiceFilter) Match ¶
func (f *ServiceFilter) Match(node Node) bool