写给大忙人的spring cloud 1.x学习指南 写给大忙人spring boot 1.x学习指南 写给大忙人spring boot 1.x学习指南

 

 这几天抽空搞了下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

  • spring boot。spring cloud基于spring boot打包方式,因此spring boot是必备的,后面咱们会详细讲解spring boot,实际上spring boot中的不少特性跟spring boot毫无关系,纯粹是设计者有意推广而不放在spring context或者spring context support中。
  • spring cloud netflix。spring cloud netflix是netflix开源的一套微服务框架,它提供了微服务架构的核心功能,包括服务注册和发现中心Eureka、客户端负载均衡器Ribbon、声明式RPC调用Feign、路由管理Zuul、熔断和降级管理Hystrix。对于大部分的RPC框架来讲,基本上都会提供除了熔断和降级管理外的全部特性,好比dubbo(http://dubbo.apache.org/)以及笔者在以前公司自行研发的rpc框架(https://gitee.com/zhjh256/io-spider)。
因此,spring cloud能够说就是围绕netflix的这套组件为主,其余都是辅助。

除了这两个核心组件外,下列组件一般在大型系统中会一块儿使用(中小型系统可能不会采用):mysql

  • spring cloud config。spring cloud config提供了集中式的配置管理中心,其存储支持文件系统和git。
  • spring cloud sleuth/zipkin。spring cloud sleuth解决了分布式系统环境中日志的上下文关联和链路追踪问题。
  • spring cloud consul。spring cloud consul提供了另外一种服务中心、配置中心选择。

在开始正式讲解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

  • 在dubbo微服务框架内,ribbon/hystrix集成到了dubbo核心包中,turbine则在dubbo-admin和dubbo-monitor中。
  • zuul proxy就是网关AR(咱们原来自研发的spider提供了该特性),这个组件在dubbo中没有对应的实现。
  • spring cloud config是分布式配置中心,dubbo开源版没有提供,阿里内部有个供HSF使用的diamon配置中心。Spring Cloud Config有自带的配置管理库,也能够和开源项目集成,包括:Consul,Eureka,zk(后面咱们会看到各配置中心的优劣势)。
    Spring Cloud Config实际上是一个基于spring boot的REST应用,不是一个单独第三方的服务器,能够嵌入在Spring Boot应用中,或者单独启动一个Spring Boot应用,其数据能够存储在文件系统中或者git仓库中。spring cloud配置中心的客户端实现原理比较简单,咱们知道在spring框架中,是经过PlaceholderConfigurerSupport实现配置文件加载的,若是不使用spring cloud的配置中心,咱们彻底能够本身扩展PlaceholderConfigurerSupport,根据启动参数,从远程配置中心进行加载。
  • dubbo使用zk和dubbo-admin做为注册和治理中心,因此spring cloud netflix eureka就至关于zk和dubbo-admin,spring cloud也集成了使用zk做为服务注册和查找中心的组件。
  • 在dubbo中,若是须要链路跟踪,咱们须要自定义dubbo filter集成zipkin,dubbo自身没有提供这个机制。在spring cloud中,提供了可集成zipkin的Spring Cloud Sleuth,它的其中一个特性是增长跟踪ID到Slf4J MDC,这一点和笔者前面设计的日志框架出入一辙,没法作到跨节点追踪的集中日志平台都是耍流氓。
  • 在dubbo中,咱们能够经过声明式的@Reference注解来直接调用rpc服务,在spring cloud中,经过Feign,能够实现声明式调用,对于微服务开发来讲,提供声明式的服务调用机制对开发效率是很重要的,它能够在编译阶段确保调用方和服务方接口一致。从技术实现来讲,现代RESTFUL接口通常签名上出入参都是对象,若是把controller同时当作接口来用的话,实现声明式调用REST微服务也不是很难的事,关键是代码实现上,咱们须要在编写controller接口的时候作些调整,跟service同样,实现接口的方式,后面咱们会详细讲到。
除此以外,同正常开发不同的点在于spring cloud依赖于spring boot,这是同标准化开发最大的区别,Spring Boot(实际上,spring boot自己简单地说就是另外一种开发时打包方式)被认为是spring cloud下微服务实现的核心技术。
在协议上,spring cloud是基于http的,这一点上会不会对延迟形成影响须要在评估下。
对spring cloud的大致介绍就到这里,后面咱们开始进入正文部分,spring cloud的学习。

=====================================spring

再重复一遍,spring cloud依赖于spring boot,因此不熟悉spring boot的同窗,先掌握下spring boot,可参考笔者的写给大忙人spring boot 1.x学习指南

有些书籍一开始就讲spring cloud config,有些书籍则几乎能够认为把官方文档翻译一遍 ,官方文档不少状况下对某些假设是很理想化的,因此,我的以为有些时候就该有得放矢,不要追求大而全,很简单的就不要大谈特谈了。

服务注册与发现spring cloud netflix eureka

笔者建议不要急着动手,咱们先来了解下服务中心的相关概念和优劣势。
传统基于ESB或者类DNS机制的服务路由机制

 

这也是早期主流自定义RPC框架的实现模式,它的最主要缺点是LB的单点失败和扩展能力受限。在极高性能要求下,对于不少简单频繁调用的微服务来讲,响应时间增长(由于网络多了一个节点)。
基于服务注册中心的服务路由机制

 

在这种状况下,服务发现层并非微服务调用自己须要的,只是一个注册和查找中心。避免了单点失败和扩展性问题。
注:若是客户端是使用http直接访问的,而不是其余微服务来访问,也就是本篇开头的spring cloud架构图。在这种架构中,zuul至关于智能化的nginx代理,只不过原生的nginx是下游服务节点须要人工静态配置,zuul增长了功能,和注册中心保持联系,使得每一个微服务实例能够主动链接到注册中心,发布本实例的服务列表,就能够感知到,从这一点上来讲,在nginx上增长一个动态反向注册的模块是合理的,这样的话,从完整的架构角度来讲,zuul就不须要了。由于如今应用几乎所有前端功能单独运行在nginx里面(除非彻底以API方式对外提供服务),nginx几乎在任何应用中都是必备的。因此,最佳的方式是nginx增长一个动态反向注册的模块,若是没有能力,退而次之,就只能nginx+zuul。虽然服务能够规划的比较好,也就是按照命名空间规划,并且新增、合并命名空间的几率较低,可是对于高并发系统来讲,增长或者减小不一样服务的实例数量,这是一个比较常见的操做。并且,原生的nginx有太多的静态配置,这也是比较诟病的。虽然能够要求前端区分API资源和非API资源,对于api直接访问zuul对应的域名,哪怕经过二级域名,不过这在推进上可能遇到比较大的阻力,除非一开始就是这么规划的。
 
Spring Cloud支持多种类型的服务注册中心,Netflix Eureka是集成最紧密(亲儿子)的,其次是consul(为何吐槽某些写书的,其实这就是一例,没见到那本书里面讲了使用consul做为服务中心,其它例子还不少),各类注册中心的比较可参考 https://luyiisme.github.io/2017/04/22/spring-cloud-service-discovery-products/,这里引用以下:

如今来看下服务的注册和调用。

Spring Cloud提供了三种调用微服务的方法:
  • DiscoveryClient,最底层
  • Ribbon-aware Spring RestTemplate,中间层
  • Netflix Feign,最抽象,也是最高效的  (注:咱们通常自研rpc框架的时候,也是这个思路,不过通常是两层,而不是三层)

因为在实际开发中,咱们基本上使用Feign开发,因此,这里咱们重点看Feign方式的RPC调用。

在使用Feign声明式调用前,开发者须要先定义一个接口,而后使用Spring Cloud的@FeignClient注解该接口,告诉Ribbon调用哪一个Eureka服务,其实原理上就是根据接口动态生成代理,跟咱们自定义RPC/dubbo的实现是同样的原理,通常来讲,该接口应该由服务方提供比较合适。能够像下面这样定义:
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); } }
要启用Feign客户端,还须要在主应用类中加上@EnableFeignClients注解。
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

 

如今来调用http://localhost:8080/v2/organizations/yidooo/licenses/x0009999,以下:

 

经过为controller定义要实现的接口,就作到了一次定义,屡次引用(这和咱们使用传统的spring mvc开发不一样,建议把RequestMapper定义在接口上)。

因此,从使用上来讲,Feign很简单,对于有过其余RPC开发经验的同窗来讲,就是换个注解而已。

熔断、降级和服务隔离Netflix Hystrix

 我记得dubbo和其余rpc在这一块作的不是特别好,虽然spring cloud提供了该特性、并且很灵活,可是它有个关键设计很鸡肋,后面会讲到。

 在spring cloud的微服务架构中,一个请求调用通过的节点内关键步骤以下:

Hystrix和Spring Cloud使用@HystrixCommand标记java方法由Hystrix电路中断器管理,当spring框架看到@HystrixCommand注解的时候,它会为方法生成一个代理,并使用特定的线程池执行调用(这会致使个问题,就是log4j的MDC是基于线程上下文的)。须要注意的是,整个被注解的方法都会由Hystrix电路中断器管理,默认状况下,只要执行超过1秒就会触发com.nextflix.hystrix.exception.HystrixRuntimeException异常,哪怕该方法内部调用了其余不少RPC服务,实际上控制应该放在调用具体远程eureka服务上,而不是整个本地方法上(这个是实现不合理的,注:经过为每一个远程服务包装一个本地代理调用,实现细粒度控制,这估计得靠代理来实现,不能一个个手写)。同时,默认状况下,由@HystrixCommand标记的全部方法都会在一个线程池中执行,这也是不合理的(虽然为不一样的远程服务定义不一样的线程池)。
@HystrixCommand注解定义了不少属性控制其行为和线程池等,其中commandProperties属性控制电路中断器的行为,具体可见javadoc(https://netflix.github.io/Hystrix/javadoc/com/netflix/hystrix/HystrixCommand.html)。
服务不可用回调,@HystrixCommand的fallbackMethod属性用来设置若是调用Hystrix失败,回调本类的哪一个方法,回调方法和原方法的签名必须相同,由于调用回调方法时会把全部参数都传递过去。
Hystrix使用线程池执行全部远程调用服务,默认状况下,这个线程池有10个线程,任何远程调用都会放在这个线程池中执行,包括jdbc,只要是远程调用都算(它的原理或者判断依据是???),以下所示:

合理的隔离机制应该是能够自定义线程池数量,以及哪些服务放在哪一个线程池。以下:

天然,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);
)
注:默认状况下,线程池任务队列使用的数据结构是LinkedBlockingQueue,由于任务一般是先进先出的策略。
由于@HystrixCommand标记的方法在另外的线程中执行,这意味着默认状况下ThreadLocal中的值在@HystrixCommand标记的方法内是取不到值的。因此,Hystrix和Spring Cloud提供了一种机制来使得能够将父线程中的上下文传递给Hystrix Thread pool。这种机制称为HystrixConcurrencyStrategy。HystrixConcurrencyStrategy是一个抽象类,经过实现它能够将父线程上下文中的信息注入到Hystrix管理的线程中,它包括三个步骤:
  1. 自定义Hystrix Concurrency Strategy类
  2. 定义一个Callable类,将UserContext注入Hystrix Command
  3. 配置Spring Cloud使用自定义的Hystrix Concurrency Strategy类
实现细节能够参考Spring microservice in action 5.9.2 The HystrixConcurrencyStrategy in action。
须要注意的是:spring cloud内置了HystrixConcurrencyStrategy处理spring secuiry相关问题,各HystrixConcurrencyStrategy之间是过滤器链设计模式,因此实现自定义HystrixConcurrencyStrategy的时候,须要确保调用了已存在的HystrixConcurrencyStrategy,这和咱们自定义过滤器或者框架相关的生命周期事件的模式同样。
 

线路熔断

默认状况下,虽然能够配置超过多少时间以后,服务抛异常,可是不少时候,若是目标系统很是忙了,这个时候请求在源源不断的发过去是没有意义的,只会让目标系统更慢,此时咱们须要一些更加灵活或者智能的机制来肯定何时不在调用到目标物理地址的服务。Hystrix的默认计算规则为:

 

控制熔断的行为是在@HystrixCommand注解的commandPoolProperties属性中设置,其中包含的是@HystrixProperty属性数组。Hystrix的完整参数列表可见 https://github.com/Netflix/Hystrix/wiki/Configuration
 

 

服务路由zuul

为何使用路由以及是否须要使用路由的缘由,在上面已经讲过了,因此,这里仍是看下核心部分。其在spring cloud架构中的角色以下:

 

经过添加spring-cloud-starter-zuul依赖以及在主应用类中添加@EnableZuulProxy注解就能够启用zuul服务器功能。添加依赖:
<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);
  }
}
注:除了 EnableZuulProxy注解外,还有一个@EnableZuulServer注解,该注解会建立Zuul Server,可是不会加载任何Zuul反向代理过滤器,也不会使用Netflix Eureka做为服务中心,它主要用于自建路由服务,因此通常状况下不须要。
Zuul设计为默认使用Spring Cloud的其余服务,因此,默认使用Eureka做为服务中心,Netflix Ribbon做为客户端负载均衡代理。链接eureka服务中心的配置都同样。以下:
eureka:
  instance:
    preferIpAddress: true
  client:
    registerWithEureka: true
    fetchRegistry: true
  serviceUrl:
    defaultZone: http://localhost:8761/eureka/
这样,运行maven spring-boot:run以后,就能够启动Zuul了。

Zuul路由配置

Zuul支持三种类型的路由机制:
  • 基于服务中心自动路由(大规模使用)
  • 使用服务中心手工路由(A/B测试使用)
  • 根据静态url路由(历史兼容使用)
Zuul全部的路由映射都维护在application.yml文件中,默认状况下,不须要作任何配置,Zuul会自动使用Eureka中的服务ID寻找下游的实例,这也是dubbo的作法。须要注意的是,实际上Zuul粒度比较粗,是根据应用名而不是严格意义上的服务来区别的。例如:
是根据organizationservice来判断下游服务,而不是经过具体的API来判断的,这种状况下,若是两个应用相同名称,可是提供的服务有差异,就坑了。
访问Zuul服务器的/routes服务能够查看全部的服务。以下所示:

 

Zuul的真正强大在于过滤器,能够用来设置全局请求ID,由于spring cloud使用http协议,因此这是能够作到的,只要在http head中进行注入便可。也能够设置路由过滤器,不只仅根据路由定义来决定路由,好比说灰度升级或者A/B测试(https://baike.baidu.com/item/AB%E6%B5%8B%E8%AF%95/9231223)的时候,就可使用路由过滤器经过某个请求参数来判断应该路由到什么节点,好比说某个区域、某个级别的客户等等。为了作到应用代码一致性,咱们须要根据上下文而不是服务名称来肯定路由逻辑。在zuul中,经过继承com.netflix.zuul.ZuulFilter,能够实现自定义过滤器。
不过动态路由除了zuul应该提供外,ribbon也应该提供,由于一般来讲,内部服务也会相互调用,思路能够参考 https://github.com/jmnarloch/ribbon-discovery-filter-spring-cloud-starter
 

分布式日志聚合Spring Cloud Sleuth

Spring Cloud Sleuth是一个Spring Cloud项目的一部分,它能够在http调用上设置相关ID,同时提供了钩子将跟踪数据发往OpenZipkin。这是经过Zuul过滤器以及其余spring组件使得相关ID能够在系统调用见透传。
Zipkin主要用来链路跟踪,以及各节点之间服务调用的性能分析。
具体实现上,可使用Zuul过滤器检查HTTP请求,若是请求中没有相关ID,就能够注入进去。相关ID存在以后,就可使用自定义的Spring HTTP过滤器将相关ID注入到自定义的UserContext对象,而后将相关ID增长到Spring’s Mapped Diagnostic Context (MDC),也能够编写一个Spring Interceptor将相关ID经过HTTP头传递出去,这两种应该来讲均可以实现,其实最主要是他们使用了HTTP协议。同时确保Hystrix线程上下文可以感知到。上述这些特性就是Spring Cloud Sleuth提供的主要功能:
  • 透明在服务调用上建立和注入相关ID(dubbo没有提供现成的功能,须要自行基于dubbo filter实现)
  • 在服务调用之间透传相关ID
  • 增长相关ID到Spring’s MDC,Spring Boots的默认SL4J和Logback会自动包含相关ID,log4j则不会自动包含(参考本博客spring boot系列的日志部分)。
  • 可选的,发布跟踪信息到Zipkin

要启用Spring Cloud sleuth,只要在pom文件中包含下列依赖便可:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
Spring Cloud Sleuth信息分为4段组成,以下:
  • 服务的应用名
  • 全局跟踪ID
  • 当前请求段ID
  • 是否发送到zipkin的标记

 

 

经过将日志聚合到ELK,咱们就能够很方便的进行搜索了。
Spring Cloud Sleuth和ELK的整合仍是比较简单的,由于目前ELK框架推荐在应用端部署Filebeat的方式,只要Filebeat配置好格式便可。参考 ELK最新版6.2.4学习笔记-Logstash和Filebeat解析(java异常堆栈下多行日志配置支持)
zipkin自身包含spring boot版本的可执行包下载,也能够本身建立一个spring boot应用,对于zipkin而言,惟一须要考虑的是数据存储在哪里,默认状况下数据存储在内存中,这意味着若是zipkin重启了,以前的监控数据就没有了。zipkin目前支持mysql、Cassandra以及Elasticsearch,由于ELK数据存储已经在ES中了,因此也建议配置到ES中,只要划一个index出来给zipkin就能够了,zipkin配置ES存储能够参考 https://github.com/openzipkin/zipkin/tree/master/zipkin-server
由于zipkin的数据整体来讲是用来分析性能的,因此Zipkin默认会将1/10的数据写到Zipkin服务器,配置参数spring.sleuth.sampler.percentage能够用来控制发送的比例,取值为0-1之间。
 
最后咱们来看下分布式配置的使用,其原理咱们在前面已经讲过了,就不重复阐述。
 

分布式配置中心spring cloud config

spring cloud config和spring cloud eureka同样,也是一个spring boot应用,只要添加maven依赖以及在主应用类上添加@EnableConfigServer注解便可,以下:
        <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的配置能够参考官方文档。

因为存在profile、label等概念,所以配置中心http请求地址和资源文件之间存在一个映射关系(若是请求的时候访问不到的时候能够帮助排查),以下:
  • /{application}/{profile}[/{label}]
  • /{application}-{profile}.yml
  • /{label}/{application}-{profile}.yml
  • /{application}-{profile}.properties
  • /{label}/{application}-{profile}.properties
对于非git存储而言,label不存在。这一点经过访问配置中心资源能够看出,见上图。
要配置客户端使用配置中心,只要加上依赖便可:
        <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就能够正常注入了。

虽然Spring Cloud配置中心可以感知底层数据源的修改,不管是否直接修改了底层文件系统或者git仓库的配置值,Spring Cloud配置中心老是能够获取最新的值。可是Spring Boot应用则是在启动时读取配置的,这意味着默认状况下,对配置中心的修改不会通知spring boot应用,哪怕spring boot应用须要知道配置变动的时候也如此,好比说log4j2配置临时调整。不过Spring Boot Actuator(https://docs.spring.io/spring-boot/docs/1.5.7.RELEASE/reference/htmlsingle/#production-ready)提供了一个@RefreshScope注解容许spring boot应用访问/refresh来强制从新读取配置,不过这对于@Value直接注入来讲是合理的,可是对于一些spring boot启动时就已经注入而且建立单例了的状况,好比jdbc/redis配置等就不适用了。只要在spring主应用类上增长@RefreshScope注解便可,以下:
@SpringBootApplication 
@RefreshScope 
public class Application { 
  public static void main(String[] args) { 
    SpringApplication.run(Application.class, args); 
  } 
}
同时在配置文件application.yml中增长
 
management:
 security:
  enabled: false
不然,spring boot 1.5.x版本在执行/refresh的时候会报401,"error":"Unauthorized","message":"Full authentication is required to access this resource."
执行post /refresh,查看spring boot日志:
2018-06-13 09:29:23.490 INFO 5288 --- [nio-8080-exec-5] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at: http://localhost:8888
2018-06-13 09:29:24.953 INFO 5288 --- [nio-8080-exec-5] c.c.c.ConfigServicePropertySourceLocator : Located environment: name=licensingservice, profiles=[default], label=null, version=null, state=null
2018-06-13 09:29:24.954 INFO 5288 --- [nio-8080-exec-5] b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource [name='configService', propertySources=[MapPropertySource {name='file:///D:/spring-cloud-example/config/licensingservice.yml'}]]
2018-06-13 09:29:24.956 INFO 5288 --- [nio-8080-exec-5] o.s.boot.SpringApplication : The following profiles are active: default
 
spring cloud配置中心存在一个问题是没有提供原生的配置变动发布功能,虽然提供了Spring Cloud Bus模块来基于MQ发布配置变动,但这引入了额外的维护工做。能够说zk最有价值的特性之一就是根据特定节点订阅变动、删除、其下节点变动的事件。
 
参考:
spring microservice in action
http://cloud.spring.io/spring-cloud-static/Edgware.SR3/index.html
https://mp.weixin.qq.com/s?__biz=MzIwMzg1ODcwMw==&mid=2247487921&idx=1&sn=1a1d9864e58fbea3f07c98d447b90e44&chksm=96c9a7d1a1be2ec752882590a53c37d59bb291db73187f097702235c606da9feddd6cb9990a4&scene=27#wechat_redirect
 
待下一系列补充、完善的要点:
Eureka 服务中心高可用
Spring Cloud Config高可用
配置中心推送机制
Spring Cloud使用Consul做为服务中心
相关文章
相关标签/搜索