自定义负载均衡器

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
EMR Serverless StarRocks,5000CU*H 48000GB*H
网络型负载均衡 NLB,每月750个小时 15LCU
简介: 自定义负载均衡器

1. 模拟调用一个服务的多个实例



我们现在有两个服务, 一个getway服务, 另一个是order服务. 使用的是nacos管理配置文件和服务注册中心


假如我现在product服务要调用order服务. product服务有1台, order服务有3台. 那么是如何实现负载均衡的呢?


下面我们来模拟一下负载均衡的实现.

package com.lxl.www.gateway.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
public class UserController {
    @Autowired
    private DiscoveryClient discoveryClient;
    @Autowired
    private RestTemplate restTemplate;
    @GetMapping("get/order")
    public String getOrder() {
        // 获取要调用的服务实例列表
        List<ServiceInstance> userServices = discoveryClient.getInstances("order");
        if (null == userServices || 0 == userServices.size()) {
            return "用户微服务没有对应的实例可用";
        }
        // 获取列表第一个服务实例---这里可以设置一个负载均衡算法,轮询,随机等
        String targetUri = userServices.get(0).getUri().toString();
        // 发送请求到第一个服务实例
        String forObject = restTemplate.getForObject(targetUri + "/config", String.class);
        System.out.println(forObject);
        return forObject;
    }
}

这里服务注册发现使用的是谁呢? 使用的是nacos, nacos提供了自己的open api. 也封装了接口. 通过DiscoveryClient就可以调用接口

1187916-20200709165035716-1313813116.png

验证启动效果


启动一个getway服务, 端口号是  8080

启动3台order服务. 8081, 8082, 8083


启动order服务的时候有一个技巧, 之前都是使用的动态端口号, 这次使用另一种方式, 更简单


1187916-20200709165825801-483898324.png


打开配置->选择要启动的Application(这里选择的是order服务)->勾选右上角的Share共享, 就可以给这个应用启动多个客户端了. 注意端口不能一样哈.

 1187916-20200709170006771-509662289.png

如上图, 看到启动按钮下标有个3么?表示当前服务启动了3台


下面来看看nacos的服务注册情况

1187916-20200709170131282-432861436.png

我们看到, 一个由两个服务, 一个是order, 共有3个实例; 另一个是gateway


order三台实例的具体详情如下: 端口号分别是8081,8082,8083


接下来,我们访问gateway的接口. 在接口里面模拟调用order服务的实例, 请求的是获取的第一个服务实例


http://localhost:8080/get/order


发送了五次请求,流量全部打到了第二个服务实例上


1187916-20200709170925942-26752725.png

总结: 以上是没有使用任何组件,  我们纯手工自己写了一个实现服务调用的方法. 这个还是比较简单的. 真实环境肯定不用我们自己写, 因为有现成的组件. 这个组件就Ribbon


2. 让RestTemplate实现自动实现负载均衡



上面这个方法的简单模拟了如何在一个服务的多个实例中完成调用.  那么最终使用的是RestTemplate. 那么接下来我们来看一看RestTemplate的源码

image.png


我们看到在RestTemplate中有各种各样的方法调用, get, post ,put,delete等等. 他们最终调用的是this.execute(....)方法, 那么我们来看看this.execute(...)方法的实现.

我们发现在execute(...)方法里直接调用了doExecute(...)


image.png


最终实现跳转的url是在doExecute方法里. 而我们可以在这里对url进行一个包装. 如何包装了, 前端传过来的是服务名, 我根据服务名查询对应的服务列表. 然后通过负载均衡算法, 确定要定位的服务器.


我们可以来重写一下RestTemplate方法

package com.lxl.www.order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.ResponseExtractor;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
@Slf4j
@Component
public class LxlRestTemplate extends RestTemplate {
    @Autowired
    private DiscoveryClient discoveryClient;
    @Override
    protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
        // 替换url
        try {
            url = replaceUrl(url);
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
        return super.doExecute(url, method, requestCallback, responseExtractor);
    }
    private URI replaceUrl(URI url) throws URISyntaxException {
        // 通过URI获取服务名
        String serviceName = url.getHost();
        log.info("调用的服务名是:{}", serviceName);
        // 获取请求路径path
        String path = url.getPath();
        log.info("[调用的服务路径是:{}]", path);
        // 调用nacos查找服务实例列表
        List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
        String targetHost = instances.get(0).getUri().toString();
        String source = targetHost + path;
        return new URI(source);
    }
}

第一: LxlRestTemplate继承自RestTemplate


第二: 重写了doExecute方法. 在里面重新包装了url, 根据服务名找到对应的服务实例列表, 然后选择一台服务器, 重新构建一个新的URI,


第三: 调用父类方法doExecute();


接下来使用我们自定义的RestTemplate

1187916-20200709183109633-1993023427.png

这样就实现了根据服务名+负载均衡策略 定向到指定服务了

 

后面要学习的ribbon最终也是通过RestTemplate调用的远程服务. 其原理和这个是类似, 但功能实现要比这个复杂得多.

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
负载均衡 算法 Java
So easy! 教你实现自定义负载均衡策略!
So easy! 教你实现自定义负载均衡策略!
882 0
|
4月前
|
负载均衡 算法 应用服务中间件
nginx自定义负载均衡及根据cpu运行自定义负载均衡
nginx自定义负载均衡及根据cpu运行自定义负载均衡
85 1
|
5月前
|
负载均衡 算法 Nacos
SpringCloud之LoadBalancer自定义负载均衡算法,基于nacos权重
ReactorLoadBalancer接口,实现自定义负载算法需要实现该接口,并实现choose逻辑,选取对应的节点。
446 0
|
6月前
|
负载均衡 算法 Java
Ribbon自定义负载均衡算法
Ribbon自定义负载均衡算法
56 1
|
负载均衡 Java 索引
Spring Cloud:自定义 Ribbon 负载均衡策略
Spring Cloud:自定义 Ribbon 负载均衡策略
|
负载均衡 算法 Nacos
Ribbon自定义负载均衡策略
如何Ribbon自定义负载均衡策略
102 0
|
负载均衡 算法 Java
Ribbon 的负载均衡策略和自定义负载均衡
Ribbon 的负载均衡策略和自定义负载均衡
253 0
|
负载均衡 Java Spring
如果想自定义负载均衡策略如何实现 ?
如果想自定义负载均衡策略,可以通过继承Ribbon提供的IRule接口,并实现自定义的负载均衡策略。
217 0
|
负载均衡 算法 容灾
Spring Cloud Alibaba - 11 Ribbon 自定义负载均衡策略(同集群优先权重负载均衡算法)
Spring Cloud Alibaba - 11 Ribbon 自定义负载均衡策略(同集群优先权重负载均衡算法)
195 0
|
负载均衡 算法 Java
Spring Cloud Alibaba - 10 Ribbon 自定义负载均衡策略(权重算法)
Spring Cloud Alibaba - 10 Ribbon 自定义负载均衡策略(权重算法)
255 0