微服务开发调试技巧
服务之间相互调用,单独调试某一个服务会有一些不便,如果通过发布到测试环境来调试需要频繁部署且效率底下,那么如何高效调试微服务呢?
技巧一:修改应用名称
适用场景: 仅调试某个服务,只需启动这一个服务,无需启动其他服务
调用关系例如 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;
}
}