本文篇幅较长,建议合理利用右上角目录进行查看(若是没有目录请刷新)。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包做为镜像放到注册中心中,那么其余镜像均可以访问到他
群关键服务器、服务间的负载均衡以及将基础设施分离到多个位置的