openfeign+hystrix
1、openfeign
openfeign概念
- Openfeign是spring cloud家族的一个成员,是一个声明式的客户端,使用feign能够让web service客户端更加简单。
- 使用方法就是在客户端即消费端定义一个服务接口然后在上面加上注解@FeignClient,也支持可拔插式的编码和解码器,spring cloud对feign进行了封装,使其支持了spring MVC标准注解和httpMessageConverters,可以与Ribbon和hystrix实现负载均衡和服务降级。
- 之前我们使用 Ribbon+RestTemplate来进行对服务的调用,但是由于对服务的调用不止一处,即一个服务可能被多处调用,所以我们就通过一个接口包含服务端的所有接口,这个可以作为客户端,通过该openfeign来实现远程服务端调用。简化的Ribbon+RestTemplate远程调用。
- 集成了Ribbon即存在负载均衡机制。
- 官网:春云开放菲恩 (spring.io)
feign和openfeign的区别:
feign | openfeign |
---|---|
Feign是一个spring cloud组件中一个轻量级RESTFul的HTTP服务客户端,feign内置了Ribbon,用于客户端的负载均衡。 | openfeign是springcloud在feign的基础上支持了springMVC的注解,如@RequestMapping,openfeign的@FeignClient可以解析SpringMVC的RequestMapping下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。 |
基本上不会再使用该方法了 | org.springframework.cloud spring-cloud-starter-openfeign 3.1.1 |
openfeign的日志打印:
- **NONE:**默认的,不显示打印日志。
- **BASIC:**仅记录请求方法、URL、响应状态码及执行时间。
- **HEADERS:**除了BASIC中定义的以外,还有请求响应头信息。
- **FULL:**除了HEADERS之外,还有请求和响应的正文即元数据。
FULL显示示例:
2、openfeign的基本使用
注意这里是的的注册中心为eureka,且为了很好的进行包的管理,这里创建一个父工程
2.1 父工程pom.xml
<dependencyManagement> <!--在父工程中才存在该标签,用于管理子工程的相关包-->
<dependencies>
<!--spring boot的包依赖下面子工程的boot版本都为这个版本-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.5</version>
<type>pom</type>
<!--表明后续不需在导入spring boot依赖-->
<scope>import</scope>
</dependency>
<!--spring cloud的包依赖下面子工程的cloud版本都为这个版本-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.1</version>
<type>pom</type>
<!--表明后续不需在导入spring cloud依赖-->
<scope>import</scope>
</dependency>
<!--spring cloud alibaba的包依赖下面子工程的cloud alibaba版本都为这个版本-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.0.1.0</version>
<type>pom</type>
<!--表明后续不需在导入spring cloud alibaba依赖-->
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2.2 创建eureka注册中心服务
- 改pom.xml
<dependencies>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--图形化依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!--一个自定义包用于公共的pojo等,解耦,可以不用-->
<dependency>
<groupId>com.qiu</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--eureka-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
- 改yaml
server:
port: 7001
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #注册中心地址
- 主启动类
package com.qiumin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer //开启eureka服务
public class eurekaServer7001 {
public static void main(String[] args) {
SpringApplication.run(eurekaServer7001.class,args);
}
}
- 启动后访问 http://localhost:7001/
2.3 创建服务提供者 provider
- 改pom.xml
<dependencies>
<!--eureka客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--自己定义的一个工程-->
<dependency>
<groupId>com.qiu</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--图形化处理-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.6.5</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>2.6.5</version>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- 改yaml
server:
port: 8001
spring:
application:
name: cloud-provide-payment #服务名称
datasource: #数据源
username: root
password: 123456
url: jdbc:mysql://localhost:3306/cloud?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
mybatis: #mybatis相关配置
mapper-locations: classpath:mybatis/mapper/*.xml
type-aliases-package: com.qiumin.pojo
eureka:
client:
register-with-eureka: true #注册到注册中心
service-url:
defaultZone: http://localhost:7001/eureka #注册中心地址
instance:
instance-id: payment8001 #处理在eureka中的显示
prefer-ip-address: true
lease-renewal-interval-in-seconds: 2 #
lease-expiration-duration-in-seconds: 4
- 主启动类
package com.qiumin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient //开启服务发现客户端
public class paymentApplication {
public static void main(String[] args) {
SpringApplication.run(paymentApplication.class, args);
}
}
- 业务类编写 【service】
package com.qiumin.service;
import com.qiumin.mapper.PaymentDao;
import com.qiumin.pojo.Payment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class PaymentServiceImpl implements PaymentService{
@Autowired
PaymentDao paymentDao;
@Override
public Payment query(int id) {
return paymentDao.query(id);
}
@Override
public int insert(Payment payment) {
return paymentDao.insert(payment);
}
}
- 业务类编写 【controller】
package com.qiumin.controller;
import com.qiumin.pojo.CommonResult;
import com.qiumin.pojo.Payment;
import com.qiumin.service.PaymentServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@Slf4j
public class PaymentController {
@Autowired
PaymentServiceImpl paymentService;
@Value("${server.port}")
String serverPort;
@RequestMapping("/payment/query/{id}")
@ResponseBody
public CommonResult<Payment> query(@PathVariable("id") int id){
Payment query = paymentService.query(id);
// int age=10/0; 用于监测是否服务降级而设置
log.info("======查询结果"+query+"=====");
if (query!=null){
return new CommonResult(200,"查询数据库成功=== serverPort为 "+serverPort,query);
}else {
return new CommonResult(444,"查询数据库失败=== serverPort为 "+serverPort,null);
}
}
@RequestMapping("/payment/create")
@ResponseBody
public CommonResult<Payment> create(Payment payment){
int result = paymentService.insert(payment);
log.info("======插入结果"+payment+"=====");
if (result>0){
return new CommonResult(200,"插入数据库成功=== serverPort为 "+serverPort,payment);
}else {
return new CommonResult(444,"插入数据库失败=== serverPort为 "+serverPort,null);
}
}
}
其他的mapper、pojo、mapper.xml自动补全这里只是演示openfeign远程调用
- 启动,就会注册到eureka注册中心
2.4 创建客户端即消费端
- 改pom.xml
<dependencies>
<!--开启openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--自定义包-->
<dependency>
<groupId>com.qiu</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--图形化处理-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.6.5</version>
</dependency>
<!--devtools-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>2.6.5</version>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- 改yaml
server:
port: 8888
spring:
application:
name: cloud-consumer-order #服务名
eureka:
client:
register-with-eureka: true #注册到注册中心
service-url:
defaultZone: http://localhost:7001/eureka #注册中心地址
ribbon:
#建立连接所用时间
ReadTimeout: 5000
#服务读取数据可用时间
ConnectTimeout: 5000 #负载均衡超时配置
logging:
level:
com.qiumin.client.paymentClient: debug #eureka日志打印
- 编写客户端
package com.qiumin.client;
import com.qiumin.pojo.CommonResult;
import com.qiumin.pojo.Payment;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Component
@FeignClient(value = "CLOUD-PROVIDE-PAYMENT") //指定注册到注册中心的该服务名下的接口方法
public interface paymentClient {
//服务端即服务提供者的接口方法
@RequestMapping("/payment/query/{id}") //服务端的访问地址
CommonResult<Payment> query(@PathVariable("id") int id);
//服务端即服务提供者的接口方法
@RequestMapping("/payment/create")
CommonResult<Payment> insert(Payment payment);
}
- 配置eureka日志
package com.qiumin.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL; //日志全打印
}
}
- 业务类编写
package com.qiumin.controller;
import com.qiumin.client.paymentClient;
import com.qiumin.pojo.CommonResult;
import com.qiumin.pojo.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class OrderController {
@Autowired
paymentClient paymentClient;
@RequestMapping("/consumer/payment/query/{id}") //调用该路径实际是通过openfeign调用远程服务
public CommonResult<Payment> query(@PathVariable("id") int id){
log.info("进入了该方法!!");
return paymentClient.query(id);
}
@RequestMapping("/consumer/payment/create")
public CommonResult<Payment> insert(Payment payment){
return paymentClient.insert(payment);
}
@RequestMapping("/test")
public String test(){
return "成功";
}
}
- 主启动类
package com.qiumin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient //开启eureka客户端
@EnableFeignClients //开启openfeign远程调用
public class consumerApplication {
public static void main(String[] args) {
SpringApplication.run(consumerApplication.class, args);
}
}
- 启动测试
- 使用openfeign远程服务调用成功。
3、openfeign整合hystrix
3.1 创建带服务降级的客户端
- 改pom.xml
<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<!--开启openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--自定义的jar包-->
<dependency>
<groupId>com.qiu</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--图形化处理依赖,与后面的图形化监控可用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.6.5</version>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>2.6.5</version>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- 改yaml
server:
port: 8888
spring:
application:
name: cloud-consumer-order
eureka:
client:
register-with-eureka: true
service-url:
defaultZone: http://localhost:7001/eureka
feign:
circuitbreaker:
enabled: true //开启服务降级
- Client
package com.qiumin.client;
import com.qiumin.pojo.CommonResult;
import com.qiumin.pojo.Payment;
import com.qiumin.service.FallbackService;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Component
@FeignClient(value = "CLOUD-PROVIDE-PAYMENT",fallback = FallbackService.class) //对应一个服务降级的类,见下
public interface paymentClient {
@RequestMapping("/payment/query/{id}")
CommonResult<Payment> query(@PathVariable("id") int id);
@RequestMapping("/payment/create")
CommonResult<Payment> create(Payment payment);
}
- 服务降级处理类
该类实现client接口,即接口中的所有方法
package com.qiumin.service;
import com.qiumin.client.paymentClient;
import com.qiumin.pojo.CommonResult;
import com.qiumin.pojo.Payment;
import org.springframework.stereotype.Component;
@Component
public class FallbackService implements paymentClient {
@Override
public CommonResult<Payment> query(int id) {
return new CommonResult(444,"请求获取数据超时或错误,进行了服务降级操作!!!",null);
}
@Override
public CommonResult<Payment> create(Payment payment) {
return new CommonResult(444,"请求获取数据超时或错误,进行了服务降级操作!!!",null);
}
}
- controller
package com.qiumin.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.qiumin.client.paymentClient;
import com.qiumin.pojo.CommonResult;
import com.qiumin.pojo.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@Slf4j
public class OrderController {
@Autowired
paymentClient paymentClient;
@RequestMapping("/consumer/payment/query/{id}")
@ResponseBody
//注意使用该方法时不需加 @HystrixCommand注解
public CommonResult<Payment> query(@PathVariable("id") int id){
log.info("进入了该方法!!");
return paymentClient.query(id);
}
@RequestMapping("/consumer/payment/create")
@ResponseBody
public CommonResult<Payment> create(Payment payment){
return paymentClient.create(payment);
}
@RequestMapping("/test")
@ResponseBody
public String test(){
return "成功";
}
}
- 主启动类
package com.qiumin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient //eureka
@EnableFeignClients //开启openfeign远程调用
@EnableCircuitBreaker //开启服务降级
public class consumerHOApplication {
public static void main(String[] args) {
SpringApplication.run(consumerHOApplication.class, args);
}
}
-
启动测试
- 成功测试
- 服务降级测试【将服务的提供方关闭即宕机】
3.2 注意
- 在客户端的controller有错误的代码【如:age=10/0】时必须单独创建一个服务降级方法兜底,否则会报错。
- 服务端可有错误代码,因为client接口中的所有方法都有兜底的服务降级方法。
- hystrix相关后续更新,详细介绍。
qiumin