SpringCloud微服務治理二(Robbin,Hystix,Feign)

SpringCloud微服務治理一(介紹,環境搭建,Eureka)
SpringCloud微服務治理二(Robbin,Hystix,Feign)
SpringCloud微服務治理三(Zuul網關)html

6.負載均衡Ribbon

在剛纔的案例中,咱們啓動了一個user-service,而後經過DiscoveryClient來獲取服務實例信息,而後獲取ip和端口來訪問。java

可是實際環境中,咱們每每會開啓不少個user-service的集羣。此時咱們獲取的服務列表中就會有多個,到底該訪問哪個呢?算法

通常這種狀況下咱們就須要編寫負載均衡算法,在多個實例列表中進行選擇。spring

接下來,咱們就來使用Ribbon實現負載均衡。json

6.1.啓動兩個服務實例

首先咱們啓動兩個user-service實例,一個8081,一個8082。mybatis

Eureka監控面板:app

1525619546904

6.2.開啓負載均衡

由於Eureka中已經集成了Ribbon,因此咱們無需引入新的依賴。直接修改代碼:負載均衡

在RestTemplate的配置方法上添加@LoadBalanced註解:框架

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
複製代碼

修改調用方式,再也不手動獲取ip和端口,而是直接經過服務名稱調用:dom

@Service
public class UserService {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    public List<User> queryUserByIds(List<Long> ids) {
        List<User> users = new ArrayList<>();
        // 地址直接寫服務名稱便可
        String baseUrl = "http://user-service/user/";
        ids.forEach(id -> {
            // 咱們測試屢次查詢,
            users.add(this.restTemplate.getForObject(baseUrl + id, User.class));
            // 每次間隔500毫秒
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        return users;
    }
}
複製代碼

6.3.負載均衡策略

Ribbon默認的負載均衡策略是簡單的輪詢,咱們能夠測試一下:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = UserConsumerDemoApplication.class)
public class LoadBalanceTest {

    @Autowired
    RibbonLoadBalancerClient client;

    @Test
    public void test(){
        for (int i = 0; i < 100; i++) {
            ServiceInstance instance = this.client.choose("user-service");
            System.out.println(instance.getHost() + ":" + instance.getPort());
        }
    }
}

複製代碼

結果:

1525622357371

符合了咱們的預期推測,確實是輪詢方式。

SpringBoot也幫咱們提供了修改負載均衡規則的配置入口:

user-service:
 ribbon:
 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
複製代碼

格式是:{服務名稱}.ribbon.NFLoadBalancerRuleClassName

6.4.重試機制

Eureka的服務治理強調了CAP原則中的AP,便可用性和可靠性。它與Zookeeper這一類強調CP(一致性,可靠性)的服務治理框架最大的區別在於:Eureka爲了實現更高的服務可用性,犧牲了必定的一致性,極端狀況下它寧願接收故障實例也不肯丟掉健康實例,正如咱們上面所說的自我保護機制。

可是,此時若是咱們調用了這些不正常的服務,調用就會失敗,從而致使其它服務不能正常工做!這顯然不是咱們願意看到的。

咱們如今關閉一個user-service實例:

1525653565855

由於服務剔除的延遲,consumer並不會當即獲得最新的服務列表,此時再次訪問你會獲得錯誤提示:

1525653715488

可是此時,8081服務實際上是正常的。

所以Spring Cloud 整合了Spring Retry 來加強RestTemplate的重試能力,當一次服務調用失敗後,不會當即拋出一次,而是再次重試另外一個服務。

只須要簡單配置便可實現Ribbon的重試:

spring:
 cloud:
 loadbalancer:
 retry:
 enabled: true # 開啓Spring Cloud的重試功能
user-service:
 ribbon:
 ConnectTimeout: 250 # Ribbon的鏈接超時時間
 ReadTimeout: 1000 # Ribbon的數據讀取超時時間
 OkToRetryOnAllOperations: true # 是否對全部操做都進行重試
 MaxAutoRetriesNextServer: 1 # 切換實例的重試次數
 MaxAutoRetries: 1 # 對當前實例的重試次數
複製代碼

根據如上配置,當訪問到某個服務超時後,它會再次嘗試訪問下一個服務實例,若是不行就再換一個實例,若是不行,則返回失敗。切換次數取決於MaxAutoRetriesNextServer參數的值

引入spring-retry依賴

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>
複製代碼

咱們重啓user-consumer-demo,測試,發現即便user-service2宕機,也能經過另外一臺服務實例獲取到結果!

1525658269456

7.Hystix

7.1.簡介

Hystix是Netflix開源的一個延遲和容錯庫,用於隔離訪問遠程服務、第三方庫,防止出現級聯失敗。

7.2.熔斷器的工做機制:

正常工做的狀況下,客戶端請求調用服務API接口:

當有服務出現異常時,直接進行失敗回滾,服務降級處理:

當服務繁忙時,若是服務出現異常,不是粗暴的直接報錯,而是返回一個友好的提示,雖然拒絕了用戶的訪問,可是會返回一個結果。

7.3.動手實踐

7.3.1.引入依賴

首先在user-consumer中引入Hystix依賴:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
複製代碼

7.3.2.開啓熔斷

7.3.2.改造消費者

咱們改造user-consumer,添加一個用來訪問的user服務的DAO,而且聲明一個失敗時的回滾處理函數:

@Component
public class UserDao {

    @Autowired
    private RestTemplate restTemplate;

    private static final Logger logger = LoggerFactory.getLogger(UserDao.class);

    @HystrixCommand(fallbackMethod = "queryUserByIdFallback")
    public User queryUserById(Long id){
        long begin = System.currentTimeMillis();
        String url = "http://user-service/user/" + id;
        User user = this.restTemplate.getForObject(url, User.class);
        long end = System.currentTimeMillis();
        // 記錄訪問用時:
        logger.info("訪問用時:{}", end - begin);
        return user;
    }

    public User queryUserByIdFallback(Long id){
        User user = new User();
        user.setId(id);
        user.setName("用戶信息查詢出現異常!");
        return user;
    }
}
複製代碼
  • @HystrixCommand(fallbackMethod="queryUserByIdFallback"):聲明一個失敗回滾處理函數queryUserByIdFallback,當queryUserById執行超時(默認是1000毫秒),就會執行fallback函數,返回錯誤提示。
  • 爲了方便查看熔斷的觸發時機,咱們記錄請求訪問時間。

在原來的業務邏輯中調用這個DAO:

@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    public List<User> queryUserByIds(List<Long> ids) {
        List<User> users = new ArrayList<>();
        ids.forEach(id -> {
            // 咱們測試屢次查詢,
            users.add(this.userDao.queryUserById(id));
        });
        return users;
    }
}
複製代碼

7.3.3.改造服務提供者

改造服務提供者,隨機休眠一段時間,以觸發熔斷:

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public User queryById(Long id) throws InterruptedException {
        // 爲了演示超時現象,咱們在這裏然線程休眠,時間隨機 0~2000毫秒
        Thread.sleep(new Random().nextInt(2000));
        return this.userMapper.selectByPrimaryKey(id);
    }
}

複製代碼

7.3.4.啓動測試

而後運行並查看日誌:

id爲九、十、11的訪問時間分別是:

1525661641660

id爲12的訪問時間:

1525661669136

所以,只有12是正常訪問,其它都會觸發熔斷,咱們來查看結果:

1525661720656

7.3.5.優化

雖然熔斷實現了,可是咱們的重試機制彷佛沒有生效,是這樣嗎?

其實這裏是由於咱們的Ribbon超時時間設置的是1000ms:

1525666632542

而Hystix的超時時間默認也是1000ms,所以重試機制沒有被觸發,而是先觸發了熔斷。

因此,Ribbon的超時時間必定要小於Hystix的超時時間。

咱們能夠經過hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds來設置Hystrix超時時間。

hystrix:
 command:
  	default:
 execution:
 isolation:
 thread:
 timeoutInMillisecond: 6000 # 設置hystrix的超時時間爲6000ms
複製代碼

8.Feign

8.1.簡介

Feign能夠把Rest的請求進行隱藏,假裝成相似SpringMVC的Controller同樣。你不用再本身拼接url,拼接參數等等操做,一切都交給Feign去作。

8.2.快速入門

8.2.1.導入依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
複製代碼

8.2.2.Feign的客戶端

@FeignClient("user-service")
public interface UserFeignClient {

    @GetMapping("/user/{id}")
    User queryUserById(@PathVariable("id") Long id);
}
複製代碼
  • 首先這是一個接口,Feign會經過動態代理,幫咱們生成實現類。這點跟mybatis的mapper很像
  • @FeignClient,聲明這是一個Feign客戶端,相似@Mapper註解。同時經過value屬性指定服務名稱
  • 接口中的定義方法,徹底採用SpringMVC的註解,Feign會根據註解幫咱們生成URL,並訪問獲取結果

改造原來的調用邏輯,再也不調用UserDao:

@Service
public class UserService {

    @Autowired
    private UserFeignClient userFeignClient;

    public List<User> queryUserByIds(List<Long> ids) {
        List<User> users = new ArrayList<>();
        ids.forEach(id -> {
            // 咱們測試屢次查詢,
            users.add(this.userFeignClient.queryUserById(id));
        });
        return users;
    }
}
複製代碼

8.2.3.開啓Feign功能

咱們在啓動類上,添加註解,開啓Feign功能

@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
@EnableFeignClients // 開啓Feign功能
public class UserConsumerDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserConsumerDemoApplication.class, args);
    }
}
複製代碼
  • 你會發現RestTemplate的註冊被我刪除了。Feign中已經自動集成了Ribbon負載均衡,所以咱們不須要本身定義RestTemplate了

8.3.負載均衡

Feign中自己已經集成了Ribbon依賴和自動配置:

1525672070679

所以咱們不須要額外引入依賴,也不須要再註冊RestTemplate對象。

另外,咱們能夠像上節課中講的那樣去配置Ribbon,能夠經過ribbon.xx來進行全局配置。也能夠經過服務名.ribbon.xx來對指定服務配置:

user-service:
 ribbon:
 ConnectTimeout: 250 # 鏈接超時時間(ms)
 ReadTimeout: 1000 # 通訊超時時間(ms)
 OkToRetryOnAllOperations: true # 是否對全部操做重試
 MaxAutoRetriesNextServer: 1 # 同一服務不一樣實例的重試次數
 MaxAutoRetries: 1 # 同一實例的重試次數
複製代碼

8.4.Feign對Hystix的集成

經過下面的參數來開啓:

feign:
 hystrix:
 enabled: true # 開啓Feign的熔斷功能
複製代碼

可是,Feign中的Fallback配置不像Ribbon中那樣簡單了。

1)首先,咱們要定義一個類,實現剛纔編寫的UserFeignClient,做爲fallback的處理類

@Component
public class UserFeignClientFallback implements UserFeignClient {
    @Override
    public User queryUserById(Long id) {
        User user = new User();
        user.setId(id);
        user.setName("用戶查詢出現異常!");
        return user;
    }
}

複製代碼

2)而後在UserFeignClient中,指定剛纔編寫的實現類

@FeignClient(value = "user-service", fallback = UserFeignClientFallback.class)
public interface UserFeignClient {

    @GetMapping("/user/{id}")
    User queryUserById(@PathVariable("id") Long id);
}

複製代碼

8.5.請求壓縮

Spring Cloud Feign 支持對請求和響應進行GZIP壓縮,以減小通訊過程當中的性能損耗。經過下面的參數便可開啓請求與響應的壓縮功能:

feign:
 compression:
 request:
 enabled: true # 開啓請求壓縮
 response:
 enabled: true # 開啓響應壓縮
複製代碼

同時,咱們也能夠對請求的數據類型,以及觸發壓縮的大小下限進行設置:

feign:
 compression:
 request:
 enabled: true # 開啓請求壓縮
 mime-types: text/html,application/xml,application/json # 設置壓縮的數據類型
 min-request-size: 2048 # 設置觸發壓縮的大小下限
複製代碼

注:上面的數據類型、壓縮大小下限均爲默認值。

8.6.日誌級別

前面講過,經過logging.level.xx=debug來設置日誌級別。然而這個對Fegin客戶端而言不會產生效果。由於@FeignClient註解修改的客戶端在被代理時,都會建立一個新的Fegin.Logger實例。咱們須要額外指定這個日誌的級別才能夠。

1)設置com.leyou包下的日誌級別都爲debug

logging:
 level:
    com.leyou: debug
複製代碼

2)編寫配置類,定義日誌級別

@Configuration
public class FeignConfig {
    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}
複製代碼

這裏指定的Level級別是FULL,Feign支持4種級別:

  • NONE:不記錄任何日誌信息,這是默認值。
  • BASIC:僅記錄請求的方法,URL以及響應狀態碼和執行時間
  • HEADERS:在BASIC的基礎上,額外記錄了請求和響應的頭信息
  • FULL:記錄全部請求和響應的明細,包括頭信息、請求體、元數據。

3)在FeignClient中指定配置類:

@FeignClient(value = "user-service", fallback = UserFeignClientFallback.class, configuration = FeignConfig.class)
public interface UserFeignClient {
    @GetMapping("/user/{id}")
    User queryUserById(@PathVariable("id") Long id);
}
複製代碼

4)重啓項目,便可看到每次訪問的日誌:

1525674544569
相關文章
相關標籤/搜索