Skip to content

Conversation

@Ferioney
Copy link
Contributor

@Ferioney Ferioney commented Apr 22, 2021

Support properties configuration per client

Fixes #914

@OlgaMaciaszek OlgaMaciaszek self-assigned this Apr 22, 2021
@spencergibb spencergibb changed the title Issue 914 properties solution Support properties configuration per load balancer client Apr 22, 2021
}

@ConditionalOnMissingBean
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@OlgaMaciaszek @spencergibb

I see an issue with enablingLoadBalancerRetryPolicy by spring.cloud.loadbalancer.retry.enabled .

I think we can use spring.cloud.loadbalancer.retry.enabled property for enabling retry in general. And then we should check in policy (and in other beans) that retry is enabled for a specific service (it has not implemented yet).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we definitely should keep spring.cloud.loadbalancer.retry.enabled to be used as a global switch, but we could probably also be able to set spring.cloud.loadbalancer.client.clientName.retry.enabled per client - wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I'm not sure how to implement switching by spring.cloud.loadbalancer.client.clientName.retry.enabled for reactive loadBalancer. Reactive loadBalance has two functions - with (RetryableLoadBalancerExchangeFilterFunction) and without (ReactorLoadBalancerExchangeFilterFunction) retry. and as I see there no possibility to create pipeline for a few filters (we can but we don't have an order guaranty). so I tried to make it in retry filter - RetryableLoadBalancerExchangeFilterFunction.java

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks fine, but I'm thinking we also need a way to handle global properties; I imagine we would do so with retries, but also possibly with caching (we should allow disabling it globally - could you add this?) and possibly other functionalities in the future, so apart from the method getLoadBalancerProperties(String serviceName) in the factory, we could also have sth like getGlobalLoadBalancerProperties() (that would just return the non-client-prefixed properties - then for the major we would have to move most of the properties to the spring.cloud.loadbalancer.client.clientName - prefixed, and only leave the global ones prefixed with just spring.cloud.loadbalancer) and in cases where we want to offer a global off switch (or some other global property) we could verify first the global properties. wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add getGlobalLoadBalancerProperties() method to the factory. But I don't see where we can use it.

For caching we use a different property class LoadBalancerCacheProperties.java. Change cache classes to use the new way of configuration not enough, functionality also should be changed (DefaultLoadBalancerCacheManager and CaffeineBasedLoadBalancerCacheManager). I think it should be done in a separate thread.

Copy link
Collaborator

@OlgaMaciaszek OlgaMaciaszek May 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taking this into account, we could actually change it and remove the getGlobalLoadBalancerProperties() while maintaining the fallback to global properties.

I think we should change the CacheManagers and properties here as well, and also possibly try to introduce this functionality for some other properties that start with spring.cloud.loadbalancer but are used in @ConditionalOnProperty - we will not be able to get this value from configuration properties at bean creation, but we could allow for an additional checkup per service within the implementation class. For example, we have:

@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.avoid-previous-instance", havingValue = "true",
				matchIfMissing = true)
		static class AvoidPreviousInstanceEnabled {

		}

where the prop is used to disable this globally, but we could also allow disabling per serviceId, by adding logic to RetryAwareServiceInstanceListSupplier that verifies property per service. Could you search by spring.cloud.loadblanacer. String and verify where else it makes sense to have both global and per-service levels?

I think we can take more time with this PR if necessary, but we should introduce a complete feature where all the properties can be set per service, unless we decide that, in a given case, it does not make much sense - we should then list these exceptional properties in the docs, though.

The issue I see here, however, is that, in keeping, with this approach, we would give higher priority to per-client properties, however, in case of bean-creation-level conditionals (as in the example above) it would work opposite and this kind of inconsistency needs to be avoided.

Alternatively, I'm thinking, we could have:

  • spring.cloud.loadbalancer.property - global level properties; in the next major we would also leave them only for a few selected flags, things that we want to be able to switch off globally like retries and caching, flags that we check during bean creation, etc. - they would have top priority and supersede any per-client properties.
  • spring.cloud.loadbalancer.client.xxx.property - per-client properties, used for most things
  • spring.cloud.loadbalancer.client.default.property - default fallback set by the user, used when per-client property is not set.
  • default property set by us (from new LoadBalancerProperties())

@Ferioney @spencergibb wdyt? Let's discuss it here first and decide on it before you introduce any new code changes, @Ferioney .

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option would be to rework the way the ServiceInstanceListSupplier beans are being created and, in place of using @ConditionalOnProperty during bean creation, use the properties bean/ factory passed in constructor and then create the bean, for example, new RetryAwareServiceInstanceListSupplier(delegate) (see usage from current code) if the flag from properties is true and just return the delegate bean if it's false. Then we would be able to always give the highest priority for spring.cloud.loadbalancer.client.xxx.property and fall back to spring.cloud.loadbalancer.property consistently for all the features. @Ferioney @spencergibb please let me know what you think about the various possible approaches/ propose others.

Copy link
Collaborator

@OlgaMaciaszek OlgaMaciaszek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ferioney thanks for submitting this. In general, this looks good. Have added some comments, though - please address. Also, please add some tests verifying specifically the new property logic. Please add information in the docs regarding this feature (in spring-cloud-commons.adoc, within the "Spring Cloud LoadBalancer" section.

@Ferioney Ferioney requested a review from OlgaMaciaszek April 27, 2021 08:22
Copy link
Collaborator

@OlgaMaciaszek OlgaMaciaszek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ferioney thanks for submitting the changes. Handling global properties needs to be further discussed and potentially some implementation added there (see comments). I have also requested a change in the way the client properties are prefixed and adding more docs (also please, rebuild the project with -Pdocs flag before pushing the changes - some documentation will be regenerated).

As we are getting closer to merging these changes, there's one more thing to consider - there are other projects that use LoadBalancerProperties directly, mainly SC Gateway and SC OpenFeign, so this PR will need to be accompanied with corresponding PRs in those projects - will you have time to work on it or do you want us to handle it once this one is merged?

@ConfigurationProperties("spring.cloud.loadbalancer.client")
public class LoadBalancerServiceProperties {

private Map<String, LoadBalancerProperties> services = new HashMap<>();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you modify the implementation, so that the property is spring.cloud.loadbalancer.client.foo1.healthcheck... as opposed to spring.cloud.loadbalancer.client.services.foo1.healthcheck...?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree

Copy link
Member

@spencergibb spencergibb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some thoughts, not totally reviewed

@ConfigurationProperties("spring.cloud.loadbalancer.client")
public class LoadBalancerServiceProperties {

private Map<String, LoadBalancerProperties> services = new HashMap<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree

@Deprecated
private LoadBalancerProperties getLoadBalancerProperties(String serviceId) {
if (propertiesFactory != null) {
return propertiesFactory.getLoadBalancerProperties(serviceId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this isn't quite right. If there isn't properties for a service, the properties object should be returned.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Factory method returns default properties when service-specific properties were not provided.

private LoadBalancerProperties getLoadBalancerServiceProperties(String serviceName) {
	return servicesProperties.getClient().getOrDefault(serviceName, servicesProperties.getDefaultClientProperties());
}

* @param context the context for the retry operation
* @return true to retry on the same service instance
*/
default boolean canRetrySameServiceInstance(String serviceId, LoadBalancerRetryContext context) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why couldn't LoadBalancerRetryContext have the serviceId so all these methods won't need to change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoadBalancerRetryContext is created in ReactorLoadBalancerClientAutoConfiguration. At that time we don't have information about service names. So I decided to add new methods.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we don't have it there.

@Ferioney
Copy link
Contributor Author

@OlgaMaciaszek thank you for your answer.
yes, it should be done in SC Gateway and SC OpenFeign. I can try to help with it.

@OlgaMaciaszek
Copy link
Collaborator

OlgaMaciaszek commented May 6, 2021

@Ferioney upon consideration, I think @spencergibb idea here makes sense. It would also resolve the global properties issue - we would automatically use per-client, but if there's none then use the global one OOTB - we can then remove the getGlobalProperties method and add information to the docs, that per-client properties will override global properties, but we will fall back to global if per-client is not set.

EDIT: It can, however, cuase inconsistencies regarding properties hierarchy (see #947 (comment)). Let's discuss it there, before adding any further code changes.

Copy link
Collaborator

@OlgaMaciaszek OlgaMaciaszek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ferioney, thanks for the updates I have added some comments regarding variable renaming, however, the real issue seems to be creating a consistent properties hierarchy for all the spring.cloud.loadbalancer-prefixed properties, as discussed here. Let's first reach a conclusion of that discussion before adding the code changes.


private final LoadBalancerProperties globalProperties;

private final LoadBalancerClientProperties servicesProperties;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename the variable to clientProperties.


private final LoadBalancerClientProperties servicesProperties;

private final boolean isServiceProperties;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename to useClientProperties

return globalProperties;
}

private LoadBalancerProperties getLoadBalancerServiceProperties(String serviceName) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename to getLoadBalancerClientProperties

}

@Bean
public LoadBalancerClientProperties loadBalancerServiceProperties() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets's rename to loadBalancerClientProperties(),

}

@Bean
public LoadBalancerClientProperties loadBalancerServiceProperties() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename to loadBalancerClientProperties()


@Bean
public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties globalProperties,
LoadBalancerClientProperties serviceProperties) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename serviceProperties to clientProperties.

@Bean
@ConditionalOnMissingBean
public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties globalProperties,
LoadBalancerClientProperties servicesProperties,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename serviceProperties to clientProperties.

@OlgaMaciaszek
Copy link
Collaborator

Regarding #947 (comment), thanks a lot - that would be great :). Let's finish working on this one first, and then we could switch to the other ones.

@OlgaMaciaszek
Copy link
Collaborator

OlgaMaciaszek commented May 12, 2021

@Ferioney - thanks a lot for the work that you've put with this PR. We've had a team discussion on this and we need some time to consider design decisions and also investigate some alternative approaches. We will be adding updates in the actual issue and get back to you once that's done.

@spencergibb
Copy link
Member

@Ferioney thanks again for the work. It led us in the right direction. @olegz from spring cloud stream gave us the missing piece.

Closing in favor of #1028

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No way to configure loadbalancer properties per client

4 participants