微服务架构整理-(十一SpringCloud实战之OpenFeign)

Posted 浦江之猿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了微服务架构整理-(十一SpringCloud实战之OpenFeign)相关的知识,希望对你有一定的参考价值。

SpringCloud实战之OpenFeign

OpenFeign介绍

OpenFeign是 Netflix 公司开发的一个声明式的 REST 调用客户端,其前身为Feign,Feign在2019年5月停止了更新,直接转为OpenFeign,OpenFeign继承了Feign的所有特性,并且在OpenFeign中可以使用SpringMvc的注解,使其在调用服务时更方便。
之前的博客中提到过Ribbon和Hystrix[1,2,3], 它们是SpringCloud进行微服务开发过程中经常使用到的基础组件,每次使用时两者基本都会同时出现,并且配置也是非常相似,因此相同代码很多,从而造成代码冗余,在此基础上OpenFeign整合了Ribbon和Hystrix两个组件,让开发变得更加简单,不仅在配置上大大简化了开发工作,同时还提供了一种声明式的 Web 服务客户端定义方式。
本文给出使用OpenFeign定义消费者,并实现负责均衡和熔断的功能。

实现消费者功能

在之前的博文中,给出了服务提供者serive-product, 以及对应的服务消费者service-order, 这里使用OpenFeign定义一个服务消费者service-openfeign来代替service-order 。

创建Spring Boot 工程

springboot版本:2.2.6.RELEASE 。工程名:open_feign。启动类为:OpenFeignApplication

添加依赖

需要添加3个依赖,分别为eureka,hystrix,openfeign.

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            <version>2.2.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>

添加注解

启动类上添加@EnableFeignClients注解

@SpringBootApplication
//开启openfeign,此注解与feign注解一样
@EnableFeignClients
public class OpenFeignApplication 

	public static void main(String[] args) 
		SpringApplication.run(OpenFeignApplication.class, args);
	



声明服务

定义一个接口,接口中声明需要调用的服务,接口上一定要加@FeignClient注解来指定服务名称(忽略大小写),进而绑定服务,然后再通过SpringMVC 中提供的注解来绑定服务提供者提供的接口。这里以绑定SERVICE-PRODUCT中的/product/id为例。

@FeignClient(value = "SERVICE-PRODUCT")
public interface OrderService 
    @GetMapping(value = "/product/id")
    public Product findById(@PathVariable("id") Long id);

SERVICE-PRODUCT中的/product/id接口源码如下:

/**
 * @author :浦江之猿
 */
@RestController
@RequestMapping("/product")
public class ProductController 
    @Autowired
    ProductService productService;

    @GetMapping(value = "/id")
    public Product findById(@PathVariable Long id) 
        Product product = productService.findById(id);
        product.setName(product.getName() + "1");
        return product;
    

    @GetMapping(value = "/id/price")
    public Product findByIdAndPrice(@PathVariable Long id, @PathVariable BigDecimal price) 
        Product product = productService.findProductByIdAndPrice(id, price);
        product.setName(product.getName() + "1");
        return product;
    

    @PostMapping(value = "/addProduct")
    public Product addProduct(@RequestParam String name, @RequestParam BigDecimal price) 
        Product product = new Product();
        product.setName(name);
        product.setPrice(price);
        return productService.addProduct(product);
    

    @PutMapping(value = "/updateProduct")
    public void updateProduct(@RequestParam Long id, @RequestParam BigDecimal price) 
        Product product = productService.findById(id);
        product.setPrice(price);
        productService.updateProduct(product);
    

    @DeleteMapping(value = "/deleteProduct/id")
    public void deleteProduct(@PathVariable Long id) 
        productService.deleteById(id);
    


在Controller 中调用服务

定义一个Controller 来调用上面的service层,可以发现service层只有接口,没有接口的实现。

/**
 * @author :浦江之猿
 */
@RestController
public class OrderController 
    @Resource
    OrderService orderService;

    @GetMapping(value = "/buy/id")
    public Product findById(@PathVariable("id") Long id) 
        return orderService.findById(id);
    

添加配置文件

因为OpenFeign需要获取服务提供者且自己本身也是一个服务,因此配置文件中需要添加eureka。

server:
  port: 9007 #端口
spring:
  favicon:
    enabled: false
  application:
    name: service-openfeign #服务名称
#配置eureka server
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      #注册到两个eureka上
      defaultZone: http://localhost:9005/eureka/,http://localhost:9006/eureka/

结果

到这,整个OpenFeign客户端就搭建好了,浏览器中输入http://localhost:9007/buy/1。其结果如下:


    "id": 1,
    "name": "apple2",
    "price": 8000.00

Ok,整个服务就打通了,接下来看看OpenFeign是如何实现负载均衡功能和熔断功能的。

实现负载均衡功能

在声明服务创建接口时,就已经添加了负载均衡功能(通过@RequestMapping注解,可以理解为OpenFeign内置了Ribbon),只是这里的负载均衡算法用的是默认算法(轮询)。如果需要改变负载算法,则需要定义一个配置类,然后在配置类中注入需要的算法。以改成随机算法为例:

/**
 * @author :浦江之猿
 * @date :Created in 2022/5/31 22:02
 */
@Configuration
public class OpenFeignConfig 
    @Bean
    public IRule getIRule() 
        return new RandomRule();
    

关于负载均衡的其算法可以参考博文SpringCloud实战之Ribbon

实现熔断功能

OpenFeign整合了Hystrix,所有熔断原理一样,当出现异常时直接进行服务降级,回调异常处理函数。但是这里的异常处理函数与接口一一对应,不是太好理解,可以看下面的例子。OpenFeign中实现熔断需要两步:

配置熔断开关

配置文件中将熔断开关打开

#熔断开关打开
feign:
  hystrix:
    enabled: true

实现回调函数

为了更好的解释异常处理函数与接口一一对应,再声明一个服务,即在OrderService接口中再绑定一个服务提供者提供的接口。

@GetMapping(value = "/product/id/price")
public Product findByIdAndPrice(@PathVariable Long id, @PathVariable BigDecimal price);

接下来实现OrderService接口,实现类中实现的每个方法就是此接口对应的熔断回调函数。注意@Component注解一定要解,不然启动失败,报找不到实例异常。

@Component
public class OrderServiceImpl implements OrderService 

    @Override
    public Product findById(Long id) 
        return handleBreakCircuit(id, "A fake product from findById");
    

    @Override
    public Product findByIdAndPrice(Long id, BigDecimal price) 
        return handleBreakCircuit(id, "A fake product from findByIdAndPrice");
    

    public Product handleBreakCircuit(Long id, String des) 
        Product product = new Product();
        product.setId(0L);
        product.setName(des);
        product.setPrice(new BigDecimal(0));
        return product;
    



最后需要在声明接口中回调OrderServiceImple中的函数,只需要在@FeignClient注解添加fallback属性即可,完整的OrderService如下:

@FeignClient(value = "SERVICE-PRODUCT", fallback = OrderServiceImpl.class )
public interface OrderService 
    @GetMapping(value = "/product/id")
    public Product findById(@PathVariable("id") Long id);

    @GetMapping(value = "/product/id/price")
    public Product findByIdAndPrice(@PathVariable Long id, @PathVariable BigDecimal price);

相应的,在服务提供者1中针对findById选择制造一个超时(默认为1000ms),服务提供者2正常。

   @GetMapping(value = "/id")
    public Product findById(@PathVariable Long id) 
        Product product = productService.findById(id);
        //故意制造一个超时
        try 
            Thread.sleep(4000);
         catch (InterruptedException e) 
            e.printStackTrace();
        
        product.setName(product.getName() + "1");
        return product;
    

最终结果在以下两者之间随机切换:

//服务提供者1返回的结果

    "id": 0,
    "name": "A fake product from findById",
    "price": 0

//服务提供者2返回的结果

    "id": 1,
    "name": "apple2",
    "price": 8000.00

获取异常信息

通过fallback可以回调服务降级函数 ,但时当服务提供抛出异常后没法获取异常信息。OpenFeign中提供了fallbackFactory属性,在fallbackFactory中实现实现OrderService接口,就可以获取异常信息。用下面的类代替上面的OrderServiceImpl不仅能够回调降级函数 ,还能获取异常信息。

//注解不能少
@Component
public class OrderFallbackFactory implements FallbackFactory<OrderService> 

    @Override
    public OrderService create(Throwable arg0) 
        return new OrderService() 

            @Override
            public Product findById(Long id) 
                return handleBreakCircuit(id, "A fake product from findById");
            

            @Override
            public Product findByIdAndPrice(Long id, BigDecimal price) 
                return handleBreakCircuit(id, arg0.getMessage());
            

            public Product handleBreakCircuit(Long id, String des) 
                Product product = new Product();
                product.setId(0L);
                product.setName(des);
                product.setPrice(new BigDecimal(0));
                return product;
            

        ;
    


相应的,在服务提供者2中针对findByIdAndPrice选择制造除0异常,服务提供者1正常。

    @GetMapping(value = "/id/price")
    public Product findByIdAndPrice(@PathVariable Long id, @PathVariable BigDecimal price) 
    //制造一个除0异常
        int i = 1 / 0;
        Product product = productService.findProductByIdAndPrice(id, price);
        product.setName(product.getName() + "2");
        return product;
    

最终结果在以下两者之间随机切换:

//服务提供者1返回的结果

    "id": 1,
    "name": "apple1",
    "price": 8000.00

//服务提供者2返回的结果

    "id": 0,
    "name": "[500] during [GET] to [http://SERVICE-PRODUCT/product/1/8000] [OrderService#findByIdAndPrice(Long,BigDecimal)]: [\\"timestamp\\":\\"2022-06-02T12:51:06.846+0000\\",\\"status\\":500,\\"error\\":\\"Internal Server Error\\",\\"message\\":\\"/ by zero\\",\\"path\\":\\"/product/1/8000\\"]",
    "price": 0

总结

关于OpenFeign的基本使用就介绍完了,使用其来不是很难,细心就可以了。最后,希望本文能帮助大家,祝大家在IT之路上少走弯路,一路绿灯不堵车,测试一性通过,bug秒解!
源码下载

以上是关于微服务架构整理-(十一SpringCloud实战之OpenFeign)的主要内容,如果未能解决你的问题,请参考以下文章

微服务架构整理-(七SpringCloud实战之RestTemplate)

微服务架构整理-(七SpringCloud实战之RestTemplate)

微服务架构整理-(八SpringCloud实战之Hystrix [1])

微服务架构整理-(八SpringCloud实战之Hystrix [1])

微服务架构整理-(十SpringCloud实战之Hystrix [3])

微服务架构整理-(十SpringCloud实战之Hystrix [3])