Feign

微服务应用中,ribbon 和 hystrix 总是同时出现,feign 整合了两者,并提供了声明式消费者客户端

总的来说就是集成工具,集成了

  • 远程调用
  • ribbon
  • hystrix
声明式的客户端

只需要声明一个抽象的接口,就可以通过接口方法调用远程服务

// 调用商品服务的声明式客户端接口
// 需要配置三条:调用哪个服务,调用这个服务的哪个路径,向这个路径提交什么参数
@FeignClient(name="item-service")//服务id
public interface ItemFeignClient {
    @GetMapping("/{orderId}")//请求路径
    JsonResult<List<Item>> getItems(@PathVariable String orderId);//传递参数
}
feign 利用了我们熟悉的 spring mvc 注解来对接口方法进行设置,方便我们使用
实现

创建feign项目 -->
添加依赖actuator/eureka client/feign/spring web -->
application.yml添加

spring: application: name: feign 
server: port: 3001 
eureka: client: service-url: defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

-->
主程序添加@EnableFeignClients -->
按业务添加声明式客户端:

@FeignClient("item-service")
public interface ItemFeignService {
    @GetMapping("/{orderId}")
    JsonResult<List<Item>> getItems(@PathVariable String orderId);
    @PostMapping("/decreaseNumber")
    JsonResult decreaseNumber(@RequestBody List<Item> items);
    }
@FeignClient("user-service")
public interface UserFeignService {
    @GetMapping("/{userId}")
    JsonResult<User> getUser(@PathVariable Integer userId);
    // 拼接路径 /{userId}/score?score=新增积分
    @GetMapping("/{userId}/score") 
    JsonResult addScore(@PathVariable Integer userId, @RequestParam Integer score);
    }

注意:由于@GetMapping等注解是由feign去解析,而不是springmvc解析,所以可能会因为环境原因无法拿到请求所传的参数,需要加上@RequestParam注解修饰参数,另外如果请求参数名与方法参数名不同,@RequestParam不能省略,并且要指定请求参数名:@RequestParam("score") Integer s -->
添加controller类,注入声明式客户端,将请求一一接受

@FeignClient("order-service")
public interface OrderFeignService {
    @GetMapping("/{orderId}")
    JsonResult<Order> getOrder(@PathVariable String orderId);

    @GetMapping("/")
    JsonResult addOrder();
}
@RestController
public class FeignController {
    @Autowired
    private ItemFeignService itemService;
    @Autowired
    private UserFeignService userService;
    @Autowired
    private OrderFeignService orderService;
    @GetMapping("/item-service/{orderId}")
    public JsonResult<List<Item>> getItems(@PathVariable String orderId) {
        return itemService.getItems(orderId);
    }
    @PostMapping("/item-service/decreaseNumber")
    public JsonResult decreaseNumber(@RequestBody List<Item> items) {
        return itemService.decreaseNumber(items);
    }
    @GetMapping("/user-service/{userId}")
    public JsonResult<User> getUser(@PathVariable Integer userId) {
        return userService.getUser(userId);
    }
    @GetMapping("/user-service/{userId}/score") 
    public JsonResult addScore(@PathVariable Integer userId, Integer score) {
        return userService.addScore(userId, score);
    }
    @GetMapping("/order-service/{orderId}")
    public JsonResult<Order> getOrder(@PathVariable String orderId) {
        return orderService.getOrder(orderId);
    }
    @GetMapping("/order-service")
    public JsonResult addOrder() {
        return orderService.addOrder();
    }
 }

feign整合ribbon

  • 无需额外配置,feign 默认已启用了 ribbon 负载均衡和重试机制。可以通过配置对参数进行调整

重试的默认配置参数:

ConnectTimeout=1000
ReadTimeout=1000
MaxAutoRetries=0
MaxAutoRetriesNextServer=1

虽然一般无需额外配置,但若是需要配置的话,可如下分别配置全局和局部:
application.yml 配置 ribbon 超时和重试

  • ribbon.xxx 全局配置
  • item-service.ribbon.xxx 对特定服务实例的配置

Feign 集成 Hystrix,添加降级代码

feign 默认没有启用 hystrix,添加配置,启用 hystrix

基础配置
  1. 添加完整hystrix依赖
  2. feign.hystrix.enabled=true
  3. 启动类上加@EnableCircuitBreaker注解
添加降级代码
  1. @FeignClient(name="服务id", fallback=降级类.class)
  2. 降级类需要实现声明式客户端接口,实现它的抽象方法
  3. 添加 @Component

例如下列代码:

@Component
public class ItemFeignServiceFB implements ItemFeignService {
    @Override
    public JsonResult<List<Item>> getItems(String orderId) {
        return JsonResult.err("无法获取订单商品列表");
    }

    @Override
    public JsonResult decreaseNumber(List<Item> items) {
        return JsonResult.err("无法修改商品库存");
    }
}
暴露监控端点
  1. 添加 actuator 依赖
  2. 暴露 hystrix.stream 监控端点
    m.e.w.e.i=hystrix.stream
management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

在order订单中添加feign调用item/user

1.添加依赖

右键点击项目编辑起步依赖,添加以下依赖:

  • actuator
  • feign
  • hystrix
<?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>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.tedu</groupId>
    <artifactId>sp04-orderservice</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sp04-orderservice</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <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>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>cn.tedu</groupId>
            <artifactId>sp01-commons</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>
                spring-cloud-starter-netflix-eureka-client
            </artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>
                spring-cloud-starter-netflix-hystrix
            </artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>
2.基本配置

application.yml配置:
ribbon 重试和 hystrix 超时这里没有设置,采用了默认值

spring:
  application:
    name: order-service
    
server:
  port: 8201  
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
      
feign:
  hystrix:
    enabled: true
    
management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

启动类配置注解:

@EnableCircuitBreaker//hystrix
@EnableFeignClients//feign
@SpringBootApplication
public class OrderserviceApplication {
    public static void main(String[] args) {
        SpringApplication.run(Sp04OrderserviceApplication.class, args);
 }
3.定义声明式接口

定义item/user声明式接口供order调用
@FeignClient(name="",fallback=)/@GetMapping/参数列表(@PathVariable/@RequestBody)

@FeignClient(name="item-service", fallback = ItemFeignServiceFB.class)
public interface ItemFeignService {
    @GetMapping("/{orderId}")
    JsonResult<List<Item>> getItems(@PathVariable String orderId);

    @PostMapping("/decreaseNumber")
    JsonResult decreaseNumber(@RequestBody List<Item> items);
}
@FeignClient(name="user-service", fallback = UserFeignServiceFB.class)
public interface UserFeignService {
    @GetMapping("/{userId}")
    JsonResult<User> getUser(@PathVariable Integer userId);

    @GetMapping("/{userId}/score") 
    JsonResult addScore(@PathVariable Integer userId, @RequestParam Integer score);
}
4.定义降级方法

仅作模拟,模拟使用缓存数据
@Component/实现重写方法

@Component
public class ItemFeignServiceFB implements ItemFeignService {

    @Override
    public JsonResult<List<Item>> getItems(String orderId) {
        if(Math.random()<0.5) {
            return JsonResult.ok().data(
            
                Arrays.asList(new Item[] {
                        new Item(1,"缓存aaa",2),
                        new Item(2,"缓存bbb",1),
                        new Item(3,"缓存ccc",3),
                        new Item(4,"缓存ddd",1),
                        new Item(5,"缓存eee",5)
                })
            
            );
        }
        return JsonResult.err("无法获取订单商品列表");
    }

    @Override
    public JsonResult decreaseNumber(List<Item> items) {
        return JsonResult.err("无法修改商品库存");
    }
}
@Component
public class UserFeignServiceFB implements UserFeignService {

    @Override
    public JsonResult<User> getUser(Integer userId) {
        if(Math.random()<0.4) {
            return JsonResult.ok(new User(userId, "缓存name"+userId, "缓存pwd"+userId));
        }
        return JsonResult.err("无法获取用户信息");
    }

    @Override
    public JsonResult addScore(Integer userId, Integer score) {
        return JsonResult.err("无法增加用户积分");
    }
}
5.orderServiceImpl完成远程调用
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    @Autowired
     private ItemClient itemClient;
     @Autowired
     private UserClient userClient;
     @Override
     public Order getOrder(String orderId) {
     //调用user-service获取用户信息 user 
     JsonResult<User> user = userClient.getUser(7);
     //调用item-service获取商品信息 items 
     JsonResult<List<Item>> items = itemClient.getItems(orderId);
     Order order = new Order();
     order.setId(orderId);
     order.setUser(user.getData());
     order.setItems(items.getData());
     return order;
     }
    @Override
     public void addOrder(Order order) {
            //调用item-service减少商品库存
     itemClient.decreaseNumber(order.getItems());
     //调用user-service增加用户积分
     userClient.addScore(order.getUser().getId(), 100);
     log.info("保存订单:"+order);
     }
 }

迈克丝
82 声望5 粉丝

一步一步学技术,踏踏实实涨经验,兴趣广泛,广交好友,希望大家多多指正/批评.