Spring Cloud 各模块复习

将一个原本独立的系统拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间通过基于HTTP的RESTful API进行通信协作。

由于有轻量级的通信协作基础, 所以这些微服务可以使用不同的语言来编写。

需要考虑网络延迟、分布式事务、异步消息等。

在微服务架构中, 通常会使用以下两种服务调用方式:

  • 第一种, 使用 HTTP 的 RESTful API 或轻量级的消息发送协议, 实现信息传递与服务调用的触发。
  • 第二种, 通过在轻量级消息总线上传递消息, 类似 RabbitMQ 等一些提供可靠异步交换的中间件。

模块

  • 服务治理: Spring Cloud Eureka 用于服务注册
  • 客户端负载均衡:Spring Cloud Ribbon
  • 服务容错保护: Spring Cloud Hystrix 熔断器
  • 声明式服务调用: Spring Cloud Feign 类似于facade模式
  • API网关服务: Spring Cloud Zuul
  • 分布式配置中心: Spring Cloud Config
  • 消息总线: Spring Cloud Bus 消息队列
  • 消息驱动的微服务: Spring Cloud Stream
  • 分布式服务跟踪: Spring Cloud Sleuth

服务治理

服务治理可是微服务架构中最为核心和基础的模块, 主要用来实现各个微服务实例的自动化注册与发现。

服务注册

  • 在服务治理框架中, 通常都会构建一个注册中心;
  • 每个服务单元向注册中心登记自己提供的服务, 将主机与端口号、 版本号、 通信协议等一些附加信息告知注册中心;
  • 注册中心按服务名分类组织服务清单
  • 服务注册中心还需要以心跳的方式去监测清单中的服务是否可用

在服务治理框架中, 通常都会构建一个注册中心, 每个服务单元向注册中心登记自己提供的服务, 将主机与端口号、 版本号、 通信协议等一些附加信息告知注册中心, 注册中心按服务名分类组织服务清单。另外, 服务注册中心还需要以心跳的方式去监测清单中的服务是否可用, 若不可用需要从服务清单中剔除, 达到排除故障服务的效果。

服务发现

  • 通过向服务名发起请求调用实现
  • 调用方需要向服务注册中心咨询服务, 并获取所有服务的实例清单
  • 服务C希望调用服务A, 服务C就需要向注册中心发起咨询服务请求, 服务注册中心就会将服务A的位置清单返回给服务C,C从本地清单调用A

由于在服务治理框架下运作, 服务间的调用不再通过指定具体的实例地址来实现, 而是通过向服务名发起请求调用实现。 所以,服务调用方在调用服务提供方接口的时候, 并不知道具体的服务实例位置。 因此, 调用方需要向服务注册中心咨询服务, 并获取所有服务的实例清单, 以实现对具体服务实例的访问。 比如,现有服务C希望调用服务A, 服务C就需要向注册中心发起咨询服务请求, 服务注册中心就会将服务A的位置清单返回给服务C, 如按上例服务A的情况,C便获得了服务A的两个可用位置 192.168.0.100:8000和192.168.0.101:8000。当服务C要发起调用的时候, 便从该清单中以某种轮询策略取出一个位置来进行服务调用, 这就是后续我们将会介绍的客户端负载均衡。

Spring Cloud Eureka

服务注册中心、服务提供者、服务消费者

Spring Cloud Eureka使用Netflix Eureka来实现服务注册与发现, 它既包含了服务端组件,也包含了客户端组件,并且服务端与客户端均采用Java编写。

支持高可用配置。

Netflix推荐每个可用的区域运行一个Eureka服务端,通过它来形成集群Eureka服务端集群)。不同可用区域的服务注册中心通过异步模式互相复制各自的状态,这意味着在任意给定的时间点每个实例关于所有服务的状态是有细微差别的。

Eureka客户端通过注解和参数配置的方式,嵌入在客户端应用程序的代码中,主要处理服务的注册与发现。在应用程序运行时,Eureka客户端向注册中心注册自身提供的服务并周期性地发送心跳来更新它的服务租约(由Eureka客户端发送心跳,服务续约)。同时,它也能从服务端查询当前注册的服务信息并把它们缓存到本地并周期性地刷新服务状态(Eureka客户端本地缓存服务信息)。

服务注册中心

通过@EnableEurekaServer 注解启动一个服务注册中心

<dependency> 
        <groupid>org.springframework.cloud</groupid> 
        <artifactid>spring-cloud-starter-eureka-server</artifactid>
</dependency>

配置yaml文件
server. port=1111 
eureka.instance.hostname=localhost 
eureka.client.register-with-eureka=false 
eureka.client.fetch-registry=false 
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/ 

eureka.client.register-with-eureka 表示是否要注册自己到服务注册中心;

eureka.client.fetch-registry表示是否要从服务注册中心中抓取那些注册客户端的信息。

Eureka 信息面板

注册服务提供者(客户端,提供服务)

通过加上 @EnableDiscoveryClient 注解, 激活 Eureka 中的DiscoveryClient 实现(自动化配置, 创建 DiscoveryClient 接口针对 Eureka 客户端的EurekaDiscoveryClient 实例)

<dependency> 
        <groupid>org.springframework.cloud</groupid> 
        <artifactid>spring-cloud-starter-eureka</artifactid>
</dependency>

配置yaml文件
spring.application.name=hello-service
eureka.client.serviceUrl.defaultZone=http://localhost:llll/eureka/

spring.application.name 命名客户端。

eureka.client.serviceUrl.defaultZone指定服务端注册中心的地址。

高可用问题(服务端-注册中心:单节点模式和高可用模式)

Eureka Server的设计一开始就考虑了高可用问题,在Eureka的服务治理设计中, 所有节点即是服务提供方, 也是服务消费方, 服务注册中心也不例外。

Eureka Server的高可用实际上就是将自己作为服务向其他服务注册中心注册自己, 这样就可以形成一组互相注册的服务注册中心, 以实现服务清单的互相同步, 达到高可用的效果。(多个Eureka Server,集群)

如我们不想使用主机名来定义注册中心的地址,也可以使用IP地址的形式,但是需要在配置文件中增加配置参数eureka.instance.prefer江p-address=true, 该值默
认为false。

服务消费者

发现服务以及消费服务

其中,服务发现的任务由Eureka的客户端完成,而服务消费的任务由Ribbon完成 。Ribbon与Eureka联合使用,服务消费者依然需要注册为Eureka的客户端来发现服务提供者(客户端)。

消费者应用从服务注册中心获取服务列表, 从而使消费者可以知道去何处调用其所需要的服务,除了使用了 Ribbon 来实现服务消费,另外还有使用 Feign 的消费方式。

对于访问实例的选择,Eureka中有Region和Zone的概念,一个Region中可以包含多个Zone, 每个服务客户端需要被注册到一个Zone中, 所以每个客户端对应一个Region和一个Zone。 在进行服务调用的时候,优先访问同处一个Zone中的服务提供方。

客户端负载均衡:Spring Cloud Ribbon

首先这是一个服务消费者,作为Eureka的客户端,会从服务注册中心抓取服务提供者。Ribbon默认提供轮询形式的负载均衡。

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

//使用restTemplate访问rest接口
restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class) . getBody()

服务容错保护: Spring Cloud Hystrix 熔断器

依赖通过远程调用的方式执行, 由于网络原因或是依赖服务自身间题出现调用故障或延迟, 而这些问题会直接导致调用方的对外服务也出现延迟, 若此时调用方的请求不断增加,最后就会因等待出现故障的依赖方响应形成任务积压,最终导致自身服务的瘫痪。

当某个服务单元发生故障(类似用电器发生短路) 之后, 通过断路器的故障监控(类似熔断保险丝), 向调用方返回一个错误响应, 而不是长时间的等待。

Hystrix具备服务降级、 服务熔断、 线程和信号隔离、 请求缓存、 请求合并以及服务监控等强大功能。

Netflix Feign

整合了 Spring Cloud Ribbon 与 Spring Cloud Hystrix, 除了提供这两者的强大功能之外,它还提供了一种声明式的 Web 服务客户端定义方式。

Feign 和Dubbo的facade模式差不多,提取出一些接口作为api,服务者和消费者同时依赖这些api,只不过Dubbo是rpc协议,Feign用的是rest协议。所以看起来Dubbo抽出来的接口对应的是Service,而Feign对应的是Controller层。

//api
@FeignClient ("HELLO-SERVICE") 
public interface HelloService { 
    @RequestMapping("/hello") 
    String hello(String name); 
}
//服务者(客户端,服务提供者)
@RestController
public class HelloController implements HelloService { 
    @Override 
    public String hello(@RequestParam("name") String name) { 
        return "Hello"+ name; 
    }
}

//消费者
@RestController 
public class ConsumerController { 
    @Autowired 
    HelloService helloService; 
    @RequestMapping(value = "/feign-consumer", method= RequestMethod.GET) 
    public String helloConsumer () { 
        return helloService.hello("name"); 
    }
}

继承FeignClient api接口的Controller的重写方法并没有RequestMapping注解,因为这些注解都卸载api接口中了。

API网关服务:Spring Cloud Zuul

为了保证对外服务的安全性, 我们在服务端实现的微服务接口, 往往都会有一定的权限校验机制, 比如对用户登录状态的校验等。每一个微服务应用提供的接口都需要这些校验逻辑。

导致的问题:冗余,每一个微服务都要实现;修改,扩展与优化麻烦,容易出错。

解决方案:API网关,它的定义类似于面向对象设计模式中的Facade模式。

所有的外部客户端访问都需要经过它来进行调度和过滤。它除了要实现请求路由、 负载均衡、 校验过滤等功能之外, 还需要更多能力, 比如与服务治理框架的结合、 请求转发时的熔断机制、 服务的聚合等一系列高级功能。

以用户登录状态的校验举栗,其一每一个微服务都实现这个方法,这肯定不可能,第二种,把用户相关抽取出来作为微服务,其他的微服务每次调用接口之前调用用户微服务。但是调用这个步骤还是有冗余的,如果用户登录状态校验接口更改,则还是麻烦;最后API网关这种模式,则是必然的选择。

分布式配置中心: Spring Cloud Config

用来为分布式系统中的基础设施和微服务应用提供集中化的外部配置支持, 它分为服务端与客户端两个部分。

服务端也称为分布式配置中心, 它是一个独立的微服务应用, 用来连接配置仓库并为客户端提供获取配置信息、 加密/解密信息等访问接口;Spring Cloud Config 实现的配置中心默认采用 Git 来存储配置信息。

Config Server

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>

Config Server 的配置需要加上 @EnableConfigServer 注解才生效

Spring_Factories-springboot_starter

@EnableConfigServer 做了什么

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ConfigServerConfiguration.class)
public @interface EnableConfigServer {
}

这个注解通过@Import引入了ConfigServerConfiguration.class

@Configuration
public class ConfigServerConfiguration {
	class Marker {}

	@Bean
	public Marker enableConfigServerMarker() {
		return new Marker();
	}
}

而ConfigServerConfiguration这个配置类非常简单,就是实例化一个空的Bean(Marker)。其他什么也没做。所以Spring Cloud Config Server的配置也不在这。

Marker唯一被使用的地方就是在一个@ConditionalOnBean的条件中,也就是说只有满足了Marker已经实例化为一个Bean的情况下,ConfigServerAutoConfiguration才会被配置,而ConfigServerAutoConfiguration作为

org.springframework.boot.autoconfigure.EnableAutoConfiguration接口的实现类被定义在jar包的spring.factories中。这也是上面放了一个介绍spring.factories链接的缘故。

/**
 * @author Spencer Gibb
 */
@Configuration
@ConditionalOnBean(ConfigServerConfiguration.Marker.class)
@EnableConfigurationProperties(ConfigServerProperties.class)
@Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class, ResourceRepositoryConfiguration.class,
		ConfigServerEncryptionConfiguration.class, ConfigServerMvcConfiguration.class })
public class ConfigServerAutoConfiguration {

}

Config 服务器配置好后:调用 http://localhost:7001/zuul/dev/cloud-config-2.0,也就是指定Git目录下的,cloud-config-2.0分支下的zuul-dev.properties。且会带上zuul.properties。

其中bbea0fe6240f7672916eacc9705903436ce1fd0c 代表Git提交的版本号;"label" 代表 Git的分支

{
    "name": "zuul",
    "profiles": [
        "dev"
    ],
    "label": "cloud-config-2.0",
    "version": "44163c383f292dd2479ec53ce785bada7ec84377",
    "state": null,
    "propertySources": [
        {
            "name": "https://github.com/yiyehu/clould-config/config-repo-demo/zuul-dev.properties",
            "source": {
                "from": "git-dev-2.0"
            }
        },
        {
            "name": "https://github.com/yiyehu/clould-config/config-repo-demo/zuul.properties",
            "source": {
                "from": "git-default-2.0"
            }
        }
    ]
}

Config Client

在bootstrap.properties中配置关于eureka server的defaultZone注册服务中心。从服务中心中获取config server 。

消息总线: Spring Cloud Bus

使用轻量级的消息代理来构建一个共用的消息主题让系统中所有微服务实例都连接上来, 由于该主题中产生的消息会被所有实例监听和消费, 所以我们称它为消息总线。 在总线上的各个实例都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息, 例如配置信息的变更或者其他一些管理操作等。

消息驱动的微服务: Spring Cloud Stream

Spring Cloud Stream 是一个用来为微服务应用构建消息驱动能力的框架。通过使用 Spring Integration来连接消息代理中间件以实现消息事件驱动。可以有效简化开发人员对消息中间件的使用复杂度, 让系统开发人员可以有更多的精力关注于核心业务逻辑的处理。

分布式服务跟踪: Spring Cloud Sleuth

实现对请求调用的跟踪可以帮助我们快速发现错误根源以及监控分析每条请求链路上的性能瓶颈等。