项目背景:目前使用的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); } }