多机部署,负载均衡-LoadBalance

文章目录

多机部署,负载均衡-LoadBalance

1. 开启多个服务

多复制两个服务,将服务全部启动起来

此时多次访问原来的接口,发现

调用的还是同一个

我们对代码进行修改:

java 复制代码
@Service
public class OrderService {
    private static final Logger log = LoggerFactory.getLogger(OrderService.class);
    @Autowired
    private OrderMapper orderMapper;

    @Resource
    private DiscoveryClient discoveryClient;
    @Autowired
    private RestTemplate restTemplate;



    private static AtomicInteger atomicInteger = new AtomicInteger(1);
    private static List<ServiceInstance> instances;
    @PostConstruct
    public void init() {
        instances = discoveryClient.getInstances("product-service");
    }
    public  OrderInfo getOrderList(int orderId){
        OrderInfo orderInfo = orderMapper.getOrderList(orderId);
        int index = atomicInteger.getAndIncrement() % instances.size();
        EurekaServiceInstance instance = (EurekaServiceInstance) instances.get(index);
        String url = instance.getUri() + "/product/getProduct?productId=" + orderInfo.getProductId();
        log.info(url);
        ProductInfo productInfo = restTemplate.getForObject(url,ProductInfo.class);
        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }
}

通过轮询的方式去将请求均衡的分配到不同的实例上,这就是负载均衡

2. 什么是负载均衡

负载均衡是高并发,高可用系统必不可少的关键组件

当服务流量增大的时候,通常会采用增加机器的方式进行扩容,负载均衡就是用来在多个机器或者其他资源中,按照一定的规则合理分配负载,也就是负载均衡策略

负载均衡的实现

负载均衡分为服务端负载均衡和客户端负载均衡

服务端负载均衡

指的是在服务端进行负载均衡的算法分配

例如Nginx就是比较出名的服务端负载均衡器,请求会先到达Nginx负载均衡器,然后通过负载均衡算法,在多个服务器之间选择一个进行访问

客户端负载均衡

在客户端进行负载均衡的算法分配

就是将负载均衡的功能以库的方式集成到客户端,而不是由一台指定的负载均衡设备集中提供

比如Spring Cloud的Ribbon,请求发送到客户端,客户端从注册中心(比如Eureka)获取服务列表,在发送请求的时候通过负载均衡算法一个服务器,然后进行访问

但是Ribbon是Spring Cloud早期的默认实现,由于不维护了,现在最新版本的Spring Cloud 负载均衡集成的是Spring Cloud LoadBalance(Spring Cloud官方维护)

客户端负载均衡器和服务端负载均衡器的最大区别在于服务清单存储的位置

3. Spring Cloud LoadBalance

快速上手

使用Spring Cloud LoadBalance实现负载均衡

给RestTemplate这个Bean 添加 @LoadBalance注解

java 复制代码
@Configuration
public class BeanConfig {
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
修改IP,端口号为服务名称
java 复制代码
@Service
public class OrderService {
    private static final Logger log = LoggerFactory.getLogger(OrderService.class);
    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private RestTemplate restTemplate;

    public  OrderInfo getOrderList(int orderId){
        OrderInfo orderInfo = orderMapper.getOrderList(orderId);
        String url = "http://product-service/product/getProduct?productId=" + orderInfo.getProductId();
        log.info(url);
        ProductInfo productInfo = restTemplate.getForObject(url,ProductInfo.class);
        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }
}
启动多个服务

进行6次访问

为了方便观察,我们在product-service里面加上日志

此时进行访问

可以看到请求被均匀的分布在3个实例上

负载均衡策略

负载均衡策略是一种思想,无论是哪种负载均衡,他们的负载均衡策略都是相似的.Spring Cloud LoadBalance仅支持两种负载均衡策略:轮询策略以及随机策略

  1. 轮询:轮询策略指的是服务器轮流处理用户的请求.这是一种实现最简单,也最常用的策略
  2. 随机选择:随机选择策略是指随机选择一个后端服务器来处理新的请求
自定义负载均衡策略

Spring Cloud LoadBalance默认的负载均衡策略是轮询策略.实现是RoundRobinLoadBalance

如果想使用随机的负载均衡策略:

  1. 定义随机算法对象,通过@Bean将其加载到Spring容器里面

这里我们使用Spring Cloud LoadBalancer提供的RandomLoadBalance

java 复制代码
public class LoadBalanceConfig {
    @Bean
    ReactorLoadBalancer<ServiceInstance> randomLoadBalance(Environment env, LoadBalancerClientFactory clientFactory) {
        String name = env.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(clientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),name);
    }
}

注意:不用@Configuration注释,要求这个类在组件扫描范围内

  1. 使用@LoadBalancerClieent或者@LoadBalancerClients注解在RestTemplate配置类上方,就可以实现对不同的服务提供方配置不同的客户端负载均衡算法策略
java 复制代码
@LoadBalancerClient(name = "product-service",configuration = LoadBalanceConfig.class)
@Configuration
public class BeanConfig {
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

其中,name表示该负载均衡策略对哪个服务生效(服务提供方),Configuration表示负载均衡策略

LoadBalance原理

LoadBalance的实现,主要是LoadBalanceInterceptor,这个类会对RestTemplate的请求进行拦截,然后从Eureka根据服务id获取服务列表,然后利用负载均衡算法得到真实的服务地址信息,替换服务Id

java 复制代码
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
    private LoadBalancerClient loadBalancer;
    private LoadBalancerRequestFactory requestFactory;

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
        this.loadBalancer = loadBalancer;
        this.requestFactory = requestFactory;
    }

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
        this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
    }

    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
        URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
        Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
        return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
    }
}

主要就是看intercept方法,拦截了用户的请求后,做了以下几件事:

  1. request.getURI()从请求中获取uri
  2. originalUri.getHost()从uri中获取路径的主机名,也就是服务Id
  3. loadBalancer.execute根据服务Id,进行负载均衡,并处理请求

我们继续看execute方法

java 复制代码
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
String hint = this.getHint(serviceId);
LoadBalancerRequestAdapter<T, TimedRequestContext> lbRequest = new LoadBalancerRequestAdapter(request, this.buildRequestContext(request, hint));
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = this.getSupportedLifecycleProcessors(serviceId);
supportedLifecycleProcessors.forEach((lifecycle) -> {
    lifecycle.onStart(lbRequest);
});
// 根据serviceId,和负载均衡策略,选择处理的服务
ServiceInstance serviceInstance = this.choose(serviceId, lbRequest);
if (serviceInstance == null) {
    supportedLifecycleProcessors.forEach((lifecycle) -> {
        lifecycle.onComplete(new CompletionContext(Status.DISCARD, lbRequest, new EmptyResponse()));
    });
    throw new IllegalStateException("No instances available for " + serviceId);
} else {
    return this.execute(serviceId, serviceInstance, lbRequest);
}
}
java 复制代码
public <T> ServiceInstance choose(String serviceId, Request<T> request) {
    // 获取负载均衡器
    ReactiveLoadBalancer<ServiceInstance> loadBalancer = this.loadBalancerClientFactory.getInstance(serviceId);
    if (loadBalancer == null) {
        return null;
    } else {
        // 根据负载均衡算法,在列表中选择一个服务实例
        Response<ServiceInstance> loadBalancerResponse = (Response)Mono.from(loadBalancer.choose(request)).block();
        return loadBalancerResponse == null ? null : (ServiceInstance)loadBalancerResponse.getServer();
    }
}
相关推荐
Rain的Java大神之路4 小时前
如何快速上传10G文件
java·spring boot·redis·后端·mysql·spring cloud·面试
需要8267 小时前
MySQL 索引 B+ 树与“查询为什么变慢了
java·spring boot·spring·spring cloud·tomcat
需要82612 小时前
Arthas 一个命令排查线上问题
java·jvm·spring·spring cloud
苏渡苇1 天前
Spring Insight 里如何把 Span 画成瀑布时间线
后端·spring·spring cloud·springboot·监控·apm
叶总没有会1 天前
Nacos—注册中心
spring cloud·微服务
苏渡苇3 天前
Spring Insight 里如何把 Span 收成一条链路
spring boot·spring·spring cloud·系统监控·apm
叶总没有会3 天前
微服务--分布式基础
java·spring·spring cloud·微服务
孙启超3 天前
【AI开发之Rust】第 7 课:错误处理 —— panic、Result 与 `?`
人工智能·分布式·后端·爬虫·spring cloud·架构·rust
行百里er4 天前
Spring Insight 里如何把 Span 收成一条链路
spring boot·spring cloud·监控
砍材农夫5 天前
spring|spring web|拦截器、过滤器、aop
java·spring boot·spring·spring cloud