SpringCloud 个人简单总结

目录

  • 简介
  • Eureka——服务注册与发现
  • 概念
  • 元信息的内容与存储
  • 自我保护机制
  • 与 Zookeeper 对比
  • Ribbon——负载均衡
  • 简介
  • 负载均衡算法
  • 使用
  • 与Nginx的比较
  • OpenFeign——服务远程调用
  • Hystrix——服务熔断降级
  • Zuul——微服务网关

简介

Spring Cloud 基于SpringBoot,是微服务系统架构的一站式解决方案。SpringCloud 通过一系列组件,提供了一套编程模型,方便开发者实现构建微服务架构时的常见操作,如服务发现注册配置中心消息总线负载均衡断路器数据监控 等。

Eureka——服务注册与发现

概念

Eureka主要分为两个角色:客户端Client、服务端Server。无论是服务的提供者Provider,还是消费者Consumer,都属于Client。

服务注册 Register:是指Eureka Client 将自身服务的元数据,发送给Server,并将自身添加到Server的服务注册表中。

服务发现 Discover:是指Eureka Client可以从Server获取注册表信息,并缓存在本地。从而根据该信息查找其它服务,并进行远程调用。

服务下线 Cancel:Eureka Clinet在程序关闭时向Server发送下线请求,从而将自身的信息从Server的注册表中删除。该下线请求不会自动完成,它需要Client主动调用以下内容:DiscoveryManager.getInstance().shutdownComponent();

服务续约 Renew:Eureka Client 会每隔30秒(默认情况下)发送一次心跳来续约。通过续约来告知 Eureka Server 该Eureka Client仍然存在,没有出现问题。

服务剔除 Eviction: 在默认的情况下,当Eureka Server 连续3个续约周期(90秒)没有收到某个Client发送的服务续约,即心跳,就会将该Client从服务注册列表删除。

注:
Eureka Server 存储注册列表信息,且整个注册表以及每个服务的信息都进行了压缩,压缩内容和没有压缩的内容完全相同。

Eureka Client 定期(默认每30秒钟)从Eureka Server 增量更新一次依赖服务的注册列表信息。若由于某种原因导致注册列表信息不能及时匹配,Client则会重新获取整个注册表信息。

Eureka Client 和 Server可以使用 JSON/XML格式进行通讯。在默认的情况下,Clinet使用压缩 JSON 格式来获取注册列表的信息。

元信息的内容与存储

Eureka Server维护了系统中服务的元信息,具体包括端口、服务健康信息、续约信息等,存储于专门为服务开辟的注册表中。

此外,还可以自定义元数据信息,使用 eureka.instance.metadata-map.xxx = value来配置。内部其实就是维护了一个 Map 来保存自定义元数据信息,可以配置在远端服务,随服务一并注册保存在 Eureka 注册表中。

自我保护机制

存在这种情况:服务A和B之间、A和Eureka Server之间的网络连通是良好的,但是B由于和Eureka Server之间的网络问题,无法及时进行续约。此时,Eureka Server不能简单地将B服务从注册表中剔除,因为实际上B服务对A服务来说是可用的。

Eureka 的自我保护机制,正是一种针对网络异常波动的安全保护措施,能使Eureka集群更加健壮、稳定地运行。

在注册服务的续约时间为默认30秒的情况下,假设Eureka Server上注册的服务数是N,则每分钟期望收到的Client续约的总数为 Renews ThresholdRenewalPercentThreshold * 2 * N,其中,RenewalPercentThreshold默认为0.85(可通过eureka.server.renewal-percent-threshold修改)。

而自我保护机制被激活的条件是:在1分钟后,Renews (last min) < Renews Threshold

自我保护机制激活后,此时:

  1. Eureka Server不再进行服务剔除。
  2. Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上。

Renews (last min) >= Renews Threshold时,Eureka Server则会退出自我保护机制。此时,当前Eureka Server新的注册信息会被同步到其它节点中。

此外,Renews Threshold的值,默认每15分钟会更新一次(可通过eureka.server.renewal-threshold-update-interval-ms修改)。

Eureka的自我保护机制带来的问题是:在激活期间不会进行服务剔除,即使这个服务是真的不可用了,因此,需要服务消费方有一些容错机制,如重试、断路器等。

与 Zookeeper 对比

Zookeeper选择保证CP。

为了保证一致性,在所有节点同步完成之前是阻塞状态的。

但Zk一旦出现Leader节点宕机的情况,则需要选择新Leader,选举的时间很长,一般在30 ~ 120秒,选举期间整个Zk集群都不可用。

Eureka 选择保证AP。

为了保证了可用性,Eureka 不会等待集群所有节点都已同步信息完成,它会无时无刻提供服务。且Eureka的设计是去中心化的,所有节点都是平等的,几个节点宕机不会影响正常节点的工作,只要还有一台节点还能正常工作,就能保证服务可用(即保证可用性)。

但是Eureka不能保证服务发现的结果是最新的(即不能保证强一致性)。

C:数据一致性;A:服务可用性;P:分区容错性(服务对网络分区故障的容错性)。

在分布式领域有一个很著名的CAP定理:在C、A、P这三个特性中,任何分布式系统最多只能保证两个。

而由于当前网络延迟故障会导致丢包等问题,所以分区容错性是必须实现的。也就是NoSqL数据库必须保证P,因此只能在一致性和可用性中进行选择,即保证AP和保证CP两种模式。

Ribbon——负载均衡

简介

Ribbon 是 Netflix 公司的一个开源的负载均衡 项目,是一个客户端的负载均衡器,运行在Consumer端(调用服务方)。

Ribbon的工作原理是,Consumer端获取到了目标服务的列表后,在内部使用负载均衡调度算法,选择对系统的调用。

负载均衡算法

Ribbon中有许多负载均衡调度算法,默认使用的是 RoundRobinRule(轮询策略)

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

Ribbon默认的负载均衡算法可以通过修改配置文件,或自定义JavaConfig类来更改。

providerName:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

并且,不仅可以更改成内置的负载均衡算法,还可以自定义负载均衡算法:算法类实现IRule接口。

使用

当脱离Eureka时,Ribbon需要在自身服务内部硬编写服务名称的地址列表,在调用服务时,从此列表中进行负载均衡调度。

但一般,Ribbon是与Eureka搭配使用的,Eureka中动态维护了微服务名称与地址的映射,Ribbon只需要提供服务名称,就可以从Eureka中获得具有该名称服务的地址列表,从而进行负载均衡调度。

与Nginx的比较

与Ribbon不同的是,Nginx是一种集中式的负载均衡器。

简单理解就是,Nginx将所有请求都集中起来,然后再由Nginx进行负载均衡,选择将请求转发到选择的服务上。

注意:Ribbon是Consumer先进行负载均衡再发出请求,而Nginx是Consumer先发出请求,再由Nginx进行负载均衡。

此外,Nginx 使用的是轮询和加权轮询算法。

OpenFeign——服务远程调用

有了Eureka负责服务注册与发现、Ribbon负责负载均衡,还需要实际进行服务调用的工具,而RestTemplate就是Spring提供的一个访问Http服务的客户端类。

事实上,微服务之间的调用使用的都是RestTemplate,包括Eureka框架中的注册、续约等,底层使用的都是RestTemplate。

如下是一段简单使用RestTemplate的伪代码:(如果是负载均衡,还需要在RestTemplate的构造方法上添加@LoadBalanced,且通过Ribbon组件来调用)

@Autowired private RestTemplate restTemplate; // 这里是目标服务的ip地址,如果使用了 Eureka 则是目标服务的注册名称 private static final String SERVICE_PROVIDER = "http://localhost:8081"; public boolean judge(Request request) { String url = SERVICE_PROVIDER + "/judge"; return restTemplate.postForObject(url, request, Boolean.class); }

从上面的伪代码可以看出,使用RestTemplate进行服务调用的代码编写十分繁琐,在每个调用其它服务的地方,都需要手动组装服务地址/名称、请求、返回类型。

OpenFeign是一个声明式的Web服务客户端。它只需要开发者定义一个接口,并加上注解,就可以像调用本地代码一样进行服务间的调用。OpenFegin也是运行在Consumer端的,且内置了Ribbon负责负载均衡。

OpenFeign的使用非常简单,导入依赖后,在Service中编写以下代码后,就可以当成本地方法一样直接调用了。

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

注意:OpenFeign是SpringCloud团队在原生Feign的基础上添加了对SpringMVC的注解,如@RequestMapping等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的借口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其它服务。

Hystrix——服务熔断降级

服务雪崩:由于上游的服务异常不可用,最终蔓延到下游的所有服务,导致所有相关下游的微服务应用不可用而系统崩溃。

在分布式环境中,常会出现调用其它服务时的失败情况。

而Hystrix是一个库,可通过添加等待时间容限和容错逻辑来帮助控制分布式服务之间的交互。

Hystrix通过隔离服务之间的访问点,停止服务之间的级联故障并提供后备选项来实现此目的,所有这些都可以提高系统的整体弹性。总体来说,Hystrix 是一个能进行熔断和降级的库。

熔断:指一段时间内,对目标服务请求的失败率到达设定阈值,系统将通过断路器断开所有对该服务的请求。是服务雪崩的有效解决手段。

降级:当对一个方法的调用出现异常时,通过执行另一种代码逻辑,来给调用方更友好的回复。是一种后备处理模式。

Zuul——微服务网关

Zuul 是Netflix开源的一个API 网关服务器,是到后端微服务的所有请求的统一入口,介于客户端到服务器端之间。

Zuul具有路由、过滤器、权限校验、限流等功能。

路由功能

Zuul本身也需要注册到Eureka中,因此就可以从Eureka中获取所有服务的注册列表信息,从而进行路由映射。

此外,Zuul还提供了一些自定义的配置功能,如统一前缀、路由策略配置(自定义路径替代微服务名称)、屏蔽微服务名访问、屏蔽指定路径、屏蔽指定请求头。

过滤功能

Zuul的过滤器分为三种类型:Pre(请求前)、Routing(上面的路由策略配置)、Post(响应前)。

可以自定义Filter,继承ZuulFilter,并注入到Spring中即可。需要重写ZuulFilter中的一些方法,达到自定义的目的:

// 指定过滤器类型
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }
    
    // 同类型自定义过滤器中的执行次序,越小越先执行
    @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;
    }

通过Zuul的过滤器功能,可以达到统计、日志、拦截(包括权限校验)、限流(令牌桶)等功能。

Zuul也存在单点问题,如果想保证高可用性,则要建立Zuul集群,并搭配Nginx进行负载均衡。