在springCloud中,restTemplate在加上@LoadBalanced后,就有了负载均衡功能。想尝试下使用其他客户端例如webService,将url域名配置成服务名,是否能够实现LoadBalanced的效果。
了解@LoadBalanced基本实现
@LoadBalanced注解只是一个标记,生效的是其中的@Qualifier注解,同时在LoadBalancerAutoConfiguration 类中标注在List restTemplates,即可将所有标注了@LoadBalanced的RestTemplate收集到restTemplates中。在loadBalancedRestTemplateInitializerDeprecated方法中
将需要负载均衡的RestTemplate扔进RestTemplateCustomizer定制器中。RestTemplateCustomizer是由restTemplateCustomizer方法实例化,作用是在RestTemplate的拦截器中加入LoadBalancerInterceptor ,而ribbonInterceptor中有生成LoadBalancerInterceptor 实例。
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
········
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
········
}
在LoadBalancerInterceptor 实例中有intercept方法,通过上一步实例化时传入的loadBalancerClient调用了execute方法。
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
loadBalancerClient是一个接口,实例化实在RibbonAutoConfiguration中,可知具体实例是RibbonLoadBalancerClient。
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
在RibbonLoadBalancerClient中由具体的执行方法代码可知,调用getLoadBalancer方法通过从上一步传入的服务名得到一个ILoadBalancer 实例,然后通过ILoadBalancer 通过具体策略choose一个具体服务server 。
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
Server server = getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonServer ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
return execute(serviceId, ribbonServer, request);
}
由此可知,负载均衡最重要的地方就是通过loadBalancerClient的getLoadBalancer(serviceId)得到ILoadBalancer ,然后调用getServer(loadBalancer, hint)得到一个具体server。也就是得到一个负载均衡规则的服务。可是getLoadBalancer是一个protected 方法,具体内容是从clientFactory得到ILoadBalancer 。也就是直接通过SpringClientFactory就可以得到ILoadBalancer 。
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId);
}
因为SpringClientFactory是由spring容器管理的,由此可知,在代码中@Autowired就可以得到SpringClientFactory。
@Autowired
SpringClientFactory springClientFactory;
ILoadBalancer loadBalancer = springClientFactory.getLoadBalancer(name);
Server server = loadBalancer.chooseServer(null);
尝试修改webservice
在配置webService客户端时,接口地址是需要配置完整域名端口的,例如 String address = “http://localhost:8080/demo/api/”
具体webService服务和客户端配置先略过。debug一下webService客户端demoService,代理类是JaxWsClientProxy。
JaxWsClientProxy类图可知实现了BindingProvider,可从getRequestContext()得到请求地址等信息。
由源码可知JaxWsClientProxy的getRequestContext是从Client的getRequestContext()得到的。
到此可得一个简单的实现思路就是在请求时从IloadBalancer得到一个server,修改JaxWsClientProxy.getClient().getRequestContext()中的请求地址中的host就可以实现负载均衡。
public Map<String, Object> getRequestContext() {
if (client == null) {
throw new IllegalStateException("The client has been closed.");
}
return new WrappedMessageContext(this.getClient().getRequestContext(),
null,
Scope.APPLICATION);
}
测试
这时候可以简单测试一下,在配置客户端时将服务地址配置成 http://testWebService/demo/api/
测试代码:
@RestController
public class TestController {
@Autowired
DemoService demoService;
@Autowired
SpringClientFactory springClientFactory;
@GetMapping("test")
public String test() throws URISyntaxException {
Client client = ((JaxWsClientProxy) Proxy.getInvocationHandler(demoService)).getClient();
//获取配置时配置的地址
URI uri = new URI(client.getEndpoint().getEndpointInfo().getAddress());
String address=uri.toString();
String serverName=uri.getHost();
ILoadBalancer loadBalancer = springClientFactory.getLoadBalancer(serverName);
Server server = loadBalancer.chooseServer(null);
address=address.replace(name,server.getHostPort());
//ENDPOINT_ADDRESS就是请求的地址,将服务名换成具体ip端口后覆盖请求的地址。
client.getRequestContext().put("org.apache.cxf.message.Message.ENDPOINT_ADDRESS",address);
return demoService.test("test");
}
}
浏览器请求测试地址,得到成功返回,功能基本能跑通的哇,这时候就可以用aop等其他方法,将换host的代码隐式实现,在使用时就可以假装实现了loadBalanced功能的说。
@Aspect
@Component
public class WebServiceAop {
@Autowired
SpringClientFactory springClientFactory;
@Pointcut("execution(* demo.DemoService.*(..))")
public void pointcutAll() {
}
@Before(value = "pointcutAll()")
public void BeforeAop(JoinPoint jp) throws URISyntaxException {
Client client = ((JaxWsClientProxy) Proxy.getInvocationHandler(jp.getTarget())).getClient();
URI uri = new URI(client.getEndpoint().getEndpointInfo().getAddress());
String address=uri.toString();
String name=uri.getHost();
ILoadBalancer loadBalancer = springClientFactory.getLoadBalancer(name);
Server server = loadBalancer.chooseServer(null);
address=address.replace(name,server.getHostPort());
client.getRequestContext().put("org.apache.cxf.message.Message.ENDPOINT_ADDRESS",address);
}
}
在使用时就可以直接用
@GetMapping("test")
public String test() throws URISyntaxException {
return demoService.test("test");
}
成功得到响应
博客地址:那个人好像一条狗啊