负载均衡 微服务架构在生成环境中,各个微服务会部署多个实例,负载均衡就是将服务消费者的请求分摊到多个服务提供者实例的过程,它可以增加系统的可用性和扩展性。
当我们使用RestTemplate来调用其他服务时,Ribbon可以方便的是实现负载均衡,版本迭代之后由LoadBalancer替代。
LoadBalancer的优势主要在于支持响应式编程方式异步访问客户端,依赖Spring Web Flux实现客户端负载均衡调用。
RestTemplate-HTTP客户端
RestTemplate是一个HTTP客户端,使用它我们可以方便的调用HTTP接口,支持GET、POST、PUT、DELETE等方法。
这里以Get方法为例,说明RestTemplate远程调用,具体可查看RestOperations接口的方法声明。
GET请求方法
getForObject方法 返回对象为响应体中数据转化成对象
getForEntity方法 返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等。
调试运行可以看到ResponseEntity对象里的具体信息
POST请求方法
PUT请求方法
DELETE请求方法
使用Ribbon实现客户端侧负载均衡 Ribbon简介 Spring Cloud Ribbon 是Spring Cloud Netflix 子项目的核心组件之一,主要给服务间调用及API网关转发提供负载均衡的功能,有助于控制HTTP和TCP客户端的行为。在服务消费者拉取服务提供者地址列表后,Ribbon基于某种负载均衡算法,自动帮助服务消费者发起请求。
Ribbon提供了很多负载均衡算法,例如轮询、随机等,还支持自定义的负载均衡算法。
在Spring Cloud中,当Ribbon与Eureka配合使用时,Ribbon自动从Eureka Server获取服务提供者地址列表,并基于负载均衡算法,请求其中一个服务提供者实例。
服务消费者整合Ribbon 由于之前的项目使用的Spring Cloud Netflix版本已经弃用Ribbon,所有我们需要修改响应的版本信息,这里就不做过多的赘述。
项目引入依赖 1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-ribbon</artifactId > </dependency >
文件配置 主要配置端口、注册中心地址以及服务调用路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 server: port: 8301 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/cloudstudy_borrow username: root password: 13851176590 ++ application: name: borrow-service mybatis: mapper-locations: - classpath:mapper/*.xml eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://root:123456@replica1:8005/eureka, http://root:123456@replica2:8006/eureka service-url: user-service: http://user-service book-service: http://book-service
在RestTemplate注入方法上添加@LoadBalanced注解 添加@LoadBalanced注解,就可为RestTemplate整合Ribbom,使其具备负载均衡的能力。
1 2 3 4 5 6 @Bean @LoadBalanced public RestTemplate restTemplate () { return new RestTemplate (); }
接口实现负载均衡的远程调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @Service public class BorrowServiceImpl implements BorrowService { @Resource BorrowMapper borrowMapper; @Autowired RestTemplate restTemplate; @Value("${service-url.user-service}") private String userServiceUrl; @Value("${service-url.book-service}") private String bookServiceUrl; @Override public UserBorrowDetail getBorrowByUser (Long uid) { List<Borrow> borrows = borrowMapper.getBorrow(uid,null ); User user = restTemplate.getForObject(userServiceUrl + "/user/" + uid, User.class); List<Book> books = borrows.stream().map(borrow -> restTemplate.getForObject(bookServiceUrl + "/book/" + borrow.getBid(), Book.class)).collect(Collectors.toList()); return new UserBorrowDetail (user, books); } @Override public BookBorrowDetail getBorrowByBook (Long bid) { List<Borrow> borrows = borrowMapper.getBorrow(null ,bid); List<User> users = borrows.stream().map(borrow -> restTemplate.getForObject(userServiceUrl + "/user/" + borrow.getUid(), User.class)).collect(Collectors.toList()); ResponseEntity<Book> entity = restTemplate.getForEntity(bookServiceUrl + "/book/" +bid, Book.class); if (entity.getStatusCode().is2xxSuccessful()) { return new BookBorrowDetail (users, entity.getBody()); } else { return new BookBorrowDetail (users, null ); } } @Override public BorrowDetail getBorrow (Long uid, Long bid) { List<Borrow> borrows = borrowMapper.getBorrow(uid, bid); List<User> users = borrows.stream().map(borrow -> restTemplate.getForObject(userServiceUrl + "/user/" + borrow.getUid(), User.class)).collect(Collectors.toList()); List<Book> books = borrows.stream().map(borrow -> restTemplate.getForObject(bookServiceUrl + "/book/" + borrow.getBid(), Book.class)).collect(Collectors.toList()); return new BorrowDetail (users, books); } }
控制层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @RestController public class BorrowController { @Resource BorrowService borrowService; @Autowired private LoadBalancerClient loadBalancerClient; private static final Logger logger = LoggerFactory.getLogger(BorrowController.class); @RequestMapping("/borrow/user/{uid}") public UserBorrowDetail getBorrowByUser (@PathVariable("uid") Long uid) { return borrowService.getBorrowByUser(uid); } @RequestMapping("/borrow/book/{bid}") public BookBorrowDetail getBorrowByBook (@PathVariable("bid") Long bid) { return borrowService.getBorrowByBook(bid); } @RequestMapping("/borrow") public BorrowDetail getBorrow (@RequestParam(value = "uid", required = true) Long uid, @RequestParam(value = "bid", required = true) Long bid) { return borrowService.getBorrow(uid, bid); } @GetMapping("/log-instance") public void logUserInstance () { ServiceInstance serviceInstance = this .loadBalancerClient.choose("user-service" ); BorrowController.logger.info("{}:{}:{}" , serviceInstance.getServiceId(), serviceInstance.getHost(), serviceInstance.getPort()); } }
Ribbon原理 负载均衡拦截器在获取用户的请求地址后,会获取微服务的虚拟主机名,再从注册中心拉取微服务的网络地址列表,在服务列表进行负载均衡。
LoadBalancerInterceptor方法
ClientHttpRequestInterceptor接口
接口方法具体实现
负载均衡接口
RibbonLoadBalancerClient
Ribbon负载均衡策略 Ribbon的负载均衡规则是由IRule的接口定义,每一个子接口对应一种规则。
com.netflix.loadbalancer.RandomRule:从提供服务的实例中以随机的方式;
com.netflix.loadbalancer.RoundRobinRule:以线性轮询的方式,就是维护一个计数器,从提供服务的实例中按顺序选取,第一次选第一个,第二次选第二个,以此类推,到最后一个以后再从头来过;
com.netflix.loadbalancer.RetryRule:在RoundRobinRule的基础上添加重试机制,即在指定的重试时间内,反复使用线性轮询策略来选择可用实例;
com.netflix.loadbalancer.WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择;
com.netflix.loadbalancer.BestAvailableRule:选择并发较小的实例;
com.netflix.loadbalancer.AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例;
com.netflix.loadbalancer.ZoneAwareLoadBalancer:采用双重过滤,同时过滤不是同一区域的实例和故障实例,选择并发较小的实例。
通过自定义定义IRule实现可以修改负载均衡规则,由以下两种方式实现:
1 2 3 4 @Bean public IRule randomRule () { return new RandomRule (); }
1 2 3 user-service: ribbon: NFLoadBalanverRuleClassName: com.netflix.loadbalancer.RandomRule
Ribbon饥饿加载 Ribbon默认是采用懒加载,即第一次访问时会去创建LoadBalanceClient,请求时间很长
使用Ribbon的饥饿加载会在项目开启时创建,降低第一次访问的耗时
开启饥饿加载的方式,配置文件:
1 2 3 4 5 6 ribbon: eager-load: enable: true clients: -user-service -book-service
使用LoadBalancer实现客户端侧负载均衡 服务消费者整合LoadBalancer 整合步骤和Ribbon的整合步骤基本一致,只是依赖版本不同且无需引入Ribbon
LoadBalancer原理
工作流程
@LoadBalanced注解开启负载均衡
客户端HTTP请求拦截器接口
负载均衡拦截器实现接口
负载均衡客户端中execute接口方法
BlockingLoadBalancerClient实现execute方法
choose具体实现
ReactiveLoadBalancer被不同规则的负载均衡器实现
默认由RoundRobinLoadBalancer即轮询负载均衡实现
自定义负载均衡策略 创建负载均衡配置类
1 2 3 4 5 6 7 public class LoadBalancerConfig { @Bean public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer (Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) { String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); return new RandomLoadBalancer (loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name); } }
为指定服务指定负载均衡策略
1 2 3 4 5 6 @LoadBalancerClients( value = { @LoadBalancerClient(value = "user-service", configuration = LoadBalancerConfig.class), @LoadBalancerClient(value = "book-service", configuration = LoadBalancerConfig.class) }, defaultConfiguration = LoadBalancerClientConfiguration.class )
我们发现远程调用user-service服务不在是通过轮询的方式,而是通过随机的负载均衡策略实现