微服务调用Ribbon负载均衡、Feign的使用
微服务调用Ribbon负载均衡、Feign的使用)
1、微服务调用Ribbon
一、简介
Ribbon是一个负载均衡组件,具有丰富的负载均衡策略、重试机制、支持多协议的异步与响应式模型、容错、缓存与批处理功能。
二、Ribbon与负载均衡
负载均衡,即利用特定方式将流量分摊到多个操作单元上,它对系统吞吐量与系统处理能力有着质的提升。
负载均衡分类:
1 集中式负载均衡:也叫做服务端负载均衡,位于英特网与服务提供者之间,负责把网络请求转发给各个提供单位,典型产品是Ngixn;
2 进程负载均衡:也叫作客户端负载均衡,位于服务提供者内部,从注册中心的已注册实例库选取实例进行流量导入,整个过程都是在进程间通信。
Ribbon就是典型的客户端负载均衡,它赋予了应用一些支配HTTP与TCP行为的能力,如果没有Ribbon,Spring Cloud构建的服务则无法横向扩展,所以Spring Cloud的Feign和Zuul默认集成了Ribbon。
三、负载均衡策略
Ribbon有7中可选的负载均衡策略,如下:
RomdomRule:随机策略,随机选择 Server;
RoundRobbinRule:轮询策略,按顺序循环选择 Server,该策略是Ribbon默认策略;
RetryRule:重试策略,在一个配置时间段内当选择 server 不成功,则一直尝试选择一个可用的 server;
BestAvailableRule:最低并发策略,逐个考察 server,如果 server 断路器打开,则忽略,再选择其中并发连接最低的 server;
AvailabilityFilteringRule:可用过滤策略,过滤掉一直连接失败并被标记为 circuit tripped 的 server,过滤掉那些高并发连接的 server(active connections 超过配置的阀值);
ResponseTimeWeightedRule:响应时间加权策略,根据 server 的响应时间分配权重。响应时间越长,权重越低,被选择到的概率就越低;响应时间越短,权重越高,被选择到的概率就越高。这个策略很贴切,综合了各种因素,如:网络、硬盘、IO等,这些因素直接影响着响应时间;
ZoneAvoidanceRule:区域权衡策略,综合判断 server 所在区域的性能和 server 的可用性轮询选择 server,并且判定一个 AWS Zone 的运行性能是否可用,剔除不可用的 Zone 中的所有 server。
四、策略设置方式
1 全局策略设置
2 基于注解的策略设置
3 基于配置文件的策略设置
五、调整Ribbon的加载模式
Ribbon在进行客户端负载均衡的时候并不是跟随服务的启动进行上下文加载,而是在发生第一次请求的时候才会去创建,即懒加载,所以有时候会导致服务第一次调用的超时,介于此问题,可以调整Ribbon的加载模式为饥饿加载,即在启动的时候便加载所有配置项的应用程序上下文。
作者:夏天的风风风
链接:https://www.jianshu.com/p/d68ca79f119c
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Ribbon是客户端负载均衡,所以肯定集成再消费端,也就是consumer端
我们修改microservice-student-consumer-80
首先,引入依赖,pom.xml 加入 ribbon相关依赖
<!--ribbon相关依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
application.yml
server:
port: 80
context-path: /
eureka:
client:
service-url:
defaultZone: http://eureka2001.xyx.com:2001/eureka/,http://eureka2002.xyx.com:2002/eureka/,http://eureka2003.xyx.com:2003/eureka/
register-with-eureka: false
SpringCloudConfig 配置类
package com.xyx.microservicestudentconsumer80.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class SpringCloudConfig {
@LoadBalanced // 引入ribbon负载均衡
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
在这里把controller里面的请求地址改成Eureka注册中心的名称就可以了
@RestController
@RequestMapping("/student")
public class StudentConsumerController {
// private final static String SERVER_IP_PORT = "http://localhost:1001";
private final static String SERVER_IP_PORT = "http://MICROSERVICE-STUDENT";
2、Ribbon负载均衡
服务端配置
建立一个microservice-student-provider子项目。前面的博客搭建了初步例子,但是还没实现真正负载均衡,我们这里要先搞三个服务提供者集群,然后才能演示负载均衡,以及负载均衡策略;
pop.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.xyx</groupId>
<artifactId>xyx-spring-cloud</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>microservice-book-provider</artifactId>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.xyx</groupId>
<artifactId>microservice-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!-- 修改后立即生效,热部署 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!--添加注册中心Eureka相关配置-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- actuator监控引入 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application. yml 配置
---
server:
port: 1001
context-path: /
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_0524?useUnicode=true&characterEncoding=utf8
username: root
password: 123
jpa:
hibernate:
ddl-auto: update
show-sql: true
application:
name: microservice-student
profiles: provider-1001
eureka:
instance:
hostname: localhost
appname: microservice-student
instance-id: microservice-student:1001
prefer-ip-address: true
client:
service-url:
defaultZone: http://eureka2001.xyx.com:2001/eureka/,http://eureka2002.xyx.com:2002/eureka/,http://eureka2003.xyx.com:2003/eureka/
info:
groupId: com.xyx.springcloud01
artifactId: microservice-student-provider-1001
version: 1.0-SNAPSHOT
userName: http://xyx.com
phone: 123456
---
server:
port: 1002
context-path: /
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_0524?useUnicode=true&characterEncoding=utf8
username: root
password: 123
jpa:
hibernate:
ddl-auto: update
show-sql: true
application:
name: microservice-student
profiles: provider-1002
eureka:
instance:
hostname: localhost
appname: microservice-student
instance-id: microservice-student:1002
prefer-ip-address: true
client:
service-url:
defaultZone: http://eureka2001.xyx.com:2001/eureka/,http://eureka2002.xyx.com:2002/eureka/,http://eureka2003.xyx.com:2003/eureka/
info:
groupId: com.xyx.springcloud01
artifactId: microservice-student-provider-1002
version: 1.0-SNAPSHOT
userName: http://xyx.com
phone: 123456
---
server:
port: 1003
context-path: /
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_0524?useUnicode=true&characterEncoding=utf8
username: root
password: 123
jpa:
hibernate:
ddl-auto: update
show-sql: true
application:
name: microservice-student
profiles: provider-1003
eureka:
instance:
hostname: localhost
appname: microservice-student
instance-id: microservice-student:1003
prefer-ip-address: true
client:
service-url:
defaultZone: http://eureka2001.xyx.com:2001/eureka/,http://eureka2002.xyx.com:2002/eureka/,http://eureka2003.xyx.com:2003/eureka/
info:
groupId: com.xyx.springcloud01
artifactId: microservice-student-provider-1003
version: 1.0-SNAPSHOT
userName: http://xyx.com
phone: 123456
MicroserviceBookProviderApplication
package com.xyx.microservicebookprovider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EntityScan("com.xyx.*.*") //这是扫描jpa实体类
@EnableEurekaClient //表面这是一个eureka 的客户端
@SpringBootApplication
public class MicroserviceBookProviderApplication {
public static void main(String[] args) {
SpringApplication.run(MicroserviceBookProviderApplication.class, args);
}
}
BookProviderController
package com.xyx.microservicebookprovider.controller;
import com.xyx.common.entity.Book;
import com.xyx.microservicebookprovider.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @Description:
* @Author: xyx
* @Date: 2019-11-18 17:15
* @Version: V1.0
*/
@RestController
@RequestMapping("/book")
public class BookProviderController {
@Autowired
private BookService bookService;
@Value("${server.port}")
private String port;
@RequestMapping("/ribbon")
public String ribbon(){
return "工号【"+port+"】正在为您服务";
}
@PostMapping(value="/save")
public boolean save(Book book){
try{
bookService.save(book);
return true;
}catch(Exception e){
return false;
}
}
@GetMapping(value="/list")
public List<Book> list(){
return bookService.list();
}
@GetMapping(value="/get/{id}")
public Book get(@PathVariable("id") Integer id){
return bookService.findById(id);
}
@GetMapping(value="/delete/{id}")
public boolean delete(@PathVariable("id") Integer id){
try{
bookService.delete(id);
return true;
}catch(Exception e){
return false;
}
}
}
在消费者microservice-student-consumer-80的controller中加上测试方法
@RequestMapping("/ribbon")
public String ribbon() {
return restTemplate.getForObject(SERVER_IP_PORT + "/student/ribbon", String.class);
}
生产者microservice-student-providerr的StudentProviderController中加上测试方式(如果创建了3个项目,那么在3个都要加上)
@Value("${server.port}")//键值对的格式
private String port;
@RequestMapping("/ribbon")
public String ribbon(){
return "工号【"+port+"】正在为您服务";
}
自定义轮询算法
指的是一个一个的轮到,然后有一个服务器宕机了 也会继续运行
/**
* 自定义轮询算法
* @return
*/
@Bean
public IRule myRule(){
return new RetryRule();
}
自定义轮询算法
指的是一个一个的轮到,然后
3、Feign简介及应用
Feign是一个声明式的Web Service客户端,它使得编写Web Serivce客户端变得更加简单。我们只需要使用Feign来创建一个接口并用注解来配置它既可完成。它具备可插拔的注解支持,包括Feign注解和JAX-RS注解。Feign也支持可插拔的编码器和解码器。Spring Cloud为Feign增加了对Spring MVC注解的支持,还整合了Ribbon和Eureka来提供均衡负载的HTTP客户端实现。
这段话看起来比较懵逼,这里说下实际使用,前面Ribbon调用服务提供者,我们通过restTemplate调用,缺点是,多个地方调用,同一个请求要写多次,不方便统一维护,这时候Feign来了,就直接把请求统一搞一个service作为FeignClient,然后其他调用Controller需要用到的,直接注入service,直接调用service方法即可;同时Feign整合了Ribbon和Eureka,所以要配置负载均衡的话,直接配置Ribbon即可,无其他特殊地方;当然Fiegn也整合了服务容错保护,断路器Hystrix,后面再说。
也就是把我们共同代码抽取出来,放入common公共部分中去,让维护更加方便
pop.xml
<!--引入Feign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
我们定义了 FeignClient,同时指定了调用的服务名称MICROSERVICE-STUDENT
common项目修改后,maven clean下 然后install下;
package com.xyx.common.service;
import com.xyx.common.entity.Book;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
/**
* Book Feign接口客户端
* @author Administrator
*
*/
@FeignClient(value = "MICROSERVICE-BOOK")//被调用方服务名
public interface BookClientService {
/**
* 根据id查询学生信息
* @param id
* @return
*/
@GetMapping(value="/book/get/{id}")
public Book get(@PathVariable("id") Integer id);
/**
* 查询学生信息
* @return
*/
@GetMapping(value="/book/list")
public List<Book> list();
/**
* 添加或者修改学生信息
* @param book
* @return
*/
@PostMapping(value="/book/save")
public boolean save(Book book);
/**
* 根据id删除学生信息
* @return
*/
@GetMapping(value="/book/delete/{id}")
public boolean delete(@PathVariable("id") Integer id);
@RequestMapping("/book/ribbon")
public String ribbon();
}
pop.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.xyx</groupId>
<artifactId>xyx-spring-cloud</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>microservice-book-consumer-feign-80</artifactId>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.xyx</groupId>
<artifactId>microservice-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<!-- 修改后立即生效,热部署 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!--ribbon相关依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!--引入Feign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
SpringCloudConfig
package com.xyx.microservicebookconsumerfeign80.config;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RetryRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class SpringCloudConfig {
@LoadBalanced // 引入ribbon负载均衡
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
/**
* 自定义调用规则(服务提供者掉线后不再调用,解决轮询问题)
* @return
*/
@Bean
public IRule myRule(){
return new RetryRule();
// return new RandomRule();
}
}
Yml.xml
server:
port: 80
context-path: /
eureka:
client:
service-url:
defaultZone: http://eureka2001.xyx.com:2001/eureka/,http://eureka2002.xyx.com:2002/eureka/,http://eureka2003.xyx.com:2003/eureka/
register-with-eureka: false
MicroserviceBookConsumerFeign80Application(启动类配置:)
package com.xyx.microservicebookconsumerfeign80;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
@EnableEurekaClient
@EnableFeignClients(basePackages = {"com.xyx"})
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
public class MicroserviceBookConsumerFeign80Application {
public static void main(String[] args) {
SpringApplication.run(MicroserviceBookConsumerFeign80Application.class, args);
}
}
BookConsumerController
package com.xyx.microservicebookconsumerfeign80.controller;
import com.xyx.common.entity.Book;
import com.xyx.common.service.BookClientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/book")
public class BookConsumerController {
@Autowired
private BookClientService bookClientService;
@PostMapping(value = "/save")
private boolean save(Book book) {
return bookClientService.save(book);
}
@GetMapping(value = "/list")
public List<Book> list() {
return bookClientService.list();
}
@GetMapping(value = "/get/{id}")
public Book get(@PathVariable("id") Integer id) {
return bookClientService.get(id);
}
@GetMapping(value = "/delete/{id}")
public boolean delete(@PathVariable("id") Integer id) {
try {
bookClientService.delete(id);
return true;
} catch (Exception e) {
return false;
}
}
@RequestMapping("/ribbon")
public String ribbon(){
return bookClientService.ribbon();
}
}
如果说你的一个服务器宕机了,可以跳过