本文篇幅較長,建議合理利用右上角目錄進行查看(若是沒有目錄請刷新)。git
本文基於《Spring 微服務實戰》一書進行總結和擴展,你們也能夠自行研讀此書。web
互聯網鏈接了社會各個部分,鏈接了全球客戶羣,帶來了全球競爭算法
將單體的應用程序,分解爲互相獨立構建和部署的小型服務,帶來的好處是:spring
如今「雲」的概念已經被過分使用。數據庫
雲是網絡、互聯網的一種比喻說法。apache
將數據的運算,放到互聯網中去。編程
新型的模式json
微服務架構核心概念是:把每個服務打包和部署爲獨立的製品。後端
咱們採用IaaS+Docker做爲部署環境api
爲何不是基於PaaS:IaaS與供應商無關,PaaS要考慮基於供應商提供的微服務抽象,因此考慮IaaS有更大靈活性,能夠跨供應商移植。
編寫單個微服務的代碼很簡單,可是要開發、運行、支持一個健壯的微服務應用
互聯網的發展與變化,要求如今的應用程序在高複雜性下仍然能保持快速交付、高彈性、高可用、高伸縮性,微服務架構就是爲了實現這個要求所出現的系統架構。
微服務架構,把系統的需求,分割成若干個能夠獨立運行的應用,並做爲服務暴露出來;而後經過服務的註冊與發現,使這些服務之間能夠互相協做,完成系統任務。
架構圖:
服務發現:在分佈式計算架構中,咱們要調用某臺計算機提供的服務時,必須知道這臺機器的物理地址,這個稱爲服務發現。
微服務架構與服務發現:
咱們下面將經過Spring Cloud和Netflix技術,來實現這一個功能。
DNS和負載均衡器的傳統服務位置解析模型:
缺點:
基於雲的微服務環境但願實現的服務發現架構有以下特色:
上面的模型中,每次調用註冊的微服務實例時,服務發現引擎就會被調用。這種方法很脆弱,由於服務客戶端徹底依賴於服務發現引擎來查找和調用服務。
增長客戶端負載均衡的服務發現模型:
在這個模型中,當服務消費者須要調用一個服務時:
使用Spring Cloud和Netflix實現的服務發現架構模型:
Eureka服務的做用是:
提供服務的註冊中心。
主POM文件:
添加Spring Cloud的版本管理,添加Spring Boot的Starter依賴和Test依賴。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>xxx</groupId> <artifactId>xxx</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <modules> <module>base-eureka</module> </modules> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.M8</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
Eureka模塊POM文件:
Eureka服務做爲項目的一個模塊進行添加,並在POM文件中添加Eureka服務器依賴以及Spring Cloud Starter依賴
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>XXX</artifactId> <groupId>XXX</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>base-eureka</artifactId> <packaging>jar</packaging> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> </dependency> </dependencies> </project>
建立Eureka服務程序引導類:
經過@EnableEurekaServer註解,啓動Eureka服務器
@SpringBootApplication @EnableEurekaServer public class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class, args); } }
添加配置文件:
經過application.yml文件對Eureka服務器進行配置
spring: application: name: eureka server: port: 8761 #啓動端口 eureka: client: registerWithEureka: false #false:不做爲一個客戶端註冊到註冊中心 fetchRegistry: false #爲true時,能夠啓動,但報異常:Cannot execute request on any known server
啓動服務器:
根據配置的端口,經過引導類啓動程序後,訪問如http://localhost:8761/,便可看到以下畫面,說明Eureka服務器啓動成功
模塊名爲:testrest
testrest模塊POM文件:
添加Spring Boot Web依賴和Eureka客戶端依賴
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>XXX</artifactId> <groupId>XXX</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>base-testrest</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies> </project>
testrest服務程序引導類:
@SpringBootApplication public class TestRestApplication { public static void main(String[] args) { SpringApplication.run(TestRestApplication.class, args); } }
testrest控制器:
@RestController @RequestMapping("/test") public class TestController { @RequestMapping(value = "/{id}", method = RequestMethod.GET) public String selectById(@PathVariable String id) { return id; } }
配置文件:
server: port: 8801 #啓動端口 spring: application: name: base-testrest #將使用Eureka註冊的服務的邏輯名稱 eureka: instance: preferIpAddress: true #註冊服務的IP,而不是服務器名稱 client: registerWithEureka: true #向Eureka註冊服務 fetchRegistry: true serviceUrl: defaultZone: http://localhost:8761/eureka/ #Eureka服務的位置
啓動客戶端:
先啓動Eureka服務器,而後再啓動testrest程序引導類,再訪問Eureka服務器界面,如http://localhost:8761/,便可看見此服務成功註冊到Eureka服務器中了
對註冊到Eureka的服務進行發現和消費,有幾種方式:
這裏僅介紹Feign客戶端
模塊名爲:testrestconsume
testrestconsume模塊POM文件:
添加Spring Boot Web依賴、Eureka客戶端依賴及Feign依賴
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>XXX</artifactId> <groupId>XXX</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>base-testrestconsume</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies> </project>
testrestconsume服務程序引到類:
添加註解@EnableFeignClients
@SpringBootApplication @EnableFeignClients public class TestRestConsumeApplication { public static void main(String[] args) { SpringApplication.run(TestRestConsumeApplication.class, args); } }
建立接口發現和消費微服務:
一個微服務的多個實例,使用其spring.application.name進行標識;
Feign客戶端利用接口和@FeignClient客戶端,使用name標識,能夠直接調用註冊到Eureka中的服務。
@FeignClient("base-testrest") public interface ITestConsumeService { @RequestMapping(value="/test/{id}",method = RequestMethod.GET) public String selectById(@PathVariable("id") String id); }
控制器調用接口:
Feign客戶端接口,直接經過@Autowired自動裝配便可使用
@RestController @RequestMapping("/testconsume") public class TestConsumeController { @Autowired private ITestConsumeService testConsumeService; @RequestMapping(value = "/{id}", method = RequestMethod.GET) public String selectById(@PathVariable String id) { return testConsumeService.selectById(id); } }
配置文件:
主要須要指明Eureka服務的地址
server: port: 8802 #啓動端口 spring: application: name: base-testrestconsume #將使用Eureka註冊的服務的邏輯名稱 eureka: instance: preferIpAddress: true #註冊服務的IP,而不是服務器名稱 client: serviceUrl: #拉取註冊表的本地副本 defaultZone: http://${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}/eureka/ #Eureka服務的位置
一般一個系統的服務不會徹底暴露給全部人使用,而是根據用戶的身份、權力來決定是否容許其使用。
咱們下面將經過Spring Cloud Security、Spring Security Oauth2和Spring Security Jwt技術,來實現這一個功能。
OAuth2是一個基於令牌的安全驗證和受權框架,它將安全性分解爲4個部分:
OAuth2運做流程:
OAuth2規範具備如下4種類型的受權,這裏僅討論密碼的方式:
主POM文件:
添加Spring Boot Web依賴、Spring Cloud Security依賴及Spring Security Oauth2依賴
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>XXX</artifactId> <groupId>XXX</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>base-authentication</artifactId> <packaging>jar</packaging> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-security</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 --> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.3.3.RELEASE</version> </dependency> </dependencies> </project>
程序引導類:
添加驗證服務器註解@EnableAuthorizationServer代表這個是驗證服務器,添加資源服務器註解@EnableResourceServer代表這個程序提供的資源受到OAuth2保護
@SpringBootApplication @EnableResourceServer @EnableAuthorizationServer public class AuthenticationApplication { public static void main(String[] args) { SpringApplication.run(AuthenticationApplication.class, args); } }
OAuch2配置類:
配置一個AuthorizationServerConfigurerAdapter類,用於配置驗證服務註冊哪些應用程序
@Configuration public class OAuth2Config extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; // 由於當前security版本,密碼須要以{0}XXXX的方式,增長密碼的編碼方式在花括號內進行傳輸 // 因此若是想直接傳XXXX的密碼,須要加這段代碼 @Bean public static NoOpPasswordEncoder passwordEncoder() { return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance(); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("eagleeye")// 容許訪問的客戶端 .secret("thisissecret")// 密碼 .authorizedGrantTypes(// 容許的受權類型 "refresh_token", "password", "client_credentials") .scopes("webclient", "mobileclient");// 引用程序做用域 } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .authenticationManager(authenticationManager) .userDetailsService(userDetailsService); } }
WebSecurity配置類:
配置一個WebSecurityConfigurerAdapter類,用於配置系統有哪些用戶,分別是什麼角色
@Configuration public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override @Bean public UserDetailsService userDetailsServiceBean() throws Exception { return super.userDetailsServiceBean(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("john.carnell") .password("password1") .roles("USER") .and() .withUser("william.woodward") .password("password2") .roles("USER", "ADMIN"); } }
配置文件:
主要設置端口號和增長了一個url路徑前綴
spring:
application:
name: authenticationservice
server:
servlet:
context-path: /auth
port: 8901
令牌驗證端點:
建立一個REST端點,用於令牌驗證
@RestController public class TokenController { @RequestMapping(value = {"/user"}, produces = "application/json") public Map<String, Object> user(OAuth2Authentication user) { Map<String, Object> userInfo = new HashMap<>(); userInfo.put( "user", user.getUserAuthentication().getPrincipal()); userInfo.put( "authorities", AuthorityUtils.authorityListToSet( user.getUserAuthentication().getAuthorities())); return userInfo; } }
運行:
啓動程序後,使用POST方式,能夠獲取到用戶對應令牌
而後使用令牌訪問令牌驗證端點,獲取令牌信息
要對其它微服務進行保護,只須要在微服務上進行一些設置便可;而後訪問微服務資源的時候就會根據要求進行令牌驗證。
主POM文件:
添加Spring Cloud Security依賴及Spring Security Oauth2依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-security</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 --> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.3.3.RELEASE</version> </dependency>
主程序引導類:
添加資源服務器註解@EnableResourceServer代表這個程序提供的資源受到OAuth2保護
@EnableResourceServer @SpringBootApplication public class TestRestApplication { public static void main(String[] args) { SpringApplication.run(TestRestApplication.class, args); } }
配置文件:
配置OAuth2驗證服務器令牌驗證服務的地址
security:
oauth2:
resource:
userInfoUri: http://localhost:8901/auth/user
配置ResourceServer配置類:
配置一個ResourceServerConfigurerAdapter配置類,用於定義哪些資源須要什麼角色、什麼權限才能訪問
@Configuration public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception{ // 只要用戶經過認證便可訪問 http.authorizeRequests().anyRequest().authenticated(); //用戶須要有相關角色和權限才能訪問 // http // .authorizeRequests() // .antMatchers(HttpMethod.DELETE, "/v1/organizations/**") // .hasRole("ADMIN") // .anyRequest() // .authenticated(); } }
運行:
把測試的微服務以及OAuth2驗證服務器都運行起來
訪問測試的微服務
當沒有令牌時,會提示:
當令牌不正確的時候,會提示:
咱們訪問驗證服務器,傳入相關數據,獲取一個新的token,並填入上面的Headers-Authorization中,才能正確訪問資源
OAuth2是一個基於令牌的驗證框架,但它並無爲如何定義其規範中的令牌提供任何標準。
爲了矯正OAuth2令牌標準的缺陷,一個名爲JSON Web Token(JWT)的新標準脫穎而出。
JWT是因特網工程任務組(Internet Engineering Task Force,IETF)提出的開放標準(RFC-7519),旨在爲OAuth2令牌提供標準結構。
JWT令牌具備以下特色:
主POM文件:
原來已添加Spring Cloud Security依賴、Spring Security Oauth2依賴,再添加Spring Security JTW依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-security</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 --> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.3.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-jwt --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> <version>1.0.9.RELEASE</version> </dependency>
JWTOAuch2配置類:
去掉原來的OAuch2Config配置類,配置一個新的AuthorizationServerConfigurerAdapter類,裏面設置使用JWT做爲令牌標準,用於配置驗證服務註冊哪些應用程序
@Component @Configuration public class ServiceConfig { @Value("${signing.key}") private String jwtSigningKey=""; public String getJwtSigningKey() { return jwtSigningKey; } }
@Configuration public class JWTTokenStoreConfig { @Autowired private ServiceConfig serviceConfig; @Bean public TokenStore tokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); } @Bean @Primary // @Primary註解用於告訴Spring,若是有多個特定類型的bean(在本例中是DefaultTokenService),那麼就使用被@Primary標註的bean類型進行自動注入 public DefaultTokenServices tokenServices() { // 用於從出示給服務的令牌中讀取數據 DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); defaultTokenServices.setTokenStore(tokenStore()); defaultTokenServices.setSupportRefreshToken(true); return defaultTokenServices; } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() {// 在JWT和OAuth2服務器之間充當翻譯 JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey(serviceConfig.getJwtSigningKey());// 定義將用於簽署令牌的簽名密鑰 return converter; } @Bean public TokenEnhancer jwtTokenEnhancer() { return new JWTTokenEnhancer(); } }
public class JWTTokenEnhancer implements TokenEnhancer { // @Autowired // private OrgUserRepository orgUserRepo; // // private String getOrgId(String userName){ // UserOrganization orgUser = orgUserRepo.findByUserName( userName ); // return orgUser.getOrganizationId(); // } @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { Map<String, Object> additionalInfo = new HashMap<>(); // String orgId = getOrgId(authentication.getName()); String orgId = "id"; additionalInfo.put("organizationId", orgId); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); return accessToken; } }
@Configuration public class JWTOAuth2Config extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Autowired private TokenStore tokenStore; @Autowired private DefaultTokenServices tokenServices; @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; @Autowired private TokenEnhancer jwtTokenEnhancer; // 由於當前security版本,密碼須要以{0}XXXX的方式,增長密碼的編碼方式在花括號內進行傳輸 // 因此若是想直接傳XXXX的密碼,須要加這段代碼 @Bean public static NoOpPasswordEncoder passwordEncoder() { return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance(); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer, jwtAccessTokenConverter)); endpoints.tokenStore(tokenStore) // 注入令牌存儲 .accessTokenConverter(jwtAccessTokenConverter) // 這是鉤子,用於告訴Spring Security OAuth2代碼使用JWT .tokenEnhancer(tokenEnhancerChain) // 注入Token擴展器 .authenticationManager(authenticationManager) .userDetailsService(userDetailsService); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("eagleeye") .secret("thisissecret") .authorizedGrantTypes("refresh_token", "password", "client_credentials") .scopes("webclient", "mobileclient"); } }
其它地方無需變動。
運行:
再次運行獲取token的服務,會獲得JWT形式的token:
此時,即便不修改微服務,也能夠經過驗證;由於驗證服務器的JWT是基於OAuth2的,因此支持客戶端使用OAuth2進行驗證。
固然,爲了使用一些JWT的特性,例如自包含等,咱們須要配置微服務使用JWT。
主POM文件:
原來已添加Spring Cloud Security依賴、Spring Security Oauth2依賴,再添加Spring Security JTW依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-security</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 --> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.3.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-jwt --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> <version>1.0.9.RELEASE</version> </dependency>
建立令牌存儲配置類:
告訴微服務使用JWT做爲令牌,並須要設置密鑰和驗證服務端對應
@Configuration public class JWTTokenStoreConfig { //JWT @Bean public TokenStore tokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); } //JWT @Bean @Primary public DefaultTokenServices tokenServices() { DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); defaultTokenServices.setTokenStore(tokenStore()); defaultTokenServices.setSupportRefreshToken(true); return defaultTokenServices; } //JWT @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey("123456"); return converter; } }
建立JWTRestTemplate Bean:
由於許可證服務調用組織服務,因此須要確保OAuth2令牌被傳播。這項工做一般是經過OAuth2RestTemplate類完成的,可是OAuth2RestTemplate類並不傳播基於JWT的令牌。爲了確保許可證服務可以作到這一點,須要添加一個自定義的RestTemplate bean來完成這個注入。
@Component public class UserContext { public static final String CORRELATION_ID = "tmx-correlation-id"; public static final String AUTH_TOKEN = "Authorization"; public static final String USER_ID = "tmx-user-id"; public static final String ORG_ID = "tmx-org-id"; private static final ThreadLocal<String> correlationId= new ThreadLocal<String>(); private static final ThreadLocal<String> authToken= new ThreadLocal<String>(); private static final ThreadLocal<String> userId = new ThreadLocal<String>(); private static final ThreadLocal<String> orgId = new ThreadLocal<String>(); public static String getCorrelationId() { return correlationId.get(); } public static void setCorrelationId(String cid) {correlationId.set(cid);} public static String getAuthToken() { return authToken.get(); } public static void setAuthToken(String aToken) {authToken.set(aToken);} public static String getUserId() { return userId.get(); } public static void setUserId(String aUser) {userId.set(aUser);} public static String getOrgId() { return orgId.get(); } public static void setOrgId(String aOrg) {orgId.set(aOrg);} }
@Component public class UserContextFilter implements Filter { private static final Logger logger = LoggerFactory.getLogger(UserContextFilter.class); @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; logger.debug("I am entering the licensing service id with auth token: ", httpServletRequest.getHeader("Authorization")); UserContextHolder.getContext().setCorrelationId(httpServletRequest.getHeader(UserContext.CORRELATION_ID)); UserContextHolder.getContext().setUserId(httpServletRequest.getHeader(UserContext.USER_ID)); UserContextHolder.getContext().setAuthToken(httpServletRequest.getHeader(UserContext.AUTH_TOKEN)); UserContextHolder.getContext().setOrgId(httpServletRequest.getHeader(UserContext.ORG_ID)); filterChain.doFilter(httpServletRequest, servletResponse); } @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void destroy() {} }
public class UserContextHolder { private static final ThreadLocal<UserContext> userContext = new ThreadLocal<UserContext>(); public static final UserContext getContext(){ UserContext context = userContext.get(); if (context == null) { context = createEmptyContext(); userContext.set(context); } return userContext.get(); } public static final void setContext(UserContext context) { Assert.notNull(context, "Only non-null UserContext instances are permitted"); userContext.set(context); } public static final UserContext createEmptyContext(){ return new UserContext(); } }
public class UserContextInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept( HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { HttpHeaders headers = request.getHeaders(); headers.add(UserContext.CORRELATION_ID, UserContextHolder.getContext().getCorrelationId()); headers.add(UserContext.AUTH_TOKEN, UserContextHolder.getContext().getAuthToken()); return execution.execute(request, body); } }
@Configuration public class JWTRestTemplateConfig { @Primary @Bean public RestTemplate getCustomRestTemplate() { RestTemplate template = new RestTemplate(); List interceptors = template.getInterceptors(); if (interceptors == null) { template.setInterceptors(Collections.singletonList(new UserContextInterceptor())); } else { interceptors.add(new UserContextInterceptor()); template.setInterceptors(interceptors); } return template; } }
運行:
運行方式和OAuth2沒有區別,有一個有意思的區別是:
獲取到token後,把驗證服務器關掉,再使用token去訪問微服務,仍然能經過,這是由於JWT是自包含的,並不須要在每一個服務聯繫驗證服務器再驗證。
需求:在微服務架構這種分佈式架構中,須要確保多個服務調用的關鍵行爲正常運做,如安全、日誌記錄、用戶跟蹤等。
問題:若是把這些工做分佈在各個微服務中實現,有時會忘記,有時須要修改則全部微服務都要修改,顯然不現實。
方案:
將服務的這些橫切關注點抽象成一個獨立的,做爲全部微服務調用的過濾器和路由器的服務。這個橫切關注點稱爲服務網關(service gateway)。
客戶端不在直接調用服務,而是由服務網管做爲單個策略執行點(Policy Enforcement Point,PEP),全部調用經過服務網關進行路由,而後被路由到目的地。
使用服務網關前:難以實現安全性、日誌等橫切關注點
使用服務網關後:客戶端調用服務網關,全部服務的調用交給服務網關進行
這樣,微服務架構的橫切關注點能夠放在服務網關實現,如:
Spring Cloud集成了Netflix開源項目Zuul。
Zuul提供了許多功能,具體包括如下幾個:
使用Zuul的步驟:
注意!在Spring Cloud的新版本中,依賴名改爲了spring-cloud-starter-netflix-zuul
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency>
@SpringBootApplication @EnableZuulProxy ⇽--- 使服務成爲一個Zuul服務器 public class ZuulServerApplication { public static void main(String[] args) { SpringApplication.run(ZuulServerApplication.class, args); } }
@EnableZuulProxy與@EnableZuulServer
@EnableZuulServer:使用此註解將建立一個Zuul服務器,它不會加載任何Zuul反向代理過濾器,也不會使用Netflix Eureka進行服務發現(咱們將很快進入Zuul和Eureka集成的主題)。
本文只會使用@EnableZuulProxy註解。
Zuul將自動使用Eureka來經過服務ID查找服務,而後使用Netflix Ribbon對來自Zuul的請求進行客戶端負載均衡。
配置src/main/resources/application.yml文件,與Eureka通訊
eureka: instance: preferIpAddress: true client: registerWithEureka: true fetchRegistry: true serviceUrl: defaultZone: http://localhost:8761/eureka/
Zuul的核心是一個反向代理中間服務器,負責捕獲客戶端的請求,而後表明客戶端調用遠程資源。
在微服務架構的狀況下,Zuul(反向代理)從客戶端接收微服務調用並將其轉發給下游服務。服務客戶端認爲它只與Zuul通訊。
Zuul要與下游服務進行溝通,Zuul必須知道如何將進來的調用映射到下游路由。Zuul有幾種機制來作到這一點,包括:
Zuul不須要配置,默認自動使用正在調用的服務的Eureka服務ID,並將其映射到下游服務實例。例如,若是要調用organizationservice並經過Zuul使用自動路由,則能夠使用如下URL做爲端點,讓客戶端調用Zuul服務實例:
http://localhost:5555/organizationservice/v1/organizations/e254f8c-c442-4ebe-
使用帶有Eureka的Zuul的優勢在於,開發人員不只能夠擁有一個能夠發出調用的單個端點,有了Eureka,開發人員還能夠添加和刪除服務的實例,而無須修改Zuul。例如,能夠向Eureka添加新服務,Zuul將自動路由到該服務,由於Zuul會與Eureka進行通訊,瞭解實際服務端點的位置。
Zuul服務器上的/routes端點能夠查看服務中全部映射的列表。如http://localhost:5555/routes
經過zuul註冊的服務的映射展現在從/route調用返回的JSON體的左邊,路由映射到的實際Eureka服務ID展現在其右邊。
Zuul容許開發人員更細粒度地明肯定義路由映射,而不是單純依賴服務的Eureka服務ID建立的自動路由。
能夠經過在zuulsvr/src/main/resources/application.yml中手動定義路由映射。
zuul: routes: organizationservice: /organization/**
經過添加上述配置,如今咱們就能夠經過訪問/organization/v1/organizations/ {organization-id}路由來訪問組織服務了。
查看route的結果
此時出現2條服務條目,一條是根據Eureka自動映射的,一條是咱們在配置文件中手動映射的。
能夠經過application.yml文件添加一個額外的Zuul參數ignored-services來排除Eureka自動映射的服務.
如下代碼片斷展現瞭如何使用ignored-services屬性從Zuul完成的自動映射中排除Eureka服務ID organizationservice。
zuul: ignored-services: 'organizationservice' routes: organizationservice: /organization/**
ignored-services屬性容許開發人員定義想要從註冊中排除的Eureka服務ID的列表,該列表以逗號進行分隔。
添加前綴標記
zuul:
ignored-services: '*' ⇽--- ignored-services被設置爲*,以排除全部基於Eureka服務ID的路由的註冊
prefix: /api ⇽--- 全部已定義的服務都將添加前綴/api
routes:
organizationservice: /organization/** ⇽--- organizationservice和licensingservice分別映射到organization和licensing
licensingservice: /licensing/**
如今,則須要經過/api/organization/v1/organization/ {organization-id}來訪問網關接口了
Zuul能夠用來路由那些不受Eureka管理的服務。在這種狀況下,能夠創建Zuul直接路由到一個靜態定義的URL。
zuul:
routes:
licensestatic: ⇽--- Zuul用於在內部識別服務的關鍵字
path: /licensestatic/** ⇽--- 許可證服務的靜態路由
url: http://licenseservice-static:8081 ⇽--- 已創建許可證服務的靜態實例,它將被直接調用,而不是由Zuul經過Eureka調用
如今,licensestatic端點再也不使用Eureka,而是直接將請求路由到http://licenseservice-static:8081端點。
這裏存在一個問題,那就是經過繞過Eureka,只有一條路徑能夠用來指向請求。
幸運的是,開發人員能夠手動配置Zuul來禁用Ribbon與Eureka集成,而後列出Ribbon將進行負載均衡的各個服務實例。
zuul: routes: licensestatic: path: /licensestatic/** serviceId: licensestatic ⇽--- 定義一個服務ID,該服務ID將用於在Ribbon中查找服務 ribbon: eureka: enabled: false ⇽--- 在Ribbon中禁用Eureka支持 licensestatic: ribbon: listOfServers: http://licenseservice-static1:8081, http://licenseservice-static2:8082 ⇽--- 指定請求會路由到的服務器列表
可是若是禁用了Ribbon與Eureka集成,Zuul沒法經過Ribbon來緩存服務的查找,那麼每次調用都會調用Eureka。
解決這些問題,能夠經過對非JVM應用程序創建單獨的Zuul服務器來處理這些路由。例如經過Spring Cloud Sidecar使用Eureka實例註冊非JVM服務,而後經過Zuul進行代理。(這裏不介紹Spring Cloud Sidecar)
動態從新加載路由的功能容許在不回收Zuul服務器的狀況下更改路由的映射。
Zuul公開了基於POST的端點路由/refresh
,其做用是讓Zuul從新加載路由配置。在訪問完refresh
端點以後,若是訪問/routes
端點,就會看到路由被刷新了。
Zuul使用Netflix的Hystrix和Ribbon庫,來幫助防止長時間運行的服務調用影響服務網關的性能。
在默認狀況下,對於任何須要用超過1 s的時間(這是Hystrix默認值)來處理請求的調用,Zuul將終止並返回一個HTTP 500錯誤。
能夠使用hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
屬性來爲全部經過Zuul運行的服務設置Hystrix超時。
zuul.prefix: /api
zuul.routes.organizationservice: /organization/**
zuul.routes.licensingservice: /licensing/**
zuul.debug.request: true
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 2500
爲特定服務設置Hystrix超時,能夠使用須要覆蓋超時的服務的Eureka服務ID名稱來替換屬性的default
部分。
hystrix.command.licensingservice.execution.isolation.thread.timeoutInMilliseconds:3000
最後,讀者須要知曉另一個超時屬性。雖然已經覆蓋了Hystrix的超時,Netflix Ribbon一樣會超時任何超過5 s的調用。儘管我強烈建議讀者從新審視調用時間超過5 s的調用的設計,但讀者能夠經過設置屬性servicename.ribbon.ReadTimeout
來覆蓋Ribbon超時。
hystrix.command.licensingservice.execution.isolation.thread.timeoutInMilliseconds: 7000
licensingservice.ribbon.ReadTimeout: 7000
經過Zuul網關代理確實簡化了服務調用,可是Zuul的真正威力在於能夠爲全部流經網關的服務調用編寫自定義邏輯。
例如:安全性、日誌記錄和對全部服務的跟蹤。
Zuul實現這個功能的方式是:過濾器。
Zuul支持這3種類型過濾器:
Zuul的運做過程:
分佈式系統的傳統彈性實現:集羣關鍵服務器、服務間的負載均衡以及將基礎設施分離到多個位置
傳統彈性實現的缺點:只能解決致命的問題,當服務器奔潰,服務沒法調用時,應用程序纔會繞過它;而當服務變得緩慢或者性能不佳時,傳統方式則沒法繞過它
傳統彈性實現的危害:可能只是一個服務的問題,如執行緩慢等,致使線程池被佔用完,或數據庫鏈接池被佔用完,結果最終致使整個服務器資源被耗盡,致使服務器崩潰;甚至可能從一個服務器,蔓延往上游服務器去蔓延,致使整個生態系統崩潰。
解決方案:客戶端彈性模式
客戶端:是指調用遠程服務或遠程資源的應用程序
客戶端彈性模式:當調用的遠程服務或遠程資源,出現緩慢或其餘問題時,客戶端執行「快速失敗」,保證本身的線程池和數據庫鏈接不被佔用,防止遠程服務或資源的問題向上遊傳播
四種模式:
在前面的服務發現中介紹到,客戶端從服務發現代理(如Netflix Eureka)查找服務的全部實例,而後緩存服務實例的物理位置。
每當服務消費者須要調用該服務實例時,客戶端負載均衡器將從它維護的服務位置池返回一個位置。
由於客戶端負載均衡器位於服務客戶端和服務消費者之間,因此負載均衡器能夠檢測服務實例是否拋出錯誤或表現不佳。
若是客戶端負載均衡器檢測到問題,它能夠從可用服務位置池中移除該服務實例,並防止未來的服務調用訪問該服務實例。
使用Netflix的Ribbon庫提供的開箱即用的功能,不須要額外的配置便可實現。
斷路器模式是模仿電路斷路器的客戶端彈性模式。
當遠程服務被調用時,斷路器將監視這個調用。
若是調用時間太長,斷路器將會介入並中斷調用。
此外,斷路器將監視全部對遠程資源的調用,若是對某一個遠程資源的調用失敗次數足夠多,那麼斷路器實現就會出現並採起快速失敗,阻止未來調用失敗的遠程資源。
後備模式中,當遠程服務調用失敗時,服務消費者將執行替代代碼路徑,並嘗試經過其餘方式執行操做,而不是生成一個異常。
這一般涉及從另外一數據源查找數據或將用戶的請求進行排隊以供未來處理。
用戶的調用結果不會顯示爲提示問題的異常,但用戶可能會被告知,他們的請求要在晚些時候被知足。
例如,假設咱們有一個電子商務網站,它能夠監控用戶的行爲,並嘗試向用戶推薦其餘能夠購買的產品。
一般來講,能夠調用微服務來對用戶過去的行爲進行分析,並返回針對特定用戶的推薦列表。
可是,若是這個偏好服務失敗,那麼後備策略多是檢索一個更通用的偏好列表,該列表基於全部用戶的購買記錄分析得出,而且更爲廣泛。
這些更通用的偏好列表數據可能來自徹底不一樣的服務和數據源。
艙壁模式是創建在造船的概念基礎上的。
經過使用艙壁模式,能夠把遠程資源的調用分到線程池中,並下降一個緩慢的遠程資源調用拖垮整個應用程序的風險。
線程池充當服務的「艙壁」。
每一個遠程資源都是隔離的,並分配給線程池。
若是一個服務響應緩慢,那麼這種服務調用的線程池就會飽和並中止處理請求,而對其餘服務的服務調用則不會變得飽和,由於它們被分配給了其餘線程池。
一個實現客戶端彈性的例子
第一種場景:
第二種場景:
第三種場景:
總結斷路器模式提供的關鍵能力:
當遠程服務處於降級狀態時,應用程序將會快速失敗,並防止一般會拖垮整個應用程序的資源耗盡問題的出現。在大多數中斷狀況下,最好是部分服務關閉而不是徹底關閉。
經過超時和快速失敗,斷路器模式使應用程序開發人員有能力優雅地失敗,或尋求替代機制來執行用戶的意圖。
例如,若是用戶嘗試從一個數據源檢索數據,而且該數據源正在經歷服務降級,那麼應用程序開發人員能夠嘗試從其餘地方檢索該數據。
有了斷路器模式做爲中介,斷路器能夠按期檢查所請求的資源是否從新上線,並在沒有人爲干預的狀況下從新容許對該資源進行訪問。
對於有幾百個服務的系統,這種自恢復能力很重要,而不是靠人爲手工去恢復這些服務的狀態。
構建斷路器模式、後備模式和艙壁模式的實現須要對線程和線程管理有深刻的理解,正確地作到這一點很困難。
咱們如今能夠藉助Spring Cloud和Netflix的Hystrix庫,輕鬆實現這些模式。
一、引入依賴和使用註解開啓斷路器模式
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> </dependency>
@EnableCircuitBreaker public class Application { }
斷路器咱們介紹2種類型:包裝數據庫調用、包裝服務調用
6、使用Spring Cloud Steam的事件驅動架構
基於消息,實現微服務間異步通訊
6.一、爲何使用消息傳遞、EDA和微服務
假設許可證服務須要調用組織服務獲取組織信息,而組織信息是較少修改的。
若是每次獲取組織信息,都調取組織服務對應的API,那麼網絡開銷較大。
一個可行方案是對組織信息進行緩存。
緩存方案實施時有如下3個核心要求:
(1)緩存的組織數據應該在許可證服務全部實例之間保持一致——表明不能在許可證服務本地緩存數據。
(2)不能將組織數據緩存在許可證服務的容器的內存中——許可證服務的運行時容器一般受到大小限制,而且能夠使用不一樣的訪問模式來對數據進行訪問。本地緩存可能會帶來複雜性,由於必須保證本地緩存與集羣中的全部其餘服務同步。
(3)在更新或刪除一個組織記錄時,開發人員但願許可證服務可以識別出組織服務中出現了狀態更改——許可證服務應該使該組織的全部緩存數據失效,並將它從緩存中刪除。
2種實現方法
6.1.一、使用同步請求——響應方式來傳遞狀態變化
一、用戶調用許可證服務,查詢許可證數據
二、許可證服務中須要組織信息,先檢查Redis緩存中是否有
三、若是沒有,則調用組織服務獲取,並寫入緩存中保存
四、用戶調用組織服務能夠更新組織數據
五、組織數據更新後,組織服務應該更新緩存中數據,能夠經過調用許可證端點或直接與緩存聯繫。
問題:
一、服務之間緊耦合
許可證服務始終依賴於組織服務來檢索數據。
若是經過調用許可證端點來更新緩存,則令組織服務又依賴於許可證服務。
若是組織服務直接聯繫Redis,也不合理,由於你直接與另一個服務的數據庫進行通訊,是不正確的,一個是權限問題,另外一個不清楚規則會破壞許可證服務的數據格式。
二、服務之間脆弱性
若是許可證服務出現問題,會影響甚至拖垮組織服務;而Redis出現問題,則會影響2個服務。
三、缺少靈活性
若是有新的服務對組織服務的變化感興趣,則須要修改組織服務,從而須要從新生成構建代碼部署代碼;並且整個網絡互相依賴,容易出現一個故障點拖垮整個網絡。
6.1.二、使用消息傳遞在服務之間傳達狀態更改
加入消息傳遞方式與上面一種方法的差異在於組織變動時如何
使用消息傳遞方式將會在許可證服務和組織服務之間注入隊列。該隊列不會用於從組織服務中讀取數據,而是由組織服務用於在組織服務管理的組織數據內發生狀態更改時發佈消息。圖8-2演示了這種方法。
在圖8-2所示的模型中,每次組織數據發生變化,組織服務都發布一條消息到隊列中。許可證服務正在監視消息隊列,並在消息進入時將相應的組織記錄從Redis緩存中清除。當涉及傳達狀態時,消息隊列充當許可證服務和組織服務之間的中介。這種方法提供瞭如下4個好處:
鬆耦合;
耐久性;
可伸縮性;
靈活性。
1.鬆耦合
微服務應用程序能夠由數十個小型的分佈式服務組成,這些服務彼此交互,並對彼此管理的數據感興趣。正如在前面提到的同步設計中所看到的,同步HTTP響應在許可證服務和組織服務之間產生一個強依賴關係。儘管咱們不能徹底消除這些依賴關係,可是經過僅公開直接管理服務所擁有的數據的端點,咱們能夠嘗試最小化依賴關係。消息傳遞的方法容許開發人員解耦兩個服務,由於在涉及傳達狀態更改時,兩個服務都不知道彼此。當組織服務須要發佈狀態更改時,它會將消息寫入隊列,而許可證服務只知道它獲得一條消息,殊不知道誰發佈了這條消息。
2.耐久性
隊列的存在讓開發人員能夠保證,即便服務的消費者已經關閉,也能夠發送消息。即便許可證服務不可用,組織服務也能夠繼續發佈消息。消息將存儲在隊列中,並將一直保存到許可證服務可用。另外一方面,經過將緩存和隊列方法結合在一塊兒,若是組織服務關閉,許可證服務能夠優雅地降級,由於至少有部分組織數據將位於其緩存中。有時候,舊數據比沒有數據好。
3.可伸縮性
由於消息存儲在隊列中,因此消息發送者沒必要等待來自消息消費者的響應,它們能夠繼續工做。一樣地,若是一個消息消費者沒有足夠的能力處理從消息隊列中讀取的消息,那麼啓動更多消息消費者,並讓它們處理從隊列中讀取的消息則是一項很是簡單的任務。這種可伸縮性方法適用於微服務模型,由於我經過本書強調的其中一件事情就是,啓動微服務的新實例應該是很簡單的,讓這些追加的微服務處理持有消息的消息隊列亦是如此。這就是水平伸縮的一個示例。從隊列中讀取消息的傳統伸縮機制涉及增長消息消費者能夠同時處理的線程數。遺憾的是,這種方法最終會受消息消費者可用的CPU數量的限制。微服務模型則沒有這樣的限制,由於它是經過增長託管消費消息的服務的機器數量來進行擴大的。
4.靈活性
消息的發送者不知道誰將會消費它。這意味着開發人員能夠輕鬆添加新的消息消費者(和新功能),而不影響原始發送服務。這是一個很是強大的概念,由於能夠在沒必要觸及現有服務的狀況下,將新功能添加到應用程序。新的代碼能夠監聽正在發佈的事件,並相應地對它們作出反應。
6.1.三、消息傳遞架構的缺點
與任何架構模型同樣,基於消息傳遞的架構也有折中。基於消息傳遞的架構多是複雜的,須要開發團隊密切關注一些關鍵的事情,包括:
消息處理語義;
消息可見性;
消息編排。
1.消息處理語義
開發人員不只須要瞭解如何發佈和消費消息
面對有序消息,開發人員須要考慮如何處理,沒有按順序處理會出現什麼狀況,而不是每條消息獨立地使用
還要考慮消息拋出異常或錯誤時,對當前消息的處理(重試or失敗?)以及對將來消息的處理
2.消息可見性
考慮使用關聯ID等,跟蹤Web服務調用以及消息的發佈,實現對用戶事務的跟蹤
3.消息編排
基於消息傳遞的應用程序很難按照順序進行業務邏輯推理,用戶事務可能也在不一樣時間不按順序執行,調試基於消息的應用程序會設計多個不一樣服務的日誌。
6.二、Spring Cloud Stream
簡介
Spring Cloud經過Spring Cloud Stream項目,輕鬆地將消息傳遞集成到基於Spring的微服務中。
Spring Cloud Stream是一個由註解驅動的框架,它容許開發人員在Spring應用程序中輕鬆地構建消息發佈者和消費者。
Spring Cloud Stream對消息傳遞平臺進行了抽象,實現消息發佈和消費是經過平臺無關的Spring接口實現的,而平臺的具體實現細節(如使用Kafka仍是RabbitMQ),則排除在應用程序以外。
架構
隨着Spring Cloud中消息的發佈和消費,有4個組件涉及發佈消息和消費消息,它們是:
發射器(source);
通道(channel);
綁定器(binder);
接收器(sink)。
1.發射器
當一個服務準備發佈消息時,它將使用一個發射器發佈消息。發射器是一個Spring註解接口,它接收一個普通Java對象(POJO),該對象表明要發佈的消息。發射器接收消息,而後序列化它(默認的序列化是JSON)並將消息發佈到通道。
2.通道
通道是對隊列的一個抽象,它將在消息生產者發佈消息或消息消費者消費消息後保留該消息。通道名稱始終與目標隊列名稱相關聯。然而,隊列名稱永遠不會直接公開給代碼,相反,通道名稱會在代碼中使用。這意味着開發人員能夠經過更改應用程序的配置而不是應用程序的代碼來切換通道讀取或寫入的隊列。
3.綁定器
綁定器是Spring Cloud Stream框架的一部分,它是與特定消息平臺對話的Spring代碼。Spring Cloud Stream框架的綁定器部分容許開發人員處理消息,而沒必要依賴於特定於平臺的庫和API來發布和消費消息。
4.接收器
在Spring Cloud Stream中,服務經過一個接收器從隊列中接收消息。接收器監聽傳入消息的通道,並將消息反序列化爲POJO。從這裏開始,消息就能夠按照Spring服務的業務邏輯來進行處理。
6.三、編寫簡單的消息發佈者和消費者
目的:A服務發佈消息,B服務打印到窗口
6.3.一、編寫消息發佈者
咱們首先修改組織服務,以便每次添加、更新或刪除組織數據時,組織服務將向Kafka主題(topic)發佈一條消息,指示組織更改事件已經發生。
從3個關鍵角色的視角解析:
架構師負責提供解決問題的工做模型,提供腳手架供開發人員構建代碼,使應用程序全部部件組合在一塊兒。
由於微服務架構中,各個服務是獨立開發獨立部署的,配置文件不可能像單體架構同樣,給每個服務代碼中放置一份配置文件來控制。
這裏提出了一種方案:經過Spring Cloud配置服務器,將配置做爲一個微服務部署起來,其它微服務經過訪問這個配置服務器來獲取配置,從而實現統一管理。
編寫對應的配置文件,放置在Spring Cloud配置服務的代碼下;其它微服務訪問獲取配置時,讀取這些文件,並返回給服務的消費者。
爲項目加上resources文件夾
下一步,添加排除的文件類型
<?xml version="1.0"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.4.RELEASE</version> </parent> <groupId>com.ltmicro</groupId> <artifactId>test01</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>test01</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Camden.SR5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
主要代碼解析:
使用註解:
@SpringBootApplication:代表Spring Boot應用程序(Spring Cloud是Spring Boot應用)
@EnableConfigServer:代表是Spring Cloud Config服務
@SpringBootApplication @EnableConfigServer public class ConfigServiceApplication { public static void main(String[] args) { SpringApplication.run(ConfigServiceApplication.class, args); } }
在src/main/resources文件夾下,添加一個application.yml文件(也能夠使用properties文件,語法不一樣)
server: port: 8888 spring: profiles: active: native cloud: config: server: native: searchLocations: classpath:config/,classpath:config/licensingservice # searchLocations: c:/Louis/Projects/JavaDefaultWorkPlace/test01/src/main/resources/config/licensingservice, # c:/Louis/Projects/JavaDefaultWorkPlace/test01/src/main/resources/config/organizationservice # searchLocations: file:///Louis/Projects/JavaDefaultWorkPlace/test01/src/main/resources/config/licensingservice, # file:///Louis/Projects/JavaDefaultWorkPlace/test01/src/main/resources/config/organizationservice
主要設置了這個程序的端口,以native(本地)的方式運行,而後配置了對應查找配置的目錄(包含了3種寫法)
我這裏使用的是Maven 的spring-boot:run命令運行,也能夠直接項目右鍵運行
訪問如下地址都可以獲得對應配置
3.1中,若是部署這個配置服務,要求部署環境必須有對應文件系統,這個有時候很不方便,例如須要用容器來部署。
因此Spring Cloud Config支持其餘後端存儲庫,如Git等,咱們這裏介紹如何基於Git來構建這個配置服務。
Git是一個開源分佈式版本管理系統,咱們能夠使用一些廠商提供的Git服務,如GitHub、Gitee(碼雲)
下面咱們使用碼雲來實現這個配置服務。
在碼雲中建立一個項目,而後建立文件夾,放入咱們在3.1中設計好的配置文件
注意訪問碼雲地址的時候,uri應該填項目地址下面這個
server: port: 8888 spring: cloud: config: server: git: uri: https://gitee.com/Louisyzh/LTMicro.git searchPaths: /** username: 你的碼雲賬號 password: 你的碼雲密碼
咱們的配置服務器通過上面已經搭建好了,是一個Spring Boot程序,打包成jar或者war包便可放到生產環境中運行。
下面介紹客戶端(其它微服務)若是獲取這些配置
(步驟同上,略)
<?xml version="1.0"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.4.RELEASE</version> </parent> <groupId>com.ltmicro</groupId> <artifactId>test01</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>test01</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Camden.SR5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
主要代碼解析:
spring:
application:
name: licensingservice
profiles:
active: default
cloud:
config:
uri: http://localhost:8888
配置好後,程序運行時,至關於把從服務器獲得的配置,做爲本身的配置文件進行初始化。
咱們這裏經過一個控制器+@Value註解讀取配置屬性來進行測試
建立一個組件用來讀取配置屬性
@Component public class ServiceConfig { @Value("${example.property}") private String exampleProperty; public String getExampleProperty() { return exampleProperty; } }
建立一個控制器用來顯示屬性
@RestController public class LicenseServiceController { @Autowired private ServiceConfig serviceConfig; @RequestMapping(value = "/config", method = RequestMethod.GET) public String getLicenses() { return serviceConfig.getExampleProperty(); } }
運行程序,訪問對應地址http://localhost:8080/config,便可顯示出獲取到的屬性。
若是咱們使用像Gitee、GitHub這樣的第三方存儲庫存放咱們的配置文件,若是敏感的信息都明文存放,會容易暴露。
因此這裏提供一個對敏感信息進行加密解密的方案。
布式調用架構、SOA架構上面演進的一種架構,也不是一種徹底創新的架構。
是爲了適應互聯網行業需求而出現的一種架構。
包括詞彙、工做方式、開發方式的演進。
目前尚未很標準的一種定義,這裏介紹只是一種實現方式。
討論集中式架構:
集中式單塊(Monolithic)架構:如MVC
優點
劣勢
討論SOA架構:
主要問題:
互聯網公司的需求:
應對大規模服務化需求:(具體的含義,或者實際的示例是什麼?須要補充)
定義:(來自Martin Fowler)
特性:(「微」的含義)
服務間並不須要組合成組建去發佈,而是每一個服務都是獨立的;同時下降服務之間的耦合。
微服務與SOA的區別和聯繫:
快速演進過程當中的架構師:
實現策略:
採用鬆散服務體系
圍繞業務組件團隊
創建Feature Team
融合技術多樣性
由於面向服務,可能遇到不少系統
使用http這些技術無關的技術進行鏈接
確保業務數據獨立
使用服務集成而不是數據集成(微服務中一個頗有用的特色)
基礎設施自動化(DevOps)
實施問題:
切入點:
一切從零開始:
第三節:
服務建模:
微服務中的如何實現鬆耦合
獨立部署單個服務並不須要修改其它服務
儘量使用輕量級通訊方式進行服務集成
微服務中的高內聚
只改一個地方就能夠發佈
儘量明確領域邊界
不該該是技術驅動建模,應該業務驅動建模
利用上下文劃分界限
服務集成:
RPC,Messaging分別表明了2中風格的通訊方式
API網關,一個統一入口
Spring Boot入門
Spring 的一個子項目
簡介:
基本示例:
Spring Boot消息傳遞
點對點,發佈訂閱
這2種方式都比較常見,因此基本的第三方消息中間件都支持這2種模型
消息傳遞的協議、規範
一、JMS(Java Message Service)協議
實現:ActiveMQ
Spring組件:spring-jms 只要實現了JMS的組件,均可以經過Spring的spring-jms抽象來操做
二、AMQP(Advanced Message Queuing Protocol)協議
實現:RabbitMQ
Spring組件:同上,有spring-rabbit
三、以上2個協議是業界標準;其它協議,如kafka,自定的規範,自爲體系
JMS規範(選修課裏面講到)
AMQP規範:
2個示例
講解了2個組件在Sping BOot中的簡單實用
Spring Boot部署與Docker
SPring Boot部署
Docker雲部署
部署建模:把部署提高到建模等級
有點相似Maven的倉庫,例如把一個jar包做爲鏡像放到註冊中心中,那麼其餘鏡像均可以訪問到他
羣關鍵服務器、服務間的負載均衡以及將基礎設施分離到多個位置的