前言
上一篇文章中我们通过原生态的方式实现了服务的发现和调用,但是在集群环境中,并没有达到负载均衡的目的,这一篇文章,我们将编写自己的负载均衡器,以实现服务调用的负载均衡功能。
自定义负载均衡
回顾
首先,我们再先来回顾一下上一篇文章中的代码:
@GetMapping("/hello")
public String hello(String name) {
List<ServiceInstance> list = discoveryClient.getInstances("provider");
ServiceInstance instance = list.get(0);
String host = instance.getHost();
int port = instance.getPort();
String returnInfo = restTemplate.getForObject("http://" + host + ":" + port + "/hello?name={1}", String.class, name);
return returnInfo;
}
我们先通过discoveryClient获取了注册中心的其中一个服务的ip和端口,然后使用restTemplate调用其getForObjec()方法进行接口的调用。
通过跟踪该方法的源码,我们发现最终都会调用其doExecute()方法,源码如下:
@Nullable
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
Assert.notNull(url, "URI is required");
Assert.notNull(method, "HttpMethod is required");
ClientHttpResponse response = null;
Object var14;
try {
ClientHttpRequest request = this.createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
this.handleResponse(url, method, response);
var14 = responseExtractor != null ? responseExtractor.extractData(response) : null;
} catch (IOException var12) {
String resource = url.toString();
String query = url.getRawQuery();
resource = query != null ? resource.substring(0, resource.indexOf(63)) : resource;
throw new ResourceAccessException("I/O error on " + method.name() + " request for \"" + resource + "\": " + var12.getMessage(), var12);
} finally {
if (response != null) {
response.close();
}
}
return var14;
}
那我们能不能继承RestTemplate类,并重写doExecute方法,在doExecute方法中调用createRequest之前对其做一下改变呢?
实现负载均衡
首先创建我们自己的RestTemplate,命名为MyRestTemplate.java,示例如下:
/**
* 自定义负载均衡器
* @Author:公众号:程序员965
* @create 2022-06-06
**/
public class MyRestTemplate extends RestTemplate {
private DiscoveryClient discoveryClient;
public MyRestTemplate (DiscoveryClient discoveryClient) {
this.discoveryClient = discoveryClient;
}
/**
* 接口调用
* @Author:公众号:程序员965
* @create 2022-06-06
**/
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
Assert.notNull(url, "URI is required");
Assert.notNull(method, "HttpMethod is required");
ClientHttpResponse response = null;
try {
System.out.println("替换前url :"+url.toString());
url = replaceUrl(url);
System.out.println("替换后url :"+url.toString());
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
handleResponse(url, method, response);
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
}
catch (IOException ex) {
String resource = url.toString();
String query = url.getRawQuery();
resource = (query != null ? resource.substring(0,resource.indexOf('?')) : resource);
throw new ResourceAccessException("I/O error on " + method.name() +
" request for \"" + resource + "\": " + ex.getMessage(), ex);
} finally {
if (response != null) {
response.close();
}
}
}
/**
* 替换url
* @Author:公众号:程序员965
* @create 2022-06-06
**/
private URI replaceUrl(URI url){
String sourceUrl = url.toString();
String [] httpUrl = sourceUrl.split("//");
int index = httpUrl[1].replaceFirst("/","@").indexOf("@");
String serviceName = httpUrl[1].substring(0,index);
List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances(serviceName);
//采取随机算法,获取其中一个服务信息
Random random = new Random();
Integer randomIndex = random.nextInt(serviceInstanceList.size());
String serviceIp = serviceInstanceList.get(randomIndex).getUri().toString();
String targetSource = httpUrl[1].replace(serviceName,serviceIp);
try {
return new URI(targetSource);
} catch (URISyntaxException e) {
e.printStackTrace();
}
return url;
}
}
在doExecute方法中,调用createRequest方法之前,我们增加了一行replaceUrl()方法调用,在该方法中,我们通过解析Url,获得服务名称,然后依然根据服务名称获取服务信息列表,并通过随机算法,返回服务信息列表中其中一个服务的uri并返回,实现了随机算法的负载均衡。
接口调用
回过头来,我们修改一下接口的调用点,首先restTemplate改为创建我们自定义的MyRestTemplate:
@Bean
RestTemplate restTemplate(DiscoveryClient discoveryClient) {
return new MyRestTemplate(discoveryClient);
}
然后修改接口,将接口地址直接改为用服务名称进行调用:
@GetMapping("/hello2")
public String hello2(String name) {
String returnInfo = restTemplate.getForObject( "http://provider/hello?name={1}", String.class, name);
return returnInfo;
}
启动调试
运行成功,正确返回了我们的结果!
这时候,可能有人会有疑问:直接在接口调用处随机获取一个ip和端口进行调用不就行了?为什么费劲扒源码然后继承RestTemplate类并改写其doExecute方法呢?不是多此一举吗?
小伙伴们?想想看,为什么要这么做呢?这个问题留到我们使用负载均衡组件的时候再讲吧。
总结
本文中,我们使用随机算法实现了负载均衡的功能,当然你也可以根据需要实现比如轮询,权重等各种各样的负载算法。你学会了吗?