Spring微服务中Sticky Session负载均衡实现求助
实现Spring Cloud Gateway + Loadbalancer的Sticky Session方案
以下是适配你当前Eureka服务发现环境的Sticky Session实现方案,基于Spring Cloud Loadbalancer扩展轮询规则实现:
一、基于Cookie的粘性会话配置
这种方式通过在客户端Cookie中记录已绑定的服务实例ID,后续请求优先路由到该实例。
1. 自定义负载均衡规则类
import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.web.server.ServerWebExchange; import java.util.List; import java.util.Optional; @Configuration public class StickySessionLoadBalancerConfig { // 自定义粘性会话Cookie名称 public static final String STICKY_SESSION_COOKIE = "STICKY_INSTANCE_ID"; @Bean public RoundRobinLoadBalancer reactorServiceInstanceLoadBalancer( LoadBalancerClientFactory loadBalancerClientFactory) { return new StickySessionRoundRobinLoadBalancer( loadBalancerClientFactory.getLazyProvider("service-provider", ServiceInstanceListSupplier.class)); } static class StickySessionRoundRobinLoadBalancer extends RoundRobinLoadBalancer { private final ServiceInstanceListSupplier serviceInstanceListSupplier; public StickySessionRoundRobinLoadBalancer(ServiceInstanceListSupplier serviceInstanceListSupplier) { super(serviceInstanceListSupplier); this.serviceInstanceListSupplier = serviceInstanceListSupplier; } @Override protected Optional<ServiceInstance> chooseInstance(List<ServiceInstance> instances) { // 从请求上下文获取当前请求的Exchange ServerWebExchange exchange = (ServerWebExchange) org.springframework.web.context.request.RequestContextHolder.getRequestAttributes(); if (exchange == null) { // 无上下文时用默认轮询 return super.chooseInstance(instances); } ServerHttpRequest request = exchange.getRequest(); // 读取Cookie中的实例ID String instanceId = request.getCookies().getFirst(STICKY_SESSION_COOKIE); if (instanceId != null) { // 查找匹配的存活实例 Optional<ServiceInstance> stickyInstance = instances.stream() .filter(instance -> instance.getInstanceId().equals(instanceId)) .findFirst(); if (stickyInstance.isPresent()) { return stickyInstance; } } // 无匹配实例时,用轮询选择新实例并设置Cookie到响应 Optional<ServiceInstance> chosenInstance = super.chooseInstance(instances); chosenInstance.ifPresent(instance -> { exchange.getResponse().addCookie( org.springframework.http.HttpCookie.from(STICKY_SESSION_COOKIE, instance.getInstanceId()) .path("/") .build() ); }); return chosenInstance; } } }
2. 绑定配置到目标服务
在application.yml中指定目标服务使用上述自定义负载均衡规则:
spring: cloud: loadbalancer: clients: service-provider: # 替换为你的Eureka服务ID configuration: com.yourpackage.StickySessionLoadBalancerConfig # 替换为你的配置类全路径
二、基于请求头的粘性会话配置
若不想使用Cookie,可改用自定义请求头(如X-Sticky-Instance)实现,只需修改上述规则类的实例选择逻辑:
@Override protected Optional<ServiceInstance> chooseInstance(List<ServiceInstance> instances) { ServerWebExchange exchange = (ServerWebExchange) org.springframework.web.context.request.RequestContextHolder.getRequestAttributes(); if (exchange == null) { return super.chooseInstance(instances); } ServerHttpRequest request = exchange.getRequest(); // 读取请求头中的实例ID String instanceId = request.getHeaders().getFirst("X-Sticky-Instance"); if (instanceId != null) { Optional<ServiceInstance> stickyInstance = instances.stream() .filter(instance -> instance.getInstanceId().equals(instanceId)) .findFirst(); if (stickyInstance.isPresent()) { return stickyInstance; } } // 选择新实例后,在响应头中返回实例ID Optional<ServiceInstance> chosenInstance = super.chooseInstance(instances); chosenInstance.ifPresent(instance -> { exchange.getResponse().getHeaders().add("X-Sticky-Instance", instance.getInstanceId()); }); return chosenInstance; }
三、关键注意事项
- 确保Eureka中服务实例的
instanceId唯一(默认生成规则为主机名:服务ID:端口,可通过eureka.instance.instance-id自定义) - 当绑定的实例下线时,负载均衡会自动切换到其他存活实例,后续请求会重新绑定新实例
- Gateway路由配置需保留
lb://前缀,确保请求通过Loadbalancer转发:
spring: cloud: gateway: routes: - id: service-provider-route uri: lb://service-provider predicates: - Path=/api/**
内容的提问来源于stack exchange,提问作者TheCoder
相关产品推荐
相关产品推荐

