服务之间相互调用,单独调试某一个服务会有一些不便,如果通过发布到测试环境来调试需要频繁部署且效率底下,那么如何高效调试微服务呢?

技巧一:修改应用名称

适用场景: 仅调试某个服务,只需启动这一个服务,无需启动其他服务

调用关系例如 serviceA -> serviceB
那么 serviceA-dev -> serviceB 也是可以调用到的,因为serviceA中FeignClient注解中的name属性依旧指向serviceB。这里将application.name = serviceA改为application.name = serviceA-dev 是因为 这两个应用名都可以从nacos中拿到dev分组下的serviceA-dev.yaml远程配置。另外,无需担心线上的服务调用到本地启动的服务,因为本地的服务名是serviceA-dev, 线上Feign调用时找的服务名是serviceA。

这样,调试serviceA时就不需要启动调用的其他服务了。

补充

即使应用名不是serviceA-dev(例如:serviceA-Tony)也可读到配置的方法如下,指定配置文件的前缀prefix。

spring:
  cloud:
    nacos:
      config:
        server-addr: 127.1.1.4:8848
        namespace: 3808c751-0e87-4819-a308-8c2efa7f3a2d
        file-extension: yaml
        prefix: xn-delivery  // 指定

技巧二:修改网关

适用场景: 前端通过网关访问某几个本地服务
缺点: 本地服务之间互相调用未访问本地服务,需要通过修改@FeignClient注解的服务名(name/value属性)或者指定本地服务地址(url属性)

有时候我们想本地与前端联调,需要本地启动网关,但是我们本地不可能把所有的服务都启动起来,这就需要当访问 gatewayhostname/serviceA时,转发到 serviceA-dev服务。所以我们需要自定义负载均衡策略。

实现较多,见本文末尾。

参考:
https://blog.csdn.net/m0_37954663/article/details/107102980

https://zhuanlan.zhihu.com/p/574044768

技巧三:自定义负载均衡策略

适用场景: 可插拔式(优雅)实现某几个本地服务之间互相调用

A需要调用其他本地服务如B,在A中给B服务配置本地优先负载策略。

### LocalFirstRule.java
// 本地服务优先 负载均衡规则
public class LocalFirstRule extends NacosRule {
    // 本地服务后缀
    public static final String LOCAL_SUFFIX = "-dev";

    private static final Logger LOGGER = LoggerFactory.getLogger(LocalFirstRule.class);
    public LocalFirstRule() {
    }
    @Resource
    private NacosDiscoveryProperties nacosDiscoveryProperties;
    @Resource
    private NacosServiceManager nacosServiceManager;

    public static final LinkedHashSet<String> LOCAL_IPV4S = NetUtil.localIpv4s();

    public Server choose(Object key) {
        try {
            String clusterName = this.nacosDiscoveryProperties.getClusterName();
            String group = this.nacosDiscoveryProperties.getGroup();
            DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer)this.getLoadBalancer();
            String name = loadBalancer.getName() + LOCAL_SUFFIX;
            NamingService namingService = this.nacosServiceManager.getNamingService(this.nacosDiscoveryProperties.getNacosProperties());
            List<Instance> instances = namingService.selectInstances(name, group, true);
            if (CollectionUtils.isEmpty(instances)) {
                LOGGER.warn("no instance in service {}", name);
                return null;
            } else {
                List<Instance> instancesToChoose = instances;
                if (StringUtils.isNotBlank(clusterName)) {
                    List<Instance> sameClusterInstances = (List)instances.stream().filter((instancex) -> {
                        return Objects.equals(clusterName, instancex.getClusterName());
                    }).collect(Collectors.toList());
                    if (!CollectionUtils.isEmpty(sameClusterInstances)) {
                        instancesToChoose = sameClusterInstances;
                    } else {
                        LOGGER.warn("A cross-cluster call occurs,name = {}, clusterName = {}, instance = {}", new Object[]{name, clusterName, instances});
                    }
                }

                // 选择本地IP的实例
                instancesToChoose = instancesToChoose.stream().filter((instancex) -> LOCAL_IPV4S.contains(instancex.getIp())).collect(Collectors.toList());
                if (CollectionUtils.isEmpty(instancesToChoose)) {
                    LOGGER.warn("local no instance in service {}", name);
                    return null;
                }

                Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToChoose);
                return new NacosServer(instance);
            }
        } catch (Exception var10) {
            LOGGER.warn("NacosRule error", var10);
            return null;
        }
    }
}
// 给某个服务配置本地优先的负载均衡规则
@RibbonClient(name = "contract", configuration = LocalFirstRule.class)
public class ContractRibbonConfig {
}

注意:如果你使用的是Spring Cloud 2020.0.x及之后的版本中,Ribbon已经被Spring Cloud LoadBalancer取代。要自定义负载均衡策略,您需要遵循Spring Cloud LoadBalancer的方式。

附录

技巧二的实现

/**
 * @author hmy
 * @date 2024年04月12日 10:59
 * @description A是为了文件排名靠前
 */
public class ARouteConfig {
    /**
     * 需要代理到本地的服务,逗号分隔
     */
    public static final String PROXY_SERVICES = "xx-quote,xx-contract";
       /**
     * 本地服务名后缀, 为了dev环境feign调用不到本地服务
     */
    public static final String LOCAL_SUFFIX = "-dev";
}
/**
 * @Description 将指定的服务路由到本地
 * @Author hmy
 * @Date 2024-01-02 17:57
 * @Version
 **/
@Configuration
public class GatewayConfig {

    @Bean
    public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client,
                                                             LoadBalancerProperties properties,
                                                             DiscoveryClient discoveryClient) {
            return new CustomLoadBalancerClientFilter(client, properties,discoveryClient);
    }

}
/**
 * @Description 自定义负载均衡
 * @Author hmy
 * @Date 2024-01-02 10:36
 * @Version
 **/
public class CustomLoadBalancerClientFilter extends LoadBalancerClientFilter implements BeanPostProcessor {

    private final DiscoveryClient discoveryClient;

    private final List<IChooseRule> chooseRules;

    public CustomLoadBalancerClientFilter(LoadBalancerClient loadBalancer,
                                          LoadBalancerProperties properties,
                                          DiscoveryClient discoveryClient) {
        super(loadBalancer, properties);
        this.discoveryClient = discoveryClient;
        this.chooseRules = new ArrayList<>();
        chooseRules.add(new CustomChooseRule());
    }

    @Override
    protected ServiceInstance choose(ServerWebExchange exchange) {
        System.out.println("----------------CustomLoadBalancerClientFilter-------------");
        if(!CollectionUtils.isEmpty(chooseRules)){
            Iterator<IChooseRule> iChooseRuleIterator = chooseRules.iterator();
            while (iChooseRuleIterator.hasNext()){
                IChooseRule chooseRule = iChooseRuleIterator.next();
                ServiceInstance choose = chooseRule.choose(exchange,discoveryClient);
                if(choose != null){
                    return choose;
                } else {
                    System.out.println("----------------choose == null-------------");
                }
            }
        }
        // 上文是自定义负载均衡规则,如果没找到可用的服务,使用默认的负载均衡规则
        String host = ((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost();
        return loadBalancer.choose(host);
    }
}
/**
 * @Description 自定义选择实例规则
 * @Author hmy
 * @Date 2024-01-02 11:03
 * @Version
 **/
public interface IChooseRule {

    /**
     * 返回null那么使用gateway默认的负载均衡策略
     * @param exchange
     * @param discoveryClient
     * @return
     */
    ServiceInstance choose(ServerWebExchange exchange, DiscoveryClient discoveryClient);

}
/**
 * @Description  微服务负载均衡策略
 * @Author hmy
 * @Date 2024-01-02 11:10
 * @Version
 **/
public class CustomChooseRule implements IChooseRule {
    public static final LinkedHashSet<String> LOCAL_IPV4S = NetUtil.localIpv4s();

    @Override
    public ServiceInstance choose(ServerWebExchange exchange, DiscoveryClient discoveryClient) {
        // 需要代理的服务名
        Stream<String> proxyServices = Stream.of(ARouteConfig.PROXY_SERVICES.split(StrUtil.COMMA));

        URI originalUrl = (URI) exchange.getAttributes().get(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        String instancesId = originalUrl.getHost();

        if(proxyServices.anyMatch(instancesId::startsWith)){
            // 获取所有实例
            List<ServiceInstance> instances = discoveryClient.getInstances(instancesId.endsWith(ARouteConfig.LOCAL_SUFFIX) ? instancesId : instancesId + ARouteConfig.LOCAL_SUFFIX);
            
            // 挑选本地服务
            NacosServiceInstance serviceInstance = (NacosServiceInstance) instances.stream()
                .filter(instance -> LOCAL_IPV4S.stream().filter(s -> !NetUtil.LOCAL_IP.equals(s)).anyMatch(instance.getHost()::equals))
                .findFirst().orElse(null);
            return serviceInstance;
           
        }

        return null;
    }
}