SpringCloud NetFlix核心组件

前言

本篇对SpringCloud微服务做了一个宏观了解(架构演变、核心问题、解决方案、技术栈等),并且记录自己使用SpringCloud NetFlix 一站式解决方案所用到的几个核心组件搭建微服务。

SpringCloud宏观了解

java架构的演变史

  • MVC架构 (三层架构)

    使多开发人员协调工作,分工明确,简化了开发!

  • 开发框架

    • Spring (IOC AOP)

      Spring是一个轻量级的框架,解决了企业级开发的复杂性问题,但是随着时间的推移还是越来越复杂了

    • SpringBoot

      SpringBoot是一个脚手架,约定大于配置,将常用的组件自动配置了

  • 微服务架构

    核心思想:拆分、模块化,分而治之!

    SpringCloud基于SpringBoot,将以前部署在单机上的服务按具体业务模块进行划分,可部署在多台服务器上,对于单体项目是本地调用本地本地方法,而微服务是A服务调用B服务的方法,对内RPC、对外RestFul。

  • 服务网格(目前最新概念)

    服务网格是下一代的微服务标准,ServerMesh

    代表解决方案:istio

微服务架构的问题

拆分这么多个微服务,会遇到4个核心问题:

  • 这么多服务,客户怎么访问?

  • 这么多服务,服务之间如何通信?

  • 这么多服务,如何被治理?
  • 这么多服务,如果发生异常宕机怎么办?

微服务解决方案

SpringCloud是一个生态,而不是一个技术,其中包含了很多技术!

  1. SpringCloud NetFlix 一站式解决方案,上面的四个核心问题,他都可以解决! (但是2018年底NetFlix宣布停止维护,可能有安全隐患)
    • Api网关:Zuul
    • 通信: Feign —基于HttpClint Http通信,同步并阻塞
    • 服务注册与发现:Enreka
    • 熔断机制:Hystrix
  2. Apache Dubbo + Zookeeper
    • Api网关:没有
    • 通信:Dubbo —基于Java的高性能Rpc框架 Rpc通信,异步非阻塞
    • 服务注册与发现:Zookeeper
    • 熔断机制:没有
  3. SpringCloud Alibaba 一站式解决方案,上面的四个核心问题,也都可以解决!

解决微服务问题的本质

  1. API网关:服务的路由
  2. 通信:Http、Rpc服务调用
  3. 服务注册与发现:高可用
  4. 熔断机制:服务降级

CAP定理

在分布式系统领域有个CAP定理(CAP theorem),又被称为布鲁尔定理(Brewer’s theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下3点。

· 一致性(Consistency):同一个数据在集群中的所有节点,同一时刻是否都是同样的值。

· 可用性(Availability):集群中一部分节点故障后,集群整体是否还能处理客户端的请求。

· 分区容忍性(Partition tolerance):是否允许数据的分区,数据分区的意思是指是否允许集群中的节点之间无法通信。

Zookeeper保证的是CP原则,即任何时刻对Zookeeper的访问请求都能的到相同的结果。

对于微服务的治理而言,核心就是注册和发现,所以对于服务发现场景来说针对同一个服务,即使注册中心保存的服务提供者信息不尽相同,也并不会造成灾难性的后果,因为对于服务消费者来说,能消费才是最重要的,及时拿到的不是正确的服务实例信息,都要尝试消费一下,也好过因为无法获取实例信息而不消费。所以对于服务发现而言,可用性比一致性更加重要,AP胜过CP。

  • Eureka保证的是AP原则,即就算大部分节点故障,只要有一个节点还能工作,这个服务就还可以处理客户端的请求,保证了高可用。
  • Zookeeper保证的是CP原则,主节点如果故障,则会进行30秒的选举时间,此时所有的服务将不可用。

微服务概述

原文:https://martinfowler.com/articles/microservices.html

汉化:https://www.cnblogs.com/liuning8023/p/4493156.html

什么是微服务?**微服务(Microservice Architecture)是近几年流行的一种架构思想,关于它的概念很

难一言以蔽之。究竟什么是微服务呢?我们在此引用 ThoughtWorks 公司的首席科学家 Martin Fowler

于2014年提出的一段话:

就目前而言,对于微服务,业界并没有一个统一的,标准的定义,但通常而言,微服务架构是一种架构模式,或者说是一种架构风格, 它提倡将单一的应用程序划分成一组小的服务,每个服务运行在其独立的自己的进程内,服务之间互相协调,互相配置,为用户提供最终价值。

单一的应用程序划分成一组小的服务

服务之间采用轻量级的通信机制互相沟通,每个服务都围绕着具体的业务进行构建,并且能够被独立的部署到生产环境中,另外,应尽量避免统一的,集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言,工具对其进行构建,可以有一个非常轻量级的集中式管理来协调这些服务,可以使用不同的语言来编写服务,也可以使用不同的数据存储。

微服务优缺点

优点

每个服务足够内聚,足够小,代码容易理解,这样能聚焦一个指定的业务功能或业务需求

开发简单,开发效率提高,一个服务可能就是专一的只干一件事

微服务能够被小团队单独开发

微服务是松耦合的,是有功能意义的服务,无论是在开发阶段或部署阶段都是独立的。

微服务能使用不同的语言开发

易于和第三方集成,微服务允许容易且灵活的方式集成自动部署,通过持续集成工具,如jenkins,Hudson,bamboo

微服务易于被一个开发人员理解,修改和维护,这样小团队能够更关注自己的工作成果。无需通过合作才能体现价值

微服务允许你利用融合最新技术

微服务只是业务逻辑的代码,不会和HTML CSS 或其他界面混合

每个微服务都有自己的存储能力,可以有自己的数据库,也可以有统一数据库

缺点

开发人员要处理分布式系统的复杂性

多服务运维难度,随着服务的增加,运维的压力也在增大

系统部署依赖

服务间通信成本

数据一致性

系统集成测试

性能监控…..

微服务技术栈有哪些

微服务条目 落地技术
服务开发 SpringBoot,Spring,SpringMVC
服务配置与管理 Netflflix公司的Archaius、阿里的Diamond等
服务注册与发现 Eureka、Consul、Zookeeper、Nacos等
服务调用 Rest、RPC、gRPC
服务熔断器 Hystrix、Envoy等
负载均衡 Ribbon、Nginx等
服务接口调用(客户端调用服务的简化工具) Feign、Dubbo等
消息队列 Kafka、RabbitMQ、ActiveMQ等
服务配置中心管理 SpringCloudConfifig、Chef等
服务路由(API网关) Zuul等
服务监控 Zabbix、Nagios、Metrics、Specatator等
全链路追踪 Zipkin、Brave、Dapper等
服务部署 Docker、OpenStack、Kubernetes等
数据流操作开发包 SpringCloud Stream(封装与Redis,Rabbit,Kafka等发送接收消息)
事件消息总线 SpringCloud Bus

SpringCloud基础项目搭建

接下来我们要构建一个基本微服务项目

创建父项目(用来管理依赖版本)

关键依赖,SpringCloud与SpringBoot用的是当前最高版本Hoxton.SR4与2.2.6

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>cn.hetonghao</groupId>
<artifactId>spring-cloud-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
</dependencies>
</dependencyManagement>

创建公共模块(用来放服务的通用代码)

每个项目必然有公共的模块,我们先创建好,父项目管理好公共模块的版本,并且其他的微服务大部分情况下都应该依赖此模块

创建服务提供者

ProviderController,提供一个/provider/hello的服务~

package cn.hetonghao.provider.action;

@RestController
@RequestMapping("provider")
public class ProviderController {
@GetMapping("hello")
public String hello() {
return "provider Hello!";
}
}

application.yml

spring:
application:
name: provider # 每个微服务都要有自己的名字!
server:
port: 8001

创建服务消费者

ConsumerAppMain,启动类中注入RestTemplate,

  • RestTemplate提供了各种HttpRest的调用方式!这是SpringBoot提供的基础Rest通信方式,使用它代码并不优雅,后面会使用Feign来代替服务间的通信!
package cn.hetonghao.consumer;

@SpringBootApplication
public class ConsumerAppMain {
public static void main(String[] args) {
SpringApplication.run(ConsumerAppMain.class, args);
}

@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

ConsumerController,消费者使用RestTemplate去调用消费者服务的接口~

package cn.hetonghao.consumer.action;

@RestController
@RequestMapping("consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;

private static final String REST_RUL = "http://localhost:8001/";

@GetMapping("consum")
public String consum() {
return restTemplate.getForEntity(REST_RUL + "provider/hello", String.class).getBody();
}
}

application.yml

spring:
application:
name: consumer
server:
port: 8002

测试服务通信

启动服务消费者与提供者,测试调用http://localhost:8002/consumer/consum

成功调用到提供者提供的服务!

NetFlix及相关核心组件

Eureka 服务注册与发现

什么是Eureka

Eureka是NetFlix的一个组件,遵循AP原则(可用性+分区容忍性)

Eureka的基本原理

架构:C/S架构

Eureka

如上图所示,服务提供者Provider在Eureka注册自己的信息,消费者Consumer从Eureka获取服务列表并缓存到本地,从而快速找到要消费的目标直接进行调用。

Eureka 包含两个组件 : Server、Client

Eureka是Server,Consumer与Provider都是Client,客户端内置负载均衡算法

服务提供者注册到Server后会定期发送心跳(30s),Server如果长时间没有收到心跳就认为此服务挂了,将移出注册中心。

构建Server端

  1. 创建Eureka子项目,导入依赖

    <dependency> 
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    <version>2.1.2.RELEASE</version>
    </dependency>
  2. 启动类中注解声明@EnableEurekaServer

  3. 编写配置文件,如果有集群配置了其它节点的地址则会将来注册的服务信息进行互相同步

    spring:
    application:
    name: eureka
    server:
    port: 7001
    eureka:
    instance:
    hostname: localhost
    client:
    register-with-eureka: false # 不需要将自己注册到Server
    fetch-registry: false # 不需要定时检测自己是否正常注册
    # 如果有集群,用逗号分隔配置多个地址即可同步注册信息
    #service-url:
    #defaultZone: http://localhost:7002/eureka
  4. 访问测试

    image-20200502153608156

将提供者注册到Server

  1. 添加注解到启动类@EnableEurekaClient,表明该服务是客户端,服务启动后会自动注册到Eureka中

  2. 配置文件增加内容

    eureka:
    client:
    service-url:
    # 如果注册中心有集群,配置多个可保证只要有任何一个注册中心是正常的也能正常注册
    defaultZone: http://localhost:7001/eureka #指定注册中心的地址
  3. 启动8001服务提供者,查看注册中心

    服务注册成功

细节优化

  • 修改实例名称,如果有多个实例可以起到见名知意的作用,配置文件增加内容。

    eureka:
    instance:
    instance-id: 服务提供者8001 # 自己起名字

实例名称

  • 完善提供者详细信息

    添加依赖

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <version>2.2.6.RELEASE</version>
    </dependency>

    配置详细信息

    info:
    app.name: 服务提供者8001
    company.name: 新科

    点击Status下的实例超链接即可获取配置信息:

    相信信息

服务发现

  1. 主启动声明注解:@EnableDiscoveryClient 或者@EnableEurekaClient //服务发现

    • @EnableDiscoveryClient:所有服务发现通用注解

    • @EnableEurekaClient:只支持Eureka的服务发现

  2. 配置文件添加配置

    eureka:
    client:
    # 如果是单纯的消费者可以不注册到注册中心,
    # register-with-eureka: false # 不需要将自己注册到Server
    # fetch-registry: false # 不需要定时检测自己是否正常注册
    service-url:
    defaultZone: http://localhost:7001/eureka
  3. controller添加方法

    @Autowired
    private DiscoveryClient discoveryClient;

    @GetMapping("discovery")
    public Object discovery() {
    var services = discoveryClient.getServices(); //var 关键字是jdk10 引入的类型推导
    log.info("services->{}", services);
    var instanceList = discoveryClient.getInstances("provider");
    instanceList.forEach(serviceInstance ->
    log.info("{},{},{},{}", serviceInstance.getServiceId(), serviceInstance.getHost()
    , serviceInstance.getPort(), serviceInstance.getUri())
    );
    return discoveryClient;
    }
  4. 启动调用http://localhost:8002/consumer/discovery

    provider服务发现

Ribbon 负载均衡

什么是Ribbon

Ribbon是一个客户端的负载均衡器(LoadBalance),主要功能就是提供各种负载均衡的算法来选取要调用服务实例。

比如说消费者在注册中心发现了消费者有3个实例,通过ribbon的负载均衡算法来选取一个最适合的实例进行调用,将消费者的请求均摊到所有节点上,从而实现HA(高可用)。

Ribbon是进程级别的LB,Nginx则是集中式的LB

集成到消费者中

  1. 添加依赖

    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    <version>2.2.2.RELEASE</version>
    </dependency>
  2. 在注册RestTemplate的地方加上注解@LoadBalanced

    @Bean
    @LoadBalanced // 是基于客户端实现的一套的负载均衡
    public RestTemplate restTemplate() {
    return new RestTemplate();
    }
  3. 改造调用Rest Url

    private static final String REST_RUL = "http://provider/";

    @GetMapping("consum")
    public String consum() {
    return restTemplate.getForEntity(REST_RUL + "provider/hello", String.class).getBody();
    }
  4. 启动多个Provider,并且修改请求结果便于区分不同实例

    启动多个Provider

  5. 测试调用

    成功与地址、端口号脱绑

负载均衡规则配置

我们要使用不同负载均衡算法只需要注册IRule接口即可:

@Bean
public IRule iRule() {
return new RandomRule(); //随机策略,还有很多策略
}

负载均衡算法可选规则

当然我们也可以照猫画虎自己实现负载均衡规则!

Feign 简化服务调用

什么是Feign

我们直接用RestTemplate看起来代码一点都不优雅,Feign就是用来解决这个问题的将其它服务的调用以面向接口的方式进行声明。

并且Feign默认集成了Ribbon实现客户端的负载均衡。

集成到通用模块、消费者服务中

因为是feign是面向接口编程的,提供者提供的接口完全可以写在通用模块里开发放给所有微服务!

  1. 因为消费者已经依赖了通用服务,所以只需要在通用模块添加依赖

    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <version>2.1.2.RELEASE</version>
    </dependency>
  2. 在通用模块中编写提供者接口

    package cn.hetonghao.provider;

    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;

    @FeignClient("provider") // 服务名称
    public interface ProviderService {
    @GetMapping("provider/hello") //api地址
    String hello();
    }
  3. 在消费者项目中加入注解@EnableFeignClients(basePackages = “cn.hetonghao”) 指定扫描接口基础包

  4. 再次改造消费者的controller

    @Autowired  
    private ProviderService providerService; //扫描到的接口会自动注入实例

    @GetMapping("consum")
    public String consum() {
    return providerService.hello();
    }
  5. 启动测试

    依然可以成功调用!

Hystrix 断路器

什么是Hystrix 断路器

在微服务的场景中调用链是十分复杂的,可能几十个服务像一张网一样互相调用,然而这时候如果有一个服务故障了,可能会导致连锁反应:请求积压,互相等待无响应,最终产生雪崩效应。

这时候我们就需要使用Hystrix 断路器来将服务进行错误熔断与降级处理。

Hystrix是一个用于处理分布式系统的延迟和容错的库,Hystrix可以保证在一个依赖出现问题的时候,进行熔断处理或者服务降级处理,不会导致整体服务错误,提高分布式系统的弹性。

集成hystrix

  1. 导入依赖

    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>2.1.2.RELEASE</version>
    </dependency>

实现熔断处理

  1. 在启动类中增加Hystrix熔断机制的支持@EnableCircuitBreaker

  2. 给提供者的api增加注解@HystrixCommand(fallbackMethod = “hystrixHello”)指定错误时调用的方法,并实现返回错误的临时结果

    @GetMapping("hello")
    @HystrixCommand(fallbackMethod = "hystrixHello")
    public String hello() {
    String value = null;
    if (value == null) {
    throw new RuntimeException("异常,需要熔断");
    }
    return "provider Hello! 7999";
    }

    public String hystrixHello() {
    return "hystrixHello";
    }
  3. 再次通过消费者调用

    成功熔断

实现服务降级

  1. 配置consumer服务的yml使feign支持hystrix

    feign:
    hystrix:
    enabled: true
  2. 实现之前feigh的接口

    package cn.hetonghao.provider.fallback;

    import cn.hetonghao.provider.ProviderService;
    import org.springframework.stereotype.Component;

    @Component //注意此处要注册到spring中,所以扫描包要确定无误!
    public class ProviderServiceFallbackImpl implements ProviderService {
    @Override
    public String hello() {
    return "ProviderServiceFallback"; //重新实现就是降级的处理
    }
    }
  3. 配置我们实现的降级服务

    package cn.hetonghao.provider;

    import cn.hetonghao.provider.fallback.ProviderServiceFallbackImpl;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;

    //value为服务名称,path为对应controller的api根名称,fallback指定降级的实现
    @FeignClient(value = "provider", path = "provider", fallback = ProviderServiceFallbackImpl.class)
    public interface ProviderService {
    @GetMapping("hello")
    String hello();
    }
  4. 重新启动,并且将提供者所有服务关闭,测试

    服务成功降级处理!

打个比方,在双十一的时候秒杀服务占用服务器资源是最多的,然而我们不想让退款、物流等服务占用秒杀的服务器资源,这时如果我们实现了服务降级,可以直接将一些不重要的服务下线,等秒杀活动过去,再上线!

集成Hystrix Dashboard

  1. 导入依赖

    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
    <version>2.0.0.RELEASE</version>
    </dependency>
  2. 启动类增加注解@EnableHystrixDashboard

  3. 在需要被监控的服务中保证有监控的依赖,并且注册一个servlet

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <version>2.2.6.RELEASE</version>
    </dependency>
    @Bean
    public ServletRegistrationBean servletRegistrationBean() {
    HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
    ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
    registrationBean.setLoadOnStartup(1);
    registrationBean.addUrlMappings("/actuator/hystrix.stream");
    registrationBean.setName("HystrixMetricsStreamServlet");
    return registrationBean;
    }
  4. 访问服务Hystrix Dashboard

    Hystrix Dashboard

  5. 在输入框输入要监控的服务地址+/actuator/hystrix.stream,比如:http://localhost:8002/actuator/hystrix.stream,这样就实现了对所有请求的健康监控~

    监控页面

Zuul路由网关

什么是Zuul

Zuul包含了对请求和路由的过滤两个主要的功能:

路由功能:负责将外部的请求转发到具体的微服务实例上,是一个实现外部统一访问的入口

过滤功能:负责对请求的处理进行干预,实现请求校验

构建Zuul网关服务

  1. 网关也是一个独立的微服务,创建新模块spring-cloud-zuul

  2. 导入核心依赖

    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>2.1.2.RELEASE</version>
    </dependency>
  3. 配置文件

    spring:
    application:
    name: zuul-gateway
    server:
    port: 7002
    eureka:
    instance:
    instance-id: zull-7002
    client:
    service-url:
    defaultZone: http://localhost:7001/eureka
  4. 注解开启Zull

    @EnableZuulProxy //开启zuul代理
    @SpringBootApplication
    public class ZuulAppMain {
    public static void main(String[] args) {
    SpringApplication.run(ZuulAppMain.class, args);
    }
    }
  5. 测试,通过路由访问,路由地址(localhost:7002)+服务名称(provider)+请求地址(provider/hello)

    通过路由访问

路由映射

  • 默认情况下是使用服务名称进行路由,如果我们不想暴露我们的服务名称可以使用路由映射功能。

  • 实现路由映射只需配置application.yml:

    zuul:
    routes:
    my-provider:
    # 路由服务id
    serviceId: provider
    # 映射路径
    path: /my-provider/**
    # 忽略服务列表
    ignored-services:
    # 不能再使用服务id:provider 进行访问
    - provider
  • 重启测试就可以通过映射的地址访问了

    路由映射访问

SpringCloud Config 分布式配置中心

文章作者: 何同昊
文章链接: http://hetonghao.cn/2020/05/SpringCloud/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 何同昊 Blog
支付宝超级火箭🚀
微信超级火箭🚀