springcloud第一篇:Eureka、Feign、Ribbon


Eureka是負責服務注冊與發現的。

Eureka分為服務端Eureka Server和客戶端Eureka Client。服務端就是注冊中心,可單機,可集群,生產上肯定集群。客戶端就是各業務應用,根據調用關系又分為服務提供者和服務調用者,這些應用都要注冊到服務端。

Eureka服務端和客戶端都是springboot應用。

Eureka服務端:本處講解單機Eureka Server。其實集群也非常簡單,就是把每個Eureka Server節點都注冊到其他Eureka server節點上。

1、在應用中引入Eureka Server依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

2、用@EnableEurekaServer標注啟動類

3、在配置文件中添加

spring.application.name=spring-cloud-eureka-server
server.port=8000
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8000/eureka/

以上兩個false表示不將自己注冊到Eureka Server。eureka.client.serviceUrl.defaultZone不能省,不然Eureka Server應用日志會一直報Network level connection to peer localhost; retrying after delay,雖然不影響注冊中心的使用。

啟動應用,訪問127.0.0.1:8000,可以看到注冊中心啟動成功

Eureka Server有定時任務:

每1分鍾會執行AbstractInstanceRegistry的run()方法。AbstractInstanceRegistry有個EvictionTask內部類,EvictionTask其實是個線程類,繼承了實現Runnable接口的TimerTask類。

每15分鍾更新renewal threshold。PeerAwareInstanceRegistryImpl類中,有一個Timer實例,每15分鍾執行一次updateRenewalThreshold()方法,這個15分鍾是由EurekaServerConfig接口的getRenewalThresholdUpdateIntervalMs()方法取的,默認15分鍾,我們可以在配置文件中用eureka.server.renewal-threshold-update-interval-ms修改間隔時間

Eureka Client之服務提供者

1、在一個對外提供RESTful接口服務的springboot應用中(比如說X管家應用)引入Eureka Client依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2、用@EnableDiscoveryClient標注啟動類

3、把自己注冊到Eureka Server。在配置文件中添加

spring.application.name=spring-cloud-eureka-client-producer
server.port=9000
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8000/eureka/

spring.application.name很重要,屆時服務調用者就根據這個來找服務提供者。eureka.client.serviceUrl.defaultZone值是Eureka Server的地址,如果Eureka Server有多個節點,則用逗號分隔。

啟動應用,刷新Eureka Server的頁面,發現Instances currently registered with Eureka處多了一行數據,正是剛剛啟動的Eureka Client,說明服務提供者注冊成功了。

Eureka Client之服務調用者

1、引入Eureka Client依賴,推薦用Feign調用服務。如果當前應用也對外提供RESTful接口,則還需要引入springboot的web starter。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</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>
</dependency>

2、用@EnableDiscoveryClient和@EnableFeignClients標注啟動類。

3、把自己注冊到Eureka Server。在配置文件中添加

spring.application.name=spring-cloud-eureka-client-consumer
server.port=9001
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8000/eureka/

4、寫Feign代碼,調用服務提供者的接口。

@FeignClient(name = "spring-cloud-eureka-client-producer")
@Component
public interface HelloRemote {
@RequestMapping(value = "/hello")
String hello(@RequestParam String name);
}

@FeignClient的name屬性值必須是Eureka Client服務提供者應用的spring.application.name值。@RequestMapping的value屬性值就是要調用的服務提供者的接口,本例中是/hello接口。用@RequestMapping標注的接口,既可以向服務提供者發送GET請求,又可以發送POST請求,默認是GET請求。如果想發起POST請求,則要用@PostMapping。不管是GET請求,還是POST請求,各參數都要用@RequestParam標注,否則發出的請求是不帶參數的。如果是發application/json請求,則要用@RequestBody標注參數,參數類型可以是Map,也可以是自定義實體類。服務提供者的接口如果用自定義實體類接收參數的話,則要有無參構造器,否則會報Invalid JSON input: Cannot construct instance。

在Controller或者Service代碼中注入HelloRemote即可,如此服務調用者就可以調用服務提供者提供的接口了。例如

@RestController
public class ConsumerController {

@Autowired
HelloRemote helloRemote;

@RequestMapping("/hello/{name}")
public String index(@PathVariable("name") String name) {
return helloRemote.hello(name);
}

}

啟動Eureka Client服務調用者,刷新Eureka Server頁面,會發現Instances currently registered with Eureka處新增了本應用。

在瀏覽器中調用服務消費者提供的/hello/{name}接口,如127.0.0.1:9001/hello/zhansan,服務消費者在處理請求過程中會調用服務提供者提供的/hello接口,即127.0.0.1:9000/hello/zhansan,服務提供者響應數據給服務調用者,服務調用者得到數據后再處理,最終響應數據給前端頁面。

Eureka client啟動的時候會向Eureka server發送注冊請求,Eureka server的類會處理這些請求。

客戶端的DiscoveryClient在實例化時,會構造幾個定時任務,拉取注冊信息的任務、發送心跳的任務,默認都是30s執行一次。

Eureka自我保護機制:

在某些情況下,Eureka Server頁面會出現

這就說明Eureka自我保護機制在起作用了。

Feign

Feign能夠在接口上添加注釋,成為一個REST API的客戶端。用@FeignClient標注接口,其name屬性值表示調用哪個服務,fallback值是當前接口的實現類的class實例,在服務提供者不穩定或宕機導致熔斷時,提供降級數據。

上面Feign的配置,在調用第三方接口時,使用的是jdk原生的HttpURLConnection發送http請求,沒有連接池。可以在服務提供者應用中查看請求的user-agent請求頭來驗證這一點,此時user-agent的值是Java。

這里可以優化下,用HttpClient,通過設置連接池、超時時間等對服務之間的調用進行優化。

如果用HttpClient,則需要修改如下

1、引入Feign和HttpClient的整合包

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
    <version>10.8</version>
</dependency>

2、在配置文件中添加

feign.httpclient.enabled=true

這樣就可以了。在服務提供者應用中查看請求的user-agent請求頭,發現user-agent的值變為Apache-HttpClient。連接池的最大連接數、每個連接的存活時間會用一些默認配置,這些默認配置可以在配置文件中用feign.httpclient.來查看,如果想更改這些參數,只需新增配置項覆蓋默認配置即可。注意,feign.httpclient.connection-timeout這個配置本是用來指定連接超時時間的,但是實際使用不生效,超時配置需要使用feign.client.config。

超時重試機制:

重試只會在超時的情況下才會發生,404、50X的情況不會重試,每次重試都會根據負載均衡策略重新找服務器。

超時重試配置:

1、在配置文件中添加:

feign.client.config.default.connect-timeout=10000
feign.client.config.default.read-timeout=10000

default表示對所有的服務都適用,本例連接超時和讀取超時都是10s。如果想針對不同的服務指定不同的超時時間,把default用特定服務名替代即可。

2、生成一個Retryer實例

@Bean
public Retryer feignRetryer() {
    return new Retryer.Default(1000, 2000, 3);
}

Retryer是個接口,可以用其內部實現類Default來構造Retryer實例。第三個參數3表示最多執行3次,最多重試2次。

重試相關的源碼在SynchronousMethodHandler的invoke()方法中:

  public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template, options);
      } catch (RetryableException e) {
        try {
          retryer.continueOrPropagate(e);
        } catch (RetryableException th) {
          Throwable cause = th.getCause();
          if (propagationPolicy == UNWRAP && cause != null) {
            throw cause;
          } else {
            throw th;
          }
        }
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

重試這里有個死循環,第一次調用,假如不拋出超時異常,則跳出循環,否則捕獲異常,在catch語句中,我們可以做一些延時操作,使得可以稍微等一會再重試。如果重試次數達到上限,則在catch語句中拋出異常,跳出死循環。在創建Retryer實例時可以指定每次重試之前等多少時間以及重試幾次,默認會最多等1s,重試5次。

Ribbon

如果有多個服務提供者,則會自動用Ribbon做負載均衡。Ribbon提供多種負載均衡策略,如默認的ZoneAvoidanceRule、輪詢RoundRobinRule、根據響應時間加權輪詢WeightedResponseTimeRule、隨機RandomRule、可用性過濾AvailabilityFilteringRule、最少連接數BestAvailableRule、RetryRule。

假設一個應用內會調用多種服務,那么如何針對不同的服務指定不同的負載均衡策略呢?

假設想調用service1時根據響應時間加權輪詢,調用service2時隨機,則只需在配置文件中添加

service1.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.WeightedResponseTimeRule
service2.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

如果想所有的服務都用相同的負載均衡策略,比如說輪詢策略,則可以在啟動類所在包或其子包下新建一個config類,里面用@Bean生成一個IRule實例:

@Bean
public IRule myRule() {
return new RandomRule();
}

不生成IRule實例的話,默認所有服務的負載均衡策略都是ZoneAvoidanceRule。

我們也可以用spring cloud balancer替換Ribbon(在配置文件中,設置spring.cloud.loadbalancer.ribbon.enabled=false),但是spring cloud balancer目前只提供一個原生的負載策略,即輪詢,其他策略需要我們自己實現,所有還是Ribbon香。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM