目录
[2.Spring Cloud LoadBalancer](#2.Spring Cloud LoadBalancer)
承接上文服务注册/服务发现-Eureka
1.负载均衡介绍
1.1问题描述
观察上个章节远程调用的代码
java
List<ServiceInstance> instances = discoveryClient.getInstances("product-service");
//服务可能有多个, 获取第⼀个
EurekaServiceInstance instance = (EurekaServiceInstance) instances.get(0);
1.根据应用名称获取了服务实例列表
2.从列表中选择了一个服务实例
**思考:**如果一个服务对应多个实例呢?流量是否可以合理的分配到多个实例
现象观察:
我们再启动2个product-service实例
选中要启动的服务,右键选择Copy Configuration
在弹出的框中选择 Configuration -> VM options
添加VM options: -Dserver.port=9091
9091为服务启动的端口号,根据自己的情况进行修改
java
11:46:05.684+08:00 INFO 23128 --- [nio-8080-exec-1]
com.bite.order.service.OrderService : LUCF:product-service:9090
11:46:06.435+08:00 INFO 23128 --- [nio-8080-exec-2]
com.bite.order.service.OrderService : LUCF:product-service:9090
11:46:07.081+08:00 INFO 23128 --- [nio-8080-exec-3]
com.bite.order.service.OrderService : LUCF:product-service:9090
先启动Eureka后启动所有实例
观察Eureka,可以看到product-service下有三个实例
访问:http://127.0.0.1:8080/order/1
访问结果:
java
11:46:05.684+08:00 INFO 23128 --- [nio-8080-exec-1]
com.bite.order.service.OrderService : LUCF:product-service:9090
11:46:06.435+08:00 INFO 23128 --- [nio-8080-exec-2]
com.bite.order.service.OrderService : LUCF:product-service:9090
11:46:07.081+08:00 INFO 23128 --- [nio-8080-exec-3]
com.bite.order.service.OrderService : LUCF:product-service:9090
通过日志可以观察到,请求多次访问,都是同一台机器。
这肯定不是我们想要的结果,启动多个实例,是希望可以分担其他机器的负荷,那么如何实现呢?
解决方案:
我们可以对上述代码进行简单修改:
java
import com.example.orderservice.mapper.OrderMapper;
import com.example.orderservice.model.OrderInfo;
import com.example.orderservice.model.ProductInfo;
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.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
private static AtomicInteger atomicInteger = new AtomicInteger(1);
public OrderInfo selectOrderById(Integer orderId){
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
// String url = "http://127.0.0.1:9090/product/"+orderInfo.getProductId();
//从Eureka中获取服务列表
List<ServiceInstance> instances = discoveryClient.getInstances("product-service");
String uri = instances.get(atomicInteger.getAndIncrement() % instances.size()).getUri().toString();
String url = uri+"/product/"+orderInfo.getProductId();
log.info("远程调用url:{}", url);
ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
}
观察日志:
java
12:02:13.245+08:00 INFO 1800 --- [nio-8080-exec-1]
com.bite.order.service.OrderService : LUCF:product-service:9091
12:02:15.723+08:00 INFO 1800 --- [nio-8080-exec-2]
com.bite.order.service.OrderService : LUCF:product-service:9090
12:02:16.534+08:00 INFO 1800 --- [nio-8080-exec-3]
com.bite.order.service.OrderService : LUCF:product-service:9092
12:02:16.864+08:00 INFO 1800 --- [nio-8080-exec-4]
com.bite.order.service.OrderService : LUCF:product-service:9091
12:02:17.078+08:00 INFO 1800 --- [nio-8080-exec-5]
com.bite.order.service.OrderService : LUCF:product-service:9090
12:02:17.260+08:00 INFO 1800 --- [nio-8080-exec-6]
com.bite.order.service.OrderService : LUCF:product-service:9092
12:02:17.431+08:00 INFO 1800 --- [nio-8080-exec-7]
com.bite.order.service.OrderService : LUCF:product-service:9091
通过日志可以看到,请求被均衡的分配在不同的实例上,这就是负载均衡
1.2什么是负载均衡
负载均衡(Load Balance,简称LB),是高并发,高可用系统必不可少的关键组件.
当服务流量增大时,通常会采用增加机器的方式进行扩容,负载均衡就是用来在多个机器或者其他资源中,按照一定的规则合理分配负载.
一个团队最开始只有一个人,后来随着工作量的增加,公司又招聘了几个人.负载均衡就是:如何把工作量均衡的分配到这几个人身上,以提高整个团队的效率
1.3负载均衡的一些实现
上面的例子中,我们只是简单的对实例进行了轮询,但真实的业务场景会更加复杂.比如根据机器的配置进行负载分配,配置高的分配的流量高,配置低的分配流量低等. 类似企业员工:能力强的员工可以多承担一些工作。
服务多机部署时,开发人员都需要考虑负载均衡的实现,所以也出现了一些负载均衡器,来帮助我们实现负载均衡.
负载均衡分为服务端负载均衡 和客户端负载均衡.
服务端负载均衡
在服务端进行负载均衡的算法分配
比较有名的服务端负载均衡器是Nginx.
请求先到达Nginx负载均衡器,然后通过负载均衡算法,在多个服务器之间选择一个进行访问.
客户端负载均衡
在客户端进行负载均衡的算法分配.
把负载均衡的功能以库的方式集成到客户端,而不再是由一台指定的负载均衡设备集中提供.
比如SpringCloud的Ribbon,请求发送到客户端,客户端从注册中心(比如Eureka)获取服务列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问.
Ribbon是SpringCloud早期的默认实现,由于不维护了,所以最新版本的Spring Cloud负载均衡集成的是SpringCloud LoadBalancer(SpringCloud官方维护)
客户端负载均衡和服务端负载均衡最大的区别在于服务清单所存储的位置
2.Spring Cloud LoadBalancer
2.1快速上手实现负载均衡
1.给RestTemplate这个Bean添加@LoadBalanced注解
java
@Configuration
public class BeanConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
2.修改IP端口号为服务名称
java
public OrderInfo selectOrderById(Integer orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
//String url = "http://127.0.0.1:9090/product/"+ orderInfo.getProductId();
String url = "http://product-service/product/" + orderInfo.getProductId();
ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
3.启动多个product-service实例
4.测试负载均衡
连续多次发起请求: http://127.0.0.1:8080/order/1
观察product-service的日志, 会发现请求被分配到这3个实例上了
2.2负载均衡策略
负载均衡策略是一种思想,无论是哪种负载均衡器,它们的负载均衡策略都是相似的.
Spring Cloud LoadBalancer仅支持两种负载均衡策略:轮询策略和随机策略
- **轮询(Round Robin):**轮询策略是指服务器轮流处理用户的请求.这是一种实现最简单,也最常用的 策略.生活中也有类似的场景,比如学校轮流值日,或者轮流打扫卫生.
- 随机选择(Random) :随机选择策略是指随机选择一个后端服务器来处理新的请求.
自定义负载均衡策略
Spring Cloud LoadBalancer默认负载均衡策略是轮询策略,实现是RoundRobinLoadBalancer,如果服务的消费者如果想采用随机的负载均衡策略,也非常简单.
参考官网:Spring Cloud LoadBalancer :: Spring Cloud Commons
1.定义随机算法对象,通过@Bean将其加载到Spring容器中
此处使⽤Spring Cloud LoadBalancer提供的 RandomLoadBalancer
java
public class CustomLoadBalancerConfiguration {
@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}
**注意:**该类需要满足
- 不用@Configuration注释
- 在组件扫描范围内
2.使用@LoadBalancerClient或者@LoadBalancerClients注解
在RestTemplate配置类上方,使用@LoadBalancerClient或@LoadBalancerClients注解,可以对不同的服务提供方配置不同的客户端负载均衡算法策略.
由于咱们项目中只有一个服务提供者,所以使用@LoadBalancerClient
java
@LoadBalancerClient(name = "product-service",configuration = CustomLoadBalancerConfiguration.class)
@Configuration
public class BeanConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@LoadBalancerClient注解说明:
- name:该负载均衡策略对哪个服务生效(服务提供方)
- configuration:该负载均衡策略用哪个负载均衡策略实现.
3.服务部署(Linux)
3.1服务构建打包
采用Maven打包,需要对3个服务分别打包:
eureka-server,order-service,product-service
1.打包方式和SpringBoot项目一致,依次对三个项目打包即可.
3.2启动服务
bash
#后台启动eureka-server, 并设置输出⽇志到logs/eureka.log
nohup java -jar eureka-server.jar >logs/eureka.log &
#后台启动order-service, 并设置输出⽇志到logs/order.log
nohup java -jar order-service.jar >logs/order.log &
#后台启动product-service, 并设置输出⽇志到logs/order.log
nohup java -jar product-service.jar >logs/product-9090.log &
再多启动两台product-service实例
bash
#启动实例, 指定端⼝号为9091
nohup java -jar product-service.jar --server.port=9091 >logs/product-9091.log &
#启动实例, 指定端⼝号为9092
nohup java -jar product-service.jar --server.port=9092 >logs/product-9092.log &