微服务实战|手把手教你开发负载均衡组件

上一篇 微服务实战|原生态实现服务的发现与调用

前言

上一篇文章中我们通过原生态的方式实现了服务的发现和调用,但是在集群环境中,并没有达到负载均衡的目的,这一篇文章,我们将编写自己的负载均衡器,以实现服务调用的负载均衡功能。

自定义负载均衡

回顾

首先,我们再先来回顾一下上一篇文章中的代码:

@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方法呢?不是多此一举吗?

小伙伴们?想想看,为什么要这么做呢?这个问题留到我们使用负载均衡组件的时候再讲吧。

总结

本文中,我们使用随机算法实现了负载均衡的功能,当然你也可以根据需要实现比如轮询,权重等各种各样的负载算法。你学会了吗?

猜你喜欢

转载自blog.csdn.net/A598853607/article/details/125286634