09.25 SpringCloud下微服务多个实例,调试时只调用本机提供服务的方法

Spring cloud的微服务,针对一个特定的服务,可能会有多个提供者,这本身对于部署以后提高系统健壮性和扩展性很有好处,但是在调试期间,会给开发者带来一定的不便。

我们发布微服务后,一般都是通过feignClient来执行调用的,也许我们的系统有十几个或者几十个微服务,相应的调用客户端也有很多,微服务之间也有各种调用关系,不可避免会出现多个微服务依赖于一个特定微服务的情况。

比如,我们发布了一个用户及授权微服务,我们的登录模块需要使用这个微服务,用户及权限管理也要使用这个微服务,很有可能登录和权限管理是两个开发者在分别维护,这样他们在调试时会分别启动各自的授权微服务,关系如图:

SpringCloud下微服务多个实例,调试时只调用本机提供服务的方法

这样,在服务注册中心那里就会得到AuthorizationServer微服务的多个服务提供者,在调试时,因为feignClient的负载平衡通过Ribbon来实现,而Ribbon默认使用的是轮询策略(RoundRobinRule),它会轮流调用所有的可用服务,这会导致什么样的后果呢?就是调试人员A、B、C、D分别在调试过程中启动了AuthorizationServer服务,他们在调试时,只有1/4的机会会调用本机的服务,为了调试一个功能,需要重试多次才能轮到自己的调试服务环境,这确实会带来一定的问题,毕竟每个人的调试的功能不同,需要的测试数据很可能不同。为了解决这个问题,需要想办法让每次调用都只调用本机提供的服务。

那么,有办法实现这个功能吗?答案是有的,而且不太复杂;我们先讨论一下实现的思路:既然feignClient通过Ribbon找到需要的服务,那么我们就可以让Ribbon实现一种只调用本机的策略就可以了。具体一点就是,

  1. 通过接口获取到所有的可用服务
  2. 获取本机的ip地址
  3. 对比每一个可用服务的ip地址,如果和本机ip匹配,就是本机提供的服务了,这个服务就是我们要返回的服务。

Ribbon的规则通过继承类AbstractLoadBalancerRule来实现,我们也可以继承一下这个类,实现自己的规则类(具体实现见代码注释):

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
/**
* 只调用本机提供的服务
*/
public class CallLocalRule extends AbstractLoadBalancerRule {
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
if (lb == null) {
return null;
}
//选中的服务
Server choose = null;

//所有可用服务列表
List<server> allList = lb.getReachableServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
//本机ip
String ip = "";
try {
InetAddress addi = InetAddress.getLocalHost();
//获取本机ip
ip = addi.getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
//遍历可用服务,找到服务地址为本机ip的服务
for (Server server : allList) {

String host = server.getHost().toLowerCase();
if (host.equals(ip)) {
choose = server;
break;
}
}
//如果没有找到本机服务,取第一个可用服务
if (choose == null) {
choose = allList.get(0);
}
return choose;
}
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
}
/<server>

写好这个规则类后,就是在系统里具体使用了,使用起来也很简单,在调用微服务的配置文件(application.yml)里设置一下即可:

AuthorizationServer:
ribbon:
NFLoadBalancerRuleClassName: com.ggnykj.tools.loadbalancer.CallLocalRule

其中,AuthorizationServer是微服务的名称,com.ggnykj.tools.loadbalancer.CallLocalRule是实现调用本地规则的类名称。

上述方法可以灵活的对服务的调用进行处理,还可以有更多形式的应用,比如根据其它系统的配置来计算需要调用的服务,或者干脆实现一个自己的轮询、随机调用等。如果只是简单的指定本地调试的服务器或者指定的服务器,还可以通过对feignclient添加url注解来实现,比如:

@FeignClient(name="AuthorizationServer",url="http://localhost:9606")

public interface UserControllerClient {

但是这种实现本地调试的原理和本文讲解的原理不一样,本文是通过负载均衡服务来获取要调用的服务的,而通过配置url注解可以直接跳过负载均衡服务,直接根据该url构造要请求的服务地址,性能会更高一些,两者的具体实现代码在类FeignClientFactoryBean中:

@Override
public Object getObject() throws Exception {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}

其中:

 if (!StringUtils.hasText(this.url)) {

判断是否已经配置了url注解,如果没有就通过负载均衡服务来获取。

如果使用这种方式,使用起来最简单,就是需要修改源代码;使用本文的方式,需要增加配置,发布前别忘了删除这个配置,毕竟这个只是方便调试使用的,正式发布的时候还是使用其他合适的规则吧。


分享到:


相關文章: