项目背景:目前使用的springcloud微服务架构,开发人员本地联调过程中,会用到许多并非自己开发的微服务支持。但是这样就需要启动多个应用,严重影响开发效率。现在架构组讨论写一个feign重负载,可以指定一次请求负载到具体ip。
大致想法:重写feign的负载均衡客户端LoadBalancerFeignClient,每次请求会执行excute方法,在excute方法中获取指定ip,替换feign已经负载好的ip。
遇到问题:配置类没加getset方法,导致无法读取配置文件;DiscoveryClient获取服务列表用getApplication方法,之前用的getInstance获取不到;版本问题,10.7.4feign-core版本拿到的url是ip,需要将ip替换为applicationname,否则调用多个feignclient会报错,具体原因还没扒出来。
上代码:
?重写的负载客户端:
@Slf4j
public class FeignReBalancer extends LoadBalancerFeignClient {
private Client delegate;
private CachingSpringLoadBalancerFactory lbClientFactory;
private SpringClientFactory clientFactory;
private ReBalancerProperties reBalancerProperties;
public FeignReBalancer(Client delegate,
CachingSpringLoadBalancerFactory lbClientFactory,
SpringClientFactory clientFactory, ReBalancerProperties reBalancerProperties, DiscoveryClient discoveryClient){
super(delegate, lbClientFactory, clientFactory);
this.delegate = delegate;
this.lbClientFactory = lbClientFactory;
this.clientFactory = clientFactory;
this.reBalancerProperties = reBalancerProperties;
}
@Override
public Response execute(Request request, Request.Options options) throws IOException {
log.info("feign开始负载均衡...");
//重新负载后的url
String balanceUrl = null;
//原始url
String url = request.url();
//应用名
String applicationName = request.requestTemplate().feignTarget().name();
balanceUrl = newURL(url,applicationName);
URI uri = URI.create(url);
//应用ip
String clientName = uri.getHost();
int port = uri.getPort();
//发起请求客户端的IP
String requestIp = PubFun.threadLocal.get()==null?null:PubFun.threadLocal.get().getClientIp();
//获取注册中心的所有服务ip
DiscoveryClient discoveryClient = SpringContextUtils.getBeanByClass(DiscoveryClient.class);
Application application = discoveryClient.getApplication(applicationName.toUpperCase());
List<InstanceInfo> instances = application.getInstances();
//如果ip没有注册,则走默认
if(!instances.stream().anyMatch(instance -> instance.getHostName().equals(requestIp))){
if(reBalancerProperties.servers != null &&
reBalancerProperties.servers.keySet().stream().anyMatch(key -> key.equals(applicationName))){
clientName = reBalancerProperties.servers.get(applicationName);
}else{
}
}else{
//ip在注册中心,则替换为传入的ip
clientName = requestIp;
}
//设置负载服务
List<Server> allServers = this.clientFactory.getLoadBalancer(clientName).getAllServers();
String finalHost = clientName;
//如果不存在ip的负载服务,则设置,存在不需要设置
if(!allServers.stream().anyMatch(server -> finalHost.equals(server.getHost()))){
this.clientFactory.getLoadBalancer(clientName).addServers(Arrays.asList(new Server(clientName, port),new Server(applicationName)));
}
Response response = this.getResponse(request, options, balanceUrl);
log.info("feign自定义负载至:"+clientName+":"+port+"完毕!");
return response;
}
private Response getResponse(Request request, Request.Options options, String newUrl) throws IOException {
//重新构建 request 对象
Request newRequest = Request.create(request.method(),
newUrl, request.headers(), request.body(),
request.charset());
return super.execute(newRequest, options);
}
/**
* 将ip替换
* @param url
* @param ipAddress
* @return
*/
private String newURL(String url,String ipAddress){
String[] split = url.split("/");
split[2] = ipAddress;
return Arrays.stream(split).reduce((s1, s2) -> s1+"/"+s2).get();
}
}
注入上面的负载客户端
@ConditionalOnProperty(prefix = ReBalancerProperties.prefix,name = "enable",havingValue = "true")
@Configuration
@EnableConfigurationProperties(value = {ReBalancerProperties.class})
public class ReBalancerConfiguration {
@Bean
public Client feignReBalancer(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory, @Autowired(required = false) DiscoveryClient discoveryClient, ReBalancerProperties reBalancerProperties) {
return new FeignReBalancer(new Client.Default(null, null),
cachingFactory, clientFactory, reBalancerProperties, discoveryClient);
}
}
配置类:
@ConfigurationProperties(prefix = ReBalancerProperties.prefix)
@Data
public class ReBalancerProperties {
static final String prefix = "rebalancer";
public Map<String,String> servers;
public String test;
}
补充:自己的负载client是在TraceFeignAspect切面类加载的
@Aspect
class TraceFeignAspect {
private static final Log log = LogFactory.getLog(TraceFeignAspect.class);
private final BeanFactory beanFactory;
TraceFeignAspect(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Around("execution (* feign.Client.*(..)) && !within(is(FinalType))")
public Object feignClientWasCalled(final ProceedingJoinPoint pjp) throws Throwable {
Object bean = pjp.getTarget();
Object wrappedBean = (new TraceFeignObjectWrapper(this.beanFactory)).wrap(bean);
if (log.isDebugEnabled()) {
log.debug("Executing feign client via TraceFeignAspect");
}
return bean != wrappedBean ? this.executeTraceFeignClient(bean, pjp) : pjp.proceed();
}
Object executeTraceFeignClient(Object bean, ProceedingJoinPoint pjp) throws IOException {
Object[] args = pjp.getArgs();
Request request = (Request)args[0];
Request.Options options = (Request.Options)args[1];
return ((Client)bean).execute(request, options);
}
}
|