Spring Cloud
代碼地址:https://github.com/jedyang/springCloud
springcloud中文社區(qū)給的定義是微服務(wù)架構(gòu)集大成者,云計算最佳業(yè)務(wù)實(shí)踐。
我的理解是分布式服務(wù)全家桶。
如果你有dubbo或其他分布式框架的使用經(jīng)驗(yàn),那么對springcloud提供的特性是很好理解的。
springcloud提供了如下特性:
- 分布式配置
- 服務(wù)注冊和發(fā)現(xiàn)
- 路由
- 遠(yuǎn)程服務(wù)調(diào)用
- 負(fù)載均衡
- 斷路器
- 全局鎖
- 主從選舉和集群狀態(tài)
- 分布式消息
主要項(xiàng)目
spring cloud config
springcloud的配置管理項(xiàng)目,使用git管理??筛鶕?jù)不同環(huán)境管理不同參數(shù)。也可用于非spring應(yīng)用。
Spring Cloud Netflix
Spring Cloud包含了非常多的子框架,其中,Spring Cloud Netflix是其中一套框架,由Netflix開發(fā)后來又并入Spring Cloud大家庭,它主要提供的模塊包括:服務(wù)發(fā)現(xiàn)(Eureka),斷路器(Hystrix),智能路由(Zuul)和客戶端負(fù)載平衡(Ribbon)。
Spring Cloud Bus
事件總線。使用分布式消息將服務(wù)和服務(wù)實(shí)例聯(lián)系起來。在集群的狀態(tài)變化傳播中作用很大。
Spring Cloud for Cloud Foundry
讓你的應(yīng)用同Cloudfoundry進(jìn)行整合。(Cloudfoundry是最近很熱的開源PaaS云平臺)。讓你很容易實(shí)現(xiàn)SSO(單點(diǎn)登錄)、OAuth2(一個關(guān)于授權(quán)的開放網(wǎng)絡(luò)標(biāo)準(zhǔn))功能,以及Cloudfoundry的服務(wù)分發(fā)器。
Spring Cloud Cloud Foundry Service Broker
提供了一個擴(kuò)展點(diǎn),以便于開發(fā)基于 Cloud Foundry管理的服務(wù)分發(fā)器。
Spring Cloud Cluster
主從選舉?;趜ookeeper,redis,hazelcast(hazelcast是一個開放源碼集群和高度可擴(kuò)展的數(shù)據(jù)分發(fā)平臺),consul(支持多數(shù)據(jù)中心下,分布式高可用的,服務(wù)發(fā)現(xiàn)和配置共享)的抽象和實(shí)現(xiàn)。
Spring Cloud Consul
基于Consul實(shí)現(xiàn)的服務(wù)發(fā)現(xiàn)和配置管理
Spring Cloud Security
提供了對OAuth2 負(fù)載均衡的客戶端,以及基于Zuul代理的頭部校驗(yàn)。
Spring Cloud Sleuth
對spring cloud分布式應(yīng)用的服務(wù)鏈路追蹤
Spring Cloud Data Flow
一個建立數(shù)據(jù)集成和實(shí)時處理管道的工具集。
簡化了應(yīng)用程序的開發(fā)和部署 將精力集中到數(shù)據(jù)處理的用例上。
Spring Cloud Stream
一個輕量級的事件驅(qū)動的微服務(wù)框架。可以快速構(gòu)建應(yīng)用和外部系統(tǒng)對接??梢栽趕pringboot應(yīng)用之間通過簡單的聲明模型,基于kafka或rabbitMq交互消息。
Spring Cloud Stream App Starters
一個基于springboot的同外部系統(tǒng)的集成應(yīng)用
Spring Cloud Task
短時任務(wù)處理框架。如定時任務(wù)。
Spring Cloud Task App Starters
對應(yīng)的具體應(yīng)用。
Spring Cloud for Amazon Web Services
方便同AWS服務(wù)集成
Spring Cloud Connectors
使各種PaaS平臺應(yīng)用連接基礎(chǔ)后端服務(wù)(如數(shù)據(jù)庫服務(wù)、消息中間件)更容易。
Spring Cloud Starters
用于使基于springcloud的依賴管理更方便
Spring Cloud CLI
是一個插件,可以用groovy語言快速創(chuàng)建spring cloud應(yīng)用
Spring Cloud Contract
是一個消費(fèi)者驅(qū)動的、面向Java的契約框架。
Spring Cloud Netflix
Netflix是spring cloud的核心框架,必學(xué)必用。
微服務(wù)架構(gòu)
首先,我們來看看一般的微服務(wù)架構(gòu)需要的功能或使用場景:
我們把整個系統(tǒng)根據(jù)業(yè)務(wù)拆分成幾個子系統(tǒng)。
每個子系統(tǒng)可以部署多個應(yīng)用,多個應(yīng)用之間使用負(fù)載均衡。
需要一個服務(wù)注冊中心,所有的服務(wù)都在注冊中心注冊,負(fù)載均衡也是通過在注冊中心注冊的服務(wù)來使用一定策略來實(shí)現(xiàn)。
所有的客戶端都通過同一個網(wǎng)關(guān)地址訪問后臺的服務(wù),通過路由配置,網(wǎng)關(guān)來判斷一個URL請求由哪個服務(wù)處理。請求轉(zhuǎn)發(fā)到服務(wù)上的時候也使用負(fù)載均衡。
服務(wù)之間有時候也需要相互訪問。例如有一個用戶模塊,其他服務(wù)在處理一些業(yè)務(wù)的時候,要獲取用戶服務(wù)的用戶數(shù)據(jù)。
需要一個斷路器,及時處理服務(wù)調(diào)用時的超時和錯誤,防止由于其中一個服務(wù)的問題而導(dǎo)致整體系統(tǒng)的癱瘓。
還需要一個監(jiān)控功能,監(jiān)控每個服務(wù)調(diào)用花費(fèi)的時間等。
Netflix
Spring Cloud Netflix框架剛好就滿足了上面所有的需求,而且最重要的是,使用起來非常的簡單。Spring Cloud Netflix包含的組件及其主要功能大致如下:
Eureka,服務(wù)注冊和發(fā)現(xiàn),它提供了一個服務(wù)注冊中心、服務(wù)發(fā)現(xiàn)的客戶端,還有一個方便的查看所有注冊的服務(wù)的界面。 所有的服務(wù)使用Eureka的服務(wù)發(fā)現(xiàn)客戶端來將自己注冊到Eureka的服務(wù)器上。
Zuul,網(wǎng)關(guān),所有的客戶端請求通過這個網(wǎng)關(guān)訪問后臺的服務(wù)。他可以使用一定的路由配置來判斷某一個URL由哪個服務(wù)來處理。并從Eureka獲取注冊的服務(wù)來轉(zhuǎn)發(fā)請求。
Ribbon,即負(fù)載均衡,Zuul網(wǎng)關(guān)將一個請求發(fā)送給某一個服務(wù)的應(yīng)用的時候,如果一個服務(wù)啟動了多個實(shí)例,就會通過Ribbon來通過一定的負(fù)載均衡策略來發(fā)送給某一個服務(wù)實(shí)例。
Feign,服務(wù)客戶端,服務(wù)之間如果需要相互訪問,可以使用RestTemplate,也可以使用Feign客戶端訪問。它默認(rèn)會使用Ribbon來實(shí)現(xiàn)負(fù)載均衡。
Hystrix,監(jiān)控和斷路器。我們只需要在服務(wù)接口上添加Hystrix標(biāo)簽,就可以實(shí)現(xiàn)對這個接口的監(jiān)控和斷路器功能。
Hystrix Dashboard,監(jiān)控面板,他提供了一個界面,可以監(jiān)控各個服務(wù)上的服務(wù)調(diào)用所消耗的時間等。
Turbine,監(jiān)控聚合,使用Hystrix監(jiān)控,我們需要打開每一個服務(wù)實(shí)例的監(jiān)控信息來查看。而Turbine可以幫助我們把所有的服務(wù)實(shí)例的監(jiān)控信息聚合到一個地方統(tǒng)一查看。這樣就不需要挨個打開一個個的頁面一個個查看。
接下來一個一個看。
Eureka
作為服務(wù)注冊與發(fā)現(xiàn)的中心。我們的demo分為兩部分,一個server,一個client。 這里的server是指注冊中心。client是指向注冊中心注冊或者訂閱服務(wù)的消費(fèi)者。
創(chuàng)建一個maven主工程
建一個server模塊。
右鍵-->new module -->Spring Initializr -->next
-->填寫相關(guān)信息-->next
-->dependencies 選Cloud Discovery-->Eureka Server
-->finish-
第一個server
// 服務(wù)注冊中心注解 @EnableEurekaServer @SpringBootApplication public class ServerApplication { public static void main(String[] args) { SpringApplication.run(ServerApplication.class, args); } }代碼非常簡單,就是在原來springboot的啟動類上加了一個注解。
@EnableEurekaServer
將該實(shí)例注冊為一個Eureka的server角色。 -
配置文件
在resources下建一個appication.yml
配置如下:
server:
port: 8761eureka: instance: hostname: localhost client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
(注意,yml對于格式要求非常嚴(yán)格,縮進(jìn)不要搞錯)
- 啟動工程
訪問http://localhost:8761。可以看到注冊中心的界面。
此時還沒有服務(wù)提供者注冊過來。

同創(chuàng)建server一樣的步驟,建一個client
-
代碼如下
@SpringBootApplication @EnableEurekaClient @RestController public class ClientApplication { @Value("${server.port}") String port; @RequestMapping("/hi") public String home(@RequestParam String name) { return "hi "+name+",i am from port:" +port; } public static void main(String[] args) { SpringApplication.run(ClientApplication.class, args); } }用
@EnableEurekaClient表明這是一個client -
配置文件
eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ server: port: 8762 spring: application: name: service-hi 啟動工程
查看http://localhost:8761已經(jīng)注冊進(jìn)來

配置文件中spring.application.name指定的name就是注冊的服務(wù)名。
其他應(yīng)用調(diào)用也是根據(jù)這個name來找。
ribbon+restTemplate
微服務(wù)架構(gòu)中,業(yè)務(wù)被拆分成單獨(dú)的服務(wù),服務(wù)之間通過rest相互調(diào)用。在springcloud中有兩種調(diào)用方式:ribbon+restTemplate和feign。
ribbon是一個負(fù)載均衡客戶端,可以很好的控制http和tcp之上的行為,feign也是使用ribbon的。
再啟動一個進(jìn)程
為了嘗試負(fù)載均衡,基于上面的工程。改一下client的配置端口,將8762改為8763再啟動一個服務(wù)提供方client。
tips:idea默認(rèn)run 是單實(shí)例的,所以再次run main會讓你停掉之前的服務(wù)。其實(shí)在run configration中配置一下,把Single instance only選項(xiàng)勾掉就可以了。查看注冊中心
測試應(yīng)該看到

已經(jīng)有兩個服務(wù)注冊進(jìn)來了。
建一個服務(wù)消費(fèi)者
新建一個springboot工程:service-consumer
在dependency時勾選web、ribbon、eureka discovery-
在resources下新建application.yml
配置:eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ server: port: 8764 spring: application: name: service-consumer -
修改啟動類
@SpringBootApplication @EnableDiscoveryClient public class ServicveConsumerApplication { public static void main(String[] args) { SpringApplication.run(ServicveConsumerApplication.class, args); } @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } }注解@EnableDiscoveryClient作用是向注冊中心注冊自己為消費(fèi)者。
@LoadBalanced表明開啟負(fù)載均衡功能。
@Bean注解聲明一個RestTemplate bean -
建一個服務(wù)類
@Service public class HelloService { @Autowired RestTemplate restTemplate; public String sayHello(String name) { return restTemplate.getForObject("http://SERVICE-HI/hi?name=" + name, String.class); } } -
建一個對應(yīng)的測試用例
@RunWith(SpringRunner.class) @SpringBootTest(classes=ServicveConsumerApplication.class) public class HelloServiceTest { @Autowired HelloService helloService; @Test public void sayHello() throws Exception { for (int i = 0; i < 10; i++){ System.out.println(helloService.sayHello("yunsheng")); } } }
跑十次看一下結(jié)果。
hi yunsheng,i am from port:8763
hi yunsheng,i am from port:8762
hi yunsheng,i am from port:8763
hi yunsheng,i am from port:8762
hi yunsheng,i am from port:8763
hi yunsheng,i am from port:8762
hi yunsheng,i am from port:8763
hi yunsheng,i am from port:8762
hi yunsheng,i am from port:8763
hi yunsheng,i am from port:8762
很明顯看到了負(fù)載均衡的效果。
當(dāng)前我們的應(yīng)用架構(gòu)是:

feign
Feign是一個聲明試的web服務(wù)客戶端??梢宰屇銓憌eb service client更簡單。Feign默認(rèn)集成了Ribbon。
還是使用上面的工程,知識需要加一個Feign的依賴。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
-
新建一個啟動類
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class FeignConsumerApplication { public static void main(String[] args) { SpringApplication.run(FeignConsumerApplication.class, args); } }新增一個
@EnableFeignClients注解開啟Feign功能 -
新建一個feign服務(wù)類
@FeignClient(value = "service-hi") public interface FeignConsumeService { @RequestMapping(value = "/hi",method = RequestMethod.GET) String sayHiFromClientOne(@RequestParam(value = "name") String name); }
這里的功能就是一個服務(wù)代理的接口,只是內(nèi)部默認(rèn)實(shí)現(xiàn)了負(fù)載均衡。
這里的requestMapping必須是服務(wù)提供者的RequestMapping保持一致。
-
controller層對外暴露一個服務(wù)調(diào)用
@RestController public class HiController { @Autowired FeignConsumeService feignConsumeService; @RequestMapping(value = "/feignHi", method = RequestMethod.GET) public String sayHi(@RequestParam(value = "name") String name){ return feignConsumeService.sayHiFromFeign(name); } }
這里的requestMapping隨便寫。
- 啟動。

可以看到消費(fèi)者已經(jīng)注冊。
- 消費(fèi)
因?yàn)槲覀冮_放的是rest服務(wù),所以直接瀏覽器測試。
瀏覽器多次訪問
http://localhost:8764/feignHi?name=yunsheng
可以看到負(fù)載均衡的效果,間隔調(diào)用8762和8763的服務(wù)。
Hystrix斷路器
在微服務(wù)架構(gòu)中,各個服務(wù)模塊獨(dú)立部署。但是由于各種原因,并不能保證服務(wù)100%成功。如果某個服務(wù)發(fā)送異常,產(chǎn)生線程阻塞。測試有大量請求進(jìn)入,會導(dǎo)致servlet線程被耗盡。由于服務(wù)之間的依賴,導(dǎo)致耽擱服務(wù)的異常被傳播擴(kuò)大,產(chǎn)生災(zāi)難性后果。為了避免這種情況,業(yè)界采用斷路器模式,當(dāng)服務(wù)不可用情況達(dá)到一定閾值后,斷路器打開,避免故障傳播。
-
添加依賴
基于service-consumer工程添加依賴<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> </dependency> -
改造啟動類
@SpringBootApplication @EnableDiscoveryClient @EnableHystrix public class ServicveConsumerApplication { public static void main(String[] args) { SpringApplication.run(ServicveConsumerApplication.class, args); } @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } }添加
@EnableHystrix注解,開啟斷路器功能 -
改造HelloService
@Service public class HelloService { @Autowired RestTemplate restTemplate; @HystrixCommand(fallbackMethod = "sayErr") public String sayHello(String name) { return restTemplate.getForObject("http://SERVICE-HI/hi?name=" + name, String.class); } public String sayErr(String name) { return "hi,"+name+",sorry,error!"; } }
給之前的sayHello方法添加@HystrixCommand注解,并指定失敗時調(diào)用的方法。
-
測試
先將8762和8763兩個服務(wù)提供者關(guān)掉。
先將 @HystrixCommand(fallbackMethod = "sayErr")注釋掉,關(guān)閉斷路器。
為了方便看出效果。也給ribbon方式新建一個rest的controller。@RestController public class HiController { @Autowired HelloService helloService; @RequestMapping(value = "/ribbonHi", method = RequestMethod.GET) public String sayHi(@RequestParam(value = "name") String name) { return helloService.sayHello("yys"); } }
瀏覽器訪問http://localhost:8764/ribbonHi?name=yys
需要等到響應(yīng)超時才能得到錯誤頁面。
但是開啟了斷路器之后,
再次嘗試,可以看到很快輸出hi,yys,sorry,error!。