/** * Deregisters the given {@code member} and returns a new {@link ConsistentHash} with updated memberships. * * @param member the member to remove from the consistent hash * @return a new {@link ConsistentHash} instance with updated memberships */ public ConsistentHash without(Member member) { Assert.notNull(member, () -> "Member may not be null"); if (!members.containsKey(member.name())) { return this; } Map<String, ConsistentHashMember> newMembers = new TreeMap<>(members); newMembers.remove(member.name()); return new ConsistentHash(newMembers, hashFunction, modCount + 1); }
/** * Returns the member instance to which the given {@code message} should be routed. If no suitable member could be * found an empty Optional is returned. * * @param routingKey the routing that should be used to select a member * @param commandMessage the command message to find a member for * @return the member that should handle the message or an empty Optional if no suitable member was found */ public Optional<Member> getMember(String routingKey, CommandMessage<?> commandMessage) { String hash = hash(routingKey); Optional<Member> foundMember = findSuitableMember(commandMessage, hashToMember.tailMap(hash).values()); if (!foundMember.isPresent()) { foundMember = findSuitableMember(commandMessage, hashToMember.headMap(hash).values()); } return foundMember; }
@Override public Optional<Member> findDestination(CommandMessage<?> message) { String routingKey = routingStrategy.getRoutingKey(message); return consistentHash.get().getMember(routingKey, message); }
/** * Update the local member and all the other remote members known by the * {@link org.springframework.cloud.client.discovery.DiscoveryClient} to be able to have an as up-to-date awareness * of which actions which members can handle. This function is automatically triggered by an (unused) * {@link org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent}. Upon this event we may assume * that the application has fully start up. Because of this we can update the local member with the correct name and * {@link java.net.URI}, as initially these were not provided by the * {@link org.springframework.cloud.client.serviceregistry.Registration} yet. * * @param event an unused {@link org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent}, serves * as a trigger for this function * @see SpringCloudCommandRouter#buildMember(ServiceInstance) */ @EventListener @SuppressWarnings("UnusedParameters") public void resetLocalMembership(InstanceRegisteredEvent event) { registered = true; Member startUpPhaseLocalMember = atomicConsistentHash.get().getMembers().stream() .filter(Member::local) .findFirst() .orElseThrow(() -> new IllegalStateException( "There should be no scenario where the local member does not exist." )); if (logger.isDebugEnabled()) { logger.debug("Resetting local membership for [{}].", startUpPhaseLocalMember); } updateMemberships(); atomicConsistentHash.updateAndGet(consistentHash -> consistentHash.without(startUpPhaseLocalMember)); }
private Optional<ConsistentHash> updateMembershipForServiceInstance(ServiceInstance serviceInstance, AtomicReference<ConsistentHash> atomicConsistentHash) { if (logger.isDebugEnabled()) { logger.debug("Updating membership for service instance: [{}]", serviceInstance); } Member member = buildMember(serviceInstance); Optional<MessageRoutingInformation> optionalMessageRoutingInfo = getMessageRoutingInformation(serviceInstance); if (optionalMessageRoutingInfo.isPresent()) { MessageRoutingInformation messageRoutingInfo = optionalMessageRoutingInfo.get(); return Optional.of(atomicConsistentHash.updateAndGet( consistentHash -> consistentHash.with(member, messageRoutingInfo.getLoadFactor(), messageRoutingInfo.getCommandFilter(serializer)) )); } else { logger.info( "Black listed ServiceInstance [{}] under host [{}] and port [{}] since we could not retrieve the " + "required Message Routing Information from it.", serviceInstance.getServiceId(), serviceInstance.getHost(), serviceInstance.getPort() ); blackListedServiceInstances.add(serviceInstance); } return Optional.empty(); }
private ConsistentHash suspect(Member member) { ConsistentHash newConsistentHash = atomicConsistentHash.updateAndGet(consistentHash -> consistentHash.without(member)); consistentHashChangeListener.onConsistentHashChanged(newConsistentHash); return newConsistentHash; }
/** * Returns the hashes covered by the member. If the hash of the routing key matches with one of the returned * hashes and the member is capable of handling the command then it will be selected as a target for the * command. * * @return the hashes covered by this member */ public Set<String> hashes() { return IntStream.range(0, segmentCount) .mapToObj(i -> hash(name() + " #" + i)) .collect(Collectors.toSet()); }
/** * Returns the member for the given <code>item</code>, that supports given <code>commandType</code>. If no such * member is available, this method returns <code>null</code>. * * @param item The item to find a node name for * @param commandType The type of command the member must support * @return The node name for the given <code>item</code>, or <code>null</code> if not found */ public String getMember(String item, String commandType) { String hash = Digester.md5Hex(item); SortedMap<String, Member> tailMap = hashToMember.tailMap(hash); Iterator<Map.Entry<String, Member>> tailIterator = tailMap.entrySet().iterator(); Member foundMember = findSuitableMember(commandType, tailIterator); if (foundMember == null) { // if the tail doesn't have a member, we should start back at the head Iterator<Map.Entry<String, Member>> headIterator = hashToMember.headMap(hash).entrySet().iterator(); foundMember = findSuitableMember(commandType, headIterator); } return foundMember == null ? null : foundMember.name(); }
/** * Returns the set of members currently registered with the connector. * <p/> * Note that any changes in membership are not reflected in the returned set. * * @return the set of members currently registered with the connector */ public Set<ConsistentHash.Member> getMembers() { return consistentHash.get().getMembers(); }
@Override public Optional<Member> findDestination(CommandMessage<?> message) { String routingKey = routingStrategy.getRoutingKey(message); return consistentHash.get().getMember(routingKey, message); }
/** * Connects this Node to the cluster and shares membership details about this node with the other nodes in the * cluster. * <p> * The Join messages have been sent, but may not have been processed yet when the method returns. Before sending * messages via this connector, await for the joining process to be completed (see {@link #awaitJoined() and * {@link #awaitJoined(long, TimeUnit)}}. * * @throws Exception when an error occurs connecting or communicating with the cluster */ public void connect() throws Exception { if (channel.getClusterName() != null && !clusterName.equals(channel.getClusterName())) { throw new ConnectionFailedException("Already joined cluster: " + channel.getClusterName()); } channel.setReceiver(this); channel.connect(clusterName); Address localAddress = channel.getAddress(); String localName = localAddress.toString(); SimpleMember<Address> localMember = new SimpleMember<>(localName, localAddress, LOCAL_MEMBER, null); members.put(localAddress, new VersionedMember(localMember, membershipVersion.getAndIncrement())); updateConsistentHash(ch -> ch.with(localMember, loadFactor, commandFilter)); if (!executorProvided) { executorService = Executors.newCachedThreadPool(new AxonThreadFactory("JGroupsConnector - " + localName)); } }
@Override public synchronized void viewAccepted(final View view) { if (currentView == null) { currentView = view; logger.info("Local segment ({}) joined the cluster. Broadcasting configuration.", channel.getAddress()); try { broadCastMembership(membershipVersion.get(), true); joinedCondition.markJoined(); } catch (Exception e) { throw new MembershipUpdateFailedException("Failed to broadcast my settings", e); } } else if (!view.equals(currentView)) { Address[][] diff = View.diff(currentView, view); Address[] joined = diff[0]; Address[] left = diff[1]; currentView = view; Address localAddress = channel.getAddress(); stream(left).forEach(lm -> updateConsistentHash(ch -> { VersionedMember member = members.get(lm); if (member == null) { return ch; } return ch.without(member); })); stream(left).forEach(members::remove); stream(joined).filter(member -> !member.equals(localAddress)) .forEach(member -> sendMyConfigurationTo(member, true, membershipVersion.get())); } currentView = view; }
/** * Returns the collection of nodes, represented as {@link ConsistentHashMember}, in the order they would be * considered for the given routing key. Whether a CommandMessage would be forwarded to each of the candidates, * depends on the Command Filter of each node. * * @param routingKey The routing key to select ordering * @return A collection containing each of the nodes, in the order they would be considered */ public Collection<ConsistentHashMember> getEligibleMembers(String routingKey) { String hash = hash(routingKey); Collection<ConsistentHashMember> tail = hashToMember.tailMap(hash).values(); Collection<ConsistentHashMember> head = hashToMember.headMap(hash).values(); LinkedHashSet<ConsistentHashMember> combined = new LinkedHashSet<>(tail); combined.addAll(head); return combined; }
private void reportDisconnectedMembers(ConsistentHashChange hashChange) { Set<ConsistentHash.Member> newMembers = hashChange.newHash.getMembers(); Set<ConsistentHash.Member> oldMembers = new HashSet<ConsistentHash.Member>(hashChange.oldHash.getMembers()); oldMembers.removeAll(newMembers); String[] memberNames = new String[oldMembers.size()]; int i=0; for (ConsistentHash.Member member : oldMembers) { memberNames[i++] = member.name(); } logger.info("Member(s) disconnected: {}. Rebuilt consistent hash ring.", Arrays.toString(memberNames)); }
/** * Registers the given {@code member} with given {@code loadFactor} and {@code commandFilter} if it is not * already contained in the {@link ConsistentHash}. It will return the current ConsistentHash if the addition is * a duplicate and returns a new ConsistentHash with updated memberships if it is not. * <p> * The relative loadFactor of the member determines the likelihood of being selected as a destination for a command. * * @param member the member to register * @param loadFactor the load factor of the new member * @param commandFilter filter describing which commands can be handled by the given member * @return a new {@link ConsistentHash} instance with updated memberships */ public ConsistentHash with(Member member, int loadFactor, CommandMessageFilter commandFilter) { Assert.notNull(member, () -> "Member may not be null"); ConsistentHashMember newMember = new ConsistentHashMember(member, loadFactor, commandFilter); if (members.containsKey(member.name()) && newMember.equals(members.get(member.name()))) { return this; } Map<String, ConsistentHashMember> newMembers = new TreeMap<>(members); newMembers.put(member.name(), newMember); return new ConsistentHash(newMembers, hashFunction, modCount + 1); }
/** * Returns the member instance to which the given {@code message} should be routed. If no suitable member could be * found an empty Optional is returned. * * @param routingKey the routing that should be used to select a member * @param commandMessage the command message to find a member for * @return the member that should handle the message or an empty Optional if no suitable member was found */ public Optional<Member> getMember(String routingKey, CommandMessage<?> commandMessage) { String hash = hash(routingKey); SortedMap<String, ConsistentHashMember> tailMap = hashToMember.tailMap(hash); Iterator<Map.Entry<String, ConsistentHashMember>> tailIterator = tailMap.entrySet().iterator(); Optional<Member> foundMember = findSuitableMember(commandMessage, tailIterator); if (!foundMember.isPresent()) { Iterator<Map.Entry<String, ConsistentHashMember>> headIterator = hashToMember.headMap(hash).entrySet().iterator(); foundMember = findSuitableMember(commandMessage, headIterator); } return foundMember; }
@Override public Optional<Member> findDestination(CommandMessage<?> commandMessage) { return atomicConsistentHash.get().getMember(routingStrategy.getRoutingKey(commandMessage), commandMessage); }
/** * Connects this Node to the cluster and shares membership details about this node with the other nodes in the * cluster. * <p> * The Join messages have been sent, but may not have been processed yet when the method returns. Before sending * messages via this connector, await for the joining process to be completed (see {@link #awaitJoined() and * {@link #awaitJoined(long, TimeUnit)}}. * * @throws Exception when an error occurs connecting or communicating with the cluster */ public void connect() throws Exception { if (channel.getClusterName() != null && !clusterName.equals(channel.getClusterName())) { throw new ConnectionFailedException("Already joined cluster: " + channel.getClusterName()); } channel.setReceiver(this); channel.connect(clusterName); Address localAddress = channel.getAddress(); String localName = localAddress.toString(); SimpleMember<Address> localMember = new SimpleMember<>(localName, localAddress, LOCAL_MEMBER, null); members.put(localAddress, new VersionedMember(localMember, membershipVersion.getAndIncrement())); updateConsistentHash(ch -> ch.with(localMember, loadFactor, commandFilter)); if (!executorProvided) { executorService = Executors.newCachedThreadPool(new AxonThreadFactory("JGroupsConnector - " + localName)); } }
@Override public synchronized void viewAccepted(final View view) { if (currentView == null) { currentView = view; logger.info("Local segment ({}) joined the cluster. Broadcasting configuration.", channel.getAddress()); try { broadCastMembership(membershipVersion.get(), true); joinedCondition.markJoined(); } catch (Exception e) { throw new MembershipUpdateFailedException("Failed to broadcast my settings", e); } } else if (!view.equals(currentView)) { Address[][] diff = View.diff(currentView, view); Address[] joined = diff[0]; Address[] left = diff[1]; currentView = view; Address localAddress = channel.getAddress(); stream(left).forEach(lm -> updateConsistentHash(ch -> { VersionedMember member = members.get(lm); if (member == null) { return ch; } return ch.without(member); })); stream(left).forEach(members::remove); stream(joined).filter(member -> !member.equals(localAddress)) .forEach(member -> sendMyConfigurationTo(member, true, membershipVersion.get())); } currentView = view; }
/** * Returns the collection of nodes, represented as {@link ConsistentHashMember}, in the order they would be * considered for the given routing key. Whether a CommandMessage would be forwarded to each of the candidates, * depends on the Command Filter of each node. * * @param routingKey The routing key to select ordering * @return A collection containing each of the nodes, in the order they would be considered */ public Collection<ConsistentHashMember> getEligibleMembers(String routingKey) { String hash = hash(routingKey); Collection<ConsistentHashMember> tail = hashToMember.tailMap(hash).values(); Collection<ConsistentHashMember> head = hashToMember.headMap(hash).values(); LinkedHashSet<ConsistentHashMember> combined = new LinkedHashSet<>(tail); combined.addAll(head); return combined; }