Spring Cloud
代碼地址:https://github.com/jedyang/springCloud
springcloud中文社區(qū)給的定義是微服務(wù)架構(gòu)集大成者,云計(jì)算最佳業(yè)務(wù)實(shí)踐。
我的理解是分布式服務(wù)全家桶。
如果你有dubbo或其他分布式框架的使用經(jīng)驗(yàn),那么對(duì)springcloud提供的特性是很好理解的。
springcloud提供了如下特性:
- 分布式配置
- 服務(wù)注冊(cè)和發(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云平臺(tái))。讓你很容易實(shí)現(xiàn)SSO(單點(diǎn)登錄)、OAuth2(一個(gè)關(guān)于授權(quán)的開放網(wǎng)絡(luò)標(biāo)準(zhǔn))功能,以及Cloudfoundry的服務(wù)分發(fā)器。
Spring Cloud Cloud Foundry Service Broker
提供了一個(gè)擴(kuò)展點(diǎn),以便于開發(fā)基于 Cloud Foundry管理的服務(wù)分發(fā)器。
Spring Cloud Cluster
主從選舉。基于zookeeper,redis,hazelcast(hazelcast是一個(gè)開放源碼集群和高度可擴(kuò)展的數(shù)據(jù)分發(fā)平臺(tái)),consul(支持多數(shù)據(jù)中心下,分布式高可用的,服務(wù)發(fā)現(xiàn)和配置共享)的抽象和實(shí)現(xiàn)。
Spring Cloud Consul
基于Consul實(shí)現(xiàn)的服務(wù)發(fā)現(xiàn)和配置管理
Spring Cloud Security
提供了對(duì)OAuth2 負(fù)載均衡的客戶端,以及基于Zuul代理的頭部校驗(yàn)。
Spring Cloud Sleuth
對(duì)spring cloud分布式應(yīng)用的服務(wù)鏈路追蹤
Spring Cloud Data Flow
一個(gè)建立數(shù)據(jù)集成和實(shí)時(shí)處理管道的工具集。
簡(jiǎn)化了應(yīng)用程序的開發(fā)和部署 將精力集中到數(shù)據(jù)處理的用例上。
Spring Cloud Stream
一個(gè)輕量級(jí)的事件驅(qū)動(dòng)的微服務(wù)框架。可以快速構(gòu)建應(yīng)用和外部系統(tǒng)對(duì)接。可以在springboot應(yīng)用之間通過簡(jiǎn)單的聲明模型,基于kafka或rabbitMq交互消息。
Spring Cloud Stream App Starters
一個(gè)基于springboot的同外部系統(tǒng)的集成應(yīng)用
Spring Cloud Task
短時(shí)任務(wù)處理框架。如定時(shí)任務(wù)。
Spring Cloud Task App Starters
對(duì)應(yīng)的具體應(yīng)用。
Spring Cloud for Amazon Web Services
方便同AWS服務(wù)集成
Spring Cloud Connectors
使各種PaaS平臺(tái)應(yīng)用連接基礎(chǔ)后端服務(wù)(如數(shù)據(jù)庫服務(wù)、消息中間件)更容易。
Spring Cloud Starters
用于使基于springcloud的依賴管理更方便
Spring Cloud CLI
是一個(gè)插件,可以用groovy語言快速創(chuàng)建spring cloud應(yīng)用
Spring Cloud Contract
是一個(gè)消費(fèi)者驅(qū)動(dòng)的、面向Java的契約框架。
Spring Cloud Netflix
Netflix是spring cloud的核心框架,必學(xué)必用。
微服務(wù)架構(gòu)
首先,我們來看看一般的微服務(wù)架構(gòu)需要的功能或使用場(chǎng)景:
我們把整個(gè)系統(tǒng)根據(jù)業(yè)務(wù)拆分成幾個(gè)子系統(tǒng)。
每個(gè)子系統(tǒng)可以部署多個(gè)應(yīng)用,多個(gè)應(yīng)用之間使用負(fù)載均衡。
需要一個(gè)服務(wù)注冊(cè)中心,所有的服務(wù)都在注冊(cè)中心注冊(cè),負(fù)載均衡也是通過在注冊(cè)中心注冊(cè)的服務(wù)來使用一定策略來實(shí)現(xiàn)。
所有的客戶端都通過同一個(gè)網(wǎng)關(guān)地址訪問后臺(tái)的服務(wù),通過路由配置,網(wǎng)關(guān)來判斷一個(gè)URL請(qǐng)求由哪個(gè)服務(wù)處理。請(qǐng)求轉(zhuǎn)發(fā)到服務(wù)上的時(shí)候也使用負(fù)載均衡。
服務(wù)之間有時(shí)候也需要相互訪問。例如有一個(gè)用戶模塊,其他服務(wù)在處理一些業(yè)務(wù)的時(shí)候,要獲取用戶服務(wù)的用戶數(shù)據(jù)。
需要一個(gè)斷路器,及時(shí)處理服務(wù)調(diào)用時(shí)的超時(shí)和錯(cuò)誤,防止由于其中一個(gè)服務(wù)的問題而導(dǎo)致整體系統(tǒng)的癱瘓。
還需要一個(gè)監(jiān)控功能,監(jiān)控每個(gè)服務(wù)調(diào)用花費(fèi)的時(shí)間等。
Netflix
Spring Cloud Netflix框架剛好就滿足了上面所有的需求,而且最重要的是,使用起來非常的簡(jiǎn)單。Spring Cloud Netflix包含的組件及其主要功能大致如下:
Eureka,服務(wù)注冊(cè)和發(fā)現(xiàn),它提供了一個(gè)服務(wù)注冊(cè)中心、服務(wù)發(fā)現(xiàn)的客戶端,還有一個(gè)方便的查看所有注冊(cè)的服務(wù)的界面。 所有的服務(wù)使用Eureka的服務(wù)發(fā)現(xiàn)客戶端來將自己注冊(cè)到Eureka的服務(wù)器上。
Zuul,網(wǎng)關(guān),所有的客戶端請(qǐng)求通過這個(gè)網(wǎng)關(guān)訪問后臺(tái)的服務(wù)。他可以使用一定的路由配置來判斷某一個(gè)URL由哪個(gè)服務(wù)來處理。并從Eureka獲取注冊(cè)的服務(wù)來轉(zhuǎn)發(fā)請(qǐng)求。
Ribbon,即負(fù)載均衡,Zuul網(wǎng)關(guān)將一個(gè)請(qǐng)求發(fā)送給某一個(gè)服務(wù)的應(yīng)用的時(shí)候,如果一個(gè)服務(wù)啟動(dòng)了多個(gè)實(shí)例,就會(huì)通過Ribbon來通過一定的負(fù)載均衡策略來發(fā)送給某一個(gè)服務(wù)實(shí)例。
Feign,服務(wù)客戶端,服務(wù)之間如果需要相互訪問,可以使用RestTemplate,也可以使用Feign客戶端訪問。它默認(rèn)會(huì)使用Ribbon來實(shí)現(xiàn)負(fù)載均衡。
Hystrix,監(jiān)控和斷路器。我們只需要在服務(wù)接口上添加Hystrix標(biāo)簽,就可以實(shí)現(xiàn)對(duì)這個(gè)接口的監(jiān)控和斷路器功能。
Hystrix Dashboard,監(jiān)控面板,他提供了一個(gè)界面,可以監(jiān)控各個(gè)服務(wù)上的服務(wù)調(diào)用所消耗的時(shí)間等。
Turbine,監(jiān)控聚合,使用Hystrix監(jiān)控,我們需要打開每一個(gè)服務(wù)實(shí)例的監(jiān)控信息來查看。而Turbine可以幫助我們把所有的服務(wù)實(shí)例的監(jiān)控信息聚合到一個(gè)地方統(tǒng)一查看。這樣就不需要挨個(gè)打開一個(gè)個(gè)的頁面一個(gè)個(gè)查看。
接下來一個(gè)一個(gè)看。
Eureka
作為服務(wù)注冊(cè)與發(fā)現(xiàn)的中心。我們的demo分為兩部分,一個(gè)server,一個(gè)client。 這里的server是指注冊(cè)中心。client是指向注冊(cè)中心注冊(cè)或者訂閱服務(wù)的消費(fèi)者。
創(chuàng)建一個(gè)maven主工程
建一個(gè)server模塊。
右鍵-->new module -->Spring Initializr -->next
-->填寫相關(guān)信息-->next
-->dependencies 選Cloud Discovery-->Eureka Server
-->finish-
第一個(gè)server
// 服務(wù)注冊(cè)中心注解 @EnableEurekaServer @SpringBootApplication public class ServerApplication { public static void main(String[] args) { SpringApplication.run(ServerApplication.class, args); } }
代碼非常簡(jiǎn)單,就是在原來springboot的啟動(dòng)類上加了一個(gè)注解。
@EnableEurekaServer
將該實(shí)例注冊(cè)為一個(gè)Eureka的server角色。 -
配置文件
在resources下建一個(gè)appication.yml
配置如下:
server:
port: 8761eureka: instance: hostname: localhost client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
(注意,yml對(duì)于格式要求非常嚴(yán)格,縮進(jìn)不要搞錯(cuò))
- 啟動(dòng)工程
訪問http://localhost:8761。可以看到注冊(cè)中心的界面。
此時(shí)還沒有服務(wù)提供者注冊(cè)過來。
同創(chuàng)建server一樣的步驟,建一個(gè)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
表明這是一個(gè)client -
配置文件
eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ server: port: 8762 spring: application: name: service-hi
啟動(dòng)工程
查看http://localhost:8761已經(jīng)注冊(cè)進(jìn)來
配置文件中spring.application.name指定的name就是注冊(cè)的服務(wù)名。
其他應(yīng)用調(diào)用也是根據(jù)這個(gè)name來找。
ribbon+restTemplate
微服務(wù)架構(gòu)中,業(yè)務(wù)被拆分成單獨(dú)的服務(wù),服務(wù)之間通過rest相互調(diào)用。在springcloud中有兩種調(diào)用方式:ribbon+restTemplate和feign。
ribbon是一個(gè)負(fù)載均衡客戶端,可以很好的控制http和tcp之上的行為,feign也是使用ribbon的。
再啟動(dòng)一個(gè)進(jìn)程
為了嘗試負(fù)載均衡,基于上面的工程。改一下client的配置端口,將8762改為8763再啟動(dòng)一個(gè)服務(wù)提供方client。
tips:idea默認(rèn)run 是單實(shí)例的,所以再次run main會(huì)讓你停掉之前的服務(wù)。其實(shí)在run configration中配置一下,把Single instance only選項(xiàng)勾掉就可以了。查看注冊(cè)中心
測(cè)試應(yīng)該看到
已經(jīng)有兩個(gè)服務(wù)注冊(cè)進(jìn)來了。
建一個(gè)服務(wù)消費(fèi)者
新建一個(gè)springboot工程:service-consumer
在dependency時(shí)勾選web、ribbon、eureka discovery-
在resources下新建application.yml
配置:eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ server: port: 8764 spring: application: name: service-consumer
-
修改啟動(dòng)類
@SpringBootApplication @EnableDiscoveryClient public class ServicveConsumerApplication { public static void main(String[] args) { SpringApplication.run(ServicveConsumerApplication.class, args); } @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } }
注解@EnableDiscoveryClient作用是向注冊(cè)中心注冊(cè)自己為消費(fèi)者。
@LoadBalanced表明開啟負(fù)載均衡功能。
@Bean注解聲明一個(gè)RestTemplate bean -
建一個(gè)服務(wù)類
@Service public class HelloService { @Autowired RestTemplate restTemplate; public String sayHello(String name) { return restTemplate.getForObject("http://SERVICE-HI/hi?name=" + name, String.class); } }
-
建一個(gè)對(duì)應(yīng)的測(cè)試用例
@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是一個(gè)聲明試的web服務(wù)客戶端。可以讓你寫web service client更簡(jiǎn)單。Feign默認(rèn)集成了Ribbon。
還是使用上面的工程,知識(shí)需要加一個(gè)Feign的依賴。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
-
新建一個(gè)啟動(dòng)類
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class FeignConsumerApplication { public static void main(String[] args) { SpringApplication.run(FeignConsumerApplication.class, args); } }
新增一個(gè)
@EnableFeignClients
注解開啟Feign功能 -
新建一個(gè)feign服務(wù)類
@FeignClient(value = "service-hi") public interface FeignConsumeService { @RequestMapping(value = "/hi",method = RequestMethod.GET) String sayHiFromClientOne(@RequestParam(value = "name") String name); }
這里的功能就是一個(gè)服務(wù)代理的接口,只是內(nèi)部默認(rèn)實(shí)現(xiàn)了負(fù)載均衡。
這里的requestMapping必須是服務(wù)提供者的RequestMapping保持一致。
-
controller層對(duì)外暴露一個(gè)服務(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隨便寫。
- 啟動(dòng)。
可以看到消費(fèi)者已經(jīng)注冊(cè)。
- 消費(fèi)
因?yàn)槲覀冮_放的是rest服務(wù),所以直接瀏覽器測(cè)試。
瀏覽器多次訪問
http://localhost:8764/feignHi?name=yunsheng
可以看到負(fù)載均衡的效果,間隔調(diào)用8762和8763的服務(wù)。
Hystrix斷路器
在微服務(wù)架構(gòu)中,各個(gè)服務(wù)模塊獨(dú)立部署。但是由于各種原因,并不能保證服務(wù)100%成功。如果某個(gè)服務(wù)發(fā)送異常,產(chǎn)生線程阻塞。測(cè)試有大量請(qǐng)求進(jìn)入,會(huì)導(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>
-
改造啟動(dòng)類
@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注解,并指定失敗時(shí)調(diào)用的方法。
-
測(cè)試
先將8762和8763兩個(gè)服務(wù)提供者關(guān)掉。
先將 @HystrixCommand(fallbackMethod = "sayErr")注釋掉,關(guān)閉斷路器。
為了方便看出效果。也給ribbon方式新建一個(gè)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)超時(shí)才能得到錯(cuò)誤頁面。
但是開啟了斷路器之后,
再次嘗試,可以看到很快輸出hi,yys,sorry,error!
。