尝试使其他客户端(例如webService)支持springCloud架构内的loadBalanced负载均衡功能

在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。

在调用接口时debug,可知服务代理类是JaxWsClientProxy
JaxWsClientProxy类图可知实现了BindingProvider,可从getRequestContext()得到请求地址等信息。
实现了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");
}

成功得到响应
在这里插入图片描述
博客地址:那个人好像一条狗啊

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值