多机部署,负载均衡-LoadBalance

多机部署,负载均衡-LoadBalance

1. 开启多个服务

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

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

调用的还是同一个

我们对代码进行修改:

@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注解

@Configuration
public class BeanConfig {
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
修改IP,端口号为服务名称
@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

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配置类上方,就可以实现对不同的服务提供方配置不同的客户端负载均衡算法策略
@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


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方法

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);
}
}
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();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值