• 欢迎访问惜文个人博客
  • 本博客最新公告:本站已经支持使用QQ和GitHub帐号快捷登录啦!
  • 访问本站建议使用火狐和谷歌浏览器哦!
  • 不知道要写什么哈哈
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏惜文博客吧
  • 源码模板插件免费下载,传送门:点我去看看传说中的安全之家!

Spring Cloud基础

开发笔记 布小星 2年前 (2018-11-17) 2103次浏览 0个评论 扫描二维码

基础

服务治理

在最初开始构建微服务系统的时候可能服务并不多,我们可以通过做一些静态配置来完成服务的调用。
比如,有两个服务A和B,其中服务A需要调用服务B来完成一个业务操作时,为了实现服务B的高可用,不论采用服务端负载均衡还是客户端负载均衡,相应的微服务应用也不断增加,我们的静态配置就会变得越来越难以维护。并且面对不断发展的业务,我们的集群规模、服务的位置、服务的命名等都可能发生变化,如果还是通过收工维护的方式,那么极易发生错误或是命名冲突等问题。同时,对于这类静态内容的维护也必将消耗大量的人力。

为了解决微服务架构中的服务实例维护问题,产生了大量的服务治理框架和产品。这些框架和产品的实现都围绕着服务注册与服务发现机制来完成对微服务应用实例的自动化管理。

服务治理的三个核心要素

服务注册中心

Eureka提供的服务端,提供服务注册与发现的功能。

服务提供者

提供服务的应用,可以是Spring Boot应用,也可以是其它技术平台且遵循Eureka通信机制的应用。

它将自己提供的服务注册到Eureka,以供其它应用发现。

服务消费者

消费者应用从服务注册中心获取服务列表,从而使消费者可以知道去何处调用其所需要的服务。

很多时候,客户端既是服务提供者,也是服务消费者。

Spring Cloud服务治理机制

服务提供者

服务注册

服务提供者 在启动的时候会通过发送REST请求的方式将自己注册到Eureka Server上,同时带上了自身服务的一些元数据信息。

Eureka Server接收到这个REST请求之后,将元数据信息存储在一个双层结构Map中,其中第一层的key是服务名,第二层的key是具体服务的实例名。

在服务注册时,需要确认一下eureka.client.register-with-eureka=true参数是否正确,该值默认为true。若设置为false将不会启动注册操作。

服务同步

服务注册中心之间互相注册为服务,当服务提供者发送注册请求到一个服务注册中心时,它会将该请求转发给集群中相连的其它注册中心,从而实现注册中心之间的服务同步。

通过服务同步,多个服务提供者的服务信息就可以通过多台服务注册中心的任意一台获取到。

服务续约

在注册完服务之后,服务提供者会维护一个心跳用来持续告诉Eureka Server:“我还活着”,以防止Eureka Server的“剔除任务”将该服务实例从服务列表中排除出去,我们称该操作为服务续约(Renew)。
关于服务续约有两个重要属性,我们可以关注并根据需要来进行调整:

eureka.instance.lease-reewal-interval-in-seconds=30
eureka.instance.lease-expiration-duration-in-seconds=90
eureka.instance.lease-reewal-interval-in-seconds
用于定义服务续约任务的调用间隔时间,默认为30秒。
eureka.instance.lease-expiration-duration-in-seconds
用于定义服务时效的时间,默认为90秒。

服务消费者

获取服务

当我们启动服务消费者的时候,它会发送一个REST请求给服务注册中心,来获取注册的服务清单。

为了性能考虑,Eureka Server会维护一份只读的服务清单来返回给客户端,同时该缓存清单会每隔30秒更新一次。

获取服务是服务消费者的基础,所以必须确保 eureka.client.fetch-registry=true 参数没有被修改成false,该值默认为true。

若希望修改缓存清单的更新时间,可以通过eureka.client.registry-fetch-interval-seconds=30 参数进行修改,该参数默认值为30,单位为秒。

服务调用

服务消费者在获取服务清单后,通过服务名可以获得具体提供服务的实例名和该实例的元数据信息。

因为有这些服务实例的详细信息,所以客户端可以根据自己的需要决定具体调用哪个实例,在Ribbon中默认采用轮询的方式进行调用,从而实现客户端的负载均衡。

对于访问实例的选择,Eureka中有Region和Zone的概念,一个Region中可以包含多个Zone。

每个服务客户端需要被注册到一个Zone中,所以每一个客户端对应一个Region和一个Zone。

在进行服务调用的时候,优选访问同处一个Zone中的服务提供方,若访问不到,就访问其它的Zone。

服务下线

当服务实例进行正常的关闭操作时,它会出发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。

服务注册中心接收到请求之后,将该服务状态置为下线(DOWN),并把该下线事件传播出去。

服务注册中心

失效剔除

有些时候,我们的服务实例并不一定会正常下线,可能会由于内存溢出、网络故障灯原因使得服务不能正常工作,而服务注册中心并未收到“服务下线”的请求。

为了从服务列表中将这些无法提供服务的实例剔除,Eureka Server在启动的时候会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除出去。

自我保护

当我们在本地调试基于Eureka的程序时,基本上都会碰到这样一个问题,在服务注册中心的信息面板中会出现类似下面的红色警告信息:EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

实际上,该警告就是出发了Eureka Server的自我保护机制。

服务注册到Eureka Server之后,会维护一个心跳连接,告诉Eureka Server自己还活着。

Eureka Server在运行期间,会统计心跳失败的比例在15分钟之内是否低于85%,如果出现低于的情况,Eureka Server会将当前的实例注册信息保护起来,让这些实例不会过期,尽可能保护这些注册信息。但是,在这段保护期间内实例若出现问题,那么客户端很容易拿到已经不存在的服务实例,会出现调用失败的情况,所以客户端必须要有容错机制,比如可以使用重试、断路器等机制。

由于本地调试很容易触发注册中心的保护机制,这会使得注册中心维护的服务实例不那么准确。

所以,我们在本地进行开发的时候,可以使用如下参数来关闭保护机制,以确保注册中心可以将不可用的实例正确剔除。

eureka.server.enable-self-preservation=false

Spring Cloud常用组件

组件名功能介绍备选组件
Spring Cloud Config配置中心组件将所有的服务的配置文件放到本地仓库或者远程仓库,配置中心负责读取仓库的配置文件,其它服务向配置中心读取配置。Spring Cloud Config使得服务的配置统一管理,并可以在不人为重启服务的情况下进行配置文件的刷新。Apollo
Diamond
Disconf
Spring Cloud Bus消息总线组件长和Spring Cloud Config配合使用,用于动态刷新服务的配置
Spring Cloud Netflix核心组件它是通过包装了Netflix公司的微服务组件实现的,包括Eureka、Hystrix、Zuul、Archaius等。
Eureka服务注册和发现组件Eureka组件提供了服务的健康监控,以及界面友好的UI。
通过Eureka组件提供的UI,Eureka组件可以让开发人员随时了解服务单元的运行情况。
Consul
Zookeeper
Hystrix熔断组件Hystrix通过控制服务的API接口的熔断来转移故障,防止微服务系统发生雪崩效应。
另外,Hystrix能够起到服务限流和服务降级的作用。
使用Hystrix Dashboard组件监控单个服务的熔断器的状态,使用Turbine组件可以聚合多个服务的熔断器的状态。
Ribbon负载均衡组件它通常和Eureka、Zuul、RestTemplate、Feign配合使用。
Ribbon和Zuul配合,很容易做到负载均衡,将请求根据复杂均衡策略分配到不同的服务实例中。
Ribbon和RestTemplate、Feign配合,在消费服务时能够做到负载均衡。
Feign声明式远程调度组件
Archaius配置管理API的组件一个基于Java的配置管理库,主要用于多配置的动态获取。
Zuul智能路由网关组件能够起到智能路由和请求过滤的作用,是服务接口统一暴露的关键模块,也是安全验证、权限控制的一道门。
Spring Cloud Security安全模块组件对Spring Security的封装,通常配合OAuth2使用来保护微服务系统的安全。
一般来说,单独在微服务系统中使用Spring Cloud Security是很少见的,一般它会配合Spring Security OAuth2组件一起使用,通过搭建授权服务,验证Token或者JWT这种形式对整个微服务系统进行安全验证。
Spring Cloud Sleuth分布式链路追踪组件服务链路追踪组件,封装了Dapper、Zipkin、Kibina等组件,可以实时监控服务的链路调用情况
Spring Cloud Stream数据流操作组件可以封装Redis、RabbitMQ、Kafka等组件,实现发送和接收消息等
Spring Cloud Data Flow大数据操作组件是Spring XD的替代品,也是一个混合后计算的模型,可以通过命令行的方式操作数据流
Spring Cloud Consul服务注册和发现组件该组件是Spring Cloud对Consul的封装,和Eureka类似,它是另一个服务注册和发现组件
Spring Cloud Zookeeper服务注册和发现组件该组件是Spring Cloud对Zookeeper的封装,和Eureka、Consul类似,用于服务的注册和发现
Spring Cloud CLI该组件是Spring Cloud对Spring Boot CLI的封装,可以让用户以命令行方式快速运行和搭建容器
Spring Cloud Task该组件基于Spring Task,提供了任务调度和任务管理的功能
Spring Cloud Connectors用于PaaS云平台连接到后端

服务治理步骤

搭建服务注册中心(eureka-server)

pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Finchley.M9</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题  -->
<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/libs-milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

Application.java

通过@EnableEurekaServer注解启动一个服务注册中心提供给其它应用进行对话。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class Application {

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

application.yml

server:  
  port: 9871  
eureka:  
  instance:  
    hostname: localhost  
  client:  
    registerWithEureka: false  
    fetchRegistry: false  
    serviceUrl:  
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  • client.registerWithEureka=false 代表不像注册中心注册自己。
  • client.fetchRegistry=false 代表不去检索服务。
  • server.port=9871 代表访问端口为9871。

启动注册中心

$ java -jar eureka-server/target/demo-0.0.1-SNAPSHOT.jar

访问 http://localhost:9871 。

注册服务提供者(provider)

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Finchley.M9</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题  -->
<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/libs-milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

Application.java

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

1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class Application {

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

application.yml

  • 通过spring.application.name属性来为服务命名
  • 通过eureka.client.serviceUrl.defaultZone属性配置来指定服务注册中心的地址
1
2
3
4
5
6
7
8
9
spring:
  application:
    name: eureka-client-provider
eureka:
  client:
    service-url:
           defaultZone: http://localhost:9871/eureka
server:
  port: 9091

ProviderController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProviderController {
    @Autowired
    private DiscoveryClient discoveryClient;

    @RequestMapping(value="hello",method= RequestMethod.GET)
    public String index(){
        return "Hello,World";
    }
}

启动服务提供者

1
$ java -jar provider/target/demo-0.0.1-SNAPSHOT.jar

访问 http://localhost:9091/hello 。

搭建服务消费者(consumer)

服务消费的任务由Ribbon完成。

Ribbon是一个基于HTTP和TCP的客户端负载均衡器,它可以在通过客户端中配置的ribbonServerList服务端列表去轮询访问以达到负载均衡的作用。

配置pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Finchley.M9</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题  -->
<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/libs-milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

eureka 里面已经包含 ribbon 了, 所以不用单独添加ribbon依赖。

配置Application.java

  • 通过@EnableDiscoveryClient注解让该应用注册为Eureka客户端应用,以获得服务发现的能力。
  • 在该主类中创建RestTemplate的Spring Bean实例
  • 通过@LoadBalanced注解开启客户端负载均衡
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableDiscoveryClient
public class Application {

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

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

配置ConsumerController

  • 实现/ribbon-consumer接口
  • 通过在上面创建的RestTemplate来实现对于Hello-Service服务提供的/hello接口进行调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class ConsumerController {
    @Autowired
    RestTemplate restTemplate;

    @RequestMapping(value="/consumer",method= RequestMethod.GET)
    public String helloConsumer(){
        return restTemplate.getForEntity("http://eureka-client-provider/hello",String.class).getBody();
    }
}

配置application.yml

1
2
3
4
5
6
7
8
9
spring:
  application:
    name: eureka-client-consumer
eureka:
  client:
    service-url:
           defaultZone: http://localhost:9871/eureka
server:
  port: 9092

启动consumer应用

1
$ java -jar consumer/target/demo-0.0.1-SNAPSHOT.jar

Spring Cloud Eureka

Spring Cloud Eureka介绍

Spring Cloud Eureka是Spring Cloud NetFix微服务套件中的一部分,它基于Netflix Eureka做了二次封装,主要负责完成微服务架构中的服务治理功能。

Spring Cloud Eureka,使用Netflix Eureka来实现服务注册与发现,它既包含了服务端组件,也包含了客户端组件,并且服务端与客户端均采用Java编写,所以Eureka主要适用于通过Java实现的分布式系统,或是与JVM兼容语言构建的系统。但是,由于Eureka服务端的服务治理机制提供了完备的RESTful API,所以它也支持将非Java语言构建的微服务应用纳入Eureka的服务治理体系中来,只是在使用其它语言平台的时候,需要自己来实现Eureka的客户端程序。

Spring Cloud通过为Eureka增加了Spring Boot风格的自动化配置,我们只需要通过简单引入依赖和注解配置就能让Spring Boot构建的微服务应用轻松地与Eureka服务治理体系进行整合。

Eureka服务端,我们也称为服务注册中心。它同其它服务注册中心一样,支持高可用配置。它依托于强一致性提供良好的服务实例可用性,可以应对多种不同的故障场景。如果Eureka以集群模式部署,当集群中有分片出现故障时,那么Eureka就转入自我保护模式。它允许在分片故障期间继续提供服务的发现和注册,当故障分片恢复运行时,集群中的其它分片会把它们的状态再次同步回来。

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

Spring Cloud中配置Eureka

配置Eureka服务端

Eureka服务端更多地类似于一个现成产品,大多数情况下,我们不需要修改它的配置信息。

关闭注册中心自我保护功能

1
eureka.server.enable-self-preservation=false

配置Eureka客户端

服务注册类配置

指定注册中心

1
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
1
eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/

为了服务注册中心的安全考虑,很多时候我们都会为服务注册中心加入安全校验。

这个时候,在配置serviceUrl时,需要在value值的URL中加入相应的安全校验信息,比如:

1
http://<username>:<password>@localhost://1111/eureka

这些参数均以eureka.client为前缀。其它配置如下:

参数名说明默认值
enabled启用Eureka客户端true
registryFetchIntervalSeconds从Eureka服务端获取注册信息的间隔时间,单位为秒30
instanceInfoReplicationIntervalSeconds更新实例信息的变化到Eureka服务端的间隔时间,单位为秒30
initialInstanceInfoReplicationIntervalSeconds初始化实例信息到Eureka服务端的间隔时间,单位为秒40
eurekaServiceUrlPollIntervalSeconds轮询Eureka服务端地址更改的间隔时间,单位为秒。当我们与Spring Cloud Config配合,动态刷新Eureka的serviceURL地址时需要关注该参数300
eurekaServerReadTimeoutSeconds读取Eureka Server信息的超时时间,单位为秒8
eurekaServerConnectTimeoutSeconds连接Eureka Server的超时时间,单位为秒5
eurekaServerTotalConnections从Eureka客户端到所有Eureka服务端的连接总数200
eurekaServerTotalConnectionsPerHost从Eureka客户端到每个Eureka服务端主机的连接总数50
eurekaConnectionIdleTimeoutSecondsEureka服务端连接的空闲关闭时间,单位为秒30
heartbeatExecutorThreadPoolSize心跳连接池的初始化线程数2
heartbeatExecutorExponentialBackOffBound心跳超时重试延迟时间的最大乘数值10
cacheRefreshExecutorThreadPoolSize缓存刷新线程池的初始化线程数2
cacheRefreshExecutorExponentialBackOffBound缓存刷新重试延迟时间的最大乘数值10
useDnsForFetchingServiceUrls使用DNS来获取Eureka服务端的serviceUrlfalse
registerWithEureka是否要将自身的实例信息注册到Eureka服务端true
preferSameZoneEureka是否偏好使用处于相同Zone的Eureka服务端true
filterOnlyUpInstances获取实例时是否过滤,仅保留UP状态的实例true
fetchRegistry是否从Eureka服务端获取注册信息true

服务实例类配置

这些配置信息都以eureka.instance为前缀。

实例名配置

1
2
3
4
5
6
management.context-path=/hello

eureka.instance.instanceId=${spring.application.name}:${random.init}
eureka.instance.metadataMap.zone=shanghai
eureka.instance.statusPageUrlPath=${management.context-path}/info
eureka.instance.healthCheckUrlPath=${management.context-path}/health

端点配置

有时候,为了安全考虑,也可能会修改/info和/health端点的原始路径,如:

1
2
3
4
5
endpoints.info.path=/appInfo
endpoints.health.path=/checkHealth

eureka.instance.statusPageUrlPath=/${endpoints.info.path}
eureka.instance.healthCheckUrlPath=${endpoints.health.path}

绝对路径的配置参数

1
2
3
eureka.instance.statusPageUrlPath=https://${eureka.instance.hostname}/info
eureka.instance.healthCheckUrlPath=https://${eureka.instance.hostname}/health
eureka.instance.homePageUrl=https://${eureka.instance.hostname}/

健康监测

配置pom.xml
1
spring-boot-starter-actuator
配置application.properties
1
eureka.client.healthcheck.enable=true

其它配置如下:

参数名说明默认值
perferIpAddress是否优先使用IP地址作为主机名的标识false
leaseRenewalIntervalInSecondsEureka客户端想服务端发送心跳的时间间隔,单位为秒30
leaseExpirationDurationInSecondsEureka服务端在收到最后一次心跳之后等待的时间上限,单位为秒。超过该时间之后服务端会将该服务实例从服务清单中剔除,从而禁止服务调用请求被发送到该实例上90
nonSecurePort非安全的通信端口号80
securePort安全的通信端口号443
nonSecurePortEnabled是否启用非安全的通信端口号true
securePortEnabled是否启用安全的通信端口号
appname服务名,默认取spring.application.name的配置值,如果没有则为unknown
hostname主机名,不配置的时候将根据操作系统的主机名来取

跨平台支持

Jersey和XStream配合JSON作为Server与Client之间的通信协议。

Eureka高可用注册中心

背景

在微服务架构这样的分布式环境中,需要充分考虑发生故障的情况,所以在生产环境中必须对各个组件进行高可用部署,对于微服务如此,对于注册中心也一样。

原理

Eureka Server的设计一开始就考虑了高可用的问题。

在Eureka的服务治理设计中,所有节点既是服务提供方,也是服务消费方,服务注册中心也不例外。

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

步骤

创建application-peer1.properties

1
2
3
4
spring.application.name=eureka-server
server.port=1111
eureka.instance.hostname=peer1
eureka.client.serviceUrl.defaultZone=http://peer2:1112/eureka/

创建application-peer2.properties

1
2
3
4
spring.application.name=eureka-server
server.port=1112
eureka.instance.hostname=peer2
eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/

修改hosts

1
2
127.0.0.1   peer1
127.0.0.1   peer2

启动peer1

1
$ java -jar eureka-server-1.0.0.jar --spring.profiles.active=peer1

启动peer2

1
$ java -jar eureka-server-1.0.0.jar --spring.profiles.active=peer2

测试

访问peer1的注册中心http://localhost:1111/ ,可以看到,registered-replicas中已经有peer2节点的eureka-server了。

访问peer2的注册中心http://localhost:1112/ ,也能看到registered-replicas中已经有peer1节点的eureka-server了。

尝试关闭peer1,刷新http://localhost:1112/ ,可以看到peer1的节点变成了不可用分片(unavailable-replicas)。

配置服务提供方application.properties

1
2
spring.application.name=xxx
eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/

yaml版

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
spring: 
  application: 
    name: register-center
  profiles: 
    active: register-center1
    
eureka:
  instance: 
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ipAddress}:${server.port}
    lease-expiration-duration-in-seconds: ${lease-expiration-duration-in-seconds}
    lease-renewal-interval-in-seconds: ${lease-renewal-interval-in-seconds}
  server: 
    enable-self-preservation: ${enable-self-preservation}  
    eviction-interval-timer-in-ms: ${eviction-interval-timer-in-ms}
  client:
    register-with-eureka: true
    fetch-registry: true
    serviceUrl: 
      defaultZone: ${register-center.urls}
      
---  
spring: 
  profiles: register-center1
      
server: 
  port: ${register-center1.server.port}
      
---
spring: 
  profiles: register-center2
  
server: 
  port: ${register-center2.server.port}

application.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#url
register-center1.server.ip=127.0.0.1
register-center2.server.ip=127.0.0.1
register-center.urls=http://${register-center1.server.ip}:${register-center1.server.port}/eureka/,http://${register-center2.server.ip}:${register-center2.server.port}/eureka/

#port
register-center1.server.port=7001
register-center2.server.port=7002

#config
enable-self-preservation=false
eviction-interval-timer-in-ms=5000
lease-expiration-duration-in-seconds=20
lease-renewal-interval-in-seconds=6

附录

如果不想使用主机名来定义注册中心的地址,也可以使用IP地址的形式,但是需要在配置文件中增加配置参数

1
eureka.instance.prefer-ip-address=true

该值默认为false。

Spring Cloud Ribbon

Spring Cloud Ribbon介绍

Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。

通过Spring Cloud的封装,可以轻松地将面向服务的REST模板请求自动转换成客户端负载均衡的服务调用。

Spring Cloud Ribbon不像服务注册中心、配置中心、API网关那样需要独立部署,它存在于每一个Spring Cloud构建的微服务和基础设施中。微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的。

微服务架构中使用客户端负载均衡的步骤

配置pom.xml

1
2
3
4
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>

配置application.yml

1
2
3
4
5
ribbon: 
  eureka:
    enabled: true
  MaxAutoRetries: 0
  MaxAutoRetriesNextServer: 0

编写RibbonService类

1
2
3
4
5
6
7
8
@Service
public class RibbonService{
    @Autowired
    @RestTemplate restTemplate;
    public String hello(String name){
        return restTemplate.getForObject("http://eureka-client/hello?name="+name,String.class);
    }
}

RestTemplate

RestTemplate介绍

RestTemplate会使用Ribbon的自动化配置,同时通过配置@LoadBalanced还能够开启客户端负载均衡。

RestTemplate不同请求类型的服务调用

GET请求

  • getForEntity(String url,Class reponseType,Object… urlVariables)
  • getForEntity(String url,Class reponseType,Map urlVariables)
  • getForEntity(String url,Class reponseType)

POST请求

  • postForEntity(String url,Object request,Class responseType,Object… uriVariables)
  • postForEntity(String url,Object request,Class responseType,Map uriVariables)
  • postForEntity(String url,Object request,Class responseType)

PUT请求

  • put(String url,Object request,Object… uriVariables)
  • put(String url,Object request,Map uriVariables)
  • put(String url,Object request)

DELETE请求

  • delete(String url,Object… urlVariables)
  • delete(String url,Map urlVariables)
  • delete(URI url)

负载均衡策略

默认负载均衡策略

负载均衡策略说明实现原理
BestAvailableRule选择最小请求数逐个考察Server,如果Server被tripped了,则忽略,在选择其中ActiveRequestsCount最小的server
AvailabilityFilteringRule过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值)使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个server的运行状态
WeightedResponseTimeRule根据响应时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低。一个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。Weight的计算也比较简单responsetime 减去每个server自己平均的responsetime是server的权重。当刚开始运行,没有形成status时,使用roubine策略选择server。
RetryRule对选定的负载均衡策略机上重试机制。在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server
RoundRobinRuleroundRobin方式轮询选择server轮询index,选择index对应位置的server
RandomRule随机选择一个server在index上随机,选择index对应位置的server
ZoneAvoidanceRule复合判断server所在区域的性能和server的可用性选择server使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个zone的运行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于过滤掉连接数过多的Server。

配置负载均衡策略

通过配置文件配置负载均衡策略

application.yml

1
2
3
lunxun:
    ribbon:
     NFLoadBalancerRuleClassName:com.netflix.loadbalancer.RandomRule

application.properties

1
lunxun.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

然后通过loadBalancerClient调用负载均衡策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
public class ConsumerController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired  
    private LoadBalancerClient loadBalancerClient;  

    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public String add(@RequestParam Integer a,@RequestParam Integer b) {
        this.loadBalancerClient.choose("lunxun");//随机访问策略,这里的 lunxun 是在配置文件中定义的策略的名称
        return restTemplate.getForEntity("http://service-B/add?a="+a+"&b="+b, String.class).getBody();
    }
}

通过代码配置负载均衡策略

1
2
3
4
5
6
7
8
@Configuration
public class RibbonConfiguration{
      @Bean
      public IRule ribbonRule(){
          //随机负载
          return new RandomRule();
     }
}

重试机制

由于Spring Cloud Eureka实现的服务治理机制强调了CAP原理中的AP(可用性与可靠性),它与Zookeeper这类强调CP(一致性、可靠性)的服务治理框架最大的区别就是,Eureka为了实现更高的服务可用性,牺牲了一定的一致性,在极端情况下它宁愿接受故障实例也不要丢掉“健康”实例。

比如,当服务注册中心的网络发生故障断开时,由于所有的服务实例无法维持续约的心跳,在强调AP的服务治理中会把所有服务实例都剔除掉,而Eureka则会因为超过85%的实例丢失心跳而会出发保护机制,注册中心将会保留此时的所有节点,以实现服务间依然可以进行互相调用的场景,即使其中有部分故障节点,但这样做可以继续保障大多数的服务正常消费。

由于Spring Cloud Eureka在可用性与一致性上的取舍,不论是由于触发了保护机制还是服务剔除的延迟,引起服务调用到故障实例的时候,我们还是希望能够增强对这类问题的容错。

所以,我们在实现服务调用的时候,通常会加入一些重试机制。

Spring Cloud整合了Spring Retry来增强RestTemplate的重试能力,对于开发者来说只需要通过简单的配置,原来那些通过RestTemplate实现的服务访问就会自动根据配置来实现重试策略。具体配置如下:

1
2
3
4
5
6
7
spring.cloud.loadbalancer.retry.enable=true
hystrix.command.default.execution.isolation.thred.timeoutInMilliseconds=1000
hello-service.ribbon.ConnectTimeout=250
hello-service.ribbon.ReadTimeout=1000
hello-service.ribbon.OkToRetryOnAllOperations=true
hello-service.ribbon.MaxAutoRetriesNextServer=2
hello-service.ribbon.MaxAutoRetries=1
参数说明
spring.cloud.loadbalancer.retry.enable该参数用来开启重试机制,默认关闭。
hystrix.command.default.execution.isolation.thred
.timeoutInMilliseconds
断路器的超时时间需要大于Ribbon的超时时间,不然不会触发重试。
hello-service.ribbon.ConnectTimeout请求连接的超时时间。
hello-service.ribbon.ReadTimeout请求处理的超时时间。
hello-service.ribbon.OkToRetryOnAllOperations对所有操作请求都进行重试。
hello-service.ribbon.MaxAutoRetriesNextServer切换实例的重试次数。
hello-service.ribbon.MaxAutoRetries对当前实例的重试次数。

附录

客户端负载均衡

在客户端负载均衡中,所有客户端节点都维护着自己要访问的服务端清单,而这些服务端的清单来自于服务注册中心。

同服务端负载均衡的架构类似,在客户端负载均衡中也需要心跳去维护服务端清单的健康性,只是这个步骤需要与服务注册中心配合完成。

在Spring Cloud实现的服务治理框架中,默认会创建针对各个服务治理框架的Ribbon自动化整合配置。

Spring Cloud Hystrix

服务容错保护

在微服务架构中,我们将系统拆分成了很多服务单元,各单元的应用间通过服务注册与订阅的方式互相依赖。由于每个单元都在不同的进程中运行,依赖通过远程调用的方式执行,这样就有可能因为网络原因或是依赖服务自身问题出现调用故障或延迟,而这些问题会直接导致调用方的对外服务也出现延迟,若此时调用方的请求不断增加,最后就会因等待出现故障的依赖方响应形成任务积压,最终导致自身服务的瘫痪。这样的架构相较传统架构更加不稳定。

为了解决这样的问题,产生了断路器等一系列的服务保护机制。

Spring Cloud Hystrix介绍

Spring Cloud Hystrix实现了断路器、线程隔离等一系列服务保护功能。

它是基于Netflix的开源框架Hystrix实现的,该框架的目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。

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

配置步骤

配置pom.xml

1
2
3
4
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

配置Application.java

使用@EnableCircuitBreaker注解开启断路器功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class Application {

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

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

还可以使用Spring Cloud应用的@SpringCloudApplication注解来修饰应用主类。该注解的具体定义如下所示:

1
2
3
4
5
6
7
8
9
@Target({ElementType.TTPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplicaton
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}

配置Service类

  • 新增XService类
  • 注入RestTemplate实例
  • 在函数上增加@HystrixCommand注解来指定回调方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class ConsumerController {
    @Autowired
    RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "fallBack")
    @RequestMapping(value="/consumer",method= RequestMethod.GET)
    public String helloConsumer(){
        return restTemplate.getForEntity("http://eureka-client-provider/hello",String.class).getBody();
    }

    public String fallBack(){
        return "error";
    }
}

依赖隔离

Hystrix使用“舱壁模式”实现进程的隔离,它会为每一个依赖服务创建一个独立的线程池,这样就算某个依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响,而不会拖慢其它的依赖服务。

通过实现对依赖服务的线程池隔离,可以带来如下优势:

  • 应用自身得到完全保护,不会受不可控的依赖服务影响。
    即便给依赖服务分配的线程池被填满,也不会影响应用自身的其余部分。
  • 可以有效降低接入新服务的影响。
    如果新服务接入后运行不稳定或存在问题,完全不会影响其它的请求。
  • 当依赖的服务从失效回复正常后,它的线程池会被清理并且能够马上恢复健康的服务,相比之下,容器级别的清理恢复速度要慢得多。
  • 当依赖的服务出现配置错误的时候,线程池会快速反映出此问题(通过失败次数、延迟、超时、拒绝等指标的增加情况)。同时,我们可以在不影响应用功能的情况下通过实时的动态属性刷新(后续会通过Spring Cloud Config与Spring Cloud Bus的联合使用)来处理它。
  • 当依赖的服务因实现机制调整等原因造成其性能出现很大变化的时候,线程池的监控指标信息会反映出这样的变化。同时,我们也可以通过实时动态刷新自身应用对依赖服务的阈值进行调整以适应依赖方的改变。
  • 每个专有线程池都提供了内置的并发实现,可以利用它为同步的依赖服务构建异步访问。

总之,通过对依赖服务实现线程池隔离,可以让我们的应用更加健壮,不会因为个别依赖服务出现问题而引起肺相关服务的异常。同时,也使我们的应用变得更加灵活,可以在不停服务的情况下,配合动态配置刷新实现性能配置上的调整。

Hystrix仪表盘

Hystrix Dashboard

Hystrix Dashboard主要用来实时监控Hystrix的各项指标信息。

通过Hystrix Dashboard反馈的实时信息,可以帮助我们快速发现系统中存在的问题,从而及时的采取对应措施。

Hystrix Dashboard配置步骤

配置pom.xml

1
2
3
4
5
6
7
8
9
10
11
<groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

配置Application.java

为应用主类加上@EnableHystrixDashboard,启用Hystrix Dashboard功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

import java.util.Map;

@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
@EnableHystrixDashboard
public class Application {

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

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

配置application.yml

1
2
3
4
5
spring:
  application:
    name: hystrix-dashboard
server:
  port: 9092

访问Hystrix Dashboard

http://localhost:9092/hystrix

在Feign上使用熔断器

由于Feign的起步依赖中已经引入了Hystrix的依赖,所以在Feign中使用Hystrix不需要引入任何的依赖。

配置pom.xml

1
2
3
4
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

配置application.yml

需要在application.yml中配置开启Hystrix功能:

1
2
3
feign:
    hystrix:
        enabled: true

这样 feign 会默认使用 HystrixFeign 的 builder 构建 FeignClient,并为其添加默认的超时处理。其默认的超时时间为 1000 毫秒。

配置接口

通过@FeignClient定义Fegin方法,并定义name和fallbackFactory。name属性可用于定义Feign方法的Hystrix的个性化配置,而不使用全局默认配置。fallbackFactory设置带有异常信息的回调方法。

1
2
3
4
5
6
@FeignClient(name="cloud-hystrix-service"
        , fallbackFactory = MyHystrixClientFallbackFactory.class )
public interface IMyHystrixClient {
    @RequestMapping(value = "/hystrix/simple", method = RequestMethod.POST, consumes="application/json; charset=UTF-8")
    String simpleHystrixClientCall(@RequestParam("time") long time);
}

配置回调类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class MyHystrixClientFallbackFactory implements FallbackFactory<IMyHystrixClient> {
    private static final Logger log = LoggerFactory.getLogger(MyHystrixClientFallbackFactory.class);
    @Override
    public IMyHystrixClient create(Throwable throwable) {
        return new IMyHystrixClient() {
            @Override
            public String simpleHystrixClientCall(long time) {
                log.error("异常处理={}", throwable);
                return "Execute raw fallback: access service fail , req= " + time + " reason = " + throwable;
            }
        };
    }
}

Spring Cloud Feign

Spring Cloud Feign介绍

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

我们使用Spring Cloud Ribbin时,通常都会利用它对RestTemplate的请求拦截来实现对依赖服务的接口调用,而RestTemplate已经实现了对HTTP请求的封装处理,形成了一套模板化的调用方法。

但是,在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以我们通常都会针对各个微服务自行封装一些客户端类来包装这些依赖服务的调用。

这个时候我们会发现,由于RestTemplate的封装,几乎每一个调用都是简单的模板化内容。

综合上述这些情况,Spring Cloud Feign在此技术上做了进一步封装,由它来帮助我们定义和实现依赖服务接口的定义。

在Spring Cloud Feign的实现下,我们只需创建一个接口并用注解的方式类配置它,即可完成对服务提供方的接口绑定,简化了在使用Spring Cloud Feign时自行封装服务调用客户端的开发量。

Spring Cloud Feign具备可插拔的注解支持。同时,为了使用Spring的广大用户,它在Netflix Feign的基础上扩展了对Spring MVC的注解支持。

开发步骤

配置pom.xml

1
2
3
4
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

配置application.yml

1
2
3
feign:
    hystrix:
        enabled: true

配置Applicaton.java

使用@enableFeiginClients注解开启Spring Cloud Feign的支持功能。

1
2
3
4
5
6
7
@SpringCloudApplication
@EnableFeignClients
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

配置Service

通过@FeignClient注解指定服务名来绑定服务。

1
2
3
4
5
@FeignClient(name="x-Service")
public interface XService{
    @RequestMapping(value="x",method=RequestMethod.GET)
    String x();
}

配置Controller

1
2
3
4
5
6
7
8
9
10
@RestController
public class XController{
    @Autowired
    XService xservice;

    @RequestMapping(value="feigin-consumer",method=RequestMethod.GET)
    public String xConsumer(){
        return xService.x();
    }
}

Spring Cloud Feign参数绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@FeignClient(name = "demo-service")
public interface DemoService {

    @RequestMapping(value = "hello", method = RequestMethod.GET)
    String hello();

    @RequestMapping(value = "hello1", method = RequestMethod.GET)
    String hello1(@HeaderParam("name") String name);

    @RequestMapping(value = "hello2", method = RequestMethod.GET)
    String hello2(@RequestParam("name") String name);

    @RequestMapping(value = "hello3", method = RequestMethod.POST)
    String hello3(@RequestBody("user") User user);
}

Spring Cloud Feign优缺点

说明
Spring Cloud Feign优点可以将接口的定义从Controller中玻璃,同时配合Maven私有仓库就可以轻易地实现接口定义的共享,实现在构建期间的接口绑定,从而有效减少服务客户端的绑定配置。
Spring Cloud Feign缺点由于接口在构建期间就建立起了依赖,接口变动就会对项目构建造成影响,可能服务提供方修改了一个接口定义,那么会直接导致客户端工程的构建失败。所以,如果开发团队通过此方法来实现接口共享的话,建议再开发评审期间严格遵守面向对象的开闭原则,尽可能做好前后版本的兼容,防止牵一发而动全身的后果,增加团队不必要的维护工作量。

Spring Cloud Zuul

背景

没有API网关的微服务架构存在的问题

从运维人员角度看

运维人员需要手工维护路由规则与服务实例列表,当有实例增减或是IP地址变动等情况发生的时候,也需要手工地去同步修改这些信息以保持实例信息与中间件配置内容的一致性。

在系统规模不大的时候,维护这些信息的工作还不会太过复杂,但是如果当前系统规模不断增大,这些看似简单的维护任务会变得越来越难,并且出现配置错误的概率也会逐渐增加。

从开发人员的角度看

大多数情况下,为了保证对外服务的安全性,我们在服务端实现的微服务接口,往往都会有一定的权限校验机制,比如对用户登录状态的校验等,同时,为了防止客户端在发起请求时被篡改等安全方面的考虑,还会有一些签名校验机制存在。

这时候,由于使用了微服务架构的理念,我们将原本处于一个应用中的多个模块拆成了多个应用,但是这些应用提供的接口都需要这些校验逻辑,我们不得不在这些应用中都实现这样一套校验逻辑。

随着微服务规模的扩大,这些校验逻辑的冗余变得越来越多,突然有一天我们发现这套校验逻辑有个BUG需要修复,或者需要对其做一些扩展和优化,此时我们就不得不去每个应用里修改这些逻辑,而这样的修改不仅会引起开发人员的抱怨,更会加重测试人员的负担。

API网关

为了解决上面这些常见的架构问题,API网关的概念应运而生。

API网关是一个更为智能的应用服务器,所有的外部客户端访问都需要经过它来进行调度和过滤。

它除了要实现请求路由、负载均衡、校验过滤等功能之外,还需要更多能力,比如与服务治理框架的结合、请求转发时的熔断机制、服务的聚合等一系列高级功能。

微服务架构虽然可以将我们的开发单元拆分得更为细致,有效降低了开发难度,但是它所引出的各种问题如果处理不当会成为实施过程中的不稳定因素,甚至掩盖掉原本实施微服务带来的优势。所以,在微服务架构的实施方案中,API网关服务的使用几乎成为了必然的选择。

Spring Cloud Zuul

Spring Cloud Zuul是基于NetFlix Zuul实现的API网关组件。
Zuul的核心是一系列过滤器,可以在Http请求和发起响应返回期间执行一系列的过滤器。

Zuul过滤器的类型

Zuul包括以下4种过滤器。

过滤器类型说明适用场景
PRE过滤器它是在请求路由到具体的服务之前执行的。安全验证。例如身份验证、参数验证等。
在集群中选择请求的微服务。
记录调试信息。
ROUTING过滤器它用于将请求路由到具体的微服务实例。默认情况下,它使用Http Client进行网络请求。
POST过滤器它是在请求已被路由到微服务后执行的。来为响应添加标准的HTTP Header。
收集统计信息、指标。
将响应传输到客户端。
ERROR过滤器它是在其它过滤器发生错误时执行的。统一错误处理。

Zuul中默认实现的Filter

类型顺序过滤器功能
pre-3ServletDetectionFilter标记处理Servlet的类型
pre-2Servlet30WrapperFilter包装HttpServletRequest请求
pre-1FormBodyWrapperFilter包装请求体
route1DebugFilter标记调试标志
route5PreDecorationFilter处理请求上下文供后续使用
route10RibbonRoutingFilterserviceId请求转发
route100SimpleHostRoutingFilterurl请求转发
route500SendForwardFilterforward请求转发
post0SendErrorFilter处理有错误的请求响应
post1000SendResponseFilter处理正常的请求响应

顺序越小越先被执行。

禁用指定的Filter

application.yml

1
2
3
4
zuul:
    FormBodyWrapperFilter:
        pre:
            disable: true

自定义Filter

实现自定义Filter,需要继承ZuulFilter的类,并覆盖其中的4个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyFilter extends ZuulFilter {
    @Override
    String filterType() {
        return "pre"; //定义filter的类型,有pre、route、post、error四种
    }

    @Override
    int filterOrder() {
        return 10; //定义filter的顺序,数字越小表示顺序越高,越先执行
    }

    @Override
    boolean shouldFilter() {
        return true; //表示是否需要执行该filter,true表示执行,false表示不执行
    }

    @Override
    Object run() {
        return null; //filter需要执行的具体操作
    }
}

将TokenFilter加入到请求拦截队列,在启动类中添加以下代码:

1
2
3
4
@Bean
public TokenFilter tokenFilter() {
    return new TokenFilter();
}

这样就将我们自定义好的Filter加入到了请求拦截中。

使用Spring Cloud Zuul构建网关步骤

配置pom.xml

1
2
3
4
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

配置Application.java

使用@EnableZuulProxy注解开启Zuul的API网关服务功能。

1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
@EnableZuulProxy
public class Application {

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

配置application.yml

1
2
3
4
5
spring:
  application:
    name: api-gateway
server:
  port: 9094

Spring Cloud Config

Spring Cloud Config介绍

Spring Cloud Config是Spring Cloud团队创建的一个全新项目,用来为分布式系统中的基础设施和微服务应用提供集中化的外部配置支持。

它分为服务端和客户端两个部分。

服务端,也称为分布式配置中心。它是一个独立的微服务应用,用来连接配置仓库并未客户端提供获取配置信息、加密/解密信息等访问接口。

客户端,是微服务架构中的各个微服务应用或基础设施,它通过指定的配置中心来管理应用资源与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。

Spring Cloud Config实现了对服务端和客户端中环境变量和属性配置的抽象映射,所以它除了适用于Spring构建的应用程序之外,也可以在任何其它预言运行的应用程序中使用。

由于Spring Cloud Config实现的配置中心默认采用Git来存储配置信息,所以使用Spring Cloud Config构建的配置服务器,天然就支持对微服务应用配置信息的版本管理,并且可以通过Git客户端工具来方便地管理和访问配置内容。它也提供了对其它存储方式的支持,如SVN仓库、本地化文件系统。

构建Spring Cloud Config Server步骤

配置pom.xml

1
2
3
4
<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
</dependency>

配置Application.java

使用@EnableConfigServer注解,开启Spring Cloud Config的服务端功能。

1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
@EnableConfigServer
public class Application {

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

配置application.yml

1
2
3
4
5
6
7
8
9
server:
  port: 8888
spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/spring-cloud-samples/config-repo
          basedir: target/config

构建Spring Cloud Config Client

配置pom.xml

1
2
3
4
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

配置bootstrap.yml

1
2
3
4
5
6
spring:
  cloud:
    config:
     uri: https://myconfig.mycompany.com
     username: user
     password: secret

总结

Spring Cloud Config 产品功能远远达不到生产级,只能小规模场景下用,中大规模企业级场景不建议采用。

Spring Cloud Bus

消息总线

在微服务架构的系统中,我们通常会使用轻量级的消息代理来构建一个公用的消息主题让系统中所有微服务实例都连接上来,由于该主题产生的消息会被所有实例监听和消费,所以我们称它为消息总线。

由于消息总线在微服务架构系统中被广泛使用,几乎是微服务架构中的必备组件。

Spring Cloud Bus

Spring Cloud Bus可以非常容易地搭建起消息总线,同时实现了一些消息总线中的常用功能,比如,配合Spring Cloud Config实现微服务应用配置信息的动态更新等。

当前版本的Spring Cloud Bus仅支持两款中间件产品:

  • RabbitMQ
  • Kafka

Spring Cloud Bus原理

未完待续。。。

RabbitMQ实现消息总线

配置pom.xml

1
2
3
4
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

配置bootstrap.yml

1
2
3
4
5
6
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

配置生产者

AmqpTemplate接口定义了一套针对AMPQ协议的基础操作。
通过注入AmqpTemplate接口的实例实现消息的发送。

1
2
3
4
5
6
7
8
9
10
11
@Component
public class Sender{
    @Autowired
    private AmpqTemplate rabbitTemplate;

    public void send(){
        String context = "hello"+ new Date();
        System.out.println("Sender:"+context);
        this.rabbitTemplate.convertAndSend("hello",context);
    }
}

配置消费者

通过@RabbitListener注解定义该类对hello队列的监听,并用@RabbitHandler注解来指定对消息的处理方法。
以下示例实现了对”queue1”队列的消费,消费操作为输出消息的字符串内容。

1
2
3
4
5
6
7
8
@Component
@RabbitListener(queues="queue1")
public class Receiver{
    @RabbitHandler
    public void process(String hello){
        System.out.println("Receiver:"+hello);
    }
}

配置RabbitMQ的配置类RabbitConfig

RabbitConfig用来配置队列、交换器、路由等高级信息。这里先以最小化配置为例:

1
2
3
4
5
6
7
8
@Configuration
public class RabbitConfig{

    @Bean
    public Queue helloQueue(){
        return new Queue("hello");
    }
}

创建消息生产单元测试类

1
2

Kafka实现消息总线

配置pom.xml

1
2
3
4
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

配置bootstrap.yml

1
2
3
4
5
6
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

Spring Cloud Stream

Spring Cloud Stream

Spring Cloud Stream是一个用来为微服务应用构建消息驱动能力的框架。
它基于Spring Boot 来创建独立的、可用于生产的Spring应用程序。
它通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。
Spring Cloud Stream为一些供应商的消息中间件产品提供了个性化的自动化配置实现,并且引入了发布-订阅、消费组以及区分这三个核心概念。

简单地说,Spring Cloud Stream本质上就是整合了Spring Boot和Spring Integration,实现了一套轻量级的消息驱动的微服务框架。
通过使用Spring Cloud Stream,可以有效简化开发人员对消息中间件的使用复杂度,让系统开发人员可以有更多精力关注于核心业务逻辑的处理。

到目前为止,Spring Cloud Stream只支持一下两个消息中间件的自动化配置:

  • RabbitMQ
  • Kafka

Spring Cloud Stream和RabbitMQ整合步骤

配置pom.xml

1
2
3
4
5
6
7
8
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

配置application.yml

1
2

配置消费者

1
2

配置生产者

1
2

Spring Cloud Stream和kafka整合步骤

配置pom.xml

1
2
3
4
5
6
7
8
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-kafka</artifactId>
</dependency>

配置application.yml

1
2

配置消费者

1
2

配置生产者

1
2

Spring Cloud Sleuth

Spring Cloud Sleuth

随着系统的发展,系统规模也会变得越来越大,各个微服务间的调用关系也变得越来越错综复杂。
通常一个由客户端发起的请求在后端系统中会经过多个不同的微服务调用来协同产生最后的请求结果,在复杂的微服务架构系统中,几乎每一个前端请求都会形成一个复杂的分布式服务调用链路,在每条链路中任何一个依赖服务出现延迟过高或错误的时候都可能引起请求的最后失败。
这时候,对于每个请求,全链路调用的跟踪就变得越来越重要,通过实现对请求调用的跟踪可以帮助我们快速发现错误根源以及监控分析每条请求链路上的性能瓶颈等。
针对上述的分布式服务跟踪问题,Spring Cloud Sleuth提供了一套完整的解决方案。

Spring Cloud Sleuth使用步骤

配置pom.xml

1
2
3
4
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

配置应用主类

1
2

配置application.yml

MySQL存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring:
  application:
    name: sleuth-zipkin-http
  datasource:
    schema: classpath:/mysql.sql
    url: jdbc:mysql://192.168.3.3:2222/zipkin
    driverClassName: com.mysql.jdbc.Driver
    username: app
    password: %jdbc-1.password%
    # Switch this on to create the schema on startup:
    initialize: true
    continueOnError: true
  sleuth:
    enabled: false

# default is mem (in-memory)
zipkin:
    storage:
       type: mysql

ES存储

1
2
3
4
5
6
7
8
9
zipkin:
  storage:
    type: elasticsearch
    elasticsearch:
      cluster: ${ES_CLUSTER:elasticsearch}
      hosts: ${ES_HOSTS:localhost:9300}
      index: ${ES_INDEX:zipkin}
      index-shards: ${ES_INDEX_SHARDS:5}
      index-replicas: ${ES_INDEX_REPLICAS:1}

跟踪原理

  1. 当请求发送到分布式系统的入口端点时,需要服务跟踪框架为该请求创建一个唯一的跟踪标识(Trace ID),同时在分布式系统内部流转的时候,框架始终保持传递该唯一标识,直到返回给请求方为止。通过Trace ID的记录,我们就能够将所有请求过程的日志关联起来。
  2. 为了统计各处理单元的时间延迟,当请求到达各个服务组件时,或是处理逻辑到达某个状态时,也通过一个唯一标识(Span ID)来标记它的开始、具体过程以及结束。每个Span必须有开始和结束两个节点,通过记录开始Span和结束Span的时间戳,就能统计出该Span的时间延迟,除了时间戳记录之外,它还可以包含一些其它元数据,比如事件名称、请求信息等。

抽样收集

与ELK整合

与Zipkin整合

总结

Spring Cloud支持基于Zipkin的调用链监控,我个人基于实践经验认为Zipkin还不能算一款企业级调用链监控产品,充其量只能算是一个半成品,很多重要的企业级特性缺失。

Zipkin最早是由Twitter在消化Google Dapper论文的基础上研发,在Twitter内部有较成功应用,但是在开源出来的时候把不少重要的统计报表功能给阉割了(因为依赖于一些比较重的大数据分析平台),只是开源了一个半成品,能简单查询和呈现可视化调用链,但是细粒度的调用性能数据报表没有开源。

配置中心技术选型

指标\配置中心Spring Cloud ConfigDiamondDisconfApollo
管理后台没有
存储Git/SVNmysqlmysqlmysql
服务发现eureka通过比较client和server的数据的MD5值感知数据变化zookeepereureka
回滚支持不支持不支持支持
实时生效不支持支持支持支持
支持语言Java,可扩展其它语言JavaJavaJava、.Net
推拉模型推拉均可拉模型(默认15s)推模型推模型
功能特性缺乏企业级功能特性需要地址服务器,客户端连接到地址服务器,取回diamond服务器的地址列表1、对配置进行持久化管理并对外提供restful接2、注解式编程,需要Spring编程环境3、支持配置的上传、下载4、支持分布式环境下的主备竞争统一管理不同环境、不同集群的配置;配置修改实时生效(热发布);版本发布管理;灰度发布;权限管理;发布审核;操作审计;客户端配置信息监控;提供Java和.Net原生客户端;提供开放平台API

参考


惜文博客 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Spring Cloud基础
喜欢 (0)
[白白]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址