1. 程式人生 > >SpringCloud微服務Eurehe和Ribbon+RestTempale/Feign元件

SpringCloud微服務Eurehe和Ribbon+RestTempale/Feign元件

Spring Cloud微服務

什麼是Spring Cloud

Spring體系下的微服務一站式解決方案,通常和Spring Boot整合在一起使用,可非常方便的開發出高效易用的微服務架構,Spring Cloud官方給出了21種元件的開發與支援

SpringCloud是基於SpringBoot的一整套實現微服務的框架。他提供了微服務開發所需的配置管理、服務發現、斷路器、智慧路由、微代理、控制匯流排、全域性鎖、決策競選、分散式會話和叢集狀態管理等元件。最重要的是,跟spring boot框架一起使用的話,會讓你開發微服務架構的雲服務非常好的方便。

關於微服務

單體架構中,所有的程式碼集中在同一個專案中。雖然便於管理,但是當專案足夠龐大時,
所有的業務模組都集中在一個JVM程序中,會面臨很多問題:
1、專案過於臃腫,難以維護
2、資源無法隔離,某個資源出現問題,整個系統崩潰
3、拓展性差,通常只能水平拓展,缺乏靈活性

什麼是微服務

SOA代表一種面向服務的架構,微服務本身就是SOA的一種延伸;

微服務架構風格是一種將一個單一應用程式開發為一組小型服務的方法,每個服務執行在自己的程序中,服務間通訊採用輕量級機制(通常用HTTP資源API);這些服務圍繞業務能力構建並且可以通過全自動部署機制獨立部署;這些服務共用一個最小型的集中式的管理,服務可用不同的語法開發,使用不同的資料儲存技術;

微服務的特點

1.根據業務模組劃分服務
2.每個服務可以獨立部署並且互相隔離
3.通過輕量的API呼叫服務(Http,Restful,RPC,MQ)
4.服務需要保證良好的高可用性

微服務和SOA架構的區別

微服務是SOA發展出來的產物,他是一種比較現代化的細粒度的SOA實現方式

討論微服務和SOA的差別意義遠不如討論微服務和單體系統的差別

網際網路近些年的發展,越來越朝去中心化的方向前進了,並沒有權威的機構來對它進行定義,使得每一個人都可以根據自己本身出發進行不同的調整

微服務的缺點

1.專案顆粒度太細,增加了專案管理的難度
2.遠端呼叫帶來的效能消耗
3.跨服務的測試更為複雜
4.運維的成本更高
5.需要強大的整體規劃與設計能力

Spring Cloud的子專案

官網 21種

基本工程結構

1.SpringBoot-pom工程
2.實現多繼承

<!--解決繼承多個父依賴-->
<!--依賴管理-->
<dependencyManagement>
    <dependencies>
        <dependency>
            <!--Spring Cloud工程的座標-->
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Finchley.SR1</version>
            <type>pom</type>
            <!--import只能用於dependencyManagement並且type必須為Pom-->
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

服務的發現與註冊-Eureka

什麼是Eureka

Eureka是Spring Cloud Netflix微服務套件中的一部分,可以與Springboot構建的微服務很容易的整合起來。

Eureka包含了伺服器端和客戶端元件。伺服器端,也被稱作是服務註冊中心,用於提供服務的註冊與發現。Eureka支援高可用的配置,當叢集中有分片出現故障時,Eureka就會轉入自動保護模式,它允許分片故障期間繼續提供服務的發現和註冊,當故障分片恢復正常時,叢集中其他分片會把他們的狀態再次同步回來。

客戶端元件包含服務消費者與服務生產者。在應用程式執行時,Eureka客戶端向註冊中心註冊自身提供的服務並週期性的傳送心跳來更新它的服務租約。同時也可以從服務端查詢當前註冊的服務資訊並把他們快取到本地並週期性的重新整理服務狀態。

Dubbo的工作模型和Eureka的工作模型

1.zookeeper是程序,Eureka是工程
服務的提供者:
zk.jar/Eureka客戶端(微服務)
服務的消費者:
zk.jar:發現服務獲得遠端呼叫地址/Eureka客戶端(微服務)
2.在Dubbo有明確的角色,在Eureka中並沒有明確,每一個微服務都需要註冊,可以互相呼叫
3.Dubbt的協議:Dubbo,Eureka協議:Http Restful
4.ZooKeeper服務註冊和發現是使用ZooKeeper協議,Euraka是使用TCP/IP協議

Eureka服務端

配置啟動類

/**
 * 使用一個元件
 * 1.新增起步依賴
 * 2.配置@EnableXXX
 * 3.配置檔案application.yml
 */
@SpringBootApplication
@EnableEurekaServer
public class SpringcloudEurekaServerApplication {

配置application.yml

server:
  port: 8080
eureka:
  instance:
    # 配置例項主機名稱
    hostname: localhost
    # 配置Eureka客戶端 - 為叢集做準備
  client:
    service-url:
      # 配置map key: value形式 defaultZone是固定的
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
    # 額外的配置
    # Eureka認為自己也是一個微服務所以也會註冊自己到服務端
    # false禁止自我註冊
    register-with-eureka: true
    # false禁止註冊中心調取服務
    fetch-registry: false
spring:
  application:
    name: eureka-server
搭建Eureka註冊中心叢集

serverName相同,hostname不同然後defaultZone配置其他兩個服務端的url即可

server:
  port: 8080
eureka:
  instance:
    hostname: www.aaa.com
  client:
    service-url:
      defaultZone: http://www.bbb.com:8082/eureka,http://www.ccc.com:8083/eureka

server:
  port: 8082
eureka:
  instance:
    hostname: www.bbb.com
  client:
    service-url:
      defaultZone: http://www.aaa.com:8080/eureka,http://www.ccc.com:8083/eureka
    register-with-eureka: true
    fetch-registry: false
spring:
  application:
    name: eureka-server
server:
  port: 8083
eureka:
  instance:
    hostname: www.ccc.com
  client:
    service-url:
      defaultZone: http://www.aaa.com:8080/eureka,http://www.bbb.com:8082/eureka
    register-with-eureka: true
    fetch-registry: false
spring:
  application:
    name: eureka-server

Eureka提供者

server:
  port: 8081
eureka:
  client:
    service-url:
      defaultZone: http://www.aaa.com:8080/eureka,http://www.bbb.com:8082/eureka,http://www.ccc.com:8083/eureka  
  instance:
    # Eureka修改註冊服務的標識名稱與ip顯示
    instance-id: provider1
    prefer-ip-address: true
spring:
  application:
    name: provider1

搭建提供者叢集

spring-application-name相同instance-id不同即可 預設輪詢

server:
  port: 8086
eureka:
  client:
    service-url:
      defaultZone: http://www.aaa.com:8080/eureka,http://www.bbb.com:8082/eureka,http://www.ccc.com:8083/eureka
  instance:
    instance-id: provider2
    prefer-ip-address: true
spring:
  application:
    name: provider1

Eureka自我保護機制

什麼是自我保護機制

當Eureka服務端在一定時間內接收不到客戶端(為服務)傳送的心跳,註冊中心本應該移除該服務的例項,但是Eureka並不會移除,而是進入自我保護機制,將該失效的服務保護起來

首先對Eureka註冊中心需要了解的是Eureka各個節點都是平等的,沒有ZK中角色的概念, 即使N-1個節點掛掉也不會影響其他節點的正常執行。

預設情況下,如果Eureka Server在一定時間內(預設90秒)沒有接收到某個微服務例項的心跳,Eureka Server將會移除該例項。但是當網路分割槽故障發生時,微服務與Eureka Server之間無法正常通訊,而微服務本身是正常執行的,此時不應該移除這個微服務,所以引入了自我保護機制。

為什麼需要自我保護機制

Eureka叢集和ZooKeeper叢集不同,ZooKeeper叢集有角色的概念,而Eureka叢集中沒有角色的概念;在Eureka叢集中,所有節點完全平等,Eureka叢集允許掛掉N-1臺伺服器;

基於不同的結果體系,兩個叢集分別提供了兩種分割槽容錯性手段
1.ZooKeeper-過半保護機制
2.Eureka叢集-自我保護機制

觸發條件

在 1 分鐘後,Renews (last min) < Renews threshold * 0.85

Renews threshold:Eureka Server 期望每分鐘收到客戶端例項續約的總數
Renews (last min):Eureka Server 最後 1 分鐘收到客戶端例項續約的總數

自我保護解決方式
eureka:
  #server:
    #關閉自我保護機制-不推薦使用
    #enable-self-preservation: false
    #設定自我保護機制的閾值-不推薦使用 預設是85% Renews threshold/Renews (last min)<85%
    #renewal-percent-threshold: 0.49
    #部署多個Eureka Server變成客戶端 register-with-eureka: true-推薦
    
    #沒有進入自我保護模式是會移除微服務的 85-100%
注意:

Eureka 的自我保護模式是有意義的,該模式被啟用後,它不會從註冊列表中剔除因長時間沒收到心跳導致租期過期的服務,而是等待修復,直到心跳恢復正常之後,它自動退出自我保護模式。這種模式旨在避免因網路分割槽故障導致服務不可用的問題。例如,兩個客戶端例項 C1 和 C2 的連通性是良好的,但是由於網路故障,C2 未能及時向 Eureka 傳送心跳續約,這時候 Eureka 不能簡單的將 C2 從登錄檔中剔除。因為如果剔除了,C1 就無法從 Eureka 伺服器中獲取 C2 註冊的服務,但是這時候 C2 服務是可用的。

Eureka叢集(採用AP)

Eureka叢集中沒有角色的概念,所有Eureka服務端完全對等

任何一個EurekaClient(提供者/消費者)可以對任何一個EurekaServer進行任意讀寫操作,所有的寫操作,EurekaServer會同步到其他EurekaServer上達到最終一致性;

ZooKeeper和Eureka叢集的區別

1.Eureka沒有像ZooKeeper那樣的leader機制
2.Eureka支援健康檢查,自我保護等,ZooKeeper有過半數存活原則
3.服務列表變更ZooKeeper服務端會有watch機制,而Eureka是長輪詢機制
4.ZooKeeper是CP設計,Eureka是AP設計

CAP原則

任何一個分散式儲存系統的叢集都應該遵從CAP原則,但是隻能同時保證其中兩個原則

一般來說分割槽問題是不能避免的,所以P分割槽容錯是必選的,因此都是在A和C中選擇的

詳解
Consistency 強一致性

任何時候,訪問任意一臺叢集節點,得到的資料都是相同的

Availability 高可用性

任何時候叢集都能正常對外提供服務,不考慮非正常情況

Partition tolerance 分割槽容錯性

分割槽問題也就是腦裂,兩個伺服器是正常工作的,但是因為網路等原因無法互相通訊,分割槽容錯就是對分割槽問題有解決方案;

思考:redis叢集是CP還是AP?

AP,如果發生腦裂,redis就會變成兩個叢集,那麼無法保證C

服務的消費者-Ribbon+RestTemplate/Fegin(常用)

在微服務架構中,業務都會被拆分成一個獨立的服務,服務與服務的通訊是基於http restful的。Spring cloud有兩種服務呼叫方式:
1、ribbon+restTemplate
2、feign。

Ribbon+RestTemplate

Ribbon簡介:是一個基於HTTP和TCP客戶端的負載均衡呼叫器,用於實現微服務之間的通訊,它可以在客戶端配置 ribbonServerList(服務端列表),然後輪詢請求以實現均衡負載

SpringCloud和Ribbon+RestTemplate消費者實現

配置application.yml

server:
  port: 8084
eureka:
  client:
    service-url:
      defaultZone: http://www.aaa.com:8080/eureka,http://www.bbb.com:8082/eureka,http://www.ccc.com:8083/eureka
  instance:
    instance-id: consumer1
    prefer-ip-address: true
spring:
  application:
    name: consumer1

啟動項

@EnableEurekaClient
//開啟ribbon
@EnableDiscoveryClient
@SpringBootApplication
public class SpringcloudConsumerRibbonApplication {

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

    @Bean
    @LoadBalanced//負載均衡  LB
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

    /**
     * 1.切換成隨機的負載均衡演算法
     * 2.選擇一個最小併發
     * BestAvailableRule
     * 3.根據響應時間分配權重
     * WeightedResponseTimeRule
     * 4.在輪詢的負載均衡加上重試機制
     * RetryRule
     * @return
     */
    @Bean
    public IRule getRule(){
        return new RoundRobinRule();
    }
}

Controller實現消費

@Controller
@RequestMapping("/my")
public class MyController {


    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("/test")
    @ResponseBody
    public String test(){
        String result = restTemplate.getForObject("http://PROVIDER1/provider/hello/ccccc",String.class);
        return result;
    }

}
Ribbon負載均衡和Nginx的負載均衡有什麼不同?

Nginx是伺服器端負載均衡,負載均衡的策略演算法是在伺服器端實現的。
Ribbon是客戶端負載均衡,負載均衡演算法是由呼叫者本身維護的

負載均衡實現
@Bean
@LoadBalanced//該註解代表負載均衡
public RestTemplate getRestTemplate(){
	return new RestTemplate();
}
注意:如果不需要負載均衡(只有一個服務提供者)也必須新增該註解
負載均衡的核心IRule

IRule是Ribbon負載均衡演算法的核心實現介面

常用實現類

1.RoundRobinRule(預設):輪詢演算法
2.RandomRule:隨機演算法
3.BestAvailableRule:選擇一個最小的併發請求的server
4.WeightedResponseTimeRule:根據響應時間分配一個weight,
響應時間越長,weight越小,被選中的可能性越低。
5.RetryRule:在輪詢的負載均衡策略加上重試機制。

設定負載均衡策略
在啟動類中新增如下程式碼:
@Bean
public IRule getRule(){
	return new RandomRule();//任意的負載均衡策略物件
}
解決如果有兩種服務,其中有一種服務是希望輪詢而另外一種希望隨機

配置兩個Config配置類

public class IRuleConfig1 {
    @Bean
    public IRule getRule(){
        return new RoundRobinRule();
    }
}
public class IRuleConfig2 {
    @Bean
    public IRule getRule(){
        return new RandomRule();
    }
}

配置啟動項

@RibbonClients({@RibbonClient(name = "PROVIDER1",configuration = IRuleConfig1.class),
        @RibbonClient(name = "PROVIDER2",configuration = IRuleConfig2.class)})
public class SpringcloudConsumerRibbonApplication {
}

服務消費者-Feign

Feign是一個宣告式的偽Http客戶端,它使得寫Http客戶端變得更簡單。
使用Feign,只需要建立一個介面並註解,它具有可插拔的註解特性。
Feign預設集成了Ribbon,並和Eureka結合,預設實現了負載均衡的效果。

有了Ribbon為什麼還要Feign?

Ribbon需要結合RestTemplate物件來實現微服務呼叫的。
但是這種呼叫方式不太符合大多數程式設計師面向介面程式設計的習慣,
因此Feign本身繼承了Ribbon,使用註解+介面的方式實現了服務間的呼叫

Feign的特點

Feign的特點可以簡化客戶端的程式設計,只需要提供一個介面並且結合註解就可以實現微服務的呼叫,對應呼叫者來說就好像呼叫本地的方法一樣(有點類似於Dubbo的RPC直接操作Service)

SpringCloud和Feign消費者實現

配置application.yml

server:
  port: 8085
eureka:
  client:
    service-url:
      defaultZone: http://www.aaa.com:8080/eureka,http://www.bbb.com:8082/eureka,http://www.ccc.com:8083/eureka
  instance:
    instance-id: consumer2
    prefer-ip-address: true
spring:
  application:
    name: consumer2

啟動項

@EnableFeignClients
@SpringBootApplication
@EnableEurekaClient
public class SpringcloudConsumerFeignApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudConsumerFeignApplication.class, args);
    }
    @Bean
    public IRule getRule(){
        return new RandomRule();
    }
}

編寫Service介面,與提供者一致,但是不需要方法體
提供者

@Controller
@RequestMapping("/provider")
public class HelloController {

    @Value("${server.port}")
    private String port;
    @RequestMapping("/hello/{param}")
    @ResponseBody
    public String hello(@PathVariable("param") String param){
        return "呼叫了提供者" + port + "引數為:" + param;
    }

}

介面

@FeignClient("provider1")
@RequestMapping("/provider")
public interface ITestService {
    @RequestMapping("/hello/{param}")
    String hello(@PathVariable("param") String param);
}

Controller呼叫

@Controller
@RequestMapping("/my")
public class MyController {
    @Autowired
    private ITestService testService;

    @RequestMapping("/test")
    @ResponseBody
    public String test(){
        return testService.hello("123");
    }
}