From 3d23110d33de8a750f26e7a53e42921032394d11 Mon Sep 17 00:00:00 2001 From: hexiaofeng Date: Thu, 9 May 2024 11:45:59 +0800 Subject: [PATCH] add sticky policy --- .../agent/governance/instance/Endpoint.java | 17 +++++ .../governance/invoke/filter/RouteFilter.java | 14 ++-- .../filter/route/LoadBalanceFilter.java | 66 ++++++++++++++----- .../invoke/filter/route/StickyFilter.java | 33 +++++++--- .../invoke/filter/route/VirtualFilter.java | 2 +- .../loadbalance/LoadBalancePolicy.java | 5 ++ .../service/loadbalance/StickyType.java | 25 +++++++ 7 files changed, 127 insertions(+), 35 deletions(-) create mode 100644 joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/policy/service/loadbalance/StickyType.java diff --git a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/instance/Endpoint.java b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/instance/Endpoint.java index 72b9da26..d5c45164 100644 --- a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/instance/Endpoint.java +++ b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/instance/Endpoint.java @@ -328,6 +328,23 @@ default Predicate getPredicate() { return null; } + /** + * Evaluates the predicate associated with this endpoint, if any, to determine + * if this endpoint satisfies the conditions defined by the predicate. + *

+ * This method will return {@code true} if no predicate is set, implying that + * the endpoint is acceptable by default. If a predicate is set, the endpoint + * will be tested against it. + *

+ * + * @return {@code true} if the predicate is {@code null} or if the predicate + * test passes for this endpoint; {@code false} otherwise. + */ + default boolean predicate() { + Predicate predicate = getPredicate(); + return predicate == null || predicate.test(this); + } + /** * {@inheritDoc} */ diff --git a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/invoke/filter/RouteFilter.java b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/invoke/filter/RouteFilter.java index fa4e48ae..5d62a99a 100644 --- a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/invoke/filter/RouteFilter.java +++ b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/invoke/filter/RouteFilter.java @@ -35,19 +35,19 @@ @Extensible(value = "RouteFilter") public interface RouteFilter { - int ORDER_VIRTUAL = 100; + int ORDER_RETRY = 100; - int ORDER_LIVE_UNIT = ORDER_VIRTUAL + 100; + int ORDER_STICKY = ORDER_RETRY + 100; - int ORDER_LOCALHOST = ORDER_LIVE_UNIT + 100; + int ORDER_LOCALHOST = ORDER_STICKY + 100; - int ORDER_RETRY = ORDER_LOCALHOST + 100; + int ORDER_HEALTH = ORDER_LOCALHOST + 100; - int ORDER_STICKY = ORDER_RETRY + 100; + int ORDER_VIRTUAL = ORDER_HEALTH + 100; - int ORDER_HEALTH = ORDER_STICKY + 100; + int ORDER_LIVE_UNIT = ORDER_VIRTUAL + 100; - int ORDER_TAG_ROUTE = ORDER_HEALTH + 100; + int ORDER_TAG_ROUTE = ORDER_LIVE_UNIT + 100; int ORDER_LANE = ORDER_TAG_ROUTE + 100; diff --git a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/invoke/filter/route/LoadBalanceFilter.java b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/invoke/filter/route/LoadBalanceFilter.java index bb65b105..151da091 100644 --- a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/invoke/filter/route/LoadBalanceFilter.java +++ b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/invoke/filter/route/LoadBalanceFilter.java @@ -22,6 +22,7 @@ import com.jd.live.agent.core.inject.annotation.InjectLoader; import com.jd.live.agent.core.inject.annotation.Injectable; import com.jd.live.agent.governance.config.GovernanceConfig; +import com.jd.live.agent.governance.context.RequestContext; import com.jd.live.agent.governance.instance.Endpoint; import com.jd.live.agent.governance.invoke.OutboundInvocation; import com.jd.live.agent.governance.invoke.RouteTarget; @@ -31,13 +32,13 @@ import com.jd.live.agent.governance.invoke.loadbalance.randomweight.RandomWeightLoadBalancer; import com.jd.live.agent.governance.policy.service.ServicePolicy; import com.jd.live.agent.governance.policy.service.loadbalance.LoadBalancePolicy; +import com.jd.live.agent.governance.request.Request; import com.jd.live.agent.governance.request.ServiceRequest; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.function.Predicate; /** * LoadBalanceFilter applies load balancing to the list of route targets. It ensures that @@ -58,23 +59,27 @@ public class LoadBalanceFilter implements RouteFilter { public void filter(OutboundInvocation invocation, RouteFilterChain chain) { RouteTarget target = invocation.getRouteTarget(); if (!target.isEmpty()) { - LoadBalancer loadBalancer = getLoadBalancer(invocation); - target.choose(endpoints -> { - List backends = endpoints; - do { - Endpoint backend = loadBalancer.choose(backends, invocation); - if (backend == null) { - return null; - } - Predicate predicate = backend.getPredicate(); - if (predicate == null || predicate.test(backend)) { - return Collections.singletonList(backend); - } - backends = backends == endpoints ? new ArrayList<>(endpoints) : backends; - backends.remove(backend); - } while (!backends.isEmpty()); - return null; - }); + List candidates = preferSticky(target); + if (candidates != null && !candidates.isEmpty()) { + target.setEndpoints(candidates); + } else { + LoadBalancer loadBalancer = getLoadBalancer(invocation); + target.choose(endpoints -> { + List backends = endpoints; + do { + Endpoint backend = loadBalancer.choose(backends, invocation); + if (backend == null) { + return null; + } + if (backend.predicate()) { + return Collections.singletonList(backend); + } + backends = backends == endpoints ? new ArrayList<>(endpoints) : backends; + backends.remove(backend); + } while (!backends.isEmpty()); + return null; + }); + } } T request = invocation.getRequest(); @@ -91,6 +96,31 @@ public void filter(OutboundInvocation chain.filter(invocation); } + /** + * Attempts to select an endpoint based on a sticky identifier from the provided route target. + *

+ * This method retrieves a preferred sticky identifier from the request context and attempts + * to find an endpoint from the route target that matches this identifier. If a matching + * endpoint is found and it satisfies any associated predicate, it is returned. Otherwise, + * this method returns {@code null}, indicating no suitable sticky endpoint was found. + *

+ * + * @param target The {@link RouteTarget} containing potential endpoints to stick to. + * @return A list containing the matched endpoint if one is found and satisfies the predicate; + * otherwise, {@code null}. + */ + private List preferSticky(RouteTarget target) { + // preferred sticky id + String id = RequestContext.removeAttribute(Request.KEY_STICKY_ID); + if (id != null) { + List backends = RouteTarget.filter(target.getEndpoints(), endpoint -> id.equals(endpoint.getId()), 1); + if (!backends.isEmpty() && backends.get(0).predicate()) { + return backends; + } + } + return null; + } + /** * Retrieves the appropriate load balancer based on the service policy of the current invocation. * diff --git a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/invoke/filter/route/StickyFilter.java b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/invoke/filter/route/StickyFilter.java index e42303ea..5cc4356f 100644 --- a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/invoke/filter/route/StickyFilter.java +++ b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/invoke/filter/route/StickyFilter.java @@ -23,6 +23,9 @@ import com.jd.live.agent.governance.invoke.RouteTarget; import com.jd.live.agent.governance.invoke.filter.RouteFilter; import com.jd.live.agent.governance.invoke.filter.RouteFilterChain; +import com.jd.live.agent.governance.policy.service.ServicePolicy; +import com.jd.live.agent.governance.policy.service.loadbalance.LoadBalancePolicy; +import com.jd.live.agent.governance.policy.service.loadbalance.StickyType; import com.jd.live.agent.governance.request.Request; import com.jd.live.agent.governance.request.ServiceRequest.OutboundRequest; @@ -38,15 +41,27 @@ public class StickyFilter implements RouteFilter { @Override public void filter(OutboundInvocation invocation, RouteFilterChain chain) { - RouteTarget target = invocation.getRouteTarget(); - // Get the sticky ID from the request, if available - String id = invocation.getRequest().getStickyId(); - // first remove sticky id from context - String ctxId = RequestContext.removeAttribute(Request.KEY_STICKY_ID); - final String stickyId = id != null && !id.isEmpty() ? id : ctxId; - // If a sticky ID is available, filter the targets to only include the one with the sticky ID - if (stickyId != null && !stickyId.isEmpty()) { - target.filter(endpoint -> stickyId.equals(endpoint.getId()), 1); + ServicePolicy servicePolicy = invocation.getServiceMetadata().getServicePolicy(); + LoadBalancePolicy loadBalancePolicy = servicePolicy == null ? null : servicePolicy.getLoadBalancePolicy(); + StickyType stickyType = loadBalancePolicy == null ? StickyType.NONE : loadBalancePolicy.getStickyType(); + stickyType = stickyType == null ? StickyType.NONE : stickyType; + if (stickyType != StickyType.NONE) { + RouteTarget target = invocation.getRouteTarget(); + // Get the sticky ID from the request, if available + String id = invocation.getRequest().getStickyId(); + // first remove sticky id from context + String ctxId = RequestContext.removeAttribute(Request.KEY_STICKY_ID); + final String stickyId = id != null && !id.isEmpty() ? id : ctxId; + // If a sticky ID is available, filter the targets to only include the one with the sticky ID + if (stickyId != null && !stickyId.isEmpty()) { + if (stickyType == StickyType.FIXED) { + target.filter(endpoint -> stickyId.equals(endpoint.getId()), 1); + } else { + RequestContext.setAttribute(Request.KEY_STICKY_ID, stickyId); + } + } + } else { + RequestContext.removeAttribute(Request.KEY_STICKY_ID); } chain.filter(invocation); } diff --git a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/invoke/filter/route/VirtualFilter.java b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/invoke/filter/route/VirtualFilter.java index fef33230..5c7fbbff 100644 --- a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/invoke/filter/route/VirtualFilter.java +++ b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/invoke/filter/route/VirtualFilter.java @@ -53,7 +53,7 @@ public class VirtualFilter implements RouteFilter { @Override public void filter(OutboundInvocation invocation, RouteFilterChain chain) { List instances = invocation.getInstances(); - if (instances != null && size > 0 && instances.size() < size) { + if (size > 0 && instances != null && !instances.isEmpty() && instances.size() < size) { List result = new ArrayList<>(size); result.addAll(instances); int remain = size - instances.size(); diff --git a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/policy/service/loadbalance/LoadBalancePolicy.java b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/policy/service/loadbalance/LoadBalancePolicy.java index bc647c42..612b7cfc 100644 --- a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/policy/service/loadbalance/LoadBalancePolicy.java +++ b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/policy/service/loadbalance/LoadBalancePolicy.java @@ -55,6 +55,11 @@ public class LoadBalancePolicy implements PolicyInheritWithId */ private String policyType; + /** + * sticky type + */ + private StickyType stickyType = StickyType.NONE; + /** * Constructs a new, empty {@code LoadBalancePolicy}. */ diff --git a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/policy/service/loadbalance/StickyType.java b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/policy/service/loadbalance/StickyType.java new file mode 100644 index 00000000..e4f32219 --- /dev/null +++ b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/policy/service/loadbalance/StickyType.java @@ -0,0 +1,25 @@ +package com.jd.live.agent.governance.policy.service.loadbalance; + +/** + * Defines the stickiness type for load balancing or session handling. + */ +public enum StickyType { + + /** + * No stickiness. Each request may be handled by any available provider + * without preference. + */ + NONE, + + /** + * Preferred stickiness. Requests prefer to be handled by a previously + * used provider but may fall back to others if necessary. + */ + PREFERRED, + + /** + * Fixed stickiness. Requests are strictly bound to a specific provider + * once they have used it for the first time. + */ + FIXED +}