多机部署,负载均衡-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();
    }
}
相关推荐
咖啡不甜不好喝2 小时前
SpringCloud之OpenFeign
spring cloud·openfeign
黄俊懿6 小时前
【深入理解SpringCloud微服务】Spring-Security作用与原理解析
java·后端·安全·spring·spring cloud·微服务·架构师
叫致寒吧11 小时前
Dockerfile
java·spring cloud·eureka
悟空码字13 小时前
从零到一搭建SpringCloud微服务,一场代码世界的“分家”大戏
java·后端·spring cloud
黄俊懿14 小时前
【深入理解SpringCloud微服务】Gateway源码解析
java·后端·spring·spring cloud·微服务·gateway·架构师
刘个Java15 小时前
手搓遥控器通过上云api执行航线
java·redis·spring cloud·docker
没有bug.的程序员15 小时前
Ribbon vs LoadBalancer 深度解析
jvm·后端·spring cloud·微服务·ribbon·架构·gc调优
黄俊懿1 天前
【深入理解SpringCloud微服务】Seata(AT模式)源码解析——全局事务的回滚
java·后端·spring·spring cloud·微服务·架构·架构师
云老大TG:@yunlaoda3601 天前
华为云国际站代理商NAT的高可用与弹性具体是如何实现的?
服务器·数据库·华为云·负载均衡
码农小卡拉1 天前
Java多线程:CompletableFuture使用详解(超详细)
java·开发语言·spring boot·python·spring·spring cloud