springcloud + nacos實現共用基礎服務(灰度版本)
阿新 • • 發佈:2022-05-10
背景:
當我們使用微服務時,若想在本地聯調就需要啟動多個服務,為了避免本地啟動過多服務,現將註冊中心等基礎服務共用。當我們在服務A開發時,都是註冊到同一個nacos,這樣本地和開發環境的服務A就會同時存在,當呼叫服務時就會使用負載均衡選擇服務,導致我們無法正常除錯介面。這時我們可以選擇使用灰度版本來進行服務的選擇。
具體實現步驟如下:
1、我們在本地配置檔案中新增版本頭
這樣我們服務註冊到nacos中點選服務列表會發現服務中都會帶VERSION
spring: cloud: nacos: discovery: metadata: VERSION: zhangsan
2、新增灰度服務介面
public interface GrayLoadBalancer {
/**
* 根據serviceId 篩選可用服務
* @param serviceId 服務ID
* @param request 當前請求
* @return ServiceInstance
*/
ServiceInstance choose(String serviceId, ServerHttpRequest request);
}
3、灰度過濾器
import lombok.extern.slf4j.Slf4j; import org.apache.http.util.Asserts; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.DefaultResponse; import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools; import org.springframework.cloud.client.loadbalancer.Response; import org.springframework.cloud.gateway.config.LoadBalancerProperties; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter; import org.springframework.cloud.gateway.support.DelegatingServiceInstance; import org.springframework.cloud.gateway.support.NotFoundException; import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.net.URI; @Slf4j @Component public class GrayReactiveLoadBalancerClientFilter extends ReactiveLoadBalancerClientFilter { private final static String SCHEME = "lb"; private static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150; private final GrayLoadBalancer grayLoadBalancer; private final LoadBalancerProperties loadBalancerProperties; public GrayReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, LoadBalancerProperties loadBalancerProperties, GrayLoadBalancer grayLoadBalancer) { super(clientFactory, loadBalancerProperties); this.loadBalancerProperties = loadBalancerProperties; this.grayLoadBalancer = grayLoadBalancer; } @Override public int getOrder() { return LOAD_BALANCER_CLIENT_FILTER_ORDER; } @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { URI url = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR); String schemePrefix = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR); // 直接放行 if (url == null || (!SCHEME.equals(url.getScheme()) && !SCHEME.equals(schemePrefix))) { return chain.filter(exchange); } // 保留原始url ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url); if (log.isTraceEnabled()) { log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url); } return choose(exchange).doOnNext(response -> { if (!response.hasServer()) { throw NotFoundException.create(loadBalancerProperties.isUse404(), "Unable to find instance for " + url.getHost()); } URI uri = exchange.getRequest().getURI(); // if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default, // if the loadbalancer doesn't provide one. String overrideScheme = null; if (schemePrefix != null) { overrideScheme = url.getScheme(); } DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(response.getServer(), overrideScheme); URI requestUrl = LoadBalancerUriTools.reconstructURI(serviceInstance, uri); if (log.isTraceEnabled()) { log.trace("LoadBalancerClientFilter url chosen: " + requestUrl); } exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl); }).then(chain.filter(exchange)); } /** * 獲取例項 * @param exchange ServerWebExchange * @return ServiceInstance */ private Mono<Response<ServiceInstance>> choose(ServerWebExchange exchange) { URI uri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR); Asserts.notNull(uri, "uri"); ServiceInstance serviceInstance = grayLoadBalancer.choose(uri.getHost(), exchange.getRequest()); return Mono.just(new DefaultResponse(serviceInstance)); } }
4、基於客戶端版本號灰度路由
當我們呼叫服務帶版本號時會優先匹配帶版本號的服務,若找不到則會隨機選擇一個服務
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.gateway.support.NotFoundException; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import java.util.List; import java.util.stream.Collectors; @Slf4j @RequiredArgsConstructor @Component public class VersionGrayLoadBalancer implements GrayLoadBalancer { private final DiscoveryClient discoveryClient; /** * 根據serviceId 篩選可用服務 * @param serviceId 服務ID * @param request 當前請求 * @return ServiceInstance */ @Override public ServiceInstance choose(String serviceId, ServerHttpRequest request) { List<ServiceInstance> instances = discoveryClient.getInstances(serviceId); // 註冊中心無例項 丟擲異常 if (CollUtil.isEmpty(instances)) { log.warn("No instance available for {}", serviceId); throw new NotFoundException("No instance available for " + serviceId); } // 獲取請求version,無則隨機返回可用例項 String reqVersion = request.getHeaders().getFirst(CommonConstant.VERSION); if (StrUtil.isBlank(reqVersion)) { return instances.get(RandomUtil.randomInt(instances.size())); } // 遍歷可以例項元資料,若匹配則返回此例項 List<ServiceInstance> availableList = instances.stream() .filter(instance -> reqVersion .equalsIgnoreCase(MapUtil.getStr(instance.getMetadata(), CommonConstant.VERSION))) .collect(Collectors.toList()); if (CollUtil.isEmpty(availableList)) { return instances.get(RandomUtil.randomInt(instances.size())); } return availableList.get(RandomUtil.randomInt(availableList.size())); } }