-
Notifications
You must be signed in to change notification settings - Fork 719
Support properties configuration per load balancer client #947
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support properties configuration per load balancer client #947
Conversation
| } | ||
|
|
||
| @ConditionalOnMissingBean | ||
| @ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true") |
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.
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).
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.
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?
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.
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
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.
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?
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.
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.
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.
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 thingsspring.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 .
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.
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.
OlgaMaciaszek
left a comment
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.
@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.
...c/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerPropertiesFactory.java
Show resolved
Hide resolved
...c/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerServiceProperties.java
Show resolved
Hide resolved
...rc/main/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptor.java
Show resolved
Hide resolved
...rc/main/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptor.java
Outdated
Show resolved
Hide resolved
...ain/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerRetryPolicy.java
Show resolved
Hide resolved
...ramework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunction.java
Show resolved
Hide resolved
.../java/org/springframework/cloud/loadbalancer/blocking/client/BlockingLoadBalancerClient.java
Show resolved
Hide resolved
.../org/springframework/cloud/loadbalancer/blocking/retry/BlockingLoadBalancedRetryFactory.java
Outdated
Show resolved
Hide resolved
.../org/springframework/cloud/loadbalancer/blocking/retry/BlockingLoadBalancedRetryFactory.java
Show resolved
Hide resolved
...rg/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformer.java
Show resolved
Hide resolved
OlgaMaciaszek
left a comment
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.
@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<>(); |
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.
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...?
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.
I agree
...c/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java
Show resolved
Hide resolved
spencergibb
left a comment
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.
Some thoughts, not totally reviewed
| @ConfigurationProperties("spring.cloud.loadbalancer.client") | ||
| public class LoadBalancerServiceProperties { | ||
|
|
||
| private Map<String, LoadBalancerProperties> services = new HashMap<>(); |
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.
I agree
| @Deprecated | ||
| private LoadBalancerProperties getLoadBalancerProperties(String serviceId) { | ||
| if (propertiesFactory != null) { | ||
| return propertiesFactory.getLoadBalancerProperties(serviceId); |
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.
I think this isn't quite right. If there isn't properties for a service, the properties object should be returned.
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.
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) { |
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.
Why couldn't LoadBalancerRetryContext have the serviceId so all these methods won't need to change
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.
LoadBalancerRetryContext is created in ReactorLoadBalancerClientAutoConfiguration. At that time we don't have information about service names. So I decided to add new methods.
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.
Yes, we don't have it there.
|
@OlgaMaciaszek thank you for your answer. |
|
@Ferioney upon consideration, I think @spencergibb idea here makes sense. It would also resolve the EDIT: It can, however, cuase inconsistencies regarding properties hierarchy (see #947 (comment)). Let's discuss it there, before adding any further code changes. |
OlgaMaciaszek
left a comment
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.
@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; |
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.
Let's rename the variable to clientProperties.
|
|
||
| private final LoadBalancerClientProperties servicesProperties; | ||
|
|
||
| private final boolean isServiceProperties; |
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.
Let's rename to useClientProperties
| return globalProperties; | ||
| } | ||
|
|
||
| private LoadBalancerProperties getLoadBalancerServiceProperties(String serviceName) { |
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.
Let's rename to getLoadBalancerClientProperties
| } | ||
|
|
||
| @Bean | ||
| public LoadBalancerClientProperties loadBalancerServiceProperties() { |
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.
Lets's rename to loadBalancerClientProperties(),
...c/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java
Show resolved
Hide resolved
| } | ||
|
|
||
| @Bean | ||
| public LoadBalancerClientProperties loadBalancerServiceProperties() { |
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.
Let's rename to loadBalancerClientProperties()
|
|
||
| @Bean | ||
| public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties globalProperties, | ||
| LoadBalancerClientProperties serviceProperties) { |
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.
Let's rename serviceProperties to clientProperties.
| @Bean | ||
| @ConditionalOnMissingBean | ||
| public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties globalProperties, | ||
| LoadBalancerClientProperties servicesProperties, |
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.
Let's rename serviceProperties to clientProperties.
|
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. |
|
@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. |
Support properties configuration per client
Fixes #914