寫給大忙人的spring cloud 1.x學習指南 寫給大忙人spring boot 1.x學習指南 寫給大忙人spring boot 1.x學習指南

 

 這幾天抽空搞了下spring cloud 1.x(2.0目前應該來講還不成熟),由於以前項目中使用dubbo以及自研的rpc框架,因此整體下來仍是比較順利,加上spring boot,不算筆記整理,三天不到一點圍繞spring boot reference和spring microservice in action主要章節都看完並完整的搭建了spring cloud環境,同時仔細的思考並解決了一些spring cloud和書籍做者理想化假設的問題,有些在網上和官方文檔中沒有明確的答案,好比spring cloud sleuth如何使用log4j 2同時保留MDC。本文還會列出spring cloud和dubbo的一些異同和各自優劣勢總結,二者應該來講各有優劣勢,理想的架構若是各方面條件容許的話,其實能夠結合spring cloud+dubbo或者自研的rpc。固然本文不會完完整整的講解spring cloud整個技術棧的詳細細節,可是對於核心要點以及關鍵的特性/邏輯組件會更多的評述組件的設計是否合理,如何解決,必要的會引述第三方資源,這和其餘系列一模一樣。html

在開始介紹spring cloud的架構以前,筆者打算先梳理下spring cloud生態的各個組件,由於對於不少新人甚至老鳥來講,初看起來,spring cloud的組件以及版本很亂,查看官方文檔https://projects.spring.io/spring-cloud/(http://cloud.spring.io/spring-cloud-static/Edgware.SR3/single/spring-cloud.html,注:spring 5.0出來以後,pdf就沒有了:(),咱們能夠發現以下:前端

相對於spring framework來講,spring cloud的組織更像是spring template各系列,獨立發展,除了核心部分外,幾乎各組件沒有關聯或者關聯性很弱,他們只是基於這個框架,除非應用架構須要其特性,不然都不須要關心這些組件。對於微服務架構(假設使用 spring cloud的rpc)來講,只有兩個必備:java

  • spring boot。spring cloud基於spring boot打包方式,因此spring boot是必備的,後面咱們會詳細講解spring boot,實際上spring boot中的不少特性跟spring boot毫無關係,純粹是設計者有意推廣而不放在spring context或者spring context support中。
  • spring cloud netflix。spring cloud netflix是netflix開源的一套微服務框架,它提供了微服務架構的核心功能,包括服務註冊和發現中心Eureka、客戶端負載均衡器Ribbon、聲明式RPC調用Feign、路由管理Zuul、熔斷和降級管理Hystrix。對於大部分的RPC框架來講,基本上都會提供除了熔斷和降級管理外的全部特性,好比dubbo(http://dubbo.apache.org/)以及筆者在以前公司自行研發的rpc框架(https://gitee.com/zhjh256/io-spider)。
因此,spring cloud能夠說就是圍繞netflix的這套組件爲主,其餘都是輔助。

除了這兩個核心組件外,下列組件一般在大型系統中會一塊兒使用(中小型系統可能不會採用):mysql

  • spring cloud config。spring cloud config提供了集中式的配置管理中心,其存儲支持文件系統和git。
  • spring cloud sleuth/zipkin。spring cloud sleuth解決了分佈式系統環境中日誌的上下文關聯和鏈路追蹤問題。
  • spring cloud consul。spring cloud consul提供了另外一種服務中心、配置中心選擇。

在開始正式講解spring cloud前,還不得不提下spring cloud組件的版本,因爲spring cloud組件衆多,且由不一樣的社區主導開發,所以spring cloud的版本命名跟eclipse相似,不是使用數字遞增,而是採用城市名命名,每一個spring cloud包含一系列的組件,經過查看spring-cloud-dependencies maven構件的定義,咱們知道各自的版本。例如Edgware.SR3版本依賴的各組件版本以下:nginx

注:spring-cloud-dependencies是個應用必定會引入到dependencyManagement的依賴,它包含了特定版本的spring cloud組件的版本管理,直接引入能夠省事不少。git

從上述對spring cloud各組件的梳理,咱們能夠知道完整的spring cloud架構以下:github

 

 

最簡的spring cloud架構以下:web

 

 

如今,咱們來看下spring cloud的主要組件的核心功能以及dubbo中對應的特性。redis

  • 在dubbo微服務框架內,ribbon/hystrix集成到了dubbo核心包中,turbine則在dubbo-admin和dubbo-monitor中。
  • zuul proxy就是網關AR(咱們原來自研發的spider提供了該特性),這個組件在dubbo中沒有對應的實現。
  • spring cloud config是分佈式配置中心,dubbo開源版沒有提供,阿里內部有個供HSF使用的diamon配置中心。Spring Cloud Config有自帶的配置管理庫,也能夠和開源項目集成,包括:Consul,Eureka,zk(後面咱們會看到各配置中心的優劣勢)。
    Spring Cloud Config實際上是一個基於spring boot的REST應用,不是一個單獨第三方的服務器,能夠嵌入在Spring Boot應用中,或者單獨啓動一個Spring Boot應用,其數據能夠存儲在文件系統中或者git倉庫中。spring cloud配置中心的客戶端實現原理比較簡單,咱們知道在spring框架中,是經過PlaceholderConfigurerSupport實現配置文件加載的,若是不使用spring cloud的配置中心,咱們徹底能夠本身擴展PlaceholderConfigurerSupport,根據啓動參數,從遠程配置中心進行加載。
  • dubbo使用zk和dubbo-admin做爲註冊和治理中心,因此spring cloud netflix eureka就至關於zk和dubbo-admin,spring cloud也集成了使用zk做爲服務註冊和查找中心的組件。
  • 在dubbo中,若是須要鏈路跟蹤,咱們須要自定義dubbo filter集成zipkin,dubbo自身沒有提供這個機制。在spring cloud中,提供了可集成zipkin的Spring Cloud Sleuth,它的其中一個特性是增長跟蹤ID到Slf4J MDC,這一點和筆者前面設計的日誌框架出入一轍,沒法作到跨節點追蹤的集中日誌平臺都是耍流氓。
  • 在dubbo中,咱們能夠經過聲明式的@Reference註解來直接調用rpc服務,在spring cloud中,經過Feign,能夠實現聲明式調用,對於微服務開發來講,提供聲明式的服務調用機制對開發效率是很重要的,它能夠在編譯階段確保調用方和服務方接口一致。從技術實現來講,現代RESTFUL接口通常簽名上出入參都是對象,若是把controller同時當作接口來用的話,實現聲明式調用REST微服務也不是很難的事,關鍵是代碼實現上,咱們須要在編寫controller接口的時候作些調整,跟service同樣,實現接口的方式,後面咱們會詳細講到。
除此以外,同正常開發不同的點在於spring cloud依賴於spring boot,這是同標準化開發最大的區別,Spring Boot(實際上,spring boot自己簡單地說就是另外一種開發時打包方式)被認爲是spring cloud下微服務實現的核心技術。
在協議上,spring cloud是基於http的,這一點上會不會對延遲形成影響須要在評估下。
對spring cloud的大致介紹就到這裏,後面咱們開始進入正文部分,spring cloud的學習。

=====================================spring

再重複一遍,spring cloud依賴於spring boot,因此不熟悉spring boot的同窗,先掌握下spring boot,可參考筆者的寫給大忙人spring boot 1.x學習指南

有些書籍一開始就講spring cloud config,有些書籍則幾乎能夠認爲把官方文檔翻譯一遍 ,官方文檔不少狀況下對某些假設是很理想化的,因此,我的以爲有些時候就該有得放矢,不要追求大而全,很簡單的就不要大談特談了。

服務註冊與發現spring cloud netflix eureka

筆者建議不要急着動手,咱們先來了解下服務中心的相關概念和優劣勢。
傳統基於ESB或者類DNS機制的服務路由機制

 

這也是早期主流自定義RPC框架的實現模式,它的最主要缺點是LB的單點失敗和擴展能力受限。在極高性能要求下,對於不少簡單頻繁調用的微服務來講,響應時間增長(由於網絡多了一個節點)。
基於服務註冊中心的服務路由機制

 

在這種狀況下,服務發現層並非微服務調用自己須要的,只是一個註冊和查找中心。避免了單點失敗和擴展性問題。
注:若是客戶端是使用http直接訪問的,而不是其餘微服務來訪問,也就是本篇開頭的spring cloud架構圖。在這種架構中,zuul至關於智能化的nginx代理,只不過原生的nginx是下游服務節點須要人工靜態配置,zuul增長了功能,和註冊中心保持聯繫,使得每一個微服務實例能夠主動鏈接到註冊中心,發佈本實例的服務列表,就能夠感知到,從這一點上來講,在nginx上增長一個動態反向註冊的模塊是合理的,這樣的話,從完整的架構角度來講,zuul就不須要了。由於如今應用幾乎所有前端功能單獨運行在nginx裏面(除非徹底以API方式對外提供服務),nginx幾乎在任何應用中都是必備的。因此,最佳的方式是nginx增長一個動態反向註冊的模塊,若是沒有能力,退而次之,就只能nginx+zuul。雖然服務能夠規劃的比較好,也就是按照命名空間規劃,並且新增、合併命名空間的機率較低,可是對於高併發系統來講,增長或者減小不一樣服務的實例數量,這是一個比較常見的操做。並且,原生的nginx有太多的靜態配置,這也是比較詬病的。雖然能夠要求前端區分API資源和非API資源,對於api直接訪問zuul對應的域名,哪怕經過二級域名,不過這在推進上可能遇到比較大的阻力,除非一開始就是這麼規劃的。
 
Spring Cloud支持多種類型的服務註冊中心,Netflix Eureka是集成最緊密(親兒子)的,其次是consul(爲何吐槽某些寫書的,其實這就是一例,沒見到那本書裏面講了使用consul做爲服務中心,其它例子還不少),各類註冊中心的比較可參考 https://luyiisme.github.io/2017/04/22/spring-cloud-service-discovery-products/,這裏引用以下:

如今來看下服務的註冊和調用。

Spring Cloud提供了三種調用微服務的方法:
  • DiscoveryClient,最底層
  • Ribbon-aware Spring RestTemplate,中間層
  • Netflix Feign,最抽象,也是最高效的  (注:咱們通常自研rpc框架的時候,也是這個思路,不過通常是兩層,而不是三層)

因爲在實際開發中,咱們基本上使用Feign開發,因此,這裏咱們重點看Feign方式的RPC調用。

在使用Feign聲明式調用前,開發者須要先定義一個接口,而後使用Spring Cloud的@FeignClient註解該接口,告訴Ribbon調用哪一個Eureka服務,其實原理上就是根據接口動態生成代理,跟咱們自定義RPC/dubbo的實現是同樣的原理,通常來講,該接口應該由服務方提供比較合適。能夠像下面這樣定義:
package com.thoughtmechanix.org.api;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@FeignClient("organizationservice")
public interface OrganizationService {
    @RequestMapping(value="/v1/organizations/{organizationId}",method = RequestMethod.GET)
    public Organization getOrganization( @PathVariable("organizationId") String organizationId);
}

而後OrganizationService就能夠被當作正常的spring bean使用了,以下:

package com.thoughtmechanix.licenses.controllers;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import com.thoughtmechanix.licenses.model.License;
import com.thoughtmechanix.org.api.Organization;
import com.thoughtmechanix.org.api.OrganizationService;

@RestController
public class LicenseServiceController implements LicenseService {
    
    private static final Logger logger = LoggerFactory.getLogger(LicenseServiceController.class);
    
    @Autowired
    private OrganizationService organizationService;

    @Override
@RequestMapping(value = "/v2/organizations/{organizationId}/licenses/{licenseId}", method = RequestMethod.GET)
public Organization getLicensesInterface(@PathVariable("organizationId")String organizationId, @PathVariable("licenseId")String licenseId) { logger.info("調用遠程Eureka服務!"); return organizationService.getOrganization(organizationId); } }
要啓用Feign客戶端,還須要在主應用類中加上@EnableFeignClients註解。
package com.thoughtmechanix.licenses;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;

@RefreshScope
@EnableFeignClients("com.thoughtmechanix.org.api")
@EnableEurekaClient
@SpringBootApplication
@EnableCircuitBreaker
@ComponentScan({"com.thoughtmechanix.licenses","com.thoughtmechanix.xyz.api"})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

注:這裏有個特殊點,Feign的接口掃描路徑定義在@EnableFeignClients註解的beanPackage屬性上,而不是@ComponentScan註解上,不然若是Feign的接口不在主應用類所在的包或者子包下,就在啓動時包bean找不到,以下所示:

Description:

Field organizationService in com.thoughtmechanix.licenses.controllers.LicenseServiceController required a bean of type 'com.thoughtmechanix.org.api.OrganizationService' that could not be found.


Action:

Consider defining a bean of type 'com.thoughtmechanix.org.api.OrganizationService' in your configuration.

[WARNING] 
java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.boot.maven.AbstractRunMojo$LaunchRunner.run(AbstractRunMojo.java:527)
	at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'licenseServiceController': Unsatisfied dependency expressed through field 'organizationService'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.thoughtmechanix.org.api.OrganizationService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
	at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122)
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693)
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:303)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107)
	at com.thoughtmechanix.licenses.Application.main(Application.java:19)
	... 6 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.thoughtmechanix.org.api.OrganizationService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1493)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1104)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
	... 25 more

 

如今來調用http://localhost:8080/v2/organizations/yidooo/licenses/x0009999,以下:

 

經過爲controller定義要實現的接口,就作到了一次定義,屢次引用(這和咱們使用傳統的spring mvc開發不一樣,建議把RequestMapper定義在接口上)。

因此,從使用上來講,Feign很簡單,對於有過其餘RPC開發經驗的同窗來講,就是換個註解而已。

熔斷、降級和服務隔離Netflix Hystrix

 我記得dubbo和其餘rpc在這一塊作的不是特別好,雖然spring cloud提供了該特性、並且很靈活,可是它有個關鍵設計很雞肋,後面會講到。

 在spring cloud的微服務架構中,一個請求調用通過的節點內關鍵步驟以下:

Hystrix和Spring Cloud使用@HystrixCommand標記java方法由Hystrix電路中斷器管理,當spring框架看到@HystrixCommand註解的時候,它會爲方法生成一個代理,並使用特定的線程池執行調用(這會致使個問題,就是log4j的MDC是基於線程上下文的)。須要注意的是,整個被註解的方法都會由Hystrix電路中斷器管理,默認狀況下,只要執行超過1秒就會觸發com.nextflix.hystrix.exception.HystrixRuntimeException異常,哪怕該方法內部調用了其餘不少RPC服務,實際上控制應該放在調用具體遠程eureka服務上,而不是整個本地方法上(這個是實現不合理的,注:經過爲每一個遠程服務包裝一個本地代理調用,實現細粒度控制,這估計得靠代理來實現,不能一個個手寫)。同時,默認狀況下,由@HystrixCommand標記的全部方法都會在一個線程池中執行,這也是不合理的(雖然爲不一樣的遠程服務定義不一樣的線程池)。
@HystrixCommand註解定義了不少屬性控制其行爲和線程池等,其中commandProperties屬性控制電路中斷器的行爲,具體可見javadoc(https://netflix.github.io/Hystrix/javadoc/com/netflix/hystrix/HystrixCommand.html)。
服務不可用回調,@HystrixCommand的fallbackMethod屬性用來設置若是調用Hystrix失敗,回調本類的哪一個方法,回調方法和原方法的簽名必須相同,由於調用回調方法時會把全部參數都傳遞過去。
Hystrix使用線程池執行全部遠程調用服務,默認狀況下,這個線程池有10個線程,任何遠程調用都會放在這個線程池中執行,包括jdbc,只要是遠程調用都算(它的原理或者判斷依據是???),以下所示:

合理的隔離機制應該是能夠自定義線程池數量,以及哪些服務放在哪一個線程池。以下:

天然,Hystrix提供了按需配置線程池的接口。@HystrixCommand註解的threadPoolKey和threadPoolProperties屬性就是用來指定線程池的,包括線程池名稱、大小、隊列長度(就線程池而言,最重要的就是名稱,核心大小,最大大小,隊列長度)。以下:

@HystrixCommand(fallbackMethod = "buildFallbackLicenseList",
          threadPoolKey
= "licenseByOrgThreadPool",
          threadPoolProperties
= {
            @HystrixProperty(name
= "coreSize",value="30"),
            @HystrixProperty(name
="maxQueueSize", value="10")
          })
public List<License> getLicensesByOrg(String organizationId){   return licenseRepository.findByOrganizationId(organizationId);
)
注:默認狀況下,線程池任務隊列使用的數據結構是LinkedBlockingQueue,由於任務一般是先進先出的策略。
由於@HystrixCommand標記的方法在另外的線程中執行,這意味着默認狀況下ThreadLocal中的值在@HystrixCommand標記的方法內是取不到值的。因此,Hystrix和Spring Cloud提供了一種機制來使得能夠將父線程中的上下文傳遞給Hystrix Thread pool。這種機制稱爲HystrixConcurrencyStrategy。HystrixConcurrencyStrategy是一個抽象類,經過實現它能夠將父線程上下文中的信息注入到Hystrix管理的線程中,它包括三個步驟:
  1. 自定義Hystrix Concurrency Strategy類
  2. 定義一個Callable類,將UserContext注入Hystrix Command
  3. 配置Spring Cloud使用自定義的Hystrix Concurrency Strategy類
實現細節能夠參考Spring microservice in action 5.9.2 The HystrixConcurrencyStrategy in action。
須要注意的是:spring cloud內置了HystrixConcurrencyStrategy處理spring secuiry相關問題,各HystrixConcurrencyStrategy之間是過濾器鏈設計模式,因此實現自定義HystrixConcurrencyStrategy的時候,須要確保調用了已存在的HystrixConcurrencyStrategy,這和咱們自定義過濾器或者框架相關的生命週期事件的模式同樣。
 

線路熔斷

默認狀況下,雖然能夠配置超過多少時間以後,服務拋異常,可是不少時候,若是目標系統很是忙了,這個時候請求在源源不斷的發過去是沒有意義的,只會讓目標系統更慢,此時咱們須要一些更加靈活或者智能的機制來肯定何時不在調用到目標物理地址的服務。Hystrix的默認計算規則爲:

 

控制熔斷的行爲是在@HystrixCommand註解的commandPoolProperties屬性中設置,其中包含的是@HystrixProperty屬性數組。Hystrix的完整參數列表可見 https://github.com/Netflix/Hystrix/wiki/Configuration
 

 

服務路由zuul

爲何使用路由以及是否須要使用路由的緣由,在上面已經講過了,因此,這裏仍是看下核心部分。其在spring cloud架構中的角色以下:

 

經過添加spring-cloud-starter-zuul依賴以及在主應用類中添加@EnableZuulProxy註解就能夠啓用zuul服務器功能。添加依賴:
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
@SpringBootApplication
@EnableZuulProxy
public class ZuulServerApplication {
  public static void main(String[] args) {
    SpringApplication.run(ZuulServerApplication.class,args);
  }
}
注:除了 EnableZuulProxy註解外,還有一個@EnableZuulServer註解,該註解會建立Zuul Server,可是不會加載任何Zuul反向代理過濾器,也不會使用Netflix Eureka做爲服務中心,它主要用於自建路由服務,因此通常狀況下不須要。
Zuul設計爲默認使用Spring Cloud的其餘服務,因此,默認使用Eureka做爲服務中心,Netflix Ribbon做爲客戶端負載均衡代理。鏈接eureka服務中心的配置都同樣。以下:
eureka:
  instance:
    preferIpAddress: true
  client:
    registerWithEureka: true
    fetchRegistry: true
  serviceUrl:
    defaultZone: http://localhost:8761/eureka/
這樣,運行maven spring-boot:run以後,就能夠啓動Zuul了。

Zuul路由配置

Zuul支持三種類型的路由機制:
  • 基於服務中心自動路由(大規模使用)
  • 使用服務中心手工路由(A/B測試使用)
  • 根據靜態url路由(歷史兼容使用)
Zuul全部的路由映射都維護在application.yml文件中,默認狀況下,不須要作任何配置,Zuul會自動使用Eureka中的服務ID尋找下游的實例,這也是dubbo的作法。須要注意的是,實際上Zuul粒度比較粗,是根據應用名而不是嚴格意義上的服務來區別的。例如:
是根據organizationservice來判斷下游服務,而不是經過具體的API來判斷的,這種狀況下,若是兩個應用相同名稱,可是提供的服務有差異,就坑了。
訪問Zuul服務器的/routes服務能夠查看全部的服務。以下所示:

 

Zuul的真正強大在於過濾器,能夠用來設置全局請求ID,由於spring cloud使用http協議,因此這是能夠作到的,只要在http head中進行注入便可。也能夠設置路由過濾器,不只僅根據路由定義來決定路由,好比說灰度升級或者A/B測試(https://baike.baidu.com/item/AB%E6%B5%8B%E8%AF%95/9231223)的時候,就可使用路由過濾器經過某個請求參數來判斷應該路由到什麼節點,好比說某個區域、某個級別的客戶等等。爲了作到應用代碼一致性,咱們須要根據上下文而不是服務名稱來肯定路由邏輯。在zuul中,經過繼承com.netflix.zuul.ZuulFilter,能夠實現自定義過濾器。
不過動態路由除了zuul應該提供外,ribbon也應該提供,由於一般來講,內部服務也會相互調用,思路能夠參考 https://github.com/jmnarloch/ribbon-discovery-filter-spring-cloud-starter
 

分佈式日誌聚合Spring Cloud Sleuth

Spring Cloud Sleuth是一個Spring Cloud項目的一部分,它能夠在http調用上設置相關ID,同時提供了鉤子將跟蹤數據發往OpenZipkin。這是經過Zuul過濾器以及其餘spring組件使得相關ID能夠在系統調用見透傳。
Zipkin主要用來鏈路跟蹤,以及各節點之間服務調用的性能分析。
具體實現上,可使用Zuul過濾器檢查HTTP請求,若是請求中沒有相關ID,就能夠注入進去。相關ID存在以後,就可使用自定義的Spring HTTP過濾器將相關ID注入到自定義的UserContext對象,而後將相關ID增長到Spring’s Mapped Diagnostic Context (MDC),也能夠編寫一個Spring Interceptor將相關ID經過HTTP頭傳遞出去,這兩種應該來講均可以實現,其實最主要是他們使用了HTTP協議。同時確保Hystrix線程上下文可以感知到。上述這些特性就是Spring Cloud Sleuth提供的主要功能:
  • 透明在服務調用上建立和注入相關ID(dubbo沒有提供現成的功能,須要自行基於dubbo filter實現)
  • 在服務調用之間透傳相關ID
  • 增長相關ID到Spring’s MDC,Spring Boots的默認SL4J和Logback會自動包含相關ID,log4j則不會自動包含(參考本博客spring boot系列的日誌部分)。
  • 可選的,發佈跟蹤信息到Zipkin

要啓用Spring Cloud sleuth,只要在pom文件中包含下列依賴便可:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
Spring Cloud Sleuth信息分爲4段組成,以下:
  • 服務的應用名
  • 全局跟蹤ID
  • 當前請求段ID
  • 是否發送到zipkin的標記

 

 

經過將日誌聚合到ELK,咱們就能夠很方便的進行搜索了。
Spring Cloud Sleuth和ELK的整合仍是比較簡單的,由於目前ELK框架推薦在應用端部署Filebeat的方式,只要Filebeat配置好格式便可。參考 ELK最新版6.2.4學習筆記-Logstash和Filebeat解析(java異常堆棧下多行日誌配置支持)
zipkin自身包含spring boot版本的可執行包下載,也能夠本身建立一個spring boot應用,對於zipkin而言,惟一須要考慮的是數據存儲在哪裏,默認狀況下數據存儲在內存中,這意味着若是zipkin重啓了,以前的監控數據就沒有了。zipkin目前支持mysql、Cassandra以及Elasticsearch,由於ELK數據存儲已經在ES中了,因此也建議配置到ES中,只要劃一個index出來給zipkin就能夠了,zipkin配置ES存儲能夠參考 https://github.com/openzipkin/zipkin/tree/master/zipkin-server
由於zipkin的數據整體來講是用來分析性能的,因此Zipkin默認會將1/10的數據寫到Zipkin服務器,配置參數spring.sleuth.sampler.percentage能夠用來控制發送的比例,取值爲0-1之間。
 
最後咱們來看下分佈式配置的使用,其原理咱們在前面已經講過了,就不重複闡述。
 

分佈式配置中心spring cloud config

spring cloud config和spring cloud eureka同樣,也是一個spring boot應用,只要添加maven依賴以及在主應用類上添加@EnableConfigServer註解便可,以下:
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
package com.thoughtmechanix.confsvr;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

而後在application.yml中設置存儲信息,以下:

server:
  port: 8888
spring:
  profiles:
    active: native
  cloud:
    config:
      server:
        native:
          searchLocations: file:///D:/spring-cloud-example/config/

這樣運行spring-boot:run就能夠啓動配置中心服務了。

注意,這裏須要注意點的是,路徑大小寫敏感,不然可能出現一直訪問不到配置文件,可是沒有報錯信息。

D:/spring-cloud-example/config/下包含以下配置文件:

tracer.property: "I AM THE DEFAULT FROM CONFIG CENTER"
spring.jpa.database: "POSTGRESQL"
spring.datasource.platform: "postgres"
spring.jpa.show-sql: "true"
spring.database.driverClassName: "org.postgresql.Driver"
spring.datasource.url: "jdbc:postgresql://database:5432/eagle_eye_local"
spring.datasource.username: "postgres"

 

咱們可使用postman訪問以下:

 

這樣,基於文件存儲的配置中心就搭建好了。

目前,spring cloud config支持使用文件系統和git做爲存儲,git的配置能夠參考官方文檔。

因爲存在profile、label等概念,所以配置中心http請求地址和資源文件之間存在一個映射關係(若是請求的時候訪問不到的時候能夠幫助排查),以下:
  • /{application}/{profile}[/{label}]
  • /{application}-{profile}.yml
  • /{label}/{application}-{profile}.yml
  • /{application}-{profile}.properties
  • /{label}/{application}-{profile}.properties
對於非git存儲而言,label不存在。這一點經過訪問配置中心資源能夠看出,見上圖。
要配置客戶端使用配置中心,只要加上依賴便可:
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>

由於全部的配置信息都不在本地,因此咱們須要一種機制告訴spring boot去哪裏找配置中心,所以spring boot提供了一個bootstrap.yml配置文件,其中定義了使用哪一個應用、哪一個profile的配置,以及服務器地址。以下所示:

spring:
 application:
   name: licensingservice
 profiles:
  active: default
 cloud:
   config:
     uri: http://localhost:8888

在spring boot應用啓動的時候,在執行任何bean的初始化前,會先加載bootstrap.yml文件,讀取配置,而後再進行其餘初始化和加載工做。

這樣配置中心的配置就和原來properties中同樣,被加載到Environment中了,@Value就能夠正常注入了。

雖然Spring Cloud配置中心可以感知底層數據源的修改,不管是否直接修改了底層文件系統或者git倉庫的配置值,Spring Cloud配置中心老是能夠獲取最新的值。可是Spring Boot應用則是在啓動時讀取配置的,這意味着默認狀況下,對配置中心的修改不會通知spring boot應用,哪怕spring boot應用須要知道配置變動的時候也如此,好比說log4j2配置臨時調整。不過Spring Boot Actuator(https://docs.spring.io/spring-boot/docs/1.5.7.RELEASE/reference/htmlsingle/#production-ready)提供了一個@RefreshScope註解容許spring boot應用訪問/refresh來強制從新讀取配置,不過這對於@Value直接注入來講是合理的,可是對於一些spring boot啓動時就已經注入而且建立單例了的狀況,好比jdbc/redis配置等就不適用了。只要在spring主應用類上增長@RefreshScope註解便可,以下:
@SpringBootApplication 
@RefreshScope 
public class Application { 
  public static void main(String[] args) { 
    SpringApplication.run(Application.class, args); 
  } 
}
同時在配置文件application.yml中增長
 
management:
 security:
  enabled: false
不然,spring boot 1.5.x版本在執行/refresh的時候會報401,"error":"Unauthorized","message":"Full authentication is required to access this resource."
執行post /refresh,查看spring boot日誌:
2018-06-13 09:29:23.490 INFO 5288 --- [nio-8080-exec-5] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at: http://localhost:8888
2018-06-13 09:29:24.953 INFO 5288 --- [nio-8080-exec-5] c.c.c.ConfigServicePropertySourceLocator : Located environment: name=licensingservice, profiles=[default], label=null, version=null, state=null
2018-06-13 09:29:24.954 INFO 5288 --- [nio-8080-exec-5] b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource [name='configService', propertySources=[MapPropertySource {name='file:///D:/spring-cloud-example/config/licensingservice.yml'}]]
2018-06-13 09:29:24.956 INFO 5288 --- [nio-8080-exec-5] o.s.boot.SpringApplication : The following profiles are active: default
 
spring cloud配置中心存在一個問題是沒有提供原生的配置變動發佈功能,雖然提供了Spring Cloud Bus模塊來基於MQ發佈配置變動,但這引入了額外的維護工做。能夠說zk最有價值的特性之一就是根據特定節點訂閱變動、刪除、其下節點變動的事件。
 
參考:
spring microservice in action
http://cloud.spring.io/spring-cloud-static/Edgware.SR3/index.html
https://mp.weixin.qq.com/s?__biz=MzIwMzg1ODcwMw==&mid=2247487921&idx=1&sn=1a1d9864e58fbea3f07c98d447b90e44&chksm=96c9a7d1a1be2ec752882590a53c37d59bb291db73187f097702235c606da9feddd6cb9990a4&scene=27#wechat_redirect
 
待下一系列補充、完善的要點:
Eureka 服務中心高可用
Spring Cloud Config高可用
配置中心推送機制
Spring Cloud使用Consul做爲服務中心
相關文章
相關標籤/搜索