自我總結式的學習:
我們使用Spring Cloud Netflix中的Eureka實現了服務註冊中心以及服務註冊與發現;而服務間通過Ribbon或Feign實現服務的消費以及均衡負載;通過Spring Cloud Config實現了應用多環境的外部化配置以及版本管理。爲了使得服務集羣更爲健壯,使用Hystrix的融斷機制來避免在微服務架構中個別服務出現異常時引起的故障蔓延。
eureka 尤里卡 服務註冊中心,爲server,其他服務可以當作client,需要在Application.java里加兩個基礎註解@EnableDiscoveryClient和@SpringBootApplication;server端加兩個@EnableEurekaServer和
@SpringBootApplication註解;
接口裏這段代碼獲取服務client實例:
@Autowired
private DiscoveryClient client;
如何使用eureka?
1.pom.xml中加入spring-cloud-starter-eureka依賴,
2.在application.properties中配置參數eureka.client.serviceUrl.defaultZone以指定服務註冊中心的位置,詳細內容如下:
# 配置服務註冊中心
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
3.在應用主類中,新增@EnableDiscoveryClient註解,用來將config-server註冊到上面配置的服務註冊中心上去。
4.啓動該應用,並訪問http://localhost:1111/,可以在Eureka Server的信息面板中看到config-server已經被註冊了。
ribbon 瑞本 客戶端的負載均衡,Application裏兩個基本註解@SpringBootApplication和@EnableDiscoveryClient;
Application里加一段代碼
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
來實現負載均衡,接口裏的方法寫:
@RequestMapping(value = "/add", method = RequestMethod.GET)
public String add() {
return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();
}
feign 費因 服務端的負載均衡,feign中Application里加註解 @SpringBootApplication、@EnableDiscoveryClient、@EnableFeignClients;在接口中添加@FeignClient("compute-service")註解
hystrix 海思垂克斯 斷路器,使用斷路器調用註冊服務的接口,如果註冊的服務不可用,將調用斷路器預設好的回調函數,返回一個正常提示錯誤的頁面。
feign結合hystrix:
@FeignClient(value = "compute-service", fallback = ComputeClientHystrix.class)
調用斷路器的回調函數,ComputeClientHystrix實現ComputeClient(接口Interface),實現接口的方法,一旦接口的方法執行中出現問題,就執行斷路器的實現方法。
ribbon結合hystrix:
Application里加註解:
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
接口:
@RequestMapping(value = "/add", method = RequestMethod.GET)
public String add() {
return computeService.addService();
}
service:
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "addServiceFallback")
public String addService() {
return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();
}
public String addServiceFallback() {
return "error";
}
config 康菲個 配置中心,並不屬於能註冊到eureka的服務,
server端:
Application.java文件添加@EnableConfigServer註解,開啓Config Server
@EnableConfigServer
@SpringBootApplication
spring.cloud.config.server.git.uri:配置git倉庫位置
spring.cloud.config.server.git.searchPaths:配置倉庫路徑下的相對搜索位置,可以配置多個
spring.cloud.config.server.git.username:訪問git倉庫的用戶名
spring.cloud.config.server.git.password:訪問git倉庫的用戶密碼
到這裏,使用一個通過Spring Cloud Config實現,並使用git管理內容的配置中心已經完成了,啓動該應用,成功後開始下面的內容。
Spring Cloud Config也提供本地存儲配置的方式。我們只需要設置屬性spring.profiles.active=native,Config Server會默認從應用的src/main/resource目錄下檢索配置文件。也可以通過spring.cloud.config.server.native.searchLocations=file:F:/properties/屬性來指定配置文件的位置。雖然Spring Cloud Config提供了這樣的功能,但是爲了支持更好的管理內容和版本控制的功能,還是推薦使用git的方式。
client端:
1.URL與配置文件的映射關係如下: 1)application是應用的名字 2)profile是環境 3)label是分支名
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
2.創建bootstrap.properties配置,來指定config server
spring.application.name=didispace
spring.cloud.config.profile=dev
spring.cloud.config.label=master
spring.cloud.config.uri=http://localhost:7001/
server.port=7002
注:
spring.application.name:對應前配置文件中的{application}部分
spring.cloud.config.profile:對應前配置文件中的{profile}部分
spring.cloud.config.label:對應前配置文件的git分支
spring.cloud.config.uri:配置中心的地址
這裏需要格外注意:上面這些屬性必須配置在bootstrap.properties中,config部分內容才能被正確加載。因爲config的相關配置會先於application.properties,而bootstrap.properties的加載也是先於application.properties。
3.通過@Value("${from}")綁定配置服務中配置的from屬性。
config註冊服務實現高可用
1.pom.xml中加入spring-cloud-starter-eureka依賴,
2.在application.properties中配置參數eureka.client.serviceUrl.defaultZone以指定服務註冊中心的位置,詳細內容如下:
# 配置服務註冊中心
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
3.在應用主類中,新增@EnableDiscoveryClient註解,用來將config-server註冊到上面配置的服務註冊中心上去。
4.啓動該應用,並訪問http://localhost:1111/,可以在Eureka Server的信息面板中看到config-server已經被註冊了。
通過eureka.client.serviceUrl.defaultZone參數指定服務註冊中心,用於服務的註冊與發現,再將spring.cloud.config.discovery.enabled參數設置爲true,開啓通過服務來訪問Config Server的功能,最後利用spring.cloud.config.discovery.serviceId參數來指定Config Server註冊的服務名。這裏的spring.application.name和spring.cloud.config.profile如之前通過URI的方式訪問時候一樣,用來定位Git中的資源。
config通過與git關聯,支持更好的管理內容和版本控制的功能,修改git的配置文件後,/refresh就可以刷新了
重新啓動config-clinet,訪問一次http://localhost:7002/from,可以看到當前的配置值
修改Git倉庫config-repo/didispace-dev.properties文件中from的值
再次訪問一次http://localhost:7002/from,可以看到配置值沒有改變
通過POST請求發送到http://localhost:7002/refresh,我們可以看到返回內容如下,代表from參數的配置內容被更新了
再次訪問一次http://localhost:7002/from,可以看到配置值已經是更新後的值了
該功能還可以同Git倉庫的Web Hook功能進行關聯,當有Git提交變化時,就給對應的配置主機發送/refresh請求來實現配置信息的實時更新。但是,當我們的系統發展壯大之後,維護這樣的刷新清單也將成爲一個非常大的負擔,而且很容易犯錯,那麼有什麼辦法可以解決這個複雜度呢?後續我們將繼續介紹如何通過Spring Cloud Bus來實現以消息總線的方式進行通知配置信息的變化,完成集羣上的自動化更新。
Zuul zing,服務網關是微服務架構中一個不可或缺的部分。通過服務網關統一向外系統提供REST API的過程中,除了具備服務路由、均衡負載功能之外,它還具備了權限控制等功能。
Spring Cloud Netflix中的Zuul就擔任了這樣的一個角色,爲微服務架構提供了前門保護的作用,同時將權限控制這些較重的非業務邏輯內容遷移到服務路由層面,使得服務集羣主體能夠具備更高的可複用性和可測試性。
使用zuul:
1.引入依賴spring-cloud-starter-zuul、spring-cloud-starter-eureka,如果不是通過指定serviceId的方式,eureka依賴不需要,但是爲了對服務集羣細節的透明性,還是用serviceId來避免直接引用url的方式吧。
2.應用主類使用@EnableZuulProxy註解開啓Zuul
(這裏用了@SpringCloudApplication註解,之前沒有提過,通過源碼我們看到,它整合了@SpringBootApplication、@EnableDiscoveryClient、@EnableCircuitBreaker,主要目的還是簡化配置。)
3.application.properties中配置Zuul應用的基礎信息,如:應用名、服務端口等。
一、服務路由
通過服務路由的功能,我們在對外提供服務的時候,只需要通過暴露Zuul中配置的調用地址就可以讓調用方統一的來訪問我們的服務,而不需要了解具體提供服務的主機信息了。
在Zuul中提供了兩種映射方式:
1)通過url直接映射,我們可以如下配置:
# routes to url
zuul.routes.api-a-url.path=/api-a-url/**
zuul.routes.api-a-url.url=http://localhost:2222/
該配置,定義了,所有到Zuul的中規則爲:/api-a-url/**的訪問都映射到http://localhost:2222/上,也就是說當我們訪問http://localhost:5555/api-a-url/add?a=1&b=2的時候,Zuul會將該請求路由到:http://localhost:2222/add?a=1&b=2上。
其中,配置屬性zuul.routes.api-a-url.path中的api-a-url部分爲路由的名字,可以任意定義,但是一組映射關係的path和url要相同,下面講serviceId時候也是如此。
通過url映射的方式對於Zuul來說,並不是特別友好,Zuul需要知道我們所有爲服務的地址,才能完成所有的映射配置。
2)而實際上,我們在實現微服務架構時,服務名與服務實例地址的關係在eureka server中已經存在了,所以只需要將Zuul註冊到eureka server上去發現其他服務,我們就可以實現對serviceId的映射。例如,我們可以如下配置:
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=service-A
zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.serviceId=service-B
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
總結:
嘗試通過服務網關來訪問service-A和service-B,根據配置的映射關係,分別訪問下面的url
http://localhost:5555/api-a/add?a=1&b=2:通過serviceId映射訪問service-A中的add服務
http://localhost:5555/api-b/add?a=1&b=2:通過serviceId映射訪問service-B中的add服務
http://localhost:5555/api-a-url/add?a=1&b=2:通過url映射訪問service-A中的add服務
推薦使用serviceId的映射方式,除了對Zuul維護上更加友好之外,serviceId映射方式還支持了斷路器,對於服務故障的情況下,可以有效的防止故障蔓延到服務網關上而影響整個系統的對外服務
二、服務過濾
在完成了服務路由之後,我們對外開放服務還需要一些安全措施來保護客戶端只能訪問它應該訪問到的資源。所以我們需要利用Zuul的過濾器來實現我們對外服務的安全控制。
在服務網關中定義過濾器只需要繼承ZuulFilter抽象類實現其定義的四個抽象函數就可對請求進行攔截與過濾。
自定義過濾器的實現,需要繼承ZuulFilter,需要重寫實現下面四個方法:
filterType:返回一個字符串代表過濾器的類型,在zuul中定義了四種不同生命週期的過濾器類型,具體如下:
pre:可以在請求被路由之前調用
routing:在路由請求時候被調用
post:在routing和error過濾器之後被調用
error:處理請求時發生錯誤時被調用
filterOrder:通過int值來定義過濾器的執行順序
shouldFilter:返回一個boolean類型來判斷該過濾器是否要執行,所以通過此函數可實現過濾器的開關。在上例中,我們直接返回true,所以該過濾器總是生效。
run:過濾器的具體邏輯。需要注意,這裏我們通過ctx.setSendZuulResponse(false)令zuul過濾該請求,不對其進行路由,然後通過ctx.setResponseStatusCode(401)設置了其返回的錯誤碼,當然我們也可以進一步優化我們的返回,比如,通過ctx.setResponseBody(body)對返回body內容進行編輯等。
在實現了自定義過濾器之後,還需要實例化該過濾器才能生效,我們只需要在應用主類中增加如下內容:
@Bean
public AccessFilter accessFilter() {
return new AccessFilter();
}
最後,總結一下爲什麼服務網關是微服務架構的重要部分,是我們必須要去做的原因:
1.不僅僅實現了路由功能來屏蔽諸多服務細節,更實現了服務級別、均衡負載的路由。
2.實現了接口權限校驗與微服務業務邏輯的解耦。通過服務網關中的過濾器,在各生命週期中去校驗請求的內容,將原本在對外服務層做的校驗前移,保證了微服務的無狀態性,同時降低了微服務的測試難度,讓服務本身更集中關注業務邏輯的處理。
3.實現了斷路器,不會因爲具體微服務的故障而導致服務網關的阻塞,依然可以對外服務。
高可用服務註冊中心 -- eureka實現高可用
1.創建application-peer1.properties,作爲peer1服務中心的配置,並將serviceUrl指向peer2
spring.application.name=eureka-server
server.port=1111
eureka.instance.hostname=peer1
eureka.client.serviceUrl.defaultZone=http://peer2:1112/eureka/
2.創建application-peer2.properties,作爲peer2服務中心的配置,並將serviceUrl指向peer1
spring.application.name=eureka-server
server.port=1112
eureka.instance.hostname=peer2
eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/
3.在/etc/hosts文件中添加對peer1和peer2的轉換
127.0.0.1 peer1
127.0.0.1 peer2
4.通過spring.profiles.active屬性來分別啓動peer1和peer2
java -jar eureka-server-1.0.0.jar --spring.profiles.active=peer1
java -jar eureka-server-1.0.0.jar --spring.profiles.active=peer2
此時訪問peer1的註冊中心:http://localhost:1111/,如下圖所示,我們可以看到registered-replicas中已經有peer2節點的eureka-server了。同樣地,訪問peer2的註冊中心:http://localhost:1112/,能看到registered-replicas中已經有peer1節點,並且這些節點在可用分片(available-replicase)之中。我們也可以嘗試關閉peer1,刷新http://localhost:1112/,可以看到peer1的節點變爲了不可用分片(unavailable-replicas)。
-- 客戶端 註冊服務
1.我們以Chapter1-1-1中的compute-service爲基礎,修改application.properties配置文件:
spring.application.name=compute-service
server.port=2222
eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/
2.下面,我們啓動該服務,通過訪問http://localhost:1111/和http://localhost:1112/,可以觀察到compute-service同時被註冊到了peer1和peer2上。若此時斷開peer1,由於compute-service同時也向peer2註冊,因此在peer2上其他服務依然能訪問到compute-service,從而實現了高可用的服務註冊中心。
假設我們有3個註冊中心,我們將peer1、peer2、peer3各自都將serviceUrl指向另外兩個節點。換言之,peer1、peer2、peer3是兩兩互相註冊的。啓動三個服務註冊中心,並將compute-service的serviceUrl指向peer1並啓動,可以獲得如下圖所示的集羣效果。
通過上面的實驗,我們可以得出下面的結論來指導我們搭建服務註冊中心的高可用集羣:
兩兩註冊的方式可以實現集羣中節點完全對等的效果,實現最高可用性集羣,任何一臺註冊中心故障都不會影響服務的註冊與發現。
消息總線
spring中集成junit測試:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MockServletContext.class)
@WebAppConfiguration
測試類
@Test
測試方法
pom.xml具體配置:
<!-- springboot父工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<!-- spring boot對單元測試的支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 對eureka的支持,客戶端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- eureka服務端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<!-- 斷路器支持 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<!-- ribbon 客戶端負載均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<!-- springboot 對web的支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- feign 服務端負載均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<!-- config服務的支持 config服務端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<!-- config客戶端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<!-- springboot版本 -->
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
springboot以jar包形式啓動,啓動項目指定環境:
java -jar xxx.jar --spring.profiles.active=test
@Resource配置文件自動刷新
1.在類上加@RefreshScope註解。
2.引入配置@Value。