SpringCloud(四)--Feign&Turbine

Feign

集成工具(功能整合)java

  • 遠程調用:聲明式客戶端
  • ribbon 負載均衡和重試
  • hystrix 降級和熔斷

feign 聲明式客戶端接口

微服務應用中,ribbon 和 hystrix 老是同時出現,feign 整合了二者,並提供了聲明式消費者客戶端web

  • 用 feign 代替 hystrix+ribbon

feign
只須要聲明一個抽象接口,就能夠經過接口作遠程調用,不須要再使用 RestTemplate 來調用spring

// 調用遠程的商品服務,獲取訂單的商品列表
// 經過註解,配置:
// 1. 調用哪一個服務
// 2. 調用服務的哪一個路徑
// 3. 向路徑提交什麼參數數據
@FeignClient(name="item-service")
public interface ItemClient {
 @GetMapping("/{orderId}")
 JsonResult<List<Item>> getItems(@PathVariable String orderId);
}

在這裏使用 @GetMapping("/{orderId}"), 指定的是向遠程服務調用的路徑shell

新建 sp09-feign 項目

在這裏插入圖片描述

在這裏插入圖片描述

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能夠拼接後臺服務的訪問路徑和提交的參數
例如:express

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

當這樣調用該方法:apache

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<List<Item>> getItems(@PathVariable String orderId);

    @PostMapping("/decreaseNumber")//(反向)將請求拼接發送
    JsonResult<?> decreaseNumber(@RequestBody List<Item> 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<User> 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<Order> 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<List<Item>> getItems(@PathVariable String orderId){
        return itemClient.getItems(orderId);
    }

    @PostMapping("/item-service/decreaseNumber")
    public JsonResult<?> decreaseNumber(@RequestBody List<Item> items){
        return itemClient.decreaseNumber(items);
    }
    //-----------user-service
    @GetMapping("/user-service/{userId}")
    public JsonResult<User> 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<Order> getOrder(@PathVariable String orderId){
        return orderClient.getOrder(orderId);
    }
    @GetMapping("/order-service")
    public JsonResult<?> addOrder(){
        return orderClient.addOrder();
    }
}
調用流程

流程

啓動服務,並訪問測試

啓動服務

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時不推薦啓用Hystrixmvc

啓用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<List<Item>> getItems(String orderId) {
        return JsonResult.err("沒法獲取訂單商品列表");
    }

    @Override
    public JsonResult decreaseNumber(List<Item> 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<User> 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<Order> getOrder(String orderId) {
        return JsonResult.err("沒法獲取商品訂單");
    }

    @Override
    public JsonResult addOrder() {
        return JsonResult.err("沒法保存訂單");
    }
}
啓動服務,訪問測試

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

訪問測試

feign + hystrix 監控和熔斷測試

監控

修改sp09-feign項目
pom.xml 添加 hystrix 起步依賴
  • feign 沒有包含完整的 hystrix 依賴
    右鍵點擊項目,編輯起步依賴,添加hystrix依賴
    編輯起步依賴
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
主程序添加 @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 依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
application.yml 暴露 hystrix.stream 端點
management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream
啓動服務,查看監控端點

http://localhost:3001/actuator
監控端點

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/

監控

熔斷測試

  • 用 ab 工具,以併發50次,來發送20000個請求
`ab -n 20000 -c 50 http://localhost:3001/item-service/35`
  • 斷路器狀態爲 Open,全部請求會被短路,直接降級執行 fallback 方法

壓力測試

order service 調用商品庫存服務和用戶服務

關係圖
sp09-feign項目關閉,再也不使用
關閉項目

修改 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

編輯起步依賴

  • 右鍵點擊項目編輯起步依賴,添加如下依賴:
  • 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<List<Item>> getItems(@PathVariable String orderId);

    @PostMapping("/decreaseNumber")
    JsonResult decreaseNumber(@RequestBody List<Item> 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<User> 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<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("沒法修改商品庫存");
    }

}
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<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("沒法增長用戶積分");
    }

}
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> user = userService.getUser(7);
        
        //調用item-service獲取商品信息
        JsonResult<List<Item>> 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
    配置啓動項
    配置啓動項
    複製
    配置啓動項
    啓動項
啓動服務,訪問測試

啓動服務

hystrix dashboard 監控 order service 斷路器

監控

hystrix dashboard 監控 order service 斷路器

監控

Turbine

hystrix + turbine 集羣聚合監控

turbine

hystrix dashboard 一次只能監控一個服務實例,使用 turbine 能夠聚集監控信息,將聚合後的信息提供給 hystrix dashboard 來集中展現和監控

新建 sp10-turbine 項目

新建項目

選擇依賴

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>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>sp10-turbine</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sp10-turbine</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
        <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-netflix-turbine</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>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

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

</project>
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);
    }

}
訪問測試

監控

相關文章
相關標籤/搜索