Class SpringCloudMemberDiscovery
- All Implemented Interfaces:
DescribableComponent
This is what both connectors route with and publish their capabilities through. Commands and queries are routed
differently: a command is hashed onto a ConsistentHash ring by its routing key, so that commands for one
entity keep landing on one member, while a query carries no routing key and goes to whichever member advertises its
name, rotating over them to spread the load. The administration behind that, which members there are and what each
of them handles, is kept in a MemberRegistry this discovery owns.
Who is in the cluster and what each member handles are learned separately, because they change for different reasons and are signalled differently:
- Membership comes from the discovery implementation, which signals a change with a
HeartbeatEvent. Eureka publishes one on every registry fetch, Spring Cloud Kubernetes whenever the set of endpoints changes. On each,updateMemberships()reads the instances discovery reports, asks the ones it has not heard from for their capabilities, and drops the ones that are gone. - Capabilities change when a member subscribes or unsubscribes a handler, which no discovery
implementation signals. Each instance's answer is therefore kept for a refresh interval, after which
refreshCapabilities()asks that instance again, without involving discovery.
startMembershipRound() runs one on the Executor this
discovery is given, so a heartbeat never waits on the requests it causes.
Members are identified by the node id each one reports about itself, not by what discovery reports about them. An
application that discovery reports more than once, for example under two service ids, is one member, reached at the
first of those instances in discovery's own order. This application recognizes its own instance the same way: it is
the one answering with this application's node id. No Spring Cloud Registration is needed for that, so the
connector works the same with discovery implementations that register the application, such as Eureka, and with
those that leave registration to the platform, such as Spring Cloud Kubernetes.
Registered by the Spring Boot autoconfiguration, which starts a membership round on every HeartbeatEvent
and InstanceRegisteredEvent, and schedules the capability refresh. Both connectors share one, so that each
publishes what it handles without erasing what the other published.
- Since:
- 5.4.0
- Author:
- Allard Buijze
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final DurationThe default for how long an instance's capabilities are taken as current before it is asked again: 30 seconds, Eureka's default registry fetch interval, at which capabilities used to be refreshed along with membership. -
Constructor Summary
ConstructorsConstructorDescriptionSpringCloudMemberDiscovery(org.springframework.cloud.client.discovery.DiscoveryClient discoveryClient, CapabilityDiscoveryMode discoveryMode) Constructs aSpringCloudMemberDiscoverydiscovering members through the givendiscoveryClient.SpringCloudMemberDiscovery(org.springframework.cloud.client.discovery.DiscoveryClient discoveryClient, CapabilityDiscoveryMode discoveryMode, Predicate<org.springframework.cloud.client.ServiceInstance> serviceInstanceFilter, @Nullable String contextRootMetadataPropertyName, Duration capabilitiesRefreshInterval, Executor executor) Constructs aSpringCloudMemberDiscoverydiscovering members through the givendiscoveryClient. -
Method Summary
Modifier and TypeMethodDescriptioncapabilitiesOf(Member member) Returns what the givenmemberhandles, as currently known.voiddescribeTo(ComponentDescriptor descriptor) Describe the properties ofthis DescribableComponentwith the givendescriptor.findAllQueryDestinations(QualifiedName queryName) Returns every member advertising the givenqueryName, in a stable order.findCommandDestination(String routingKey, QualifiedName commandName) Resolves the member a command of the givencommandNamecarrying the givenroutingKeyshould be handled by.findQueryDestination(QualifiedName queryName) Resolves the member a query of the givenqueryNameshould be handled by.Returns theMemberrepresenting this application, identified by this application's node id.voidmarkUnreachable(Member member) Takes the givenmemberoff the ring, as a message could not be delivered to it.members()Returns every member currently known, this application included when it is part of the cluster, whatever they handle.onMembershipChanged(Consumer<ConsistentHash> listener) Registers alistenerto be notified after every change to the ring, with the ring that replaced the previous one.voidpublishLocalCommands(int loadFactor, Set<QualifiedName> commands) Publishes the givencommandsas the commands this application handles, asking for the givenloadFactorworth of the command load.voidpublishLocalQueries(Set<QualifiedName> queries) Publishes the givenqueriesas the queries this application handles.voidAsks the instances whose capabilities are older than the capabilities refresh interval, or that have not answered before, for their capabilities again.SchedulesrefreshCapabilities()on the givenscheduler, twice per capabilities refresh interval, so that no instance's capabilities are older than one and a half intervals.voidStarts a membership round on this discovery's executor, as discovery signalled that the instances it reports may have changed.voidBrings the ring in line with the instances discovery currently reports.
-
Field Details
-
DEFAULT_CAPABILITIES_REFRESH_INTERVAL
The default for how long an instance's capabilities are taken as current before it is asked again: 30 seconds, Eureka's default registry fetch interval, at which capabilities used to be refreshed along with membership.
-
-
Constructor Details
-
SpringCloudMemberDiscovery
public SpringCloudMemberDiscovery(org.springframework.cloud.client.discovery.DiscoveryClient discoveryClient, CapabilityDiscoveryMode discoveryMode) Constructs aSpringCloudMemberDiscoverydiscovering members through the givendiscoveryClient.Capabilities are refreshed after
DEFAULT_CAPABILITIES_REFRESH_INTERVAL, and rounds run on virtual threads.- Parameters:
discoveryClient- the client reporting the service instances making up the clusterdiscoveryMode- the mode used to learn which member each discovered instance is, and what it handles
-
SpringCloudMemberDiscovery
public SpringCloudMemberDiscovery(org.springframework.cloud.client.discovery.DiscoveryClient discoveryClient, CapabilityDiscoveryMode discoveryMode, Predicate<org.springframework.cloud.client.ServiceInstance> serviceInstanceFilter, @Nullable String contextRootMetadataPropertyName, Duration capabilitiesRefreshInterval, Executor executor) Constructs aSpringCloudMemberDiscoverydiscovering members through the givendiscoveryClient.- Parameters:
discoveryClient- the client reporting the service instances making up the clusterdiscoveryMode- the mode used to learn which member each discovered instance is, and what it handlesserviceInstanceFilter- decides which discovered instances are considered at all. Instances rejected here are never asked for their capabilities, which is cheaper than relying on the ignore list when whole services can be excluded up front.contextRootMetadataPropertyName- themetadatakey holding an instance's context root, to be appended to its URI, ornullwhen instances are served from the rootcapabilitiesRefreshInterval- how long an instance's capabilities are taken as current before it is asked again. A zero or negative interval asks every instance on every round, and leavesscheduleCapabilityRefresh(ScheduledExecutorService)nothing to scheduleexecutor- runs the roundsstartMembershipRound()or the capability refresh starts, and the capability requests within them. A round waits for its requests, so the executor must run more than one task at a time
-
-
Method Details
-
findCommandDestination
Resolves the member a command of the givencommandNamecarrying the givenroutingKeyshould be handled by.- Parameters:
routingKey- the routing key of the command to resolve a member forcommandName- theQualifiedNameof the command to resolve a member for- Returns:
- the member that should handle the command, or
Optional.empty()when no known member handles commands of the givencommandName
-
findQueryDestination
Resolves the member a query of the givenqueryNameshould be handled by.Unlike a command, a query carries no routing key and asks nothing of where it is handled, so any member advertising the name will do. Successive queries of the same name therefore rotate over the members advertising it, spreading the load rather than sending every query of a name to the same member. Members are rotated over in name order, so that the rotation is over a stable sequence even as the ring changes.
This member is one of the candidates like any other. Preferring the local handler is a policy of the
DistributedQueryBus, which applies it before the query reaches a connector at all.- Parameters:
queryName- theQualifiedNameof the query to resolve a member for- Returns:
- the member that should handle the query, or
Optional.empty()when no known member handles queries of the givenqueryName
-
findAllQueryDestinations
Returns every member advertising the givenqueryName, in a stable order.Where
findQueryDestination(QualifiedName)picks one member to answer a query, a subscription query needs them all: an update is emitted on whichever member's state changed, and a member only matches a subscription it holds a registration for. A subscriber that reached only one member would miss every update emitted on the others.- Parameters:
queryName- theQualifiedNameof the query to resolve members for- Returns:
- every member advertising the given
queryName, empty when none does
-
onMembershipChanged
Registers alistenerto be notified after every change to the ring, with the ring that replaced the previous one.A subscription query relies on this: it has to know when a member that advertises its name appears, because it cannot have received the updates that member emitted before it was subscribed to. Notified outside the lock the ring is replaced under, so a listener may read this discovery freely. It runs on whichever thread made the change, which for a discovery round is the thread running that round.
- Parameters:
listener- notified with the new ring after every membership change- Returns:
- a registration cancelling the notification
-
publishLocalCommands
Publishes the givencommandsas the commands this application handles, asking for the givenloadFactorworth of the command load.Reflected in this application's own ring right away, so that a command dispatched right after its handler subscribed can already be routed here, and visible to other members on their next round.
- Parameters:
loadFactor- the share of the command load this application asks forcommands- the names of the commands this application handles
-
publishLocalQueries
Publishes the givenqueriesas the queries this application handles.Reflected in this application's own ring right away, and visible to other members on their next round.
- Parameters:
queries- the names of the queries this application handles
-
localMember
Returns theMemberrepresenting this application, identified by this application's node id.- Returns:
- the member representing this application
-
members
Returns every member currently known, this application included when it is part of the cluster, whatever they handle.- Returns:
- every member currently known, in no particular order
-
capabilitiesOf
Returns what the givenmemberhandles, as currently known.- Parameters:
member- the member to look up the capabilities of- Returns:
- the capabilities of the given
member, orOptional.empty()when it is not a known member
-
startMembershipRound
public void startMembershipRound()Starts a membership round on this discovery's executor, as discovery signalled that the instances it reports may have changed.The thread signalling the change does not wait on the capability requests the round makes.
-
updateMemberships
public void updateMemberships()Brings the ring in line with the instances discovery currently reports.Instances discovery no longer reports are dropped. Instances that are new, that have not answered before, or whose answer is older than the capabilities refresh interval are asked for their capabilities; the others keep what they answered before.
Exposed so that a deployment whose discovery implementation publishes no heartbeats, or a test, can drive membership itself. Runs on the calling thread, after any round in progress has finished.
-
refreshCapabilities
public void refreshCapabilities()Asks the instances whose capabilities are older than the capabilities refresh interval, or that have not answered before, for their capabilities again.Does not consult discovery: which instances there are is up to
updateMemberships(). This is what keeps the ring current with discovery implementations that only signal membership changes, such as Spring Cloud Kubernetes, while a member subscribes or unsubscribes handlers. Runs on the calling thread, after any round in progress has finished. -
scheduleCapabilityRefresh
SchedulesrefreshCapabilities()on the givenscheduler, twice per capabilities refresh interval, so that no instance's capabilities are older than one and a half intervals.The
scheduleronly decides when a refresh is due. The refresh itself runs on this discovery's executor, and is skipped while the previous one has not finished, so theschedulermay be shared with other work that must not be held up. Does nothing when the capabilities refresh interval is zero or negative.- Parameters:
scheduler- the scheduler deciding when a refresh is due- Returns:
- a registration cancelling the schedule
-
markUnreachable
Takes the givenmemberoff the ring, as a message could not be delivered to it.What the member answered is forgotten as well, so that the next round does not put it back before it has answered again. It is asked again on that round, so this only keeps messages away from a member while it is unreachable. It does not weaken any consistency guarantee: routing a command to one member is a matter of locality, while consistency is enforced where the events are appended.
- Parameters:
member- the member that could not be reached
-
describeTo
Description copied from interface:DescribableComponentDescribe the properties ofthis DescribableComponentwith the givendescriptor.Components should call the appropriate
describePropertymethods on the descriptor to register their properties. The descriptor is responsible for determining how these properties are formatted and structured in the final output.Best Practices: As a general rule, all relevant fields of a
DescribableComponentimplementation should be described in this method. However, developers have discretion to include only the fields that make sense in the context. Not every field may be meaningful for description purposes, especially internal implementation details. Furthermore, components might want to expose different information based on their current state. The final decision on what properties to include lies with the person implementing thedescribeTomethod, who should focus on providing information that is useful for understanding the component's configuration and state.Example implementation:
public void describeTo(ComponentDescriptor descriptor) { descriptor.describeProperty("name", this.name); descriptor.describeProperty("enabled", this.enabled); descriptor.describeProperty("configuration", this.configuration); // A nested component descriptor.describeProperty("handlers", this.eventHandlers); // A collection }- Specified by:
describeToin interfaceDescribableComponent- Parameters:
descriptor- The component descriptor to describethis DescribableComponentn its properties in.
-