微服务调用Ribbon
简介
前面讲了eureka服务注册与发现,但是结合eureka集群的服务调用没讲。
这里的话 就要用到Ribbon,结合eureka,来实现服务的调用;
Ribbon是Netflix发布的负载均衡器,它有助于控制HTTP和TCP的客户端的行为。为Ribbon配置服务提供者地址后,Ribbon就可基于某种负载均衡算法,自动地帮助服务消费者去请求。Ribbon默认为我们提供了很多负载均衡算法,例如轮询、随机等。当然,我们也可为Ribbon实现自定义的负载均衡算法。
在Spring Cloud中,当Ribbon与Eureka配合使用时,Ribbon可自动从Eureka Server获取服务提供者地址列表,并基于负载均衡算法,请求其中一个服务提供者实例。展示了Ribbon与Eureka配合使用时的架构。
Ribbon 使用
消费者端
在调用放(消费者)的pom.xml中加入如下配置:
说明:Ribbon是客户端负载均衡,所以肯定集成再消费端,也就是consumer端
<!--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.cpc.com:2001/eureka/,http://eureka2002.cpc.com:2002/eureka/,http://eureka2003.cpc.com:2003/eureka/
register-with-eureka: false
SpringCloudConfig 配置类
package com.cpc.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 {
@Bean
@LoadBalanced // 引入ribbon负载均衡
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
因为和eureka整合,所以消费者启动类加个注解 @EnableEurekaClient
BookProviderController
package com.cpc.controller;
import com.cpc.common.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
* @Description:
* @Author: cpc
* @Date: 2019-11-18 17:15
* @Version: V1.0
*/
@RestController
@RequestMapping("/book")
public class BookProviderController {
@Autowired //这是服务端的调用名
private final static String SERVER_IP_PORT = "http://MICROSERVICE-BOOK";
@Autowired
private RestTemplate restTemplate;
@PostMapping(value="/save")
private boolean save(Book book){
return restTemplate.postForObject(SERVER_IP_PORT+"/book/save", book, Boolean.class);
}
@GetMapping(value="/list")
public List<Book> list(){
return restTemplate.getForObject(SERVER_IP_PORT+"/book/list", List.class);
}
@GetMapping(value="/get/{id}")
public Book get(@PathVariable("id") Integer id){
return restTemplate.getForObject(SERVER_IP_PORT+"/book/get/"+id, Book.class);
}
@GetMapping(value="/delete/{id}")
public boolean delete(@PathVariable("id") Integer id){
try{
restTemplate.getForObject(SERVER_IP_PORT+"/book/delete/"+id, Boolean.class);
return true;
}catch(Exception e){
return false;
}
}
@RequestMapping("/ribbon")
public String ribbon(){
return restTemplate.getForObject(SERVER_IP_PORT + "/book/ribbon", String.class);
}
}
特别注意:
这个是服务端的调用名,也就是对应
服务端配置
建立一个microservice-student-provider子项目。前面的博客搭建了初步例子,但是还没实现真正负载均衡,我们这里要先搞三个服务提供者集群,然后才能演示负载均衡,以及负载均衡策略;
服务端 pom.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.cpc</groupId>
<artifactId>cpc-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.cpc</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/cpc?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
username: root
password: root
jpa:
hibernate:
ddl-auto: update
show-sql: true
application:
name: microservice-book
profiles: provider-1001
eureka:
instance:
hostname: localhost
appname: microservice-book
instance-id: microservice-book:1001
prefer-ip-address: true
client:
service-url:
defaultZone: http://eureka2001.cpc.com:2001/eureka/,http://eureka2002.cpc.com:2002/eureka/,http://eureka2003.cpc.com:2003/eureka/
info:
groupId: com.cpc.testSpringcloud
artifactId: microservice-book-provider-1001
version: 1.0-SNAPSHOT
userName: http://cpc.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/cpc?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
username: root
password: root
jpa:
hibernate:
ddl-auto: update
show-sql: true
application:
name: microservice-book
profiles: provider-1002
eureka:
instance:
hostname: localhost
appname: microservice-book
instance-id: microservice-book:1002
prefer-ip-address: true
client:
service-url:
defaultZone: http://eureka2001.cpc.com:2001/eureka/,http://eureka2002.cpc.com:2002/eureka/,http://eureka2003.cpc.com:2003/eureka/
info:
groupId: com.cpc.testSpringcloud
artifactId: microservice-book-provider-1002
version: 1.0-SNAPSHOT
userName: http://cpc.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/cpc?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
username: root
password: root
jpa:
hibernate:
ddl-auto: update
show-sql: true
application:
name: microservice-book
profiles: provider-1003
eureka:
instance:
hostname: localhost
appname: microservice-book
instance-id: microservice-book:1003
prefer-ip-address: true
client:
service-url:
defaultZone: http://eureka2001.cpc.com:2001/eureka/,http://eureka2002.cpc.com:2002/eureka/,http://eureka2003.cpc.com:2003/eureka/
info:
groupId: com.cpc.testSpringcloud
artifactId: microservice-book-provider-1003
version: 1.0-SNAPSHOT
userName: http://cpc.com
phone: 123456
启动类
package com.cpc.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.cpc.*.*") //这是扫描jpa实体类
@EnableEurekaClient //表面这是一个eureka 的客户端
@SpringBootApplication
public class MicroserviceBookProviderApplication {
public static void main(String[] args) {
SpringApplication.run(MicroserviceBookProviderApplication.class, args);
}
}
BookProviderController
package com.cpc.microservicebookprovider.controller;
import com.cpc.common.entity.Book;
import com.cpc.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: cpc
* @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;
}
}
}
配置一波:
测试
开启 eureka 集群,开启服务提供者集群、开启消费者,下面是eureka 中显示的结果
客户端调用:
多刷新几次 看结果,我们看到 有默认的轮询策略,访问对应的服务提供者;
自定义轮询算法
上面的代码能实现简单的负载均衡,但是这种默认的轮询策略肯定是不能满足实际需求的,比如有3个服务提供者,突然挂了一个,这样的话,默认轮询 ,总有1/3的概率访问失败; 所以我们看下ribbon默认给我们提供的策略有哪些;
策略名 | 策略声明 | 策略新 |
---|---|---|
BestAvailableRule | public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule | 选择一个最小的并发请求的server |
AvailabilityFilteringRule | public class AvailabilityFilteringRule extends PredicateBasedRule | 过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值) |
WeightedResponseTimeRule | public class WeightedResponseTimeRule extends RoundRobinRule | 根据响应时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低。 |
RetryRule | public class RetryRule extends AbstractLoadBalancerRule | 对选定的负载均衡策略机上重试机制。 |
RoundRobinRule | public class RoundRobinRule extends AbstractLoadBalancerRule | roundRobin方式轮询选择server |
RandomRule | public class RandomRule extends AbstractLoadBalancerRule | 随机选择一个server |
ZoneAvoidanceRule | public class ZoneAvoidanceRule extends PredicateBasedRule | 复合判断server所在区域的性能和server的可用性选择server |
默认7个策略,根据具体产品需求选用;代码中通过如下方法指定:
这里我们演示用 RetryRule,大伙可以自行测试;服务消费端 SpringCloudConfig配置类
package com.cpc.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 {
@Bean
@LoadBalanced // 引入ribbon负载均衡
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
/**
* 自定义轮询算法
* @return
*/
@Bean
public IRule myRule(){
return new RetryRule();
}
}
用法很简单;
Feign简介及应用
简介
声明式服务调用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,后面再说。
使用 Feign
修改 microservice-common (项目通用模块)
在common项目里建一个service(实际项目肯定是多个service)作为Feign客户端,用Feign客户端来调用服务器提供者,当然可以配置负载均衡;Feign客户端定义的目的,就是为了方便给其他项目调用
pom.xml引入Feign依赖:
<!--引入Feign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
我们定义了 FeignClient,同时指定了调用的服务名称MICROSERVICE-STUDENT
common项目修改后,maven clean下 然后install下;
package com.cpc.common.service;
import com.cpc.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();
}
新建一个Feign消费者项目使用 Feign 的方式调用服务提供者提供的方法
pom.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.cpc</groupId>
<artifactId>cpc-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.cpc</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.cpc.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文件
server:
port: 80
context-path: /
eureka:
client:
service-url:
defaultZone: http://eureka2001.cpc.com:2001/eureka/,http://eureka2002.cpc.com:2002/eureka/,http://eureka2003.cpc.com:2003/eureka/
register-with-eureka: false
启动类配置:
package com.cpc.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.cpc"})
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
public class MicroserviceBookConsumerFeign80Application {
public static void main(String[] args) {
SpringApplication.run(MicroserviceBookConsumerFeign80Application.class, args);
}
}
BookConsumerController
package com.cpc.microservicebookconsumerfeign80.controller;
import com.cpc.common.entity.Book;
import com.cpc.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();
}
}
因为现在用Fiegn,所以把restTemplate去掉,改成注入service,调用service方法来实现服务的调用;
测试
调用的时候我们发现同样是实现的负载均衡