springcloud配置需要主要的地方


Eureka服務端 注冊中心

<!-- Eureka服務端 -->
<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

添加  @EnableEurekaServer // 聲明這個應用是一個EurekaServer

編寫配置:

server:
  port: 10086 # 端口
spring:
  application:
    name: eureka-server # 應用名稱,會在Eureka中顯示
eureka:
  client:
    register-with-eureka: false # 是否注冊自己的信息到EurekaServer,默認是true
    fetch-registry: false # 是否拉取其它服務的信息,默認是true
    service-url: # EurekaServer的地址,現在是自己的地址,如果是集群,需要加上其它Server的地址。
      defaultZone: http://127.0.0.1:${server.port}/eureka

啟動服務,並訪問:http://127.0.0.1:10086/eureka

將user-service注冊到Eureka  服務提供者

<!-- Eureka客戶端 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

通過添加@EnableDiscoveryClient來開啟Eureka客戶端功能

編寫配置

server:
  port: 8081
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb01
    username: root
    password: 123
    hikari:
      maximum-pool-size: 20
      minimum-idle: 10
  application:
    name: user-service # 應用名稱
mybatis:
  type-aliases-package: com.leyou.userservice.pojo
eureka:
  client:
    service-url: # EurekaServer地址
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true # 當調用getHostname獲取實例的hostname時,返回ip而不是host名稱
    ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的話會自己尋找

注意:

  • 這里我們添加了spring.application.name屬性來指定應用名稱,將來會作為應用的id使用。
  • 不用指定register-with-eureka和fetch-registry,因為默認是true 

消費者從Eureka獲取服務  服務消費者

方法與消費者類似,只需要在項目中添加EurekaClient依賴,就可以通過服務名稱來獲取信息了!

<!-- Eureka客戶端 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
@EnableDiscoveryClient // 開啟Eureka客戶端

修改配置:
server:
  port: 8080
spring:
  application:
    name: consumer # 應用名稱
eureka:
  client:
    service-url: # EurekaServer地址
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true # 當其它服務獲取地址時提供ip而不是hostname
    ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的話會自己尋找

高可用的Eureka Server

就是說啟用兩個注冊中心,互相向對方注冊。然后客戶端這邊的url寫成用逗號分隔的url地址

1)我們修改原來的EurekaServer配置:

server:
  port: 10086 # 端口
spring:
  application:
    name: eureka-server # 應用名稱,會在Eureka中顯示
eureka:
  client:
    service-url: # 配置其他Eureka服務的地址,而不是自己,比如10087
      defaultZone: http://127.0.0.1:10087/eureka

2)另外一台配置恰好相反:

server:
  port: 10087 # 端口
spring:
  application:
    name: eureka-server # 應用名稱,會在Eureka中顯示
eureka:
  client:
    service-url: # 配置其他Eureka服務的地址,而不是自己,比如10087
      defaultZone: http://127.0.0.1:10086/eureka

客戶端注冊服務到集群

因為EurekaServer不止一個,因此注冊服務的時候,service-url參數需要變化:

eureka:
  client:
    service-url: # EurekaServer地址,多個地址以','隔開
      defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka

服務提供者  心跳檢測

服務提供者要向EurekaServer注冊服務,並且完成服務續約等工作。

服務注冊

服務提供者在啟動時,會檢測配置屬性中的:eureka.client.register-with-erueka=true參數是否正確,事實上默認就是true。如果值確實為true,則會向EurekaServer發起一個Rest請求,並攜帶自己的元數據信息,Eureka Server會把這些信息保存到一個雙層Map結構中。第一層Map的Key就是服務名稱,第二層Map的key是服務的實例id。

服務續約

在注冊服務完成以后,服務提供者會維持一個心跳(定時向EurekaServer發起Rest請求),告訴EurekaServer:“我還活着”。這個我們稱為服務的續約(renew);

有兩個重要參數可以修改服務續約的行為:

eureka:
  instance:
    lease-expiration-duration-in-seconds: 90   
    lease-renewal-interval-in-seconds: 30      
  • lease-renewal-interval-in-seconds:服務續約(renew)的間隔,默認為30秒
  • lease-expiration-duration-in-seconds:服務失效時間,默認值90秒 

也就是說,默認情況下每個30秒服務會向注冊中心發送一次心跳,證明自己還活着。如果超過90秒沒有發送心跳,EurekaServer就會認為該服務宕機,會從服務列表中移除,這兩個值在生產環境不要修改,默認即可。

是在開發時,這個值有點太長了,經常我們關掉一個服務,會發現Eureka依然認為服務在活着。所以我們在開發階段可以適當調小

實例id

在Eureka監控頁面,查看服務注冊信息:

在status一列中,顯示以下信息:

  • UP(1):代表現在是啟動了1個示例,沒有集群
  • DESKTOP-2MVEC12:user-service:8081:是示例的名稱(instance-id),
    • 默認格式是:${hostname} + ${spring.application.name} + ${server.port}
    • instance-id是區分同一服務的不同實例的唯一標准,因此不能重復。

我們可以通過instance-id屬性來修改它的構成:

eureka:
  instance:
    instance-id: ${spring.application.name}:${server.port}

服務消費者   

獲取服務列表

當服務消費者啟動是,會檢測eureka.client.fetch-registry=true參數的值,如果為true,則會從Eureka Server服務的列表只讀備份,然后緩存在本地。並且每隔30秒會重新獲取並更新數據。我們可以通過下面的參數來修改:

eureka:
  client:
    registry-fetch-interval-seconds: 5

生產環境中,我們不需要修改這個值。

但是為了開發環境下,能夠快速得到服務的最新狀態,我們可以將其設置小一點。

失效剔除和自我保護---------------------eureka服務端需要的設置

失效剔除

有些時候,我們的服務提供方並不一定會正常下線,可能因為內存溢出、網絡故障等原因導致服務無法正常工作。Eureka Server需要將這樣的服務剔除出服務列表。因此它會開啟一個定時任務,每隔60秒對所有失效的服務(超過90秒未響應)進行剔除。

可以通過eureka.server.eviction-interval-timer-in-ms參數對其進行修改,單位是毫秒,生成環境不要修改。

這個會對我們開發帶來極大的不變,你對服務重啟,隔了60秒Eureka才反應過來。開發階段可以適當調整,比如10S

自我保護

我們關停一個服務,就會在Eureka面板看到一條警告:

這是觸發了Eureka的自我保護機制。當一個服務未按時進行心跳續約時,Eureka會統計最近15分鍾心跳失敗的服務實例的比例是否超過了85%。在生產環境下,因為網絡延遲等原因,心跳失敗實例的比例很有可能超標,但是此時就把服務剔除列表並不妥當,因為服務可能沒有宕機。Eureka就會把當前實例的注冊信息保護起來,不予剔除。生產環境下這很有效,保證了大多數服務依然可用。

但是這給我們的開發帶來了麻煩, 因此開發階段我們都會關閉自我保護模式:

eureka:
  server:
    enable-self-preservation: false # 關閉自我保護模式(缺省為打開)
    eviction-interval-timer-in-ms: 1000 # 掃描失效服務的間隔時間(缺省為60*1000ms)

負載均衡Robbin

在剛才的案例中,我們啟動了一個user-service,然后通過DiscoveryClient來獲取服務實例信息,然后獲取ip和端口來訪問。

但是實際環境中,我們往往會開啟很多個user-service的集群。此時我們獲取的服務列表中就會有多個,到底該訪問哪一個呢?

一般這種情況下我們就需要編寫負載均衡算法,在多個實例列表中進行選擇。

不過Eureka中已經幫我們集成了負載均衡組件:Ribbon,簡單修改代碼即可使用。

開啟負載均衡

因為Eureka中已經集成了Ribbon,所以我們無需引入新的依賴。直接修改代碼:

在RestTemplate的配置方法上添加@LoadBalanced注解:

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}

負載均衡策略

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

user-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

格式是:{服務名稱}.ribbon.NFLoadBalancerRuleClassName,值就是IRule的實現類。

重試機制

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

但是,此時如果我們調用了這些不正常的服務,調用就會失敗,從而導致其它服務不能正常工作!這顯然不是我們願意看到的。

因為服務剔除的延遲,consumer並不會立即得到最新的服務列表,此時再次訪問你會得到錯誤提示:

但是此時,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宕機,也能通過另一台服務實例獲取到結果!

Hystix,即熔斷器

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

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

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
@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函數,返回錯誤提示。
  • 為了方便查看熔斷的觸發時機,我們記錄請求訪問時間。

優化

雖然熔斷實現了,但是我們的重試機制似乎沒有生效,是這樣嗎?

其實這里是因為我們的Ribbon超時時間設置的是1000ms:

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

所以,Ribbon的超時時間一定要小於Hystix的超時時間。

我們可以通過hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds來設置Hystrix超時時間

hystrix:
  command:
    default:
        execution:
          isolation:
            thread:
              timeoutInMillisecond: 6000 # 設置hystrix的超時時間為6000ms

 Feign

在前面的學習中,我們使用了Ribbon的負載均衡功能,大大簡化了遠程調用時的代碼:

String baseUrl = "http://user-service/user/";
User user = this.restTemplate.getForObject(baseUrl + id, User.class)

如果就學到這里,你可能以后需要編寫類似的大量重復代碼,格式基本相同,無非參數不一樣。有沒有更優雅的方式,來對這些代碼再次優化呢?

這就是我們接下來要學的Feign的功能了。

導入依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

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,並訪問獲取結果 

開啟Feign功能

我們在啟動類上,添加注解,開啟Feign功能

@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
@EnableFeignClients // 開啟Feign功能
public class UserConsumerDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserConsumerDemoApplication.class, args);
    }
}

負載均衡

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

因此我們不需要額外引入依賴,也不需要再注冊RestTemplate對象。

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

Hystix支持

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

請求壓縮(了解)

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 # 設置觸發壓縮的大小下限

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

日志級別(了解)

前面講過,通過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:記錄所有請求和響應的明細,包括頭信息、請求體、元數據。

在FeignClient中指定配置類:

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

Zuul網關

快速入門

我們使用Spring Cloud Netflix中的Eureka實現了服務注冊中心以及服務注冊與發現;而服務間通過Ribbon或Feign實現服務的消費以及均衡負載;通過Spring Cloud Config實現了應用多環境的外部化配置以及版本管理。為了使得服務集群更為健壯,使用Hystrix的融斷機制來避免在微服務架構中個別服務出現異常時引起的故障蔓延。

在該架構中,我們的服務集群包含:內部服務Service A和Service B,他們都會注冊與訂閱服務至Eureka Server,而Open Service是一個對外的服務,通過均衡負載公開至服務調用方。我們把焦點聚集在對外服務這塊,直接暴露我們的服務地址,這樣的實現是否合理,或者是否有更好的實現方式呢?

先來說說這樣架構需要做的一些事兒以及存在的不足:

  • 首先,破壞了服務無狀態特點。 
  • 為了保證對外服務的安全性,我們需要實現對服務訪問的權限控制,而開放服務的權限控制機制將會貫穿並污染整個開放服務的業務邏輯,這會帶來的最直接問題是,破壞了服務集群中REST API無狀態的特點。
    從具體開發和測試的角度來說,在工作中除了要考慮實際的業務邏輯之外,還需要額外考慮對接口訪問的控制處理。

  • 其次,無法直接復用既有接口。
  • 當我們需要對一個即有的集群內訪問接口,實現外部服務訪問時,我們不得不通過在原有接口上增加校驗邏輯,或增加一個代理調用來實現權限控制,無法直接復用原有的接口

面對類似上面的問題,我們要如何解決呢?答案是:服務網關!

為了解決上面這些問題,我們需要將權限控制這樣的東西從我們的服務單元中抽離出去,而最適合這些邏輯的地方就是處於對外訪問最前端的地方,我們需要一個更強大一些的均衡負載器的 服務網關。

服務網關是微服務架構中一個不可或缺的部分。通過服務網關統一向外系統提供REST API的過程中,除了具備服務路由、均衡負載功能之外,它還具備了權限控制等功能。Spring Cloud Netflix中的Zuul就擔任了這樣的一個角色,為微服務架構提供了前門保護的作用,同時將權限控制這些較重的非業務邏輯內容遷移到服務路由層面,使得服務集群主體能夠具備更高的可復用性和可測試性。

  • 不管是來自於客戶端(PC或移動端)的請求,還是服務內部調用。一切對服務的請求都會經過Zuul這個網關,然后再由網關來實現 鑒權、動態路由等等操作。Zuul就是我們服務的統一入口。 
<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>

通過@EnableZuulProxy注解開啟Zuul的功能

編寫配置

server:
  port: 10010 #服務端口
spring: 
  application:  
    name: api-gateway #指定服務名

zuul:
  routes:
    user-service: # 這里是路由id,隨意寫
      path: /user-service/** # 這里是映射路徑
      url: http://127.0.0.1:8081 # 映射路徑對應的實際url地址

我們將符合path 規則的一切請求,都代理到 url參數指定的地址

本例中,我們將 /user-service/**開頭的請求,代理到http://127.0.0.1:8081

面向服務的路由

在剛才的路由規則中,我們把路徑對應的服務地址寫死了!如果同一服務有多個實例的話,這樣做顯然就不合理了。

我們應該根據服務的名稱,去Eureka注冊中心查找 服務對應的所有實例列表,然后進行動態路由才對!

添加Eureka客戶端依賴

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

開啟Eureka客戶端發現功能

@SpringBootApplication
@EnableZuulProxy // 開啟Zuul的網關功能
@EnableDiscoveryClient
public class ZuulDemoApplication {

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

添加Eureka配置,獲取服務信息

zuul:
  routes:
    user-service: # 這里是路由id,隨意寫
      path: /user-service/** # 這里是映射路徑
      serviceId: user-service # 指定服務名稱

eureka:
  client:
    registry-fetch-interval-seconds: 5 # 獲取服務列表的周期:5s
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true
    ip-address: 127.0.0.1

 簡化的路由配置

在剛才的配置中,我們的規則是這樣的:

  • zuul.routes.<route>.path=/xxx/**: 來指定映射路徑。<route>是自定義的路由名
  • zuul.routes.<route>.serviceId=/user-service:來指定服務名。

而大多數情況下,我們的<route>路由名稱往往和 服務名會寫成一樣的。因此Zuul就提供了一種簡化的配置語法:zuul.routes.<serviceId>=<path>

比方說上面我們關於user-service的配置可以簡化為一條:

zuul:
  routes:
    user-service: /user-service/** # 這里是映射路徑

省去了對服務名稱的配置。

默認的路由規則

在使用Zuul的過程中,上面講述的規則已經大大的簡化了配置項。但是當服務較多時,配置也是比較繁瑣的。因此Zuul就指定了默認的路由規則:

  • 默認情況下,一切服務的映射路徑就是服務名本身。
    • 例如服務名為:user-service,則默認的映射路徑就是:/user-service/**

也就是說,剛才的映射規則我們完全不配置也是OK的,不信就試試看。

路由前綴

配置示例:

zuul:
  prefix: /api # 添加路由前綴
  routes:
      user-service: # 這里是路由id,隨意寫
        path: /user-service/** # 這里是映射路徑
        service-id: user-service # 指定服務名稱

我們通過zuul.prefix=/api來指定了路由的前綴,這樣在發起請求時,路徑就要以/api開頭。

路徑/api/user-service/user/1將會被代理到/user-service/user/1

網關過濾器

Zuul作為網關的其中一個重要功能,就是實現請求的鑒權。而這個動作我們往往是通過Zuul提供的過濾器來實現的。

ZuulFilter

ZuulFilter是過濾器的頂級父類。在這里我們看一下其中定義的4個最重要的方法:

public abstract ZuulFilter implements IZuulFilter{

    abstract public String filterType();

    abstract public int filterOrder();

    boolean shouldFilter();// 來自IZuulFilter

    Object run() throws ZuulException;// IZuulFilter
}
  • shouldFilter:返回一個Boolean值,判斷該過濾器是否需要執行。返回true執行,返回false不執行。
  • run:過濾器的具體業務邏輯。
  • filterType:返回字符串,代表過濾器的類型。包含以下4種:
    • pre:請求在被路由之前執行
    • routing:在路由請求時調用
    • post:在routing和errror過濾器之后調用
    • error:處理請求時發生錯誤調用
  • filterOrder:通過返回的int值來定義過濾器的執行順序,數字越小優先級越高。 

過濾器執行生命周期

  • 正常流程:
    • 請求到達首先會經過pre類型過濾器,而后到達routing類型,進行路由,請求就到達真正的服務提供者,執行請求,返回結果后,會到達post過濾器。而后返回響應。
  • 異常流程:
    • 整個過程中,pre或者routing過濾器出現異常,都會直接進入error過濾器,再error處理完畢后,會將請求交給POST過濾器,最后返回給用戶。
    • 如果是error過濾器自己出現異常,最終也會進入POST過濾器,而后返回。
    • 如果是POST過濾器出現異常,會跳轉到error過濾器,但是與pre和routing不同的時,請求不會再到達POST過濾器了。  

使用場景

場景非常多:

  • 請求鑒權:一般放在pre類型,如果發現沒有訪問權限,直接就攔截了
  • 異常處理:一般會在error類型和post類型過濾器中結合來處理。
  • 服務調用時長統計:pre和post結合使用。 

網關自定義過濾器

接下來我們來自定義一個過濾器,模擬一個登錄的校驗。基本邏輯:如果請求中有access-token參數,則認為請求有效,放行。

定義過濾器類

@Component
public class LoginFilter extends ZuulFilter{
    @Override
    public String filterType() {
        // 登錄校驗,肯定是在前置攔截
        return "pre";
    }

    @Override
    public int filterOrder() {
        // 順序設置為1
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        // 返回true,代表過濾器生效。
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        // 登錄校驗邏輯。
        // 1)獲取Zuul提供的請求上下文對象
        RequestContext ctx = RequestContext.getCurrentContext();
        // 2) 從上下文中獲取request對象
        HttpServletRequest req = ctx.getRequest();
        // 3) 從請求中獲取token
        String token = req.getParameter("access-token");
        // 4) 判斷
        if(token == null || "".equals(token.trim())){
            // 設置為fasle,意思是不讓后面的過濾器再執行,直接返回
            ctx.setSendZuulResponse(false);
            // 返回401狀態碼。也可以考慮重定向到登錄頁。
            ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
        }
        // 校驗通過,可以考慮把用戶信息放入上下文,繼續向后執行
        return null;
    }
}

網關負載均衡和熔斷

Zuul中默認就已經集成了Ribbon負載均衡和Hystix熔斷機制。但是所有的超時策略都是走的默認值,比如熔斷超時時間只有1S,很容易就觸發了。因此建議我們手動進行配置:

zuul:
  retryable: true
ribbon:
  ConnectTimeout: 250 # 連接超時時間(ms)
  ReadTimeout: 2000 # 通信超時時間(ms)
  OkToRetryOnAllOperations: true # 是否對所有操作重試
  MaxAutoRetriesNextServer: 2 # 同一服務不同實例的重試次數
  MaxAutoRetries: 1 # 同一實例的重試次數
hystrix:
  command:
    default:
        execution:
          isolation:
            thread:
              timeoutInMillisecond: 6000 # 熔斷超時時長:6000ms

微服務檢測接口(可以在每個springboot項目中加入)

目的,加入之后,不用寫controller也可以訪問,因為加入之后會自動給你提供幾個接口。比如 /info

<!--是springboot提供的微服務檢測接口,默認對外提供幾個接口:/info-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

配置文件集合

EurekaServer

server:
  port: 10086
spring:
  application:
    name: ly-registry
eureka:
  client:
    fetch-registry: false
    register-with-eureka: false
    service-url:
      defaultZone: http://127.0.0.1:${server.port}/eureka
  server:
    enable-self-preservation: false # 關閉自我保護
    eviction-interval-timer-in-ms: 5000 # 每隔5秒進行一次服務列表清理

Zuul網關

server:
  port: 10010
spring:
  application:
    name: api-gateway
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
    registry-fetch-interval-seconds: 5
  instance:
    prefer-ip-address: true
    ip-address: 127.0.0.1
    instance-id: ${spring.application.name}:${server.port}
zuul:
  prefix: /api # 添加路由前綴
  retryable: true
ribbon:
  ConnectTimeout: 250 # 連接超時時間(ms)
  ReadTimeout: 2000 # 通信超時時間(ms)
  OkToRetryOnAllOperations: true # 是否對所有操作重試
  MaxAutoRetriesNextServer: 1 # 同一服務不同實例的重試次數
  MaxAutoRetries: 1 # 同一實例的重試次數
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMillisecond: 10000 # 熔斷超時時長:10000ms

 


免責聲明!

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



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