SpringCloud入门总结

SpringCloud各组件概括

SpringCloud主要包括以下几个组件

  • 核心组件

Eureka:服务提供者注册中心

Ribbon:服务消费者相关组件,可直接通过服务提供者名称调用提供者所注册的服务,摒弃IP+Port调用的方式

Hystrix:服务提供者组件,主要作用是服务熔断和服务降级,解决服务调用时系统阻塞的情况(即解决服务雪崩效应)

Zuul:在用户访问服务消费者前加锁,提高微服务架构系统的安全性;将域名+Port映射到IP2+Port

Config:解决分布式多模块开发配置文件统一管理的问题,主要是方便维护

  • 扩展组件

Feign:基于Ribbon的一个组件,对Ribbon公用的代码进行抽取,形成一个接口,在消费者中直接注入即可使用,不需要在消费者服务者进行硬编码

Dashboard:用来监控集成了Hystrix的消费者,不单独使用,通常与Turbine组合使用

Turbine:基于Dashboard,与Dashboard组合使用,用来监控Hystrix消费者集群

SpringCloud架构图如下:

其实SpringCloud就是微服务系统架构的一站式解决方案,在平时我们构建微服务的过程中需要做如服务发现注册 、配置中心  、负载均衡 、断路器 、数据监控等操作,而SpringCloud为我们提供了一套简易的编程模型,使我们能在SpringBoot的基础上轻松地实现微服务项目的构建


SpringCloud的服务发现框架--Eureka

Eureka是一个服务发现框架,那何为服务,何又为发现呢?

举一个生活中的例子,就比如我们平时租房子找中介的事情。在没有中介的时候我们需要一个一个去寻找是否有房屋要出租的房东,这显然会非常的费力,一凭自己一个人的能力是找不到很多房源供你选择的,再者你也懒得这么找下去。这个例子里的我们就相当于微服务中的Consumer,而那些房东就相当于微服务中的Provider;消费者Consumer需要调用提供者Provider提供的一些服务,就像我们现在需要租他们的房子一样

但是如果只是租客和房东之间进行寻找的话,那效率是很低的,房东找不到租客赚不到钱,租客找不到房东住不了房。所以,后来房东肯定就想到了广播自己的房源信息(比如在街边贴贴小广告),这样对于房东来说已经完成他的任务(将房源公布出去),但是有两个问题就出现了。第一,其他不是租客的都能收到这种租房消息,这在现实世界没什么,但是在计算机的世界中就会出现资源消耗的问题了;第二,租客这样还是很难找到你,试想一下我需要租房,我还需要东一个西一个地去找街边小广告,麻不麻烦?

那怎么办呢?我们当然不会那么傻乎乎的,第一时间就是去找中介呀,它为我们提供了统一房源的地方,我们消费者只需要跑到它那里去找就行了;而对于房东来说,他们也只需要把房源在中介那里发布就行了

那么现在,我们的模式就是这样的:

但是,这个时候还会出现一些问题:

  1. 房东注册之后如果不想租房子了怎么办?我们是不是需要让房东定期续约?如果房东不进行续约是不是要将他们从中介那里的注册列表中移除
  2. 租客是不是也要进行注册呢?不然合同乙方怎么来呢?
  3. 中介可不可以做连锁店呢?如果这一个店因为某些不可抗力因素而无法使用,那么我们是否可以换一个连锁店呢?

针对上面的问题我们来重新构建一下上面的模式图:

举完这个例子我们就可以来看关于Eureka的一些基础概念了,你会发现这东西理解起来怎么这么简单

服务发现:其实就是一个“中介”,整个过程中有三个角色:服务提供者(出租房子的)、服务消费者(租客)、服务中介(房屋中介)

服务提供者: 就是提供一些自己能够执行的一些服务给外界

服务消费者: 就是需要使用一些服务的“用户”

服务中介: 其实就是服务提供者和服务消费者之间的“桥梁”,服务提供者可以把自己注册到服务中介那里,而服务消费者如需要消费一些服务(使用一些功能)就可以在服务中介中寻找注册在服务中介的服务提供者

  • 服务注册Register

官方解释:当Eureka客户端向Eureka Server注册时,它提供自身的元数据,比如IP地址、端口,运行状况指示符URL,主页等

结合中介来进行理解:房东(提供者Eureka Client Provider)在中介(服务器Eureka Server)那里登记房屋的信息,比如面积,价格,地段等等(元数据metaData)

  • 服务续约Renew

官方解释:Eureka客户会每隔30秒(默认情况下)发送一次心跳来续约。 通过续约来告知Eureka ServerEureka客户仍然存在,没有出现问题。 正常情况下,如果Eureka Server在90秒没有收到Eureka客户的续约,它会将实例从其注册表中删除

结合中介来进行理解:房东(提供者Eureka Client Provider)定期告诉中介(服务器Eureka Server)我的房子还租(续约) ,中介(服务器Eureka Server)收到之后继续保留房屋的信息

  • 获取注册列表信息Fetch Registries

官方解释:Eureka客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进行远程调用,该注册列表信息定期(每30秒钟)更新一次。每次返回注册列表信息可能与Eureka客户端的缓存信息不同,Eureka客户端会自动处理。如果由于某种原因导致注册列表信息不能及时匹配,Eureka客户端则会重新获取整个注册表信息。Eureka服务器缓存注册列表信息,整个注册表以及每个应用程序的信息进行了压缩,压缩内容和没有压缩的内容完全相同。Eureka客户端和Eureka 服务器可以使用JSON/XML格式进行通讯。在默认的情况下Eureka客户端使用压缩JSON格式来获取注册列表的信息

结合中介理解:租客(消费者Eureka Client Consumer) 去中介(服务器Eureka Server)那里获取所有的房屋信息列表(客户端列表Eureka Client List) ,而且租客为了获取最新的信息会定期向中介(服务器Eureka Server)那里获取并更新本地列表

  • 服务下线Cancel

官方解释:Eureka客户端在程序关闭时向Eureka服务器发送取消请求。 发送请求后,该客户端实例信息将从服务器的实例注册表中删除。该下线请求不会自动完成,它需要调用以下内容:DiscoveryManager.getInstance().shutdownComponent();

结合中介理解:房东(提供者Eureka Client Provider)告诉中介(服务器Eureka Server)我的房子不租了,中介之后就将注册的房屋信息从列表中剔除

  • 服务剔除Eviction

官方解释:在默认的情况下,当Eureka客户端连续90秒(3个续约周期)没有向Eureka服务器发送服务续约,即心跳,Eureka服务器会将该服务实例从服务注册列表删除,即服务剔除

结合中介理解:房东(提供者Eureka Client Provider)会定期联系中介(服务器Eureka Server)告诉他我的房子还租(续约),如果中介 (服务器Eureka Server)长时间没收到提供者的信息,那么中介会将他的房屋信息给下架(服务剔除)

下面就是Netflix官方给出的Eureka架构图,你会发现和前面画的中介图别无二致:


 负载均衡之Ribbon

首先了解下什么是RestTemplate?

RestTemplateSpring提供的一个访问Http服务的客户端类,怎么说呢?就是微服务之间的调用是使用的RestTemplate 。比如消费者B需要调用提供者A所提供的服务就需要这么写,如下面的伪代码:

@Autowired
private RestTemplate restTemplate;
//这里是提供者A的ip地址,但是如果使用了Eureka那么就应该是提供者A的名称
private static final String SERVICE_PROVIDER_A = "http://localhost:8081";

@PostMapping("/judge")
public boolean judge(@RequestBody Request request) {    
	String url = SERVICE_PROVIDER_A + "/service1";    
	return restTemplate.postForObject(url, request, Boolean.class);
}

如果你对源码感兴趣的话,你会发现上面所讲的Eureka框架中的注册续约等,底层都是使用的RestTemplate

那为什么需要Ribbon?

RibbonNetflix公司一个开源的负载均衡项目,是一个客户端/进程内负载均衡器,运行在消费者端。再举个例子,比如我们设计了一个秒杀系统,但是为了整个系统的高可用 ,我们需要将这个系统做一个集群,而这个时候我们消费者就可以拥有多个秒杀系统的调用途径了,如下图:

如果这个时候没有进行一些均衡操作,如果我们对秒杀系统1进行大量的调用,而另外两个基本不请求,就会导致秒杀系统1崩溃,而另外两个就变成了傀儡,那么我们为什么还要做集群,高可用体现的意义又在哪呢?所以Ribbon就出现了,注意上面加粗的几个字——运行在消费者端,指的是Ribbon是运行在消费者端的负载均衡器,如下图:

其工作原理就是Consumer端获取到了所有的服务列表之后,在其内部使用负载均衡算法,进行对多个系统的调用

Nginx和Ribbon的对比

提到负载均衡就不得不提到大名鼎鼎的Nignx了,和Ribbon不同的是,它是一种集中式的负载均衡器

何为集中式呢?简单理解就是将所有请求都集中起来,然后再进行负载均衡,如下图:

可以看到Nginx是接收了所有的请求进行负载均衡的,而对于Ribbon来说它是在消费者端进行的负载均衡,如下图:

请注意Request的位置,在Nginx中请求是先进入负载均衡器,而在Ribbon中是先在客户端进行负载均衡才进行请求的

Ribbon的几种负载均衡算法

负载均衡,不管Nginx还是Ribbon都需要其算法的支持,Nginx使用的是轮询和加权轮询算法。而在Ribbon中有更多的负载均衡调度算法,其默认是使用的RoundRobinRule轮询策略

  • RoundRobinRule:轮询策略,Ribbon默认采用的策略。若经过一轮轮询没有找到可用的provider,其最多轮询10轮。若最终还没有找到,则返回null
  • RandomRule:随机策略,从所有可用的provider中随机选择一个
  • RetryRule:重试策略,先按照RoundRobinRule策略获取provider,若获取失败,则在指定的时限内重试。默认的时限为 500 毫秒

当然,在Ribbon中你还可以自定义负载均衡算法,只需要实现IRule接口,然后修改配置文件或者自定义Java Config


什么是Open Feign

有了EurekaRestTemplateRibbon后就可以愉快地进行服务间的调用了,但是使用RestTemplate还是不方便,每次都要进行这样的调用:

@Autowiredprivate 
RestTemplate restTemplate;
//这里是提供者A的ip地址,但是如果使用了Eureka那么就应该是提供者A的名称
private static final String SERVICE_PROVIDER_A = "http://localhost:8081";

@PostMapping("/judge")
public boolean judge(@RequestBody Request request) {    
	String url = SERVICE_PROVIDER_A + "/service1";    
	//这样是不是太麻烦了???每次都要url、请求、返回类型   
	return restTemplate.postForObject(url, request, Boolean.class);
}

这样每次都调用RestRemplateAPI是否太麻烦,能不能像调用原来代码一样进行各个服务间的调用呢?

聪明的小朋友肯定想到了,那就用映射呀,就像域名和IP地址的映射。我们可以将被调用的服务代码映射到消费者端,这样我们就可以 “无缝开发”啦

OpenFeign也是运行在消费者端的,使用Ribbon进行负载均衡,所以OpenFeign直接内置了Ribbon

在导入了Open Feign之后我们就可以进行愉快编写Consumer端代码了:

//使用@FeignClient注解来指定提供者的名字
@FeignClient(value = "eureka-client-provider")
public interface TestClient {    
	//这里一定要注意需要使用的是提供者那端的请求相对路径,这里就相当于映射了    
	@RequestMapping(value = "/provider/xxx",method = RequestMethod.POST)    
	CommonResponse<List<Plan>> getPlans(@RequestBody planGetRequest request);
}

然后我们在Controller就可以像原来调用Service层代码一样调用它了:

@RestController
public class TestController {    
	//这里就相当于原来自动注入的Service    
	@Autowired    
	private TestClient testClient;    
	
	//controller调用service层代码    
	@RequestMapping(value = "/test", method = RequestMethod.POST)    
	public CommonResponse<List<Plan>> get(@RequestBody planGetRequest request) {        
		return testClient.getPlans(request);    
	}
}

必不可少的Hystrix

什么是Hystrix熔断和降级?在分布式环境中,不可避免地会有许多服务依赖项中的某些失败。Hystrix是一个库,可通过添加等待时间容限和容错逻辑来帮助您控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点,停止服务之间的级联故障并提供后备选项来实现此目的,所有这些都可以提高系统的整体弹性

总体来说Hystrix就是一个能进行熔断降级的库,通过使用它能提高整个系统的弹性

那么什么是熔断和降级呢?再举个例子,此时我们整个微服务系统是这样的:服务A调用了服务B,服务B再调用了服务C,但是因为某些原因,服务C顶不住了,这个时候大量请求会在服务C阻塞:

服务C阻塞了还好,毕竟只是一个系统崩溃了。但是请注意这个时候因为服务C不能返回响应,那么服务B调用服务C的的请求就会阻塞,同理服务B阻塞了,那么服务A也会阻塞崩溃,这就叫服务雪崩

请注意,为什么阻塞会崩溃?因为这些请求会消耗占用系统的线程、IO等资源,消耗完你这个系统服务器就崩了

熔断就是服务雪崩的一种有效解决方案。当指定时间窗内的请求失败率达到设定阈值时,系统将通过断路器直接将此请求链路断开。也就是上面服务B调用服务C在指定时间窗内,调用的失败率到达了一定的值,那么Hystrix则会自动将服务B与C之间的请求都断了,以免导致服务雪崩现象

其实这里所讲的熔断就是指的Hystrix中的断路器模式 ,可以使用简单的@HystrixCommand注解来标注某个方法,这样Hystrix就会使用断路器来“包装”这个方法,每当调用时间超过指定时间时(默认为1000ms),断路器将会中断对这个方法的调用

当然也可以对这个注解的很多属性进行设置,比如设置超时时间,像这样:

@HystrixCommand(commandProperties = {
	@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1200")
})
public List<Xxx> getXxxx() {    
	//省略代码逻辑...
}

以我的理解,降级是为了更好的用户体验,当一个方法调用异常时,通过执行另一种代码逻辑来给用户友好的回复。这也就对应着Hystrix后备处理模式。你可以通过设置fallbackMethod来给一个方法设置备用的代码逻辑。比如这个时候有一个热点新闻出现了,我们会推荐给用户查看详情,然后用户会通过id去查询新闻的详情,但是因为这条新闻太火了,大量用户同时访问可能会导致系统崩溃,那么我们就进行服务降级 ,将一些请求做降级处理比如提示当前人数太多请稍后查看等等:

//指定了后备方法调用
@HystrixCommand(fallbackMethod = "getHystrixNews")
@GetMapping("/get/news")
public News getNews(@PathVariable("id") int id) {    
	//调用新闻系统的获取新闻api,代码逻辑省略...
}

public News getHystrixNews(@PathVariable("id") int id) {    
	//做服务降级    
	//如返回当前人数太多,请稍后查看
}

微服务网关--Zuul

通过上面的介绍我们知道了服务提供者消费者通过Eureka Server进行访问的,即Eureka Server服务提供者的统一入口。那么整个应用中存在那么多消费者需要用户进行调用,这个时候用户该怎样访问这些消费者工程呢?当然可以像之前那样直接访问这些工程。但这种方式没有统一的消费者工程调用入口,不便于访问与管理,而Zuul就是这样的一个对于消费者的统一入口

如果学过前端的肯定都知道Router吧,比如Vue、React中的路由,用了Zuul你会发现在路由功能方面和前端配置路由基本是一个理

简单来讲网关是系统唯一对外的入口,介于客户端与服务器端之间,用于对请求进行鉴权限流、 路由监控等功能,而网关有的功能,Zuul基本都有,Zuul中最关键的就是路由和过滤器

Zuul的路由功能

  • 简单配置

比如我们已经向Eureka Server 注册了两个Consumer 、三个Provicer,这个时候我们再加个Zuul网关应该变成这样子了:

首先,Zuul需要向Eureka进行注册,那注册有啥好处呢?

Consumer都向Eureka Server进行注册了,那网关是不是只要注册就能拿到所有Consumer的信息了?

那拿到信息有什么好处呢?

拿到信息后是不是就可以获取所有的Consumer的元数据(名称,ip,端口)

那拿到这些元数据有什么好处呢?

拿到了是不是就直接可以做路由映射?比如原来用户调用Consumer1的接口是localhost:8001/studentInfo/update这个请求,那是不是可以这样进行调用了呢?localhost:9000/consumer1/studentInfo/update

上面的理解了,那么就能理解关于Zuul最基本的配置了:

server:
  port: 9000
eureka:
  client:
    service-url:
      #这里只要注册Eureka就行了
      defaultZone: http://localhost:9997/eureka

然后在启动类上加入@EnableZuulProxy注解就行了。没错,就是那么简单!

  • 统一前缀

这个很简单,就是可以在前面加一个统一的前缀,比如刚刚调用的是localhost:9000/consumer1/studentInfo/update,这个时候在yml配置文件中添加如下:

zuul:
  prefix: /zuul

这样就需要通过localhost:9000/zuul/consumer1/studentInfo/update来进行访问了

  • 路由策略配置

你会发现前面的访问方式(直接使用服务名),会将微服务名称暴露给用户,会存在安全性问题。所以,可以自定义路径来替代微服务名称,即自定义路由策略:

zuul:
  routes:
    consumer1: /FrancisQ1/**
    consumer2: /FrancisQ2/**

这个时候就需要通过localhost:9000/zuul/FrancisQ1/studentInfo/update来进行访问了

  • 服务名屏蔽

这个时候你别以为就好了,你可以试试,在你配置完路由策略之后使用微服务名称还是可以访问的,这个时候还需要将服务名屏蔽:

zuul:
  ignore-services: "*"
  • 路径屏蔽

Zuul还可以指定屏蔽掉的路径URL,即只要用户请求中包含指定的URL路径,那么该请求将无法访问到指定的服务,通过该方式可以限制用户的权限:

zuul:
  ignore-patterns: **/auto/**

这样包含auto的请求就可以被过滤掉(**代表匹配多级任意路径,*代表匹配一级任意路径)

  • 敏感请求头屏蔽

默认情况下,像Cookie、Set-Cookie等敏感请求头信息会被Zuul屏蔽掉,我们可以将这些默认屏蔽去掉,当然,也可以添加要屏蔽的请求头

Zuul的过滤功能

如果说,路由功能是Zuul的基操的话,那么过滤器就是Zuul的利器了。毕竟所有请求都经过网关(Zuul),那么我们可以进行各种过滤,这样我们就能实现限流灰度发布权限控制等等。

  • 简单实现一个请求时间日志打印

要实现自己定义的Filter只需要继承ZuulFilter然后将这个过滤器类以@Component注解加入Spring容器中就行了

首先解释一下关于过滤器的一些注意点:

过滤器类型:Pre、Routing、Post,Pre前置过滤器就是在请求之前进行过滤,Routing路由过滤器就是上面所讲的路由策略,而Post后置过滤器就是在Response之前进行过滤的过滤器,可以观察上图结合理解:

package com.ue.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;

//加入Spring容器
@Component
public class PreRequestFilter extends ZuulFilter {
    //返回过滤器类型
    //这里是前置过滤器    
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    //指定过滤顺序,越小越先执行,这里第一个执行    
    //当然不是只真正第一个,在Zuul内置中有其他过滤器会先执行
    //那是写死的,比如SERVLET_DETECTION_FILTER_ORDER = -3    
    @Override
    public int filterOrder() {
        return 0;
    }

    //什么时候该进行过滤
    //这里我们可以进行一些判断,这样我们就可以过滤掉一些不符合规定的请求等等    
    @Override
    public boolean shouldFilter() {
        return true;
    }

    //如果过滤器允许通过则怎么进行处理    
    @Override
    public Object run() throws ZuulException {
        //这里设置了全局的RequestContext并记录了请求开始时间        
        RequestContext ctx = RequestContext.getCurrentContext();
        ctx.set("startTime", System.currentTimeMillis());
        return null;
    }
}
package com.ue.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
public class AccessLogFilter extends ZuulFilter {
    private Logger log = LoggerFactory.getLogger(getClass());

    @Override
    public String filterType() {
        //指定该过滤器的过滤类型
        //此时是后置过滤器
        return FilterConstants.POST_TYPE;
    }

    @Override
    public int filterOrder() {
        //SEND_RESPONSE_FILTER_ORDER是最后一个过滤器
        //我们此过滤器在它之前执行
        return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    //过滤时执行的策略
    @Override
    public Object run() throws ZuulException {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        //从RequestContext获取原先的开始时间并通过它计算整个时间间隔
        Long startTime = (Long) context.get("startTime");
        //这里获取HttpServletRequest来获取URI并且打印出来
        String uri = request.getRequestURI();
        long duration = System.currentTimeMillis() - startTime;
        log.info("uri: " + uri + ", duration: " + duration + "ms");
        return null;
    }
}

上面就简单实现了请求时间日志打印功能

  • 简单实现令牌桶限流

当然不仅仅是令牌桶限流方式,只要是限流的活Zuul都能干,这里只是简单举个例子:

先来解释一下什么是令牌桶限流,首先会有个桶,如果里面没有满的话就会以固定的速率往里面放令牌,一个请求过来首先要从桶中获取令牌,如果没有获取到,那么这个请求就拒绝,如果获取到那么就放行

下面我们就通过Zuul的前置过滤器来实现一下令牌桶限流:

package com.ue.filter;

import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;

@Component
public class RouteFilter extends ZuulFilter {
    private Logger log = LoggerFactory.getLogger(getClass());
    //定义一个令牌桶,每秒产生2个令牌,即每秒最多处理2个请求
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(2);

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return -5;
    }

    @Override
    public Object run() throws ZuulException {
        log.info("放行");
        return null;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext context = RequestContext.getCurrentContext();
        if (!RATE_LIMITER.tryAcquire()) {
            log.warn("访问量超载");
            //指定当前请求未通过过滤
            context.setSendZuulResponse(false);
            //向客户端返回响应码429,请求数量过多
            context.setResponseStatusCode(429);
            return false;
        }
        return true;
    }
}

这样就能将请求数量控制在每秒两个

当然,Zuul作为网关肯定也存在单点问题 ,如果我们要保证Zuul的高可用,就需要进行Zuul的集群配置,这个时候可以借助额外的一些负载均衡器比如Nginx


Spring Cloud配置管理--Config

为什么要使用配置管理?

当我们的微服务系统开始慢慢地庞大起来,那么ConsumerProviderEureka ServerZuul系统都会持有自己的配置,这个时候在项目运行的时候可能需要更改某些应用的配置,如果不进行配置的统一管理,则只能去每个应用下一个一个寻找配置文件然后修改配置文件再重启应用

首先对于分布式系统而言就不应该去每个应用下去分别修改配置文件,再者对于重启应用来说,服务无法访问所以直接抛弃了可用性,这是我们更不愿见到的

那么有没有一种方法既能对配置文件统一地进行管理,又能在项目运行时动态修改配置文件呢?

那就是Spring Cloud Config,它能将各个应用/系统/模块的配置文件存放到统一的地方然后进行管理(Git或者SVN)

你想一下,我们的应用是不是只有启动的时候才会进行配置文件的加载,那么我们的Spring Cloud Config就暴露出一个接口给启动应用来获取它所想要的配置文件,应用获取到配置文件然后再进行它的初始化工作,就如下图:

当然这里你肯定还会有一个疑问,如果我在应用运行时去更改远程配置仓库(Git)中的对应配置文件,那么依赖于这个配置文件的已启动的应用会不会进行其相应配置的更改呢?答案是不会的。

那怎么进行动态修改配置文件呢?这不是出现了配置漂移吗?

一般我们会使用Bus消息总线+SpringCloud Config进行配置的动态刷新


引出Spring Cloud Bus

可以简单理解为Spring Cloud Bus的作用就是管理和广播分布式系统中的消息,也就是消息引擎系统中的广播模式;当然作为消息总线Spring Cloud Bus可以做很多事而不仅仅是客户端的配置刷新功能。拥有了Spring Cloud Bus之后,我们只需要创建一个简单的请求,并且加上@ResfreshScope注解就能进行配置的动态修改了,下面这张图供你理解:


常用注解及yml配置

学习过程中所用所有模块如下:

  • microservice-common模块
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
因为该Module不访问数据库,但是SpringBoot默认又会加载数据库,所以要加此注解,默认排除数据库相关配置信息加载

@FeignClient(value="MICROSERVICE-STUDENT",fallbackFactory=StudentClientFallbackFactory.class)
Feign与Hystrix整合所用,用来对熔断降级代码进行抽取

具体可参考:SpringCloud简介、入门案例


  • microservice-eureka-server-*模块
@EnableEurekaServer
启动类上开启注册中心服务
eureka:
  instance:
    hostname: localhost  #eureka客户端主机实例名称
    appname: microservice-student  #客户端服务名
    instance-id: microservice-student:1001  #客户端实例名称
    prefer-ip-address: true #显示IP
  client:
    service-url:
      defaultZone: http://localhost:2001/eureka  #把服务注册到eureka注册中心

 具体可参考:服务治理组件Eureka简介、初步使用

                     Eureka高可用集群配置及自我保护机制


  • microservice-student-consumer-80模块
//引入ribbon负载均衡
@LoadBalanced  
在配置类中添加此注解,由Spring管理RestTemplate,因为SpringCloud底层就是通过restful进行服务之间的调用
eureka:
  client:
    register-with-eureka: false #false由于注册中心的职责就是维护服务实例,它并不需要去检索服务,所以也设置为false
    service-url:
      defaultZone: http://eureka2001.test.com:2001/eureka/,http://eureka2002.test.com:2002/eureka/,http://eureka2003.test.com:2003/eureka/

具体可参考:微服务调用Ribbon


  • microservice-student-consumer-feign-80模块
@EnableEurekaClient
开启Eureka客户端调用服务

@EnableFeignClients(value = "com.ue.*")
启用Feign客户端调用,由于只有Feign的接口,没有被实现,不能直接注入到controller中,需要加入包扫描
#feign整合Hystrix
feign:
  hystrix:
    enabled: true

#Ribbon的超时时长设置后才能设置Hystrix的
ribbon:
  ReadTimeout: 10000
  ConnectTimeout: 9000
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000

具体可参考:Feign简介及应用

                    Feign与Hystrix整合(将服务熔断、服务降级彻底解耦)


  • microservice-student-provider-*模块
@EntityScan("com.ue.entity")
服务提供者读取不到common Module下的实体类,需要扫描

@EnableEurekaClient
注册中心服务启动
eureka:
  instance:
    #单机hostname: localhost #eureka注册中心实例名称
    hostname: eureka2001.test.com #集群
  client:
    register-with-eureka: false     #false由于该应用为注册中心,所以设置为false,代表不向注册中心注册自己。
    fetch-registry: false     #false由于注册中心的职责就是维护服务实例,它并不需要去检索服务,所以也设置为false
    service-url:
      defaultZone: http://eureka2002.test.com:2002/eureka/,http://eureka2003.test.com:2003/eureka/ #集群
      #设置与Eureka注册中心交互的地址,查询服务和注册服务用到
      #单机defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/       

具体可参考:Ribbon的负载均衡


  • microservice-student-provider-hystrix-*模块 
#使用Hystrix不整合Feign的情况,超时设置放在Hystrix这边
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000
@EnableCircuitBreaker
启动类加此注解,代表启用断路器的意思
/**
 * 测试Hystrix服务降级
 * Controller加此,表明服务降级具体处理方案
 * @return
 * @throws InterruptedException
 */
@ResponseBody
@GetMapping(value="/getInfo")
@HystrixCommand(fallbackMethod="getInfoFallback")
public Map<String,Object> getInfo() throws InterruptedException{
	Thread.sleep(2000);
	Map<String,Object> map=new HashMap<String,Object>();
	map.put("code", 200);
	map.put("info", "业务数据xxxxx");
	return map;
}

public Map<String,Object> getInfoFallback() throws InterruptedException{
	Map<String,Object> map=new HashMap<String,Object>();
	map.put("code", 500);
	map.put("info", "系统出错,稍后重试");
	return map;
}

具体可参考: Hystrix断路器简介及应用


  •  microservice-student-consumer-hystrix-dashboard-90模块
@EnableHystrixDashboard
启动类加此,代表启用Hystrix监控仪表盘

具体可参考:Hystrix服务监控Dashboard


  • microservice-student-consumer-hystrix-turbine-91模块
turbine:
  app-config: microservice-student   #指定要监控的应用名称,以逗号隔开
  clusterNameExpression: "'default'"   #表示集群的名字为default
spring:
  application:
    name: turbine
@EnableTurbine
启动类加此注解,表明启用turbine

具体可参考:Hystrix集群监控Turbine


  • microservice-zuul-3001模块
@EnableZuulProxy
启动类加此注解,表明启用路由代理
zuul:
  routes:
    studentServer.serviceId: microservice-student
    studentServer.path: /studentServer/**
  ignored-services: "*"
  prefix: /javajl

配置映射关系,拦截原始url服务,映射后的url加前缀

具体可参考:Zuul API路由网关服务


  • microservice-config-server-4001模块
@EnableConfigServer
启动类加此注解,表明是config配置中心服务端
spring:
  application:
    name: microservice-config
  cloud:
    config:
      server:
        git:
          uri: https://github.com/java-LJ/microservice-config.git

主要是要配置一个git仓库地址
  • microservice-config-client-5001模块
spring:
  application:
    name: application-dev
  cloud:
    config:
      name: crm
      uri: http://configserver.test.com:4001
      profile: test
      label: master
      fail-fast: true

注:这是bootstrap.yml配置文件的内容,主要是配置config service服务的地址

具体可参考:SpringCloud Config简介、Config Server及Client的使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值