-
Notifications
You must be signed in to change notification settings - Fork 718
Request based sticky session #860
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
Changes from 6 commits
799ed77
6b01147
a840163
0b4a5f7
693915f
2975189
e068cf1
7e66615
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -970,6 +970,32 @@ public class CustomLoadBalancerConfiguration { | |
|
|
||
| WARNING: `HealthCheckServiceInstanceListSupplier` has its own caching mechanism based on Reactor Flux `replay()`. Therefore, if it's being used, you may want to skip wrapping that supplier with `CachingServiceInstanceListSupplier`. | ||
|
|
||
| === Request-based Sticky Session for LoadBalancer | ||
|
|
||
| It is possible to set up the LoadBalancer in such a way that it will prefer the instance with `instanceId` provided in a request cookie. We currently support this if the request is being passed to the LoadBalancer either via the `ClientRequestContext` or `ServerHttpRequestContext`, which are being used by the SC LoadBalancer exchange filter functions and filters. | ||
|
|
||
| For that, you will need to use the `RequestBasedStickySessionServiceInstanceListSupplier`. It can be configured either by setting the value of `spring.cloud.loadbalancer.configurations` to `request-based-sticky-session` or by providing your own `ServiceInstanceListSupplier` bean, for example: | ||
|
||
|
|
||
| [[health-check-based-custom-loadbalancer-configuration]] | ||
| [source,java,indent=0] | ||
| ---- | ||
| public class CustomLoadBalancerConfiguration { | ||
|
|
||
| @Bean | ||
| public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier( | ||
| ConfigurableApplicationContext context) { | ||
| return ServiceInstanceListSupplier.builder() | ||
| .withDiscoveryClient() | ||
| .withRequestBasedStickySession() | ||
| .build(context); | ||
| } | ||
| } | ||
| ---- | ||
|
|
||
| For that functionality, it will be useful to have the selected service instance (that can be different from the one in the original request cookie in case that one was not available) to be updated before sending the request forward. In order to do that set the value of `spring.cloud.loadbalancer.sticky-session.add-service-instance-cookie` to `true`. | ||
|
||
|
|
||
| By default, the name of the cookie is `sc-lb-instance-id`. It can be modified by changing the value of `spring.cloud.loadbalancer.instance-id-cookie-name` property. | ||
|
||
|
|
||
| [[spring-cloud-loadbalancer-hints]] | ||
| === Spring Cloud LoadBalancer Hints | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| /* | ||
| * Copyright 2012-2020 the original author or authors. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.springframework.cloud.loadbalancer.core; | ||
|
|
||
| import java.util.Collections; | ||
| import java.util.List; | ||
|
|
||
| import org.apache.commons.logging.Log; | ||
| import org.apache.commons.logging.LogFactory; | ||
| import reactor.core.publisher.Flux; | ||
|
|
||
| import org.springframework.cloud.client.ServiceInstance; | ||
| import org.springframework.cloud.client.loadbalancer.ClientRequestContext; | ||
| import org.springframework.cloud.client.loadbalancer.Request; | ||
| import org.springframework.cloud.client.loadbalancer.ServerHttpRequestContext; | ||
| import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties; | ||
| import org.springframework.http.HttpCookie; | ||
| import org.springframework.http.server.reactive.ServerHttpRequest; | ||
| import org.springframework.web.reactive.function.client.ClientRequest; | ||
|
|
||
| /** | ||
| * A session cookie based implementation of {@link ServiceInstanceListSupplier} that gives | ||
| * preference to the instance with an id specified in a request cookie. | ||
| * | ||
| * @author Olga Maciaszek-Sharma | ||
| * @since 3.0.0 | ||
| */ | ||
| public class RequestBasedStickySessionServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier { | ||
|
|
||
| private static final Log LOG = LogFactory.getLog(RequestBasedStickySessionServiceInstanceListSupplier.class); | ||
|
|
||
| private final LoadBalancerProperties properties; | ||
|
|
||
| public RequestBasedStickySessionServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, | ||
| LoadBalancerProperties properties) { | ||
| super(delegate); | ||
| this.properties = properties; | ||
| } | ||
|
|
||
| @Override | ||
| public String getServiceId() { | ||
| return delegate.getServiceId(); | ||
| } | ||
|
|
||
| @Override | ||
| public Flux<List<ServiceInstance>> get() { | ||
| return delegate.get(); | ||
| } | ||
|
|
||
| @SuppressWarnings("rawtypes") | ||
| @Override | ||
| public Flux<List<ServiceInstance>> get(Request request) { | ||
| String instanceIdCookieName = properties.getStickySession().getInstanceIdCookieName(); | ||
| Object context = request.getContext(); | ||
| if ((context instanceof ClientRequestContext)) { | ||
| ClientRequest originalRequest = ((ClientRequestContext) context).getClientRequest(); | ||
| // We expect there to be one value in this cookie | ||
| String cookie = originalRequest.cookies().getFirst(instanceIdCookieName); | ||
| if (cookie != null) { | ||
| return get().map(serviceInstances -> selectInstance(serviceInstances, cookie)); | ||
| } | ||
| if (LOG.isDebugEnabled()) { | ||
| LOG.debug("Cookie not found. Returning all instances returned by delegate."); | ||
| } | ||
| return get(); | ||
| } | ||
| if ((context instanceof ServerHttpRequestContext)) { | ||
| ServerHttpRequest originalRequest = ((ServerHttpRequestContext) context).getClientRequest(); | ||
| HttpCookie cookie = originalRequest.getCookies().getFirst(instanceIdCookieName); | ||
| if (cookie != null) { | ||
| return get().map(serviceInstances -> selectInstance(serviceInstances, cookie.getValue())); | ||
| } | ||
| if (LOG.isDebugEnabled()) { | ||
| LOG.debug("Cookie not found. Returning all instances returned by delegate."); | ||
| } | ||
| return get(); | ||
| } | ||
| if (LOG.isDebugEnabled()) { | ||
| LOG.debug("Searching for instances based on cookie not supported for ClientRequestContext type." | ||
| + " Returning all instances returned by delegate."); | ||
| } | ||
| // If no cookie is available, we return all the instances provided by the | ||
| // delegate. | ||
| return get(); | ||
| } | ||
|
|
||
| private List<ServiceInstance> selectInstance(List<ServiceInstance> serviceInstances, String cookie) { | ||
| for (ServiceInstance serviceInstance : serviceInstances) { | ||
| if (cookie.equals(serviceInstance.getInstanceId())) { | ||
| if (LOG.isDebugEnabled()) { | ||
| LOG.debug(String.format("Returning the service instance: %s. Found for cookie: %s", | ||
| serviceInstance.toString(), cookie)); | ||
| } | ||
| return Collections.singletonList(serviceInstance); | ||
| } | ||
| } | ||
| // If the instances cannot be found based on the cookie, | ||
| // we return all the instances provided by the delegate. | ||
| if (LOG.isDebugEnabled()) { | ||
| LOG.debug(String.format( | ||
| "Service instance for cookie: %s not found. Returning all instances returned by delegate.", | ||
| cookie)); | ||
| } | ||
| return serviceInstances; | ||
| } | ||
|
|
||
| } |
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.
Change to:
You can set up the
LoadBalancerin such a way that it prefers the instance withinstanceIdprovided in a request cookie. We currently support this if the request is being passed to theLoadBalancerthrough eitherClientRequestContextorServerHttpRequestContext, which are used by the SCLoadBalancerexchange filter functions and filters.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.
As a rule, you can nearly always remove "will" and use the indefinite present (also called the simple present). Doing so makes the doc more consistent with our other docs and often makes the sentences shorter.