這幾天抽空搞了下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
除了這兩個核心組件外,下列組件一般在大型系統中會一塊兒使用(中小型系統可能不會採用):mysql
在開始正式講解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
=====================================spring
再重複一遍,spring cloud依賴於spring boot,因此不熟悉spring boot的同窗,先掌握下spring boot,可參考筆者的寫給大忙人spring boot 1.x學習指南。
有些書籍一開始就講spring cloud config,有些書籍則幾乎能夠認爲把官方文檔翻譯一遍 ,官方文檔不少狀況下對某些假設是很理想化的,因此,我的以爲有些時候就該有得放矢,不要追求大而全,很簡單的就不要大談特談了。
如今來看下服務的註冊和調用。
因爲在實際開發中,咱們基本上使用Feign開發,因此,這裏咱們重點看Feign方式的RPC調用。
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); } }
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
經過爲controller定義要實現的接口,就作到了一次定義,屢次引用(這和咱們使用傳統的spring mvc開發不一樣,建議把RequestMapper定義在接口上)。
因此,從使用上來講,Feign很簡單,對於有過其餘RPC開發經驗的同窗來講,就是換個註解而已。
我記得dubbo和其餘rpc在這一塊作的不是特別好,雖然spring cloud提供了該特性、並且很靈活,可是它有個關鍵設計很雞肋,後面會講到。
在spring cloud的微服務架構中,一個請求調用通過的節點內關鍵步驟以下:
合理的隔離機制應該是能夠自定義線程池數量,以及哪些服務放在哪一個線程池。以下:
天然,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);
)
<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); } }
eureka: instance: preferIpAddress: true client: registerWithEureka: true fetchRegistry: true serviceUrl: defaultZone: http://localhost:8761/eureka/
要啓用Spring Cloud sleuth,只要在pom文件中包含下列依賴便可:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency>
<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的配置能夠參考官方文檔。
<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就能夠正常注入了。
@SpringBootApplication @RefreshScope public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
management: security: enabled: false