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