(1). 需求
随着微服务的流行,对开发人员的要求也越来越高, 特别是当一个新入职的程序员,如何在开发环境给新手开发员更好的体验,而不是在开发环境上造成入职当天就离职呢?所以,这个需求是针对开发的环境的.
当微服务工程越来越多之后,开发人员能否如上图所示,只下载并开发它所要的工程,而工程所依赖的其它信息,都来自于一个稳定的环境呢?
(2). 开发步骤
- 一套稳定测试环境(包括:Eureka).
- 开发环境都依赖这套稳定测试环境(全都往这个Eureka过行注册).
- 开发下载工程,进行开发,那么开发,如何调试?如何对Request路由呢?.
- 在要路由的HTTP请求头上,打标记(例如:route=user-service/192.168.100.202:8080)
- 配置一个拦截器,拦截请求头上(route),并设置到ThreadLocal里.
- 对RibbonLoadBalancerClient进行扩展.当ThreadLocal里有标记(user-service/192.168.100.202:8080),并且微服务名称(user-service)也相同,则直接返回:192.168.100.202:8080.
- 要自定义一个:ClientHttpRequestInterceptor,让ThreadLocal里的信息(route),一直在整个请求链路上传递.
- 我在这里,只做一个引导,不把所有逻辑写出来.
(3). RibbonLoadBalancerClientProxy
package help.lixin.samples.ribbon;
import java.io.IOException;
import java.net.URI;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.RibbonServer;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.loadbalancer.Server;
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
public class RibbonLoadBalancerClientProxy implements LoadBalancerClient {
private RibbonLoadBalancerClient ribbonLoadBalancerClient;
public void setProxyTarget(RibbonLoadBalancerClient ribbonLoadBalancerClient) {
this.ribbonLoadBalancerClient = ribbonLoadBalancerClient;
}
public RibbonLoadBalancerClient getProxyTarget() {
return ribbonLoadBalancerClient;
}
public void setRibbonLoadBalancerClient(RibbonLoadBalancerClient ribbonLoadBalancerClient) {
this.ribbonLoadBalancerClient = ribbonLoadBalancerClient;
}
public RibbonLoadBalancerClient getRibbonLoadBalancerClient() {
return ribbonLoadBalancerClient;
}
@Override
public ServiceInstance choose(String serviceId) {
return ribbonLoadBalancerClient.choose(serviceId);
}
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
// TODO
// 1. 从ThreadLocal中获得用户定义的serviceId与IP:Port的关系.
// 2. serviceId进行比较相同的话,直接构建:RibbonServer
// 3. serviceId不同的话,直接放过.
InstanceInfo instanceInfo = InstanceInfo.Builder.newBuilder()
// serviceId
.setAppName("test-provider")
.build();
Server server = new DiscoveryEnabledServer(instanceInfo, false);
server.setHost("127.0.0.1");
server.setPort(8081);
server.setSchemea("http");
RibbonServer ribbonServer = new RibbonServer(serviceId, server, false, null);
return this.execute(serviceId, ribbonServer, request);
}
@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request)
throws IOException {
return ribbonLoadBalancerClient.execute(serviceId, serviceInstance, request);
}
@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
return ribbonLoadBalancerClient.reconstructURI(instance, original);
}
}
(4). RouteRequestInterceptor
package help.lixin.samples.ribbon;
import java.io.IOException;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
public class RouteRequestInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
// ... ...
// 从ThreadLocal获得route信息,并设置到:HttpRequest对象里
return execution.execute(request, body);
}
}
(5). RibbonLoadBalancerClientProxyConfig
package help.lixin.samples.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
import help.lixin.samples.ribbon.RibbonLoadBalancerClientProxy;
import help.lixin.samples.ribbon.RouteRequestInterceptor;
@Configuration
public class RibbonLoadBalancerClientProxyConfig implements ApplicationContextAware, InitializingBean {
private ApplicationContext applicationContext;
/**
* 对LoadBalancerClient进行扩展.
* @param springClientFactory
* @return
*/
@Bean
// @Primary
public LoadBalancerClient loadBalancerClient(SpringClientFactory springClientFactory) {
// 自定义逻辑的的:LoadBalancerClient
RibbonLoadBalancerClientProxy proxy = new RibbonLoadBalancerClientProxy();
// 要代理的目标对象
RibbonLoadBalancerClient target = new RibbonLoadBalancerClient(springClientFactory);
proxy.setProxyTarget(target);
return proxy;
}
/**
* 创建:RouteRequestInterceptor,把route信息继续往下链路中传递
* @return
*/
@Bean
public ClientHttpRequestInterceptor routeRequestInterceptor() {
return new RouteRequestInterceptor();
}
@Override
public void afterPropertiesSet() throws Exception {
// 获取:RestTemplate
RestTemplate restTemplate = applicationContext.getBean(RestTemplate.class);
if (null != restTemplate) {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
// 配置自定义的拦截器.
// 要在:
// LoadBalancerAutoConfiguration$LoadBalancerInterceptorConfig.restTemplateCustomizer
// 之前配置好自定义的拦截器.
list.add(routeRequestInterceptor());
restTemplate.setInterceptors(list);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
(6). 总结
RibbonLoadBalancerClientProxy可以代理你的业务逻辑,也可以直接放过,这样对开发比较透明.