SpringCloud(四)--Feign&Turbine

Feign

集成工具(功能整合)

  • 远程调用:声明式客户端
  • ribbon 负载均衡和重试
  • hystrix 降级和熔断

feign 声明式客户端接口

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

  • 用 feign 代替 hystrix+ribbon

SpringCloud(四)--Feign&Turbine_第1张图片
只需要声明一个抽象接口,就可以通过接口做远程调用,不需要再使用 RestTemplate 来调用

// 调用远程的商品服务,获取订单的商品列表
// 通过注解,配置:
// 1. 调用哪个服务
// 2. 调用服务的哪个路径
// 3. 向路径提交什么参数数据
@FeignClient(name="item-service")
public interface ItemClient {
 @GetMapping("/{orderId}")
 JsonResult> getItems(@PathVariable String orderId);
}

在这里使用 @GetMapping("/{orderId}"), 指定的是向远程服务调用的路径

新建 sp09-feign 项目

SpringCloud(四)--Feign&Turbine_第2张图片

SpringCloud(四)--Feign&Turbine_第3张图片

pom.xml
  • 需要添加 sp01-commons 依赖
application.yml
spring:
  application:
    name: feign
    
server:
  port: 3001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

主程序添加 @EnableDiscoveryClient@EnableFeignClients

package cn.tedu.sp09;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class Sp09FeignApplication {

    public static void main(String[] args) {
        SpringApplication.run(Sp09FeignApplication.class, args);
    }
}
feign 声明式客户端

feign 利用了熟悉的 spring mvc 注解来对接口方法进行设置,降低了我们的学习成本。
通过这些设置,feign可以拼接后台服务的访问路径和提交的参数
例如:

@GetMapping("/{userId}/score") 
JsonResult addScore(@PathVariable Integer userId, @RequestParam Integer score); 

当这样调用该方法:

service.addScore(7, 100);

那么 feign 会向服务器发送请求:

http://用户微服务/7/score?score=100
  • 注意:如果 score 参数名与变量名不同,需要添加参数名设置:
@GetMapping("/{userId}/score") 
JsonResult addScore(@PathVariable Integer userId, @RequestParam("score") Integer s

ItemClient

package cn.tedu.sp09.feign;

import cn.tedu.sp01.pojo.Item;
import cn.tedu.web.util.JsonResult;
import org.springframework.cloud.openfeign.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.RequestBody;

import java.util.List;

//根据服务id,从注册表得到主机地址
@FeignClient(name = "item-service")
public interface ItemClient {//封装了RestTemplate
    @GetMapping("{orderId}")//(反向)将请求拼接发送
    JsonResult> getItems(@PathVariable String orderId);

    @PostMapping("/decreaseNumber")//(反向)将请求拼接发送
    JsonResult decreaseNumber(@RequestBody List items);
}

UserClient

package cn.tedu.sp09.feign;

import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;


//根据服务id,从注册表得到主机地址
@FeignClient(name = "user-service")
public interface UserClient {//封装了RestTemplate
    @GetMapping("{userId}")//(反向)将请求拼接发送
    JsonResult getUser(@PathVariable Integer userId);

    //....../8/score?score=1000
    @GetMapping("/{userId}/score")//(反向)将请求拼接发送
    JsonResult addScore(@PathVariable Integer userId,
                           @RequestParam Integer score);//@RequestParam 不能省略
}

OrderClient

package cn.tedu.sp09.feign;

import cn.tedu.sp01.pojo.Order;
import cn.tedu.web.util.JsonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;


//根据服务id,从注册表得到主机地址
@FeignClient(name = "order-service")
public interface OrderClient {//封装了RestTemplate
    @GetMapping("{orderId}")//(反向)将请求拼接发送
    JsonResult getOrder(@PathVariable String orderId);

    @GetMapping("/")//(反向)将请求拼接发送
    JsonResult addOrder();
}

FeignController

package cn.tedu.sp09.controller;

import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
import cn.tedu.sp09.feign.ItemClient;
import cn.tedu.sp09.feign.OrderClient;
import cn.tedu.sp09.feign.UserClient;
import cn.tedu.web.util.JsonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@Slf4j
public class FeignController {
    @Autowired
    private ItemClient itemClient;
    @Autowired
    private UserClient userClient;
    @Autowired
    private OrderClient orderClient;
    //-----------item-service
    @GetMapping("/item-service/{orderId}")
    public JsonResult> getItems(@PathVariable String orderId){
        return itemClient.getItems(orderId);
    }

    @PostMapping("/item-service/decreaseNumber")
    public JsonResult decreaseNumber(@RequestBody List items){
        return itemClient.decreaseNumber(items);
    }
    //-----------user-service
    @GetMapping("/user-service/{userId}")
    public JsonResult getUser(@PathVariable Integer userId){
        return userClient.getUser(userId);
    }
    @GetMapping("/user-service/{userId}/score")
    public JsonResult addScore(@PathVariable Integer userId,Integer score){
        return userClient.addScore(userId, score);
    }
    //-----------order-service
    @GetMapping("/order-service/{orderId}")
    public JsonResult getOrder(@PathVariable String orderId){
        return orderClient.getOrder(orderId);
    }
    @GetMapping("/order-service")
    public JsonResult addOrder(){
        return orderClient.addOrder();
    }
}
调用流程

SpringCloud(四)--Feign&Turbine_第4张图片

启动服务,并访问测试

SpringCloud(四)--Feign&Turbine_第5张图片

Feign 集成 Ribbon 负载均衡和重试

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

重试的默认配置参数:

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

application.yml 配置 ribbon 超时和重试

  • ribbon.xxx 全局配置
  • item-service.ribbon.xxx 对特定服务实例的配置
spring:
  application:
    name: feign
server:
  port: 3001
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka,http://eureka2:2002/eureka
# 调整 ribbon 的重试次数
# 针对所有服务的通用配置
ribbon:
  MaxAutoRetries: 1
  MaxAutoRetriesNextServer: 2
  ConnectTimeout: 1000
  ReadTimeout: 500
#只针对 item-service这一个服务有效,对其它服务不应用这个配置
item-service:
  ribbon:
    MaxAutoRetries: 2
启动服务,访问测试

http://localhost:3001/item-service/35

Feign 集成 Hystrix降级和熔断

Feign默认不启用Hystrix,使用feign时不推荐启用Hystrix

启用Hystrix基础配置:

  1. hystrix起步依赖
  2. yml中配置启用hystrix
  3. 启动类添加注解 @EnableCircuitBreaker
application.yml 添加配置
feign:
  hystrix:
    enabled: true
添加配置,暂时减小降级超时时间,以便后续对降级进行测试
......

feign:
  hystrix:
    enabled: true
    
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 500

feign + hystrix 降级

feign 远程接口中指定降级类
ItemFeignService
...
@FeignClient(name="item-service", fallback = ItemFeignServiceFB.class)
public interface ItemFeignService {
...
UserFeignService
...
@FeignClient(name="user-service", fallback = UserFeignServiceFB.class)
public interface UserFeignService {
...
OrderFeignService
...
@FeignClient(name="order-service",fallback = OrderFeignServiceFB.class)
public interface OrderFeignService {
...
降级类

降级类需要实现声明式客户端接口,在实现的抽象方法中添加降级代码,
降级类需要添加 @Component 注解

ItemFeignServiceFB
package cn.tedu.sp09.service;

import java.util.List;
import org.springframework.stereotype.Component;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.web.util.JsonResult;

@Component
public class ItemFeignServiceFB implements ItemFeignService {

    @Override
    public JsonResult> getItems(String orderId) {
        return JsonResult.err("无法获取订单商品列表");
    }

    @Override
    public JsonResult decreaseNumber(List items) {
        return JsonResult.err("无法修改商品库存");
    }
}
UserFeignServiceFB
package cn.tedu.sp09.service;

import org.springframework.stereotype.Component;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;

@Component
public class UserFeignServiceFB implements UserFeignService {

    @Override
    public JsonResult getUser(Integer userId) {
        return JsonResult.err("无法获取用户信息");
    }

    @Override
    public JsonResult addScore(Integer userId, Integer score) {
        return JsonResult.err("无法增加用户积分");
    }
}
OrderFeignServiceFB
package cn.tedu.sp09.service;

import org.springframework.stereotype.Component;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.web.util.JsonResult;

@Component
public class OrderFeignServiceFB implements OrderFeignService {

    @Override
    public JsonResult getOrder(String orderId) {
        return JsonResult.err("无法获取商品订单");
    }

    @Override
    public JsonResult addOrder() {
        return JsonResult.err("无法保存订单");
    }
}
启动服务,访问测试

http://localhost:3001/item-service/35

SpringCloud(四)--Feign&Turbine_第6张图片

feign + hystrix 监控和熔断测试

SpringCloud(四)--Feign&Turbine_第7张图片

修改sp09-feign项目
pom.xml 添加 hystrix 起步依赖
  • feign 没有包含完整的 hystrix 依赖
    右键点击项目,编辑起步依赖,添加hystrix依赖
    SpringCloud(四)--Feign&Turbine_第8张图片

    org.springframework.cloud
    spring-cloud-starter-netflix-hystrix
主程序添加 @EnableCircuitBreaker
package cn.tedu.sp09;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableCircuitBreaker
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class Sp09FeignApplication {

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

sp09-feign 配置 actuator,暴露 hystrix.stream 监控端点

actuator 依赖

查看pom.xml, 确认已经添加了 actuator 依赖


    org.springframework.boot
    spring-boot-starter-actuator
application.yml 暴露 hystrix.stream 端点
management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream
启动服务,查看监控端点

http://localhost:3001/actuator
SpringCloud(四)--Feign&Turbine_第9张图片

hystrix dashboard

启动 hystrix dashboard 服务,填入 feign 监控路径,开启监控
访问 http://localhost:4001/hystrix

  • 填入 feign 监控路径:
    http://localhost:3001/actuator/hystrix.stream
  • 访问微服务,以产生监控数据

http://localhost:3001/item-service/35

http://localhost:3001/user-service/7
http://localhost:3001/user-service/7/score?score=100

http://localhost:3001/order-service/123abc
http://localhost:3001/order-service/

SpringCloud(四)--Feign&Turbine_第10张图片

熔断测试

  • 用 ab 工具,以并发50次,来发送20000个请求
`ab -n 20000 -c 50 http://localhost:3001/item-service/35` 
  • 断路器状态为 Open,所有请求会被短路,直接降级执行 fallback 方法

SpringCloud(四)--Feign&Turbine_第11张图片

order service 调用商品库存服务和用户服务

SpringCloud(四)--Feign&Turbine_第12张图片

修改 sp04-orderservice 项目,添加 feign,调用 item service 和 user service
  1. pom.xml
  2. application.yml
  3. 主程序
  4. ItemFeignService
  5. UserFeignService
  6. ItemFeignServiceFB
  7. UserFeignServiceFB
  8. OrderServiceImpl
pom.xml

SpringCloud(四)--Feign&Turbine_第13张图片

  • 右键点击项目编辑起步依赖,添加以下依赖:
  • actuator
  • feign
  • hystrix
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
主程序
package cn.tedu.sp04;

import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

//@EnableDiscoveryClient
//@SpringBootApplication

@EnableFeignClients
@SpringCloudApplication
public class Sp04OrderserviceApplication {

    public static void main(String[] args) {
        SpringApplication.run(Sp04OrderserviceApplication.class, args);
    }
}
ItemFeignService
package cn.tedu.sp04.order.feignclient;

import java.util.List;

import org.springframework.cloud.openfeign.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.RequestBody;

import cn.tedu.sp01.pojo.Item;
import cn.tedu.web.util.JsonResult;

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

    @PostMapping("/decreaseNumber")
    JsonResult decreaseNumber(@RequestBody List items);
}
UserFeignService
package cn.tedu.sp04.order.feignclient;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;

@FeignClient(name="user-service", fallback = UserFeignServiceFB.class)
public interface UserFeignService {
    @GetMapping("/{userId}")
    JsonResult getUser(@PathVariable Integer userId);

    @GetMapping("/{userId}/score") 
    JsonResult addScore(@PathVariable Integer userId, @RequestParam Integer score);
}
ItemFeignServiceFB
  • 获取商品列表的降级方法,模拟使用缓存数据
package cn.tedu.sp04.order.feignclient;

import java.util.Arrays;
import java.util.List;

import org.springframework.stereotype.Component;

import cn.tedu.sp01.pojo.Item;
import cn.tedu.web.util.JsonResult;

@Component
public class ItemFeignServiceFB implements ItemFeignService {

    @Override
    public JsonResult> 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 items) {
        return JsonResult.err("无法修改商品库存");
    }

}
UserFeignServiceFB
  • 获取用户信息的降级方法,模拟使用缓存数据
package cn.tedu.sp04.order.feignclient;

import org.springframework.stereotype.Component;

import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;

@Component
public class UserFeignServiceFB implements UserFeignService {

    @Override
    public JsonResult 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("无法增加用户积分");
    }

}
OrderServiceImpl
package cn.tedu.sp04.order.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
import cn.tedu.sp01.service.OrderService;
import cn.tedu.sp04.order.feignclient.ItemFeignService;
import cn.tedu.sp04.order.feignclient.UserFeignService;
import cn.tedu.web.util.JsonResult;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
    
    @Autowired
    private ItemFeignService itemService;
    @Autowired
    private UserFeignService userService;

    @Override
    public Order getOrder(String orderId) {
        //调用user-service获取用户信息
        JsonResult user = userService.getUser(7);
        
        //调用item-service获取商品信息
        JsonResult> items = itemService.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减少商品库存
        itemService.decreaseNumber(order.getItems());
        
        //TODO: 调用user-service增加用户积分
        userService.addScore(7, 100);
        
        log.info("保存订单:"+order);
    }

}
order-service 配置启动参数,启动两台服务器
  • --server.port=8201
  • --server.port=8202
    SpringCloud(四)--Feign&Turbine_第14张图片
启动服务,访问测试

SpringCloud(四)--Feign&Turbine_第15张图片

hystrix dashboard 监控 order service 断路器

SpringCloud(四)--Feign&Turbine_第16张图片

hystrix dashboard 监控 order service 断路器

SpringCloud(四)--Feign&Turbine_第17张图片

Turbine

hystrix + turbine 集群聚合监控

SpringCloud(四)--Feign&Turbine_第18张图片

hystrix dashboard 一次只能监控一个服务实例,使用 turbine 可以汇集监控信息,将聚合后的信息提供给 hystrix dashboard 来集中展示和监控

新建 sp10-turbine 项目

SpringCloud(四)--Feign&Turbine_第19张图片

SpringCloud(四)--Feign&Turbine_第20张图片

pom.xml


    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.2.1.RELEASE
         
    
    cn.tedu
    sp10-turbine
    0.0.1-SNAPSHOT
    sp10-turbine
    Demo project for Spring Boot

    
        1.8
        Hoxton.RELEASE
    

    
        
            org.springframework.cloud
            spring-cloud-starter-netflix-eureka-client
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-turbine
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
            
                
                    org.junit.vintage
                    junit-vintage-engine
                
            
        
    

    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

application.yml
spring:
  application:
    name: turbin
    
server:
  port: 5001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
      
turbine:
  app-config: order-service
  cluster-name-expression: new String("default")
主程序

添加 @EnableTurbine@EnableDiscoveryClient 注解

package cn.tedu.sp10;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.turbine.EnableTurbine;

@EnableTurbine
@EnableDiscoveryClient
@SpringBootApplication
public class Sp10TurbineApplication {

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

}
访问测试

SpringCloud(四)--Feign&Turbine_第21张图片

你可能感兴趣的:(springcloud,feign)